1#!/bin/sh 2# 3# ipfw2dshield: a DShield client for ipfw logs. 4 5version="0.5" 6 7########################################################################### 8# 9# Copyright (C) 2002, 2004 Frank W. Josellis 10# 11# This program is free software; you can redistribute it and/or modify 12# it under the terms of the GNU General Public License as published by 13# the Free Software Foundation; either version 2 of the License, or 14# (at your option) any later version. 15# 16# This program is distributed in the hope that it will be useful, 17# but WITHOUT ANY WARRANTY; without even the implied warranty of 18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19# GNU General Public License for more details. 20# 21# You should have received a copy of the GNU General Public License 22# along with tis program; if not, write to the Free Software 23# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. 24# 25########################################################################### 26# 27############################################################################### 28# Handy shell functions. 29############################################################################### 30 31usage(){ 32 echo "Usage: $prog [-t] [-d logdir] [-b logbase] [-s stampfile]" 33 echo " $prog [-h|-v]" 34 echo -e " 35 -t\tToggle test mode. [Sets dshield id to 0, mails report 36\tto root@localhost, and doesn't update the stampfile.]\n 37 -d logdir 38\tThe directory containing the logfiles to be inspected. 39\t[default: $_logdir]\n 40 -b logbase 41\tThe basename of the logfiles to be inspected, i.e., 42\twithout the possible suffixes inherited from log rotation. 43\t[default: $_logbase]\n 44 -s stampfile 45\tThe stampfile in /var/tmp used for keeping track of the 46\treports to prevent multiple submission. 47\t[default: $_stampfile]\n 48 -h\tShow this help.\n 49 -v\tVersion info.\n" 50} 51 52unixdate(){ 53 udate=`strpdate -f "%Y %b %e %T" "$year $1"` 54 [ $udate -gt $now ] && \ 55 udate=`strpdate -f "%Y %b %e %T" "$last_year $1"` 56} 57 58# 59# Collect all matching records respecting the date limits. Attempt to choose 60# a reasonable value for 'year' and convert each record's timestamp to Unix. 61# 62filter1(){ 63 repeated="last message repeated" 64 read input 65 record="" 66 while [ "$input" ]; do 67 if [ "`echo $input | grep \"$search\"`" ]; then 68 # Match according to search pattern. 69 logdate=`echo $input | cut -f1-3 -d' '` 70 unixdate "$logdate" 71 if [ $udate -ge $ref_date -a $udate -lt $now ]; then 72 record="$udate `echo $input | cut -f9- -d' '`" 73 echo $record 74 else 75 record="" 76 fi 77 elif [ "$record" -a "`echo $input | grep \"$repeated\"`" ]; then 78 # Match according to "last message repeated r times". 79 logdate=`echo $input | cut -f1-3 -d' '` 80 unixdate "$logdate" 81 if [ $udate -ge $ref_date -a $udate -lt $now ]; then 82 record="$udate `echo $record | cut -f2- -d' '`" 83 r=`echo $input | cut -f8 -d ' '` 84 while [ $r -gt 0 ]; do 85 echo $record 86 r=`expr $r - 1` 87 done 88 else 89 record="" 90 fi 91 else 92 # No match. 93 record="" 94 fi 95 96 read input 97 done 98} 99 100 101# 102# Count the multiplicity of the previously collected records and discard 103# identical entries. Convert a record's timestamp to the dshield format. 104# 105filter2(){ 106 date_cmd="date" 107 case $utc_timestamps in 108 [Yy][Ee][Ss]) 109 date_cmd="date -u" 110 ;; 111 esac 112 113 read input 114 while [ "$input" ]; do 115 date=`echo $input | cut -f1 -d' '` 116 gmtoff=`$date_cmd -r $date +%z` 117 gmtoff_H=`expr $gmtoff : '\(.\{3\}\).\{2\}'` 118 gmtoff_M=`expr $gmtoff : '.\{3\}\(.\{2\}\)'` 119 offset="$gmtoff_H:$gmtoff_M" 120 fdate=`$date_cmd -r $date +"%Y-%m-%d %T"` 121 122 record="$fdate $offset `echo $input | cut -f2- -d' '`" 123 if [ "`grep \"$record\" $tmpfile2`" ]; then 124 read input 125 continue 126 fi 127 128 count="`grep \"$input\" $tmpfile1 | wc -l | tr -d [:space:]`" 129 echo $count 130 echo $record 131 132 read input 133 done 134} 135 136# 137# Read the multiplicity of a record and then the record. Rewrite the 138# input according to the dshield requirements. Currently recognized 139# protocols are TCP, UDP, ICMP. 140# 141filter3(){ 142 read count 143 while [ "$count" ]; do 144 read input 145 146 fdate="`echo $input | cut -f1-3 -d' '`" 147 proto="`echo $input | cut -f4 -d' '`" 148 source="`echo $input | cut -f5 -d' '`" 149 target="`echo $input | cut -f6 -d' '`" 150 151 case $proto in 152 ICMP*) 153 s_addr=$source 154 t_addr=$target 155 ports="`echo $proto | cut -f2 -d:`" 156 s_port="`echo $ports | cut -f1 -d.`" 157 t_port="`echo $ports | cut -f2 -d.`" 158 proto=ICMP 159 ;; 160 161 TCP|UDP) 162 s_addr="`echo $source | cut -f1 -d:`" 163 s_port="`echo $source | cut -f2 -d:`" 164 t_addr="`echo $target | cut -f1 -d:`" 165 t_port="`echo $target | cut -f2 -d:`" 166 ;; 167 168 *) read count 169 continue 170 ;; 171 esac 172 173 check_addr source $proto $s_addr $s_port 174 if [ $? -ne 0 ]; then 175 read count 176 continue 177 fi 178 179 check_addr target $proto $t_addr $t_port 180 if [ $? -ne 0 ]; then 181 read count 182 continue 183 fi 184 185 ip="$s_addr\t$s_port\t$t_addr\t$t_port\t$proto" 186 echo -e "$fdate\t$userid\t$count\t$ip" 187 188 read count 189 done 190} 191 192 193# 194# Check if an IP address matches an exclusion list. 195# 196check_addr(){ 197 eval droplist1=\$drop_$1 198 eval droplist2=\$drop_$1_$2 199 for drop in $droplist1 $droplist2; do 200 drop_a=`echo $drop: | cut -f1 -d:` 201 drop_p=`echo $drop: | cut -f2 -d: | tr , ' '` 202 203 if [ "$drop_a" = "$3" ]; then 204 if [ "$drop_p" ]; then 205 for port_spec in $drop_p; do 206 p0=`echo $port_spec | cut -f1 -d-` 207 p1=`echo $port_spec | cut -f2 -d-` 208 [ "$p1" -a ! "$p0" ] && p0=0 209 [ "$p0" -a ! "$p1" ] && p1=65535 210 [ $4 -ge $p0 -a $4 -le $p1 ] && return 1 211 done 212 else 213 return 1 214 fi 215 fi 216 217 [ "$ipaddr" ] || continue 218 mask=`echo $drop_a/32 | cut -f2 -d/` 219 if [ $mask -lt 32 -a $mask -ge 0 ]; then 220 if [ "$drop_a" = "`$ipaddr $3/$mask | grep CIDR | cut -f3`" ]; then 221 if [ "$drop_p" ]; then 222 for port_spec in $drop_p; do 223 p0=`echo $port_spec | cut -f1 -d-` 224 p1=`echo $port_spec | cut -f2 -d-` 225 [ "$p1" -a ! "$p0" ] && p0=0 226 [ "$p0" -a ! "$p1" ] && p1=65535 227 [ $4 -ge $p0 -a $4 -le $p1 ] && return 1 228 done 229 else 230 return 1 231 fi 232 fi 233 fi 234 done 235 return 0 236} 237 238 239# 240# Update the reference time. 241# 242update_ref(){ 243 echo $now > $ref_tm 244 touch -t `date -r $now +%Y%m%d%H%M.%S` $ref_tm 245} 246 247 248# 249# Clean termination. 250# 251bye(){ 252 echo "$prog: $1" >&2 253 rm -f $tmpfile1 $tmpfile2 $tmpfile3 254 exit 1 255} 256 257 258############################################################################### 259# 260# Main section. 261# 262prog="`basename $0`" 263 264[ `id -u` -eq 0 ] || bye "You need to be root to run this program." 265 266uname=`uname` 267[ "$uname" = "FreeBSD" -o "$uname" = "Darwin" -o "$uname" = "DragonFly"] || \ 268 bye "You can't run this program on $uname." 269 270# Check if strpdate is installed. 271[ -x "`which strpdate`" ] || \ 272 bye "Can't find 'strpdate' on this system." 273 274# See if ipaddr is installed. 275if [ -x "`which ipaddr`" ]; then 276 ipaddr="`which ipaddr`" 277else 278 echo "$prog: Warning: Can't find 'ipaddr' on this system." >&2 279fi 280 281# Suck in the config file. 282rc=/usr/local/etc/$prog.rc 283[ -e ~/.$prog.rc ] && rc=~/.$prog.rc 284[ -r $rc ] || bye "Permission denied: $rc" 285. $rc 286_logdir=$logdir 287_logbase=$logbase 288_stampfile=$stampfile 289 290# Get options. 291args=`getopt b:d:s:thv $*` 292if [ $? != 0 ]; then 293 usage 294 exit 1 295fi 296set -- $args 297for i; do 298 case $i in 299 -b) 300 logbase="$2" 301 shift; shift 302 ;; 303 -d) 304 logdir="$2" 305 shift; shift 306 ;; 307 -s) 308 stampfile="`basename $2`" 309 shift; shift 310 ;; 311 -t) 312 mode="TEST" 313 userid="0" 314 mailto="root@localhost" 315 shift 316 ;; 317 -h) 318 usage 319 exit 1 320 ;; 321 -v) 322 echo $prog version $version 323 exit 0 324 ;; 325 esac 326done 327 328# The chronologically ordered logfiles. 329_logfiles=`echo $logdir/$logbase | tr -s "/"` 330logfiles=`ls -rt $_logfiles* 2> /dev/null` 331[ "$logfiles" ] || \ 332 bye "No such logfiles found: $_logfiles*" 333 334now=`date +%s` 335year=`date -r $now +%Y` 336last_year=`expr $year - 1` 337 338# The reference time -- normally the date of the previous inspection. 339ref_tm=/var/tmp/$stampfile 340 341# If a reference time is not given set it to Unix Epoch. 342if [ ! -r $ref_tm ]; then 343 echo 0 > $ref_tm && touch -t `date -r 0 +%Y%m%d%H%M.%S` $ref_tm 344fi 345ref_date=`cat $ref_tm` 346 347# Use mktemp if possible (missing on older Darwin releases). 348if [ -x `which mktemp` ]; then 349 for i in 1 2 3; do 350 eval tmpfile$i=`mktemp /var/tmp/$prog.XXXXXX` 351 done 352else 353 for i in 1 2 3; do 354 eval tmpfile$i=/var/tmp/$prog\_$$_$i 355 eval rm -f \$tmpfile$i 356 eval touch \$tmpfile$i \&\& chmod 600 \$tmpfile$i 357 done 358fi 359 360 361# 362# Step 1 363# 364for log in $logfiles; do 365 # Skip log if it is older than 6 months. 366 [ "`ls -l $log | tr -s [:space:] | cut -f8 -d' ' | grep :`" ] || continue 367 368 # Skip log if it is older than our reference time. 369 [ $log -ot $ref_tm ] && continue 370 371 # Check for compressed/gzipped/bzipped2 or plain ASCII log data. 372 if [ "`file $log | grep compress`" ]; then 373 if [ "`file $log | grep bzip2`" ]; then 374 cat_cmd=bzcat 375 else 376 cat_cmd=zcat 377 fi 378 else 379 cat_cmd=cat 380 fi 381 382 echo -n "$prog: Reading from $log ... " 383 $cat_cmd $log | filter1 >> $tmpfile1 384 echo "done." 385done 386 387 388# Go home if no new entries have been found. 389[ -s $tmpfile1 ] || bye "Nothing to update." 390 391 392# 393# Step 2 394# 395echo -n "$prog: Processing records ... " 396filter2 < $tmpfile1 >> $tmpfile2 397echo "done." && rm $tmpfile1 398 399 400# 401# Step 3 402# 403echo -n "$prog: Formatting output ... " 404filter3 < $tmpfile2 >> $tmpfile3 405echo "done." && rm $tmpfile2 406 407 408# 409# Mail the report and clean up. 410# 411subject="FORMAT DSHIELD USERID $userid" 412[ "$mailcc" ] && mail_opts="-c $mailcc" 413[ "$mailbcc" ] && mail_opts="$mail_opts -b $mailbcc" 414[ "$sender" ] && sendmail_opts="-f $sender" 415cat $tmpfile3 | mail -s "$subject" $mail_opts $mailto $sendmail_opts && \ 416 echo "$prog: Report sent to $mailto" 417rm $tmpfile3 418[ "$mode" != "TEST" ] && update_ref 419 420exit 0 421