#!/usr/local/bin/wermit +

; (change the first line to point to C-Kermit 8.0 or later executable)
; (and give this file execute permission)

; p o r t l o g
;
; Absorbs and logs attacks on a given TCP Port, such as 80.
; Sends hourly summaries to the selected e-mail address.
; Uploads hourly logs to the selected FTP destination.
;
; Default port is 80.  You can specify a different port as the
; first command-line arg, or you can tell kermit to "define port 443"
; (or whatever) and then "take portlog" or simply "take portlog 443".
; Runs forever.  Stop it with Ctrl-C.  To run in background, redirect
; stdout and stderr to a file or to /dev/null.
;
; Requires C-Kermit 8.0 later.
;
; Default port is 80 (HTTP).  Works for any HTTP attack: Code Red,
; Nimba, etc.  Requires privilege for ports < 1000 in UNIX.
; Requires that no other process is listening on the same port.
;
; IMPORTANT: Change all lines marked (*) as needed.
;
; F. da Cruz, Columbia University, September 2001.
; Last update: Fri Sep 21 09:44:38 2001
;
if < \v(version) 800200 exit 1 Fatal - C-Kermit 8.0 required.

local ftppass ; So it will disappear automatically when script terminates.

; PARAMETERS - ADJUST AS NEEDED

.truncate := 0                           ;(*) Truncate log records at 79 cols?
.doftp    := 1                           ;(*) Upload full logs with FTP?
.ftpuser  := fdc                         ;(*) FTP info... CHANGE THIS
.ftphost  := ftp.kermit.columbia.edu     ;(*) CHANGE THIS
.ftpdir   := ~kermit/cu/port80logs       ;(*) CHANGE THIS
.address  := security@columbia.edu       ;(*) E-mail address for summary

.mailcmd  := Mail                        ;(*) External mail command
if match \v(platform) *HP-UX* .mailcmd = mailx
if k-95 .mailcmd = echo                  ; No way to mail from Windows

if def \%1 .port := \%1                  ; Listen port
if not def port .port := 80              ;(*) Default listen port

;(*) The following macro checks if its argument string contains
;(*) an IP address or IP hostname in the local administrative domain.
;(*) CHANGE THE PATTERN to match your own site's addresses.

def CHKLOCAL {                           ;(*) 
    if match \%1 *{128.59.*.,160.39.*.,columbia.edu,barnard.edu}* end 0
    end 1
}
; END OF ADJUSTABLE PARAMETERS

def NEWLOGNAME {                         ; Make new log file name
  .logfile := \v(host)_\m(port)_\v(ndate)_\freplace(\v(time),:,).log
}

.\%n = 0                                 ; Event count
.hour  := \fstripx(\fstripx(\v(time),:),:) ; Start hour
.start := \fcvtdate()                    ; Start date-time
newlogname                               ; Get new log name

define ON_CTRLC {                        ; Ctrl-C trap
    echo INTERRUPTED AT \v(time)...
    echo Connections: \%n in \fdiffdate(now,\m(start))
    getok "Do dump? [yes or no] "
    if success dodump
    getok "Debug? [yes or no] "
    if success prompt Debug>
    getok "Exit? [yes or no] "
    if success exit
}

; Routine to summarize, upload, and reset log.

define DODUMP {                           ; Send summary and start new log
    local a \%u \%c \%x
    echo "-----------------"
    echo REPORTING AT \v(time)...
    if ( doftp ) {
        set flag off
        ftp open \m(ftphost) /user:\m(ftpuser) /password:\m(ftppass)
        if \v(ftp_loggedin) {
            ftp cd \m(ftpdir)
            if success {
                ftp put \m(logfile)
                if success {
                    set flag on
                    ftp chmod 664 \m(logfile)
                }
            }
        }
        if not flag echo WARNING: FTP UPLOAD LOG FAILED
        ftp bye
    }
    fopen /read \%c \m(logfile)             ; Open current log
    if fail end 1 \m(logfile): \f_errmsg()  ; Check that we did
    .\%n := 0                               ; Init record counter
    while not \f_eof(\%c) {                 ; Loop to read each record
	fread /line \%c line                ; Read one record
	if fail break                       ; Check
	incr \%n                            ; Count
	.a := \s(line[19])                  ; Remove timestamp
	.\%x ::= \findex({"},\m(a)) - 1     ; Remove attack string
	.a := \ftrim(\s(a[1:\%x]))          ; Remove any surrounding whitespace
	.a := \fltrim(\m(a))
	_increment aa<\m(a)>                ; Count a hit from this host
    }
    fclose \%c                              ; Close log file

    .\%k := \faaconvert(aa,&a,&b)           ; Convert to pair of regular arrays
    .\%u := 0                               ; Local domain counter
    array sort /reverse /numeric b a        ; Sort in descending order of hits
    .tmpfile := \freplace(\m(logfile),.log,.txt)
    fopen /write \%c \m(tmpfile)
    if fail end 1 Can't create report file
    for \%i 1 \%k 1 {                       ; Count hits from local domain
        chklocal {\&a[\%i]}
        if success incr \%u
    }
    fwrite /line \%c Port \m(port) probe summary on \v(host) -
[\fname2addr(\v(host))]
    fwrite /line \%c Interval: \m(start) - \fcvtdate()
    fwrite /line \%c
    fwrite /line \%c "  Hits:               \flpad(\%n,5)"
    fwrite /line \%c "  Unique hosts:       \flpad(\%k,5)"
    fwrite /line \%c "  Unique local hosts: \flpad(\%u,5)"
    fwrite /line \%c
    for \%i 1 \%k 1 {
	fwrite /line \%c \frpad(\&a[\%i],60) \flpad(\&b[\%i],5)
    }
    fclose \%c
    !\m(mailcmd) -s "Port \m(port) Probe Summary" \m(address) < \m(tmpfile)
    newlogname                                 ; Get new log name
    .start := \fcvtdate()                      ; New start time etc
    .hour  := \fstripx(\fstripx(\v(time),:),:)
    .\%n := 0
    echo NEW LOG: \m(logfile)
}

if K-95 {                               ; K95: Appropriate window color
    set command color white red
    cls
}

echo LISTENING ON PORT \m(port)...

; Get FTP access info.

if DOFTP {
    echo For uploading logs...
    echo Logs will be uploaded hourly by FTP to:
    echo Host: \m(ftphost) Directory: \m(ftpdir)
    getok " OK? [yes or no] "
    if fail .doftp := 0
    while doftp {
        undef ftppass
        while not def ftppass {
            askq ftppass { FTP password for \m(ftpuser) at \m(ftphost): }
        }
        echo Testing...
        ftp open \m(ftphost) /user:\m(ftpuser) /password:\m(ftppass)
        if success if \v(ftp_loggedin) {
            ftp bye
            break
        }
        ftp bye
        echo { Test failed - try again.}
    }
}

; Here goes...

set input echo off                      ; Input is logged to a file
set xfer display brief                  ; No showing off
set tcp reverse-dns off                 ; For accurate source reporting

while true {                                ; Loop forever
    .x := \fstripx(\fstripx(\v(time),:),:)  ; Current hour
    if ( != \m(x) \m(hour) ) {              ; If hour turned over
        dodump                              ; Send report and reset
        if fail echo WARNING: Report failed
    }
    echo "-----------------"
    .stamp := \fcvtdate()
    xecho \m(stamp)...        
    set host * \m(port)                     ; Wait for incoming connection
    if fail {
        echo Net Open Failure: \v(errstring)
        if ( == \v(errno) 13 || == \v(errno) 48 ) exit 1
        hangup
        continue
    }
    clear input                         ; Capture the attack string
    input 8 \10                         ; (terminate at linefeed = ASCII 10)
    increment \%n                                 ; Count this probe
    echo \flpad(\%n,4). \m(stamp): \v(line)       ; Log to screen
    if ( = 0 \fverify(0123456789.,\v(line)) ) {   ; Have IP address
	.addr := \v(line)
	.name := \faddr2name(\m(addr))            ; Lookup name in DNS
    } else {                                      ; Have IP name
	.name := \v(line)
	.addr := \fname2addr(\m(name))            ; Get its address
    }
    hangup                                        ; Close connection quickly

    .record := \m(stamp): \m(name) [\m(addr)] "\ftrim(\v(input))"
    if \m(truncate) if > \flen(\m(record)) 79 .record := \s(record[1:76]).."

    set flag off
    for \%i 1 3 1 {                               ; Write to log
        fopen /append \%c \m(logfile)             ; It might be busy
        if fail {                                 ; (can happen in Windows)
            echo LOG APPEND FAILURE: \f_errmsg()
            sleep \%i
            continue
        }
        fwrite /line \%c \m(record)
        if success set flag on
        fclose \%c
        if flag break
    }
    if not flag echo RECORD LOST: \m(record)        
}
