If you're running an anonymous FTP server, you should already be constantly monitoring what happens in the ~ftp/pub directory (which is usually where uploads are allowed), but any FTP server requires you to keep an eye on things.
The ftp daemon's transfer log (xferlog) file format is definitely one of the most cryptic in Unix, which makes analyzing it in a script rather tricky. Worse, there's a standard, common xferlog file format that just about everyone uses (and which this script expects), and there's an abbreviated ftpd.log format that some BSD versions of ftpd use that's just about impossible to analyze in a script.
So we'll focus on the xferlog format. The columns in an xferlog are as shown in Table 10-2.
Column |
Value |
---|---|
1–5 |
Current time |
6 |
Transfer time (secs) |
7 |
Remote host |
8 |
File size |
9 |
Filename |
10 |
Transfer type |
11 |
Special action flag |
12 |
Direction |
13 |
Access mode |
14 |
Username |
15 |
Service name |
16 |
Authentication method |
17 |
Authenticated user ID |
18-? |
Additional codes as added by the specific fptd program (usually omitted) |
A sample line from an xferlog is as cryptic as you might expect:
Mon Nov 4 12:22:46 2002 2 192.168.124.152 2170570 \ /home/ftp/pub/openssl-0.9.5r.tar.gz b _ i r leoftp 0 * c
This script quickly scans through xferlog, highlighting connections and files uploaded and downloaded, and producing other useful statistics.
#!/bin/sh # xferlog - Analyzes and summarizes the FTP transfer log. A good doc # detailing the log format is http://aolserver.am.net/docs/2.3/ftp-ch4.htm. stdxferlog="/var/log/xferlog" temp="/tmp/$(basename $0).$$" nicenum="$HOME/bin/nicenumber" # Script #4 trap "/bin/rm -f $temp" 0 extract() { # Called with $1 = desired accessmode, $2 = section name for output if [ ! -z "$(echo $accessmode | grep $1)" ] ; then echo "" ; echo "$2" if [ "$1" = "a" -o "$1" = "g" ] ; then echo " common account (entered password) values:" else echo " user accounts accessing server: " fi awk "\$13 == \"$1\" { print \$14 }" $log | sort | \ uniq -c | sort -rn | head -10 | sed 's/^/ /' awk "\$13 == \"$1\" && \$12 == \"o\" { print \$9 }" $log | sort | \ uniq -c | sort -rn | head -10 | sed 's/^/ /' > $temp if [ -s $temp ] ; then echo " files downloaded from server:" ; cat $temp fi awk "\$13 == \"$1\" && \$12 == \"i\" { print \$9 }" $log | sort | \ uniq -c | sort -rn | head -10 | sed 's/^/ /' > $temp if [ -s $temp ] ; then echo " files uploaded to server:" ; cat $temp fi fi } ###### The main script block case $# in 0 ) log=$stdxferlog ;; 1 ) log="$1" ;; * ) echo "Usage: $(basename $0) {xferlog name}" >&2 exit 1 esac if [ ! -r $log ] ; then echo "$(basename $0): can't read $log." >&2 exit 1 fi # Ascertain whether it's an abbreviated or standard ftp log file format. If # it's the abbreviated format, output some minimal statistical data and quit: # The abbreviated format is too difficult to analyze in a short script, # unfortunately. if [ ! -z $(awk '$6 == "get" { short=1 } END{ print short }' $log) ] ; then bytesin="$(awk 'BEGIN{sum=0} $6=="get" {sum+=$9} END{print sum}' $log)" bytesout="$(awk 'BEGIN{sum=0} $6=="put" {sum+=$9} END{print sum}' $log)" echo -n "Abbreviated ftpd xferlog from " echo -n $(head -1 $log | awk '{print $1, $2, $3 }') echo " to $(tail -1 $log | awk '{print $1, $2, $3}')" echo " bytes in: $($nicenum $bytesin)" echo " bytes out: $($nicenum $bytesout)" exit 0 fi bytesin="$(awk 'BEGIN{sum=0} $12=="i" {sum += $8} END{ print sum }' $log )" bytesout="$(awk 'BEGIN{sum=0} $12=="o" {sum += $8} END{ print sum }' $log )" time="$(awk 'BEGIN{sum=0} {sum += $6} END{ print sum }' $log)" echo -n "Summary of xferlog from " echo -n $(head -1 $log | awk '{print $1, $2, $3, $4, $5 }') echo " to $(tail -1 $log | awk '{print $1, $2, $3, $4, $5}')" echo " bytes in: $($nicenum $bytesin)" echo " bytes out: $($nicenum $bytesout)" echo " transfer time: $time seconds" accessmode="$(awk '{print $13}' $log | sort -u)" extract "a" "Anonymous Access" extract "g" "Guest Account Access" extract "r" "Real User Account Access" exit 0
In an xferlog, the total number of incoming bytes can be calculated by extracting just those lines that have direction="i" and then summing up the eighth column of data. Outgoing bytes are in the same column, but for direction="o".
bytesin="$(awk 'BEGIN{sum=0} $12=="i" {sum += $8} END{ print sum }' $log )" bytesout="$(awk 'BEGIN{sum=0} $12=="o" {sum += $8} END{ print sum }' $log )"
Ironically, the slower the network connection, the more accurate the total connection time is. On a fast network, smaller transfers are logged as taking zero seconds, though clearly every transfer that succeeds must be longer than that.
Three types of access mode are possible: a is anonymous, g is for users who utilize the guest account (usually password protected), and r is for real or regular users. In the case of anonymous and guest users, the account value (field 14) is the user's password. People connecting anonymously are requested by their FTP program to specify their email address as their password, which is then logged and can be analyzed.
Of this entire xferlog output stream, the most important entries are those with an anonymous access mode and a direction of i, indicating that the entry is an upload listing. If you have allowed anonymous connections and have either deliberately or accidentally left a directory writable, these anonymous upload entries are where you'll be able to see if skript kiddies, warez hackers, and other characters of ill repute are exploiting your system. If such an entry lists a file uploaded to your server, it needs to be checked out immediately, even if the file-name seems quite innocuous.
This test occurs in the following statement in the extract function:
awk "\$13 == \"$1\" && \$12 == \"i\" { print \$9 }" $log | sort | \ uniq -c | sort -rn | head -10 | sed 's/^/ /' > $temp
In this rather complex awk invocation, we're checking to see whether field 13 matches the anonymous account code (because extract is called as extract "a" "Anonymous Access") and whether field 12 indicates that it's an upload with the code i. If both of these conditions are true, we process the value of field 9, which is the name of the file uploaded.
If you're running an FTP server, this is definitely a script for a weekly (or even daily) cron job.
If invoked without any arguments, this script tries to read and analyze the standard ftpd transfer log /var/log/xferlog. If that's not the correct log file, a different filename can be specified on the command line.
The results depend on the format of the transfer log the script is given. If it's an abbreviated form, some minimal statistics are generated and the script quits:
$ xferlog succinct.xferlog Abbreviated ftpd xferlog from Aug 1 04:20:11 to Sep 1 04:07:41 bytes in: 215,300,253 bytes out: 30,305,090
When a full xferlog in standard format is encountered, considerably more information can be obtained and displayed by the script:
$ xferlog Summary of xferlog from Mon Sep 1 5:03:11 2003 to Tue Sep 30 17:38:50 2003 bytes in: 675,840 bytes out: 3,989,488 transfer time: 11 seconds Anonymous Access common account (entered password) values: 1 taylor@intuitive.com 1 john@doe files downloaded from server: 1 /MySubscriptions.opml files uploaded to server: 1 /tmp/Find.Warez.txt Real User Account Access user accounts accessing server: 7 rufus 2 taylor files downloaded from server: 7 /pub/AllFiles.tgz 2 /pub/AllFiles.tar
Security Alert! Did you notice that someone using anonymous FTP has uploaded a file called /tmp/Find.Warez.txt? "Warez" are illegal copies of licensed software — not something you want on your server. Upon seeing this, I immediately went into my FTP archive and deleted the file.
This HTML Help has been published using the chm2web software. |