sac-1.9/CHANGES0100644000000000000000000002603710032107006011620 0ustar rootrootVersion 1.9 - Added Makefile option to use Called-Station-Id as hostname in radius processing. - Fixed per user octet/packet accounting when -s or -e is used. - Fixed segmentation fault in radius processing due to month array not being properly exported to gronk.c. - Added crude support for BSD operating systems. - Split sac.c into two c files (sac.c/gronk.c) and three header files (sac.h/utmp.h/proto.h). Some internal code cleanup. - Fixed problem with packet accounting and per user reporting. - Added --cutoff H[:M[:S]] and --discard H[:M[:S]] that will discard and/or cutoff time accounted if a session exceeds the amount of time specified. - Added --libc5 (-5) and --glibc (-6) options to specify libc5/glibc utmp sizes when attempting to read one when compiled for the other. (not yet implemented) Version 1.8 - Made sac recognize that shutdowns with a line of ~~ are indeed shutdowns. - Fixed bug w/ reguard to daylight savings time in Brazil, where they skip over midnight entirely during the time change. - Added --longdate option to print dates in long form. - Added -I hours[:min[:sec]] option to ignore specific amount of time before starting accounting (per person (-p) accounting only). - Added --seconds option to display time in seconds. - Properly handles null usernames in radius detail logs (reported as the username "UNKNOWN"). - Added simple fixtmp program to fixers directory. - Define in the Makefile USE_FRAMED_IP_ADDR to 1 and sac will use the Framed-IP-Address as the users hostname instead of NAS-IP-Address if present (otherwise use NAS-IP-Address). - Added support for min/max (-m) usage with per user (-p) accounting. - If the --verbose option is given, sac will print out various error messages if it encounters something amiss while processing the wtmp file. Such as an alert and approximate time stamp if it encounters a zapped (zeroed out) wtmp entry. These often indicate a hacker hiding their tracks. Logouts w/o logins also may be suspicious, but sac does not as yet report such occurances. - Added -U option to report simultaneous usage on tty lines. Useful for determining how often (-a) and when (-h) you reach peak modem usage. (Not quite sure this option is 100%. Some seconds seem to be lost or gained when compaired to the output of the -t option). - removed punctuation (,.) from output. - -l option now honors -s & -e options. - Fixed login reporting under tty accounting (-at) when -s or -e used. - Added some support for Solaris machines (*ick*). - Discovered min/max problem when processing more than one log. - Updated fixtime slightly to work better. - Added writetmp program. - Updated INSTALL and README files. - Fixed small error in -l logging where login listing could be attributed to wrong day (if login wrapped at midnight) and could be cut out with the -s option. - Added -P option to perform IP accounting with radius detail logs that log the octets and packet usage for each login. (Known to work with Ascend terminal servers). - Internal changes to support more accounting features than wtmp provides, such as those provided in radius logs. Optimized some function value passing (switched to pointer passing instead of stack). Version 1.7 - Fixed error in rawtmp where it could get stuck in an infinite loop. - Changed formatting of -p option to nicely format up to 16 character usernames (for glibc) > 16 character usernames will still screw up formatting. - Fixed error in do_reboot() where usr was not being moved forward to avoid problems with the clipping logic, which caused segmentation faults on glibc machines. - Excluded users (via -x) will not be accounted for even if listed by the -u command. - Added support for reading lists of users, excluded users, ttys and host names from files if the file name is preceded with a '@' sign. (i.e. sac -apm @/path/to/userlist) - Add support for wildcards with the -R option to select multiple portmasters more easily. (i.e. sac -apm -R "*") - Stops processing logs and reports what it has when it hits major time corruption -- to avoid endlessly processing corrupted logs. - Added fixterm.c courtesy of Thomas Roessler. - Moved fixterm.c and fixtime.c into fixers subdir with their own Makefile. - Added -a option (show contents of ut_addr field) to rawtmp. Version 1.6 - Added support for NAS-* type records in radius logs. - Fixed "-s ". Sac thought the start date was > than the end date and exited without reporting. - Added HTML/CGI interface example to scripts directory. - Fixed problem with "-" (read from stdin) for filename to -w option (sac thought it was a new option). - Added experimental fast seek option (-S). uses binary search of wtmp to position for reading just before start of date given with -s option. Fast seeking will ignore any data before the start location it finds so accounting may not be accurate. If it fails for any reason it will attempt to rewind input and start from the beginning just like normal. - Strips off leading ! on usernames in radius logs. - Strips off @hostname in radius logs - Added -D option to use @hostname (from user@host) in hostname field of radius logs instead of the portmasters hostname. - Added time format options --hms (hours:minutes:seconds) --hm (hours:minutes) --hours (hours only) and --round (round to nearest hour (--hours) or minute (--hm)). - Added -i option to support tacacs accounting which puts accounting info for multiple term servers in one log. - Fixed radius processing where last radius record would be lost due to end of file detection. - Added missing start detection to radius code. If at the beginning of a detail file, there are stop records with no start records, sac simulates the start records so all accounting information is used. Also detects and ignores extraneous stop records in the detail log. - Support for more radius detail file formats added. - Added -l option, which lists each login during the total time period, day, user or tty, depending on which report was asked for. - Included experimental "fixtime" program to remove netdate time warps by resyncing the time stamps on wtmp records. - Modified to support glibc 2.0. Version 1.5 - Changed the way sac handles -w so that it works similar to -R so you may specify multiple wtmp's (or radius detail files). The type of wtmp file or radius file is determined by its position in the command line. - Added -r option to sac to output "raw" output. Prints about everything sac knows about your wtmp. Quite verbose. Useful for a back-end to a graphics filter or accounting package. - Small fix to fix daylight savings time that broke for Brazil, possibly others. - Added --version option to sac. - Displays # of hours used for each hour in the hourly profile display. - Fixed -t option when used with -s and/or -e options. - Added some argument processing sanity checks. - Fixed hourly profile output I broke in 1.4. - Fixed -t option which would report incorrect times when using an old wtmp file. - Added support for tacacs 4.x utmp format with -X4 option. 3.4 - 3.5 format is the default with -X3 or just -X. - Made output of -am report only # of logins that are applied to total time. - Added radius support with -R [portmaster-name [portmaster-name [...]]]. The radius accounting directory is compile time definable in the Makefile. - Rawtmp: added -X[3|4] and --version options. Version 1.4 - Added wildcard support to -H and -T options, to make it easy to select many ttys or hostnames with wildcards. "*", "?", "[...]" and "[^...]" are supported. - Fixed xtacacs wtmp support, does anyone need this anymore? - Maybe, possibly, hopefully, finally fixed the damn daylight savings time problem! ARRRGHH!!! We no longer assume 24 hours in a day. - Added -d option to rawtmp to show time in a more human readable format, and made rawtmp read from stdin if "-" given as filename to -w option. - Made +0/-0 work with the -s and -e options. - Fixed "check" script to actually work properly with fractions. - Changed -t (read xtacacs format) to -X. Also changed in rawtmp. - Report by tty line (-t option), nice for looking at the usage on your modems. - Added -M option to perform accounting for specific hours of a day instead of the entire day. Version 1.3.1 - Fixed problem with -s and -e options which would not function during daylight savings time (We don't have the cursed daylight savings time in Indiana, so it took a while before I realized there really was a problem). - Small insignificant code changes. Version 1.3 - Added -f option to perform ftp login accounting in addition to normal login accounting. - Added -F option to perform ftp login accounting only. - Added some example scripts. - Fixed speeling misteaks. - Fixed error where clipping could in certain cases incorrectly report the correct amount of clipped login time while the user was stilled logged in. - Added -m option to show minimum and maximum concurrent logins. - Made sac read from stdin if "-" given as filename to -w option. - Added -H host-list to perform accounting on logins from specific hosts. - Made sac utmp size independent, for huge utmp sizes. (256 bytes for a host-name? That's gonna make wtmp HUGE! 6x as large as it is now! I already have a wtmp that grows 30MB in a month! Must be a Posix thing.) - _Finally_ made sac properly handle -s and -e options with per user accounting. STOP BOTHERING ME ABOUT IT ALREADY! =) Version 1.2 - Added -o option to handle old or broken wtmp's not created by linux itself, such as those created by the tacacs terminal server. - Added -t option to handle some ancient format that tacacs apparently uses. - Added -b for producing a determination of how much login time has precisely been used over the last few hours. Useful for determining if someone has been logged in say the last 4 hours: sac -bp 4 bubba - Added -c option to perform login clipping, thus only show how much login is really being used by counting multiple logins as only one login. - Handles xterms that work (sorta) by logging login and logout as a user process and dead process respectfully. - Added --help option to print a verbose usage listing. - Added -x user-list for excluding certain users from accounting. - Added -u user-list which is the default. - Added -T tty-list to perform accounting on only certain ttys. - Added --help, -s, -e, -b, and -t options to rawtmp. Version 1.1 - Added the -s and -e options for starting and ending dates. - Handles time changes that netdate logs in wtmp file (I think). - Fixed, err, made more robust, some possible divide by zero errors. - Throws out null wtmp entries (where do these come from?) - Corrected(?) argument parsing. - Added rawtmp command. Version 1.0 - The original, perfect as it was. sac-1.9/INSTALL0100644000000000000000000000242407317163700011667 0ustar rootrootSteps to install sac: Default installation: Installs sac, rawtmp, writetmp and wcat in /usr/local/bin and the manual pages in /usr/man/man[18]/ 0) Get the latest here: ftp://mama.indstate.edu/linux/sac/ 1) make install Custom installation: 1) edit "Makefile" - Set ARCH to SOLARIS for solaris machines, BSD for BSD based machines like FreeBSD, or LINUX for linux machines. - Change RADIUS_DIR to reflect your radius accounting files location. Example: If you're detail file is located in /usr/adm/acct/termserver/detail then set RADIUS_DIR to /usr/adm/acct, and use '-R termserver' on the command line to refer to the specific detail file. - Change DETAIL_NAME to reflect the name of your detail file. - Set USE_FRAMED_IP_ADDR option to 1 to use Framed-IP-Address for the users hostname instead of NAS-IP-Address. - Change BINDIR to put sac and rawtmp in a directory other than /usr/local/bin. - Change MANDIR to put the manuals in a different directory (such as /usr/local/man). - Change SAC_DEST, RAW_DEST, WCAT_DEST and/or WRITETMP_DEST to change the name of the sac, rawtmp and/or writetmp binaries. 2) make - Makes everything, optional. 3) make install - Makes everything if needed then installs sac. sac-1.9/Makefile0100644000000000000000000000566510032107142012272 0ustar rootroot# $Copyright: $ # Copyright (c) 1995-2001 by Steve Baker # All Rights reserved # # This software is provided as is without any express or implied # warranties, including, without limitation, the implied warranties # of merchant-ability and fitness for a particular purpose. # # Thanks to Edward S. Marshall for extending the Makefile. # CC=gcc # For Intel platforms (use -m386 w/ AMD CPU's for better speed): #CFLAGS=-Wall -O2 -fomit-frame-pointer -m486 # For everything else: #CFLAGS=-Wall -O2 # For debugging CFLAGS=-ggdb # Comment this if doing debugging. #LDFLAGS=-s # Define acording to your architechure. Only SOLARIS, BSD and LINUX are valid # thus far. Most OS's should work with one of them. # Solaris 2.x only: #ARCH=SOLARIS # FreeBSD: #ARCH=BSD # Linux libc5 / glibc 2.x: ARCH=LINUX # Radius Options: DETAIL_NAME=detail RADIUS_DIR=/usr/adm/radacct # Set to "1" if you wish to use Called-Station-Id or Framed-IP-Address for the # users hostname. Called-Station-Id takes precidence over Framed-IP-Address # if both are selected. USE_CALLED_STATION_ID=0 USE_FRAMED_IP_ADDR=0 RADOPTS=-DRADIUS_DIR=\"${RADIUS_DIR}\" -DDETAIL_NAME=\"${DETAIL_NAME}\" \ -DUSE_FRAMED_IP_ADDR=${USE_FRAMED_IP_ADDR} \ -DUSE_CALLED_STATION_ID=${USE_CALLED_STATION_ID} # General options: SAC_DEST=sac RAW_DEST=rawtmp WRITETMP_DEST=writetmp WCAT_DEST=wcat BINDIR=/usr/local/bin MANS=sac.8 rawtmp.1 writetmp.8 MANDIR=/usr/man all: sac rawtmp writetmp wcat sac: sac.o gronk.o $(CC) $(CFLAGS) $(LDFLAGS) -o $(SAC_DEST) sac.o gronk.o sac.o: sac.c sac.h utmp.h proto.h $(CC) $(CFLAGS) -D$(ARCH)=1 $(RADOPTS) -c sac.c gronk.o: gronk.c sac.h utmp.h proto.h $(CC) $(CFLAGS) -D$(ARCH)=1 $(RADOPTS) -c gronk.c rawtmp: rawtmp.o $(CC) $(LDFLAGS) -o $(RAW_DEST) rawtmp.o writetmp: writetmp.o $(CC) $(LDFLAGS) -o $(WRITETMP_DEST) writetmp.o wcat: wcat.o $(CC) $(LDFLAGS) -o $(WCAT_DEST) wcat.o rawtmp.o: rawtmp.c $(CC) $(CFLAGS) -D$(ARCH)=1 -c rawtmp.c writetmp.o: writetmp.c $(CC) $(CFLAGS) -D$(ARCH)=1 -c writetmp.c wcat.o: writetmp.c $(CC) $(CFLAGS) -D$(ARCH)=1 -c wcat.c clean: if [ -f $(SAC_DEST) ]; then rm $(SAC_DEST); fi if [ -f $(RAW_DEST) ]; then rm $(RAW_DEST); fi if [ -f $(WRITETMP_DEST) ]; then rm $(WRITETMP_DEST); fi if [ -f $(WCAT_DEST) ]; then rm $(WCAT_DEST); fi if [ -f sac.o -o -f raw.o -o -f writetmp.o -o -f wcat.o ]; then rm *.o; fi install: all install -d $(BINDIR) if [ -f $(SAC_DEST) ]; then \ install -m 0111 -s $(SAC_DEST) $(BINDIR)/$(SAC_DEST); \ fi if [ -f $(RAW_DEST) ]; then \ install -m 0111 -s $(RAW_DEST) $(BINDIR)/$(RAW_DEST); \ fi if [ -f $(WRITETMP_DEST) ]; then \ install -m 0111 -s $(WRITETMP_DEST) $(BINDIR)/$(WRITETMP_DEST); \ fi if [ -f $(WCAT_DEST) ]; then \ install -m 0111 -s $(WCAT_DEST) $(BINDIR)/$(WCAT_DEST); \ fi for man in $(MANS); do \ m=`expr match $$man '.*\(.\)'`; \ install -m 0444 $$man $(MANDIR)/man$$m/$$man; \ done VERSION=1.9 tarball: cd ..; \ tar cvzf sac-${VERSION}.tgz `cat sac-${VERSION}/.tarball` sac-1.9/README0100644000000000000000000000361007037416027011516 0ustar rootroot This is Sac, which stands for System ACcounting or maybe Steve's AC... =) This program is absolutely free and is freely distributable open source with no limitations on copying or redistribution. This software is provided as is without any express or implied warranties, including, without limitation, the implied warranties of merchant-ability and fitness for a particular purpose. Feel free to improve it if you can. There are no restrictions on in-house modifications, although I do ask that in the spirit in which sac is offered you submit back useful changes. Wtmp documentation I have really is lacking and so I may not be handling some certain cases, althought I have gone to great lengths to handle everything I know about, even time warps. A nice graphical front end for showing pretty graphs of usage would be nice, and perhaps a GUI front end for controlling it's myriad options. A wtmp decorruptor would also be nice. You can for the time being contact me via e-mail at ice@mama.indstate.edu. I'll try and help you add in new features or whatever else you need. The primary stable distribution site is: ftp://sunsite.unc.edu/pub/Linux/system/Admin/login And the primary unstable site is: ftp://mama.indstate.edu/linux/sac/ This was developed on Linux, for Linux... You're welcome to port it to other systems, but I don't have the time nor the resources to devote to troubleshooting and porting to other systems. If you wish to use this on any other unix, you're basically on your own, unless it actually pertains to the operation of sac, and not to system specific porting issues (for which there should really be none). Support for radius and tacacs terminal servers is an essential feature of sac, and if sac does not support your version of tacacs or radius, please feel free to contact me for support. - Steve Baker ice@mama.indstate.edu UNIX System Administrator sac-1.9/README.sacl0100644000000000000000000000105407037441751012441 0ustar rootroot I have setup a mailing list devoted to this and other software I have written. The list is intended as a resource for users of my software to help each other and as a means of distributing announcements of new software. If you wish to subscribe to it, e-mail majordomo@mama.indstate.edu with the following line in the body of the message: subscribe sacl [address] To unsubscribe, you would send this command to the same e-mail address unsubscribe sacl [address] All normal majordomo commands apply. Hope to see you on sacl. - Steve sac-1.9/THANKS0100644000000000000000000001365607636645720011575 0ustar rootroot Thanks go out to the following people who have helped me debug and extend sac to the pinnacle of perfection that it is. =) Ian Harris - Found bug in the way sac releases logins when EOF is reached that made clipping not perform correctly (only for logins currently active). - Suggested extending -s and -e options to allow for more precision than just days. Thus: mm/dd/yy [hh:mm[:ss]] (implemented as -M [hourrange] for the time being). Jason Lavoie - Provided tacacs wtmp to implement -t (now -X[3|4]) option to support tacacs special wtmp format. Ben Deptola - Suggested the -b option. Scott Burch - Suggested -o option to support tacacs old wtmp format. Provided wtmp for testing. Edward S. Marshall - Added functionality to the Makefile and FSSTND compliance. Thomas Pawlikaniec - Got me to implement clipping finally. - Suggested --help option. - Made C code comments reflect reality. - Suggested -x option. Erik Horn - Suggested allowing wildcards for -T option. Thomas Moore - Contributed patmatch() code for wildcarding. Marcelo ? Luis Felipe Balbinot James Murray - Helped diagnose and provided wtmp files to fix daylight savings time problem. Also bugged the hell out of me. =) Daniel Lafraia - Helped diagnose and provided wtmp files to fix daylight savings time problem. Also bugged the hell out of me. =) - Provided Cistron Radius radwtmp and detail for testing. Necessitated -C option. Jason Philbrook - Provided radius detail file and other files for helping implement radius support. Jaime Garcia Ghirelli - Provided tacacs 4.x wtmp for sac to support (via -X4) it's new utmp format. - Pointed out problem with -t option. Bruno Lopes F. Cabral - Pointed out problems with -t option when used with -s and/or -e. - Suggested --version option. - Suggested gnuplot graphs for usage. (looking into it). Jordan Mendelson - Showed me how screwed up my radius processing was. - Suggested allowing more than one wtmp on the command line (make -w option list oriented). Dave Anderson - Discovered bug with -w where cleanup() was not called after each wtmp. - Detected problem in radius processing where the last radius entry would be lost due to end of file detection. - Detected problem in output of the report when compiled with older libs and/or compilers. - Ran tests that suggest using fclose() in doitradius() and possibly unbuffering radius input reduces memory usage in sac. Ronald Wiplinger - Suggested stripping leading !'s from usernames in radius detail files. - Suggested removing @hostname from usernames in radius detail files. - Inspired -D option. Facchini Stefano (and at least one other) - Suggested to format time in H:M:S instead of fractions of hours. --hms, --hm, --hours, --round. Luis Felipe Balbinot - Suggested the -i option and provided wtmp for testing. Jon Lewis - Suggested radius processing wouldn't be complete if it didn't account for stop records with no start records at the beginning of a log. Raimundo Alba - Provided detail log entries for testing with yet another version of radius. - Suggested Makefile define for name of "detail" file for with other languages. - Suggested the -l option. Craig Sanders - provided information on changes necessary to make sac compile under glibc 2.0. Jerzy Hodor - Suggested that sac give up processing a log and just report what it has if it hits a time warp instead of trying to go ahead and deal with a bad log, which consumes major memory and CPU time. Thomas Roessler - Contributed fixterm.c source, to fix xterm logouts in wtmp file. Doug Woodward - Suggested @ includes for -u, -x, -T and -H options. - Suggested -R to easily select multiple radius logs. - Pointed out that -x should remove users specified by -u option from accounting. Bert de Bruijn - Pointed out problem with clipping causing segmentation fault and provided wtmp for testing. Tomas Pospisek - Provided radwtmp to debug Cistron radius support. - Discovered bug in rawtmp w/ reguard to radwtmp files. - Suggested I tell people where to get sac in my README and INSTALL files. - Suggested minor sub-version numbers. ARGH! Dmitry Blednov - Spotted error with -l when logins wrapped past midnight. Claudio Neves - Discovered problem with min/max output when processing more than one log at a time. Provided radius logs for testing. - Suggested -m work with -p. - Suggested -l option be able to show Framed-IP-Address as the "hostname" for radius logs instead of NAS-IP-Address. Seth Chaiklin - Discovered problem in tty reporting where # logins reported with -a option was incorrect. - Suggest removal of punctuation from output. - Suggested simultaneous tty usage option (-U) and helped debug the -U option. Charles Edinaldo Vila - Discovered bug in radius detail processing where a null username would crash sac. Null usernames are now reported with the username of "UNKNOWN". Shuman Anam - Requsted option to ignore specific amount of time before beginning accounting (-I option). Useful for this kind of accounting: First X hours: free, charge after X hours of usage: Time slots A: Tk a/min B: Tk b/min C: Tk c/min Bob Phillips - Provided new sac.cgi and sac.html files as example scripts. Works with RADIUS but can be easily modified to work with wtmp. Andre Gerhard - Discovered that shutdowns with a line of ~~ where not being considered. - Suggested the --cutoff and --discard options. Denzil Palmer - Discovered problem with packet accounting and per user reporting. Robert Dalton - Discovered multiple problems with packet accounting and per user reporting. - Supplied detail file for testing, and verified accuracy. Plus others who might have gotten lost in the heap of mail I get from all the mailing lists I subscribe to. Mostly these people just complain, so who cares eh? =) sac-1.9/TODO0100644000000000000000000000421307317163733011332 0ustar rootrootThings to do: - Actually think about doing what I put in this file. - Try to make -l work properly with mulitple wtmp's. - Split sac into multiple source files for easier maintenance. Sac has grown from 4k to ~ 70k. In progress. - Handle wtmp corruption. There are numerous reasons for this happening, including hard resets which result in lost data that was never written to the wtmp file. I believe that it would be possible to "re-sync" a wtmp file rather easily by looking for strings like "LOGIN" or "tty" or "ftp" and then backtrack until we reach the bad entry or entries and then remove them. Determining at what point wtmp is corrupted, if it really is, could be problematic. Wild changes in time could just be root playing with the clock, not true corruption, as someone may have to do when first setting up the system when the cmos clock is incorrectly set. A separate utility would be required to actually fix wtmp, as several options would no doubt be required to control how and in what direction it attempts to fix wtmp. - Make sac's output formattable so that it can be more easily used in scripts for web stuff and to improve the resolution of the time shown (since sac maintains login time to the last second, might as well be able to show it). Also would make it easier to plug into a web interface. [Sort of obsoleted by --hms, etc] - Replace the -M option with something that would allow outputting multiple time periods for the same day, so that you need only one pass over wtmp to get all your billing info. Perhaps not replace -M, but make multiple -M's acceptable: > sac -M 0-7 -M 8-18 -M 19-24 bubba Total [00-07]: 15.51 over 31 days. Total [08-18]: 3.02 over 31 days. Total [19-24]: 119.60 over 31 days. How to format this would be a problem. Exspecially with reguard to the daily usage option. Perhaps even making the -M option consider the day of week would be usefull (Does anyone charge differently during weekends?). - Make the -b option read the wtmp file backwards to improve it's speed significantly, making it ideal for clobbering time hogs. - Add more features!! sac-1.9/gronk.c0100644000000000000000000005570410032107134012116 0ustar rootroot/* $Copyright: $ * Copyright (c) 1995 - 2001 by Steve Baker (ice@mama.indstate.edu) * All Rights reserved * * This software is provided as is without any express or implied * warranties, including, without limitation, the implied warranties * of merchant-ability and fitness for a particular purpose. */ #undef MAIN #include "sac.h" /* extern char **month; extern char radhost, hosttoo, verbose; extern int sd, ed; extern time_t lastent; extern signed int sm; extern char *buf; */ #define FUDGE_FACTOR 3600 /* 1 hour */ void try_fastseek(int fd, struct file *f) { struct utmp start, end, cur; struct stat st; u_long s, e, m, t; if (f->ftype == ACCT_RADIUS || fd == 0) return; if (fstat(fd,&st) < 0) return; if ((st.st_size % sizeof(struct utmp)) != 0) return; if (lseek(fd,0,SEEK_SET) < 0) return; if (read(fd,&start,sizeof(struct utmp)) != sizeof(struct utmp)) return restore(fd); if ((t = lseek(fd,-sizeof(struct utmp),SEEK_END)) < 0) return restore(fd); if (read(fd,&end,sizeof(struct utmp)) != sizeof(struct utmp)) return restore(fd); s = 0; e = t/sizeof(struct utmp); m = e/2; if (!sd) { if (!sm) return restore(fd); } if (start.ut_time >= sd) return restore(fd); while(s < e) { if (lseek(fd,m*sizeof(struct utmp),SEEK_SET) < 0) return restore(fd); if (read(fd,&cur,sizeof(struct utmp)) < 0) return restore(fd); if (cur.ut_time < sd) { if (cur.ut_time+FUDGE_FACTOR >= sd) return; s = m; m += (e-s)/2; } else { e = m; m -= (e-s)/2; } } } void restore(int fd) { if (verbose) fprintf(stderr,"sac: fastseek failed, rewinding input.\n"); lseek(fd,0,SEEK_SET); return; } #ifdef USER_PROCESS void gronk_sysv(int fd) { struct sactmp su; struct utmp *u = &su.u; void *ut = &su.u; int n, m; lastent = 0; while (1) { for(n=m=read(fd,ut,sizeof(struct utmp));n < (int)sizeof(struct utmp);n+=m=read(fd,ut+n,sizeof(struct utmp)-n)) { if (m < 0) { perror("sac"); return; } if (!m) return; } /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */ if (u->ut_time == 0) { time_t t,h,m,s; if (!end) { if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n"); continue; } t = lastent - end->start; h=t/3600; m=(t-(h*3600))/60; s=t-(h*3600)-(m*60); if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s); continue; } lastent = u->ut_time; /* Do we have this day allocated? */ if (checkday(u->ut_time) < 0) { u->ut_time = lastent; do_reboot(u->ut_time); if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n"); return; } /* Q: Why does the following bother me? */ /* A: It may not handle all possible cases. Wtmp documentation sucks. * Programs are also pretty free to put whatever the hell they want * in wtmp. */ if (u->ut_line[0]) { if (u->ut_user[0]) { if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strncmp("shutdown",u->ut_user,8))) do_reboot(u->ut_time); else if (u->ut_type == USER_PROCESS && strncmp("ftp",u->ut_line,3)) saclogin(su); else if (u->ut_type == DEAD_PROCESS && strncmp("ftp",u->ut_line,3)) saclogout(su,FALSE); else if (u->ut_type == LOGIN_PROCESS) saclogout(su,FALSE); } else { if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line); else saclogout(su,FALSE); } } } } #endif /* * Do it the bsd way, where u.ut_type is invalid or doesn't get used or * doesn't even exist. * This is also for non-standard wtmp's kept by programs such as tacacs. * Note: Versions of Xterm may write a wtmp entry on exit that looks like * a login entry if we ignore the ut_type field (which is "dead * process" in this case). This will obviously screw things up. * * This may not be 100%. */ void gronk_bsd(int fd) { struct sactmp su; struct utmp *u = &su.u; void *ut = &su.u; int n, m; lastent = 0; while (1) { for(n=m=read(fd,ut,sizeof(struct utmp));n < (int)sizeof(struct utmp);n+=m=read(fd,ut+n,sizeof(struct utmp)-n)) { if (m < 0) { perror("sac"); return; } if (!m) return; } /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */ if (u->ut_time == 0) { time_t t,h,m,s; if (!end) { if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n"); continue; } t = lastent - end->start; h=t/3600; m=(t-(h*3600))/60; s=t-(h*3600)-(m*60); if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s); continue; } lastent = u->ut_time; if (checkday(u->ut_time) < 0) { u->ut_time = lastent; do_reboot(u->ut_time); if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n"); return; } if (u->ut_line[0]) { if (u->ut_user[0]) { if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strncmp("shutdown",u->ut_user,8))) do_reboot(u->ut_time); else if (strcmp("LOGIN",u->ut_user) && strcmp("~",u->ut_line) && strncmp("ftp",u->ut_line,3)) saclogin(su); else saclogout(su,FALSE); } else { if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line); else saclogout(su,FALSE); } } } } /* * Do it the tacacs 3.4-3.5 way, with tacacs ancient utmp format, which may work * for other ancient (such as old BSD) programs. * This too may not be 100%. */ void gronk_tacacs3(int fd) { struct tacacs3_utmp t; struct sactmp su; struct utmp *u = &su.u; void *ut = &t; int n, m; lastent = 0; while (1) { for(n=m=read(fd,ut,sizeof(struct tacacs3_utmp));n < (int)sizeof(struct tacacs3_utmp);n+=m=read(fd,ut+n,sizeof(struct tacacs3_utmp)-n)) { if (m < 0) { perror("sac"); return; } if (!m) return; } /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */ if (u->ut_time == 0) { time_t t,h,m,s; if (!end) { if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n"); continue; } t = lastent - end->start; h=t/3600; m=(t-(h*3600))/60; s=t-(h*3600)-(m*60); if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s); continue; } lastent = u->ut_time; /* put tacacs_utmp into a normal utmp for the rest of this */ u->ut_time = t.ut_tactime; strncpy(u->ut_line,t.ut_line,8); strncpy(u->ut_user,t.ut_user,8); strncpy(u->ut_host,t.ut_host,16); /* u->ut_line[8] = u->ut_user[8] = 0; */ if (checkday(u->ut_time) < 0) { u->ut_time = lastent; do_reboot(u->ut_time); if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n"); return; } if (u->ut_line[0]) { if (u->ut_user[0]) { if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strncmp("shutdown",u->ut_user,8))) do_reboot(u->ut_time); else if (strcmp("LOGIN",u->ut_user) && strcmp("~",u->ut_line) && strncmp("ftp",u->ut_line,3)) saclogin(su); else saclogout(su,FALSE); } else { if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line); else saclogout(su,FALSE); } } } } /* * Do it the new 4.x xtacacs way... Added the comment field -- 16 bytes to store * the PID? * This too may not be 100%. Should be merged with above. */ void gronk_tacacs4(int fd) { struct tacacs4_utmp t; struct sactmp su; struct utmp *u = &su.u; void *ut = &t; int n, m; lastent = 0; while (1) { for(n=m=read(fd,ut,sizeof(struct tacacs4_utmp));n < (int)sizeof(struct tacacs4_utmp);n+=m=read(fd,ut+n,sizeof(struct tacacs4_utmp)-n)) { if (m < 0) { perror("sac"); return; } if (!m) return; } /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */ if (u->ut_time == 0) { time_t t,h,m,s; if (!end) { if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n"); continue; } t = lastent - end->start; h=t/3600; m=(t-(h*3600))/60; s=t-(h*3600)-(m*60); if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s); continue; } lastent = u->ut_time; /* put tacacs_utmp into a normal utmp for the rest of this */ u->ut_time = t.ut_tactime; strncpy(u->ut_line,t.ut_line,8); strncpy(u->ut_user,t.ut_user,8); strncpy(u->ut_host,t.ut_host,16); /* u->ut_line[8] = u->ut_user[8] = 0; */ if (checkday(u->ut_time) < 0) { u->ut_time = lastent; do_reboot(u->ut_time); if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n"); return; } if (u->ut_line[0]) { if (u->ut_user[0]) { if (u->ut_user[0] == '?') saclogout(su,FALSE); else if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strncmp("shutdown",u->ut_user,8))) do_reboot(u->ut_time); else if (strcmp("LOGIN",u->ut_user) && strcmp("~",u->ut_line) && strncmp("ftp",u->ut_line,3)) saclogin(su); else saclogout(su,FALSE); } else { if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line); saclogout(su,FALSE); } } } } /* * Perform accounting on ftp logins only. This handles ftp entries generated * from wu-ftpd at least. */ void gronk_ftp(int fd) { struct sactmp su; struct utmp *u = &su.u; void *ut = &su.u; int n, m; lastent = 0; while (1) { for(n=m=read(fd,ut,sizeof(struct utmp));n < (int)sizeof(struct utmp);n+=m=read(fd,ut+n,sizeof(struct utmp)-n)) { if (m < 0) { perror("sac"); return; } if (!m) return; } /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */ if (u->ut_time == 0) { time_t t,h,m,s; if (!end) { if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n"); continue; } t = lastent - end->start; h=t/3600; m=(t-(h*3600))/60; s=t-(h*3600)-(m*60); if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s); continue; } lastent = u->ut_time; if (checkday(u->ut_time) < 0) { u->ut_time = lastent; do_reboot(u->ut_time); if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n"); return; } if (u->ut_line[0]) { if (u->ut_user[0]) { if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strcmp("shutdown",u->ut_user))) do_reboot(u->ut_time); else if (!strncmp("ftp",u->ut_line,3)) saclogin(su); } else { if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line); else if (!strncmp("ftp",u->ut_line,3)) saclogout(su,FALSE); } } } } /* * Perform accounting on both normal logins and ftp simultaneously. */ #ifdef USER_PROCESS void gronk_both(int fd) { struct sactmp su; struct utmp *u = &su.u; void *ut = &su.u; int n, m; lastent = 0; while (1) { for(n=m=read(fd,ut,sizeof(struct utmp));n < (int)sizeof(struct utmp);n+=m=read(fd,ut+n,sizeof(struct utmp)-n)) { if (m < 0) { perror("sac"); return; } if (!m) return; } /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */ if (u->ut_time == 0) { time_t t,h,m,s; if (!end) { if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n"); continue; } t = lastent - end->start; h=t/3600; m=(t-(h*3600))/60; s=t-(h*3600)-(m*60); if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s); continue; } lastent = u->ut_time; if (checkday(u->ut_time) < 0) { u->ut_time = lastent; do_reboot(u->ut_time); if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n"); return; } if (u->ut_line[0]) { if (u->ut_user[0]) { if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strncmp("shutdown",u->ut_user,8))) do_reboot(u->ut_time); else if (u->ut_type == USER_PROCESS || !strncmp("ftp",u->ut_line,3)) saclogin(su); else if (u->ut_type == DEAD_PROCESS) saclogout(su,FALSE); else if (u->ut_type == LOGIN_PROCESS) saclogout(su,FALSE); } else { if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line); else saclogout(su,FALSE); } } } } #else void gronk_both(int fd) { struct sactmp su; struct utmp *u = &su.u; void *ut = &su.u; int n, m; lastent = 0; while (1) { for(n=m=read(fd,ut,sizeof(struct utmp));n < (int)sizeof(struct utmp);n+=m=read(fd,ut+n,sizeof(struct utmp)-n)) { if (m < 0) { perror("sac"); return; } if (!m) return; } /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */ if (u->ut_time == 0) { time_t t,h,m,s; if (!end) { if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n"); continue; } t = lastent - end->start; h=t/3600; m=(t-(h*3600))/60; s=t-(h*3600)-(m*60); if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s); continue; } lastent = u->ut_time; if (checkday(u->ut_time) < 0) { u->ut_time = lastent; do_reboot(u->ut_time,FALSE); if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n"); return; } if (u->ut_line[0]) { if (u->ut_user[0]) { if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strncmp("shutdown",u->ut_user,8))) do_reboot(u->ut_time,FALSE); else if (strcmp("LOGIN",u->ut_user) && strcmp("~",u->ut_line)) saclogin(su,FALSE); else saclogout(su,FALSE); } else { if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line); else saclogout(su,FALSE); } } } } #endif /* Modify this to be as large as you like. */ /* The linux kernel may give a bias towards 32k */ #define RADIUS_BUFFER_SIZE 32768 #define RADIUS_HASH_SIZE 256 #define radhash(x) (x%RADIUS_HASH_SIZE) struct ltable { int port; struct utmp last; struct ltable *nxt; } *ltable[RADIUS_HASH_SIZE]; /* * Perform accounting for radius log files for livingston portmasters. */ void gronk_radius(int fd) { struct sactmp su; struct utmp *u = &su.u; struct ltable *lp, *cp; FILE *rfd; int r; static char buffer[RADIUS_BUFFER_SIZE]; /* I don't want to re-invent the wheel, until I know it works, cause no one may want to use this anyway. */ rfd = fdopen(fd,"r"); /* This seems to make things fast enough for most people. Faster by far than perl */ setvbuf(rfd,buffer,_IOFBF,RADIUS_BUFFER_SIZE); /* Comment out above to reduce sac memory usage. */ for(r=0;rut_time) < 0) { u->ut_time = lastent; do_reboot(u->ut_time); if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry (before %.24s), possible detail corruption.\n",ctime(&lastent)); return; } if (u->ut_line[0]) { if (u->ut_user[0]) saclogin(su); else saclogout(su,TRUE); } lastent = u->ut_time; } while(r >= 0); for(r=0;rnxt; free(lp); } } fclose(rfd); } /* * Reads a radius entry. * * Radius logs suck suck suck... It would be nice if you had the option of * storing the logs in a nice machine readable format since it would probably * make cutting through the log 100 times faster at least. * * There are at least 5 different radius detail log formats, all slightly * different. Sac tries to support them all. * Using the Acct-Session-Time field is problematic, although not impossible, * because at least some radius entries will sometimes write multiple stop * records for a single logout. The only way to detect this is to check the * current record against the last (for that particular port) and if it * matches, ignore it. * * In this function we read forward until we get to a date entry, then parse * the date, and read the info block. */ int read_radius(FILE *rfd, struct sactmp *su) { char **s, name[UT_NAMESIZE]; int n, i, r; struct tm t; time_t st; struct sactmp tu; struct ltable *lp; int port; memset(su,0,sizeof(struct sactmp)); while(fgets(buf,1024,rfd) != NULL) { if (buf[0] == '\n' || buf[0] == ' ') continue; s = split(buf, " \f\n\r\t\v:\"", &n); if (n < 4) continue; if (n == 7) { for(i=0;i<12;i++) if (!strcmp(s[1],month[i])) t.tm_mon = i; t.tm_mday = stoi(s[2]); t.tm_hour = stoi(s[3]); t.tm_min = stoi(s[4]); t.tm_sec = stoi(s[5]); t.tm_year = stoi(s[6]) - 1900; t.tm_isdst = -1; } else if (n == 4){ s[0][2] = 0; t.tm_mday = stoi(s[0]); s[0][5] = 0; t.tm_mon = stoi(s[0]+3)-1; t.tm_year = stoi(s[0]+6) - 1900; t.tm_hour = stoi(s[1]); t.tm_min = stoi(s[2]); t.tm_sec = stoi(s[3]); t.tm_isdst = -1; } su->u.ut_time = mktime(&t); r = read_block(rfd,su,&port,&st,name); /* printf("%ld: [%-8.8s] %-8.8s %.16s\n",su->u.ut_time,su->u.ut_user,su->u.ut_line,su->u.ut_host); */ for(lp=ltable[radhash(port)];lp;lp=lp->nxt) if (lp->port == port) break; if (st) { struct user *p; for(p=usr;p;p=p->nxt) if (!strncmp(su->u.ut_line,p->line,UT_LINESIZE) && (hosttoo? !strncmp(su->u.ut_host,p->host,UT_HOSTSIZE) : TRUE)) goto found_one; if (lp && !strncmp(lp->last.ut_user,su->u.ut_user,UT_NAMESIZE) && (hosttoo? !strncmp(lp->last.ut_host,su->u.ut_host,UT_HOSTSIZE) : TRUE)) { /* printf("Extraneous stop ignored.\n"); */ goto found_one; } /* printf("Stop entry with no start [%ld seconds]:\n",st); printf("%ld: [%-8.8s] %-8.8s %.16s\n",su->u.ut_time,name,su->u.ut_line,su->u.ut_host); */ tu.u.ut_time = su->u.ut_time - st; strncpy(tu.u.ut_user,name,UT_NAMESIZE); strncpy(tu.u.ut_host,su->u.ut_host,UT_HOSTSIZE); strncpy(tu.u.ut_line,su->u.ut_line,UT_LINESIZE); if (!checkday(tu.u.ut_time)) saclogin(tu); } found_one: if (!lp) { lp = bmalloc(sizeof(struct ltable)); lp->port = port; lp->nxt = ltable[radhash(port)]; ltable[radhash(port)] = lp; } memcpy(&lp->last,&su->u,sizeof(struct utmp)); if (r < 0) return -1; if (r == 0) continue; return 0; } return -1; } enum { USER_NAME, CLIENT_ID, CLIENT_PORT_ID, ACCT_STATUS_TYPE, ACCT_SESSION_TIME, ACCT_INPUT_OCTETS, ACCT_OUTPUT_OCTETS, ACCT_INPUT_PACKETS, ACCT_OUTPUT_PACKETS, DATA_RATE, FRAMED_IP_ADDRESS, CALLED_STATION_ID, USELESS }; /* This ought to be initialized into a tree or hash table for speed. */ struct radius_str { char *id; char use; } radius_strs[] = { {"User-Name", USER_NAME}, {"Client-Id", CLIENT_ID}, {"NAS-IP-Address", CLIENT_ID}, {"NAS-Identifier", CLIENT_ID}, {"Client-Port-Id", CLIENT_PORT_ID}, {"NAS-Port", CLIENT_PORT_ID}, {"NAS-Port-Id", CLIENT_PORT_ID}, {"Acct-Status-Type", ACCT_STATUS_TYPE}, {"Acct-Session-Time", ACCT_SESSION_TIME}, {"Acct-Input-Octets", ACCT_INPUT_OCTETS}, {"Acct-Output-Octets", ACCT_OUTPUT_OCTETS}, {"Acct-Input-Packets", ACCT_INPUT_PACKETS}, {"Acct-Output-Packets", ACCT_OUTPUT_PACKETS}, {"Ascend-Data-Rate", DATA_RATE}, {"Framed-IP-Address", FRAMED_IP_ADDRESS}, {"Called-Station-Id", CALLED_STATION_ID}, {NULL, USELESS} }; /* * Check the ID strings in the block against those above, if it's one of them, * then process it. Exit when we hit a blank line. */ int read_block(FILE *rfd, struct sactmp *su, int *port, time_t *st, char *name) { char **s, *p, stopflg; int i, n, flg = FALSE; int framed = FALSE, callid = FALSE; *st = 0; for(stopflg = FALSE;fgets(buf,1024,rfd) != NULL;) { if (buf[0] == '\n') { if (stopflg) { strncpy(name,su->u.ut_user,UT_NAMESIZE); su->u.ut_user[0] = 0; } return 1; } s = split(buf, " \f\n\r\t\v:\"", &n); for(i=0;radius_strs[i].id;i++) if (!strcmp(radius_strs[i].id,s[0])) switch(radius_strs[i].use) { case USER_NAME: if (s[2] == NULL) { strncpy(su->u.ut_user,"UNKNOWN",UT_NAMESIZE); break; } if (s[2][0] == '!') s[2]++; else if ((p=index(s[2],'@'))) { *p++ = 0; if (radhost) strncpy(su->u.ut_host,p,UT_HOSTSIZE); } strncpy(su->u.ut_user,s[2],UT_NAMESIZE); flg = TRUE; break; #if USE_CALLED_STATION_ID != 0 case CALLED_STATION_ID: strncpy(su->u.ut_host,s[2],UT_HOSTSIZE); callid = TRUE; break; #endif #if USE_FRAMED_IP_ADDR != 0 case FRAMED_IP_ADDRESS: if (!radhost && !callid) strncpy(su->u.ut_host,s[2],UT_HOSTSIZE); framed = TRUE; break; #endif case CLIENT_ID: if (!framed && !radhost && !callid) strncpy(su->u.ut_host,s[2],UT_HOSTSIZE); break; case CLIENT_PORT_ID: *port = stoi(s[2]); strncpy(su->u.ut_line,s[2],UT_LINESIZE); break; case ACCT_STATUS_TYPE: if (!strcmp("Stop",s[2])) stopflg = TRUE; break; case ACCT_SESSION_TIME: *st = stoi(s[2]); break; case ACCT_INPUT_OCTETS: su->inoct = (double)stoi(s[2]); break; case ACCT_OUTPUT_OCTETS: su->outoct = (double)stoi(s[2]); break; case ACCT_INPUT_PACKETS: su->inpkt = (double)stoi(s[2]); break; case ACCT_OUTPUT_PACKETS: su->outpkt = (double)stoi(s[2]); break; case DATA_RATE: su->datarate = stoi(s[2]); break; } } if (stopflg) { strncpy(name,su->u.ut_user,UT_NAMESIZE); su->u.ut_user[0] = 0; } if (flg) return 1; return -1; } /* * split the line into words broken by whitespace, colons, and quotes */ /* char **split(s, n) char *s; int *n; { static char *w[256]; int i; for(i=0;*s;) { while (*s && (isspace(*s) || *s == ':' || *s == '"')) s++; if (!*s) break; w[i++] = s; while (*s && !isspace(*s) && *s != ':' && *s != '"') s++; if (!*s) break; *s = 0; s++; } w[*n = i] = NULL; return w; } */ char **split(char *str, char *delim, int *nwrds) { static char *w[256]; *nwrds = 0; w[*nwrds = 0] = strtok(str,delim); while (w[*nwrds] && *nwrds < 99) w[++(*nwrds)] = strtok(NULL,delim); w[*nwrds] = NULL; return w; } /* * This will probably not be supported unless there is some demand for it. */ void gronk_radiuslogfile(int fd) { } /* * A simple atoi, for speed. * Atoi() might actually be faster. Ought to test that. */ u_long stoi(char *s) { u_long n,i; if (s[1] == 0) { return s[0] - '0'; } else if (s[2] == 0) { return ((s[0]-'0')*10) + (s[1]-'0'); } else { for(i=n=0;s[i];i++) n = (n*10) + (s[i]-'0'); return n; } } sac-1.9/proto.h0100644000000000000000000000361407636443022012156 0ustar rootroot/* sac.c */ int main(int argc, char **argv); void usage(int n); struct file *parseradiusdir(char *base, char *pattern, char *file); time_t getsacdate(char *s); time_t churn_time(char *s); u_long hourrangespec(char *s); struct simuse *usagespec(char *t); int checkday(time_t t); struct day *mkday(time_t t); struct day *mkday(time_t t); void saclogin(struct sactmp su); void saclogout(struct sactmp su, int usext); void do_reboot(time_t t); void changetime(time_t t, char *line); void cleanup(void); void release(struct user *u, time_t t, struct sactmp *su, int usext, int siminc); void addlogin(struct login **login, struct login **last, time_t in, time_t out, char *name, char *tty, char *host); void apply_hours(time_t in, time_t out,time_t start, time_t h[24]); void user_apply_hours(struct user *u, time_t out, struct day *d); void tty_apply_hours(time_t in, time_t out, struct day *d, struct tty *t); char *getwrd(char *s); struct usr *adduser(struct usr **up, char *s); struct usr *finduser(struct usr *up, char *s); void addexclude(char *s); int isexcluded(char *s); struct tty *findtty(struct tty *tt, char *l); void addtty(char *s); int istty(char *s); void addhost(char *s); int ishost(char *s); struct simuse *findsim(struct simuse **s, int i); int ispattern(char *s); int patmatch(char *sbuf, char *pat); void *bmalloc(size_t s); void report(void); void print_hours(time_t h[24], time_t total); char *dtime(time_t t, int d); /* gronk.c */ void try_fastseek(int fd, struct file *f); void restore(int fd); void gronk_sysv(int fd); void gronk_bsd(int fd); void gronk_tacacs3(int fd); void gronk_tacacs4(int fd); void gronk_ftp(int fd); void gronk_both(int fd); void gronk_radius(int fd); int read_radius(FILE *rfd, struct sactmp *su); int read_block(FILE *rfd, struct sactmp *su, int *port, time_t *st, char *name); char **split(char *str, char *delim, int *nwrds); void gronk_radiuslogfile(int fd); u_long stoi(char *s); sac-1.9/rawtmp.10100644000000000000000000000531006475066005012233 0ustar rootroot.\" $Copyright: $ .\" Copyright (c) 1995, 1996, 1997, 1998 by Steve Baker (ice@mama.indstate.edu) .\" All Rights reserved .\" .\" This software is provided as is without any express or implied .\" warranties, including, without limitation, the implied warranties .\" of merchant-ability and fitness for a particular purpose. .\" ... .V= $Header: rawtmp.1 1.4 1998 $ .TH RAWTMP 1 "\*(V)" "UNIX Manual" .SH NAME rawtmp \- display wtmp entries in raw form. .SH SYNOPSIS \fBrawtmp\fP [\fB-da\fP] [\fB-w\fP \fIwtmp\fP|\fB-\fP] [\fB-X[3|4]d\fP] [\fB-s\fP \fIstart\fP] [\fB-e\fP \fIend\fP] [\fB-b\fP \fIH:M:S\fP] [\fB--help\fP] [\fB--version\fP] .br .SH DESCRIPTION \fIRawtmp\fP is a utility to dump the raw data in a wtmp or utmp file to the screen for viewing. It may be useful to anyone who wishes to divine the nature of the data stored in the wtmp or utmp files. It may also be useful to extract special wtmp entries that are not documented anywhere (like those netdate puts in the wtmp file). If on a logout, when the username is encoded in the ut_user field by replacing the first character of the username with a null, rawtmp will print the contents of the user field with a leading dot '.' to denote the null character. Only \fIagetty\fP and \fItacacs\fP control software are currently known to use this logging method. .br .SH OPTIONS \fIRawtmp\fP understands the following command line switches: .TP \fB--help\fP Outputs a verbose usage listing. .PP .TP \fB--version\fP Displays the version of rawtmp. .PP .TP \fB-w\fP \fIwtmp\fP Select a different input file instead of the default (\fI/var/log/wtmp\fP). .PP .TP \fB-X[3]\fP Read a wtmp file maintained by versions 3.3 or 3.4 Tacacs terminal server access control software. .PP .TP \fB-X4\fP Read a wtmp file maintained by version 4.0 of Tacacs terminal server access control software. .PP .TP \fB-d\fP Output the time in MMM DD HH:MM:SS format instead of raw time for a more human readable form (and to actually know what day you're looking at!). .PP .TP \fB-a\fP Print the contents of the ut_addr field (in quad-dotted notation) instead of using the ut_host field. Note: ut_addr is almost never used and more than likely contains garbage information. .PP .TP \fB-b\fP \fIhours\fP[\fI:minutes\fP[\fI:seconds\fP]] Consider only those utmp entries that fall within the last few hours/minutes/seconds from the current time, disregarding the rest. .PP .TP \fB-s\fP \fIstart\fP Selects the starting date of the report, in mm/dd/yy format. .PP .TP \fB-e\fP \fIend\fP Selects the ending date of the report, in mm/dd/yy format. .PP .SH FILES /var/log/wtmp login database. .SH AUTHOR Steve Baker (ice@mama.indstate.edu) .SH BUGS Could use some filtering options. .SH SEE ALSO .BR last (1), .BR sac (8) sac-1.9/rawtmp.c0100644000000000000000000002305707261702312012315 0ustar rootroot/* $Copyright: $ * Copyright (c) 1995, 1996, 1997, 1998 by Steve Baker (ice@mama.indstate.edu) * All Rights reserved * * This software is provided as is without any express or implied * warranties, including, without limitation, the implied warranties * of merchant-ability and fitness for a particular purpose. */ #ifdef SOLARIS #include #else #include #endif #include #include #include #include #include #include #include #include #include #ifdef SOLARIS #define UT_NAMESIZE 32 #define UT_LINESIZE 32 #define UT_HOSTSIZE 257 #define UT_UNKNOWN 0 #define utmp utmpx #define ut_time ut_tv.tv_sec #endif extern int errno; static char *version = "$Version: $ rawtmp v1.7 (c) 1995, 1996, 1997, 1998 by Steve Baker $"; static char *month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; #ifdef SOLARIS #define _PATH_WTMP WTMPX_FILE #else #ifndef _PATH_WTMP #define _PATH_WTMP "/var/log/wtmp" #endif #endif enum { FALSE=0, TRUE=1 }; struct tacacs3_utmp { char ut_line[8]; char ut_user[8]; char ut_host[16]; time_t ut_tactime; }; struct tacacs4_utmp { char ut_line[8]; char ut_user[8]; char ut_host[16]; time_t ut_tactime; char ut_comment[16]; }; char *num(), *host(), *user(); void usage(), doit(), doit_tacacs3(), doit_tacacs4(); time_t getsacdate(), churn_time(); char dflg = FALSE, useaddr = FALSE; char *start = NULL, *end = NULL, *back = NULL; time_t sd = 0, ed = 0, backtime = 0, curtime= 0; int fd; /* * rawtmp [-w wtmp] [-adX[3|4]] [-s start] [-e end] [-b hours:minutes:seconds] * [--help] [--version] */ int main(int argc, char **argv) { char *file = _PATH_WTMP, tacacs3 = FALSE, tacacs4 = FALSE; int i, j, n; for(n=i=1;i ed) continue; c = u.ut_id[0] & 0x7F; u.ut_id[0] = (c && (c > '~' || c < ' '))? '?' : c; c = u.ut_id[1] & 0x7F; u.ut_id[1] = (c && (c > '~' || c < ' '))? '?' : c; if (dflg) { tm = localtime(&u.ut_time); printf("%3s %02d %02d:%02d:%02d: ", month[tm->tm_mon], tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } else printf ("%-10ld: ",u.ut_time); #ifdef SOLARIS printf("%-8.8s [%-2.2s] %-12.12s %-16.16s [%5d] %s\n", user(u.ut_user),u.ut_id,u.ut_line,u.ut_host,u.ut_pid & 0xFFFF, (u.ut_type>0 && u.ut_type<9?ltype[u.ut_type]:num(u.ut_type))); #else printf("%-8.8s [%-2.2s] %-12.12s %-16.16s [%5d] %s\n", user(u.ut_user),u.ut_id,u.ut_line,host(u.ut_host,u.ut_addr),u.ut_pid & 0xFFFF, (u.ut_type>0 && u.ut_type<9?ltype[u.ut_type]:num(u.ut_type))); #endif /* printf("%-8.8s [%-2.2s] %-12.12s %3d.%03d.%03d.%03d [%5d] %s\n", u.ut_user,u.ut_id,u.ut_line, u.ut_addr&0xFF,(u.ut_addr>>8)&0xFF,(u.ut_addr>>16)&0xFF,(u.ut_addr>>24)&0xFF, u.ut_pid & 0xFFFF,(u.ut_type>0 && u.ut_type<9?ltype[u.ut_type]:num(u.ut_type))); */ } } void doit_tacacs3() { struct tm *tm; struct tacacs3_utmp u; void *ut = &u; int n, m; if (dflg) printf(" "); printf(" Time User Line Host\n"); while (1) { for(n=m=read(fd,ut,sizeof(struct tacacs3_utmp));n < (int)sizeof(struct tacacs3_utmp);n+=m=read(fd,ut+n,sizeof(struct tacacs3_utmp)-n)) { if (m < 0) { perror("rawtmp"); return; } if (!m) return; } if (back && u.ut_tactime < backtime) continue; if (start && u.ut_tactime < sd) continue; if (end && u.ut_tactime > ed) continue; if (dflg) { tm = localtime(&u.ut_tactime); printf("%3s %02d %02d:%02d:%02d: ", month[tm->tm_mon], tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } else printf ("%-10ld: ",u.ut_tactime); printf("%-8.8s [%-12.12s] %-16.16s\n", user(u.ut_user),u.ut_line,u.ut_host); } } void doit_tacacs4() { struct tm *tm; struct tacacs4_utmp u; void *ut = &u; int n, m; if (dflg) printf(" "); printf(" Time User Line Host Comment\n"); while (1) { for(n=m=read(fd,ut,sizeof(struct tacacs4_utmp));n < (int)sizeof(struct tacacs4_utmp);n+=m=read(fd,ut+n,sizeof(struct tacacs4_utmp)-n)) { if (m < 0) { perror("rawtmp"); return; } if (!m) return; } if (back && u.ut_tactime < backtime) continue; if (start && u.ut_tactime < sd) continue; if (end && u.ut_tactime > ed) continue; if (dflg) { tm = localtime(&u.ut_tactime); printf("%3s %02d %02d:%02d:%02d: ", month[tm->tm_mon], tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } else printf ("%-10ld: ",u.ut_tactime); printf("%-8.8s [%-12.12s] %-16.16s %-16.16s\n", user(u.ut_user),u.ut_line,u.ut_host,u.ut_comment); } } char *num(n) unsigned short n; { static char nbuf[30]; sprintf(nbuf,"UNKNOWN (%d)",n); return nbuf; } /* * Turn "mm/dd/yy" into time in seconds. */ time_t getsacdate(s) char *s; { struct tm tm; int y; time_t t; /* * * Need a real date parser that can handle different formats and separators */ if (!isdigit(s[0]) || !isdigit(s[1]) || isdigit(s[2])) usage(2); if (!isdigit(s[3]) || !isdigit(s[4]) || isdigit(s[5])) usage(2); if (!isdigit(s[6]) || !isdigit(s[7]) || s[8]) usage(2); tm.tm_mon = atoi(s) -1; tm.tm_mday = atoi(s+3); y = atoi(s+6); tm.tm_year = (y < 70? 100 + y : y); tm.tm_isdst = tm.tm_hour = tm.tm_min = tm.tm_sec = 0; t = mktime(&tm); if (t == (time_t)(-1)) usage(2); return t; } time_t churn_time(s) char *s; { time_t t = 0, mult=3600; u_char nbuf[20], p; for(p=0;*s;s++) { if (*s == ':') { nbuf[p] = (u_char)0; t += atoi(nbuf) * mult; p = 0; if (mult > 1) mult /= 60; else usage(3); } else if (isdigit(*s)) { if (p < 15) nbuf[p++] = (u_char)*s; else usage(3); } else usage(3); } nbuf[p] = (u_char)0; t += atoi(nbuf) * mult; return t; } char *host(h,a) char *h; u_long a; { static char buf[20]; if (!useaddr) return h; sprintf(buf,"%d.%d.%d.%d",(int)(a&255),(int)((a>>8)&255),(int)((a>>16)&255),(int)(a>>24)); return buf; } char *user(s) char *s; { static char ubuf[10]; int i; if (*s || (!*s && !*(s+1))) return s; for(i=0;i/detail each in turn until all the detail files have been processed. If no portmaster name is given, a detail file must be specified with the `\fB-w\fP' option. If a wildcard pattern is given, sac will attempt to find all portmaster directories that match the pattern located in the radacct directory. A detail file may be specified with the `\fB-w\fP' option in addition to the `\fB-R\fP' option. .PP .TP \fB-D\fP When processing radius logs, this option specifies that sac should use the @hostname part of user@hostname for the hostname field instead of portmasters hostname. Useful for -H filtering when using radius logs. .PP .TP \fB-P\fP Perform packet and octet accounting when reading from a detail file that logs packet and octet information (i.e. Ascend terminal servers). .PP .TP \fB-b\fP \fIhours\fP[\fI:minutes\fP[\fI:seconds\fP]] Consider only those utmp entries that fall within the last few hours/minutes/seconds from the current time, disregarding the rest. This option is useful for determining if someone has been on in the last few hours. .PP .TP \fB-s\fP \fIstart\fP Selects the starting date of the report. .PP .TP \fB-e\fP \fIend\fP Selects the ending date of the report. .PP .TP \fB-M\fP \fIhour-range\fP[,...]] Select only specific hours in a day to perform accounting on instead of all the hours in the day. The \fIhour-range\fP format is: (0-23)[-(0-23)[,\fIhour-range\fP[,...]]]. The hour given applies to the whole hour, so a range of "5-6" is a time range from 5am to 6:59:59am. .PP .TP \fB-f\fP Perform ftp login accounting in addition to normal shell accounting. .PP .TP \fB-F\fP Perform ftp login accounting only. .PP .TP \fB-m\fP Show minimum and maximum number of concurrent logins over the total time span or per day/per user when used with the -d/-p option. .PP .TP \fB-u\fP \fIuser-list\fP Selects only those users to perform accounting on. .PP .TP \fB-x\fP \fIuser-list\fP Selects those users to not perform accounting on. .PP .TP \fB-T\fP \fItty-list\fP Selects those ttys to perform accounting on. Each tty specifier may be a wildcard. .PP .TP \fB-H\fP \fIhost-list\fP Selects those hosts to perform accounting on. Each host specifier may be a wildcard. .PP .SH FILES /var/log/wtmp login database .br /usr/adm/radacct/.../detail Radius accounting logs .SH AUTHOR Steve Baker (ice@mama.indstate.edu) .SH BUGS The documentation for wtmp is lacking. It's not clear at all what all gets put in wtmp or the significance of any of it. The -o and -X options handle what is a login and a logout differently than normally (because there is no ut_type field), making sac incorrectly identify \fIxterm\fP log-outs as a login (\fIxterm\fP does not write a "login" entry, only a "logout" entry that looks just like a login in all respects save the contents of the ut_type field). It should also be noted that \fIlast\fP incorrectly handles xterm log-outs as well. The -f or -F options should not be used with -o -X[3|4] or -R options, as sac will default back to a normal utmp format, or ignore the -f or -F directives depending on where they occur on the command line. Using the -S option will cause sac to skip over accounting information which may well apply to the days you are inspecting. The only sure way to get all the accounting information is to start at the beginning or at least a day before the start you are interested in. The -m option does not accurately report true min/max usage when inspecting more than one logfile if those logfiles overlap the same time range. The -U option may report incorrect amounts of time when compared to the -t option. As yet I have no idea why. Sac (probably) only handles changes in time logged in the wtmp file made by \fInetdate\fP. \fIRdate\fP does not log time changes. Clipping can affect the output of the average option, as described above. Radius accounting uses Acct-Session-Time to determine usage when a stop record has no start record. Clipping will not function correctly when there are missing start records. The ut_addr field doesn't seem to be consistently used by all programs, so it cannot be used for exact host-name filtering. Even if it were, it would be too much work for this lazy programmer anyway. Radius detail logs suck. There is not one standard radius detail file format. Sac is not guaranteed to work with your detail file. If you suspect sacs' output is not correct, please contact the author at the e-mail address above. Null usernames in radius detail logs are represented as "UNKNOWN" by sac, which may be a valid username. Too much accounting results in big brother... citizen. .SH SEE ALSO .BR ac (1), .BR last (1), .BR rawtmp (1), .BR wtmp (5), .BR netdate (8L) sac-1.9/sac.c0100644000000000000000000016125607637210621011561 0ustar rootroot/* $Copyright: $ * Copyright (c) 1995 - 2001 by Steve Baker (ice@mama.indstate.edu) * All Rights reserved * * This software is provided as is without any express or implied * warranties, including, without limitation, the implied warranties * of merchant-ability and fitness for a particular purpose. */ #define MAIN #include "sac.h" static char *version = "$Version: $ sac v1.9 (c) 1995 - 2001 by Steve Baker $"; /* * sac [-w wtmplist|-] [-acdDfFhilmoprtSUX[3|4]] [-b hour[:min[:sec]]] * [-s start] [-e end] [[-u] userlist] [-x [userlist]] [-T [ttylist]] * [-H [hostlist]] [-M hourrangespec[,hourrangspec[,...]]] * [-R [portmasterlist]] [-I hours[:min[:sec]]] [--seconds] [--hms] * [--hm] [--hours] [--round] [--longdate] [--help] [--cutoff H[:M[:S]]] * [--discard H[:M[:S]]] [--verbose] [--version] */ int main(int argc, char **argv) { struct exc *xp; struct usr *u, *cp, *pp; struct file *f, *files = NULL, *l, *p; char *start = NULL, *end = NULL; char listtype = USERLIST; int i, j, n, fd=0; time_t t; #ifdef USER_PROCESS void (*gronk)() = gronk_sysv; #else void (*gronk)() = gronk_bsd; #endif char fastseek = FALSE; char ft= ACCT_WTMP; for(n=i=1;ifile = scopy(argv[i]); else { if (ispattern(argv[i])) { free(f); while ((f = parseradiusdir(RADIUS_DIR,argv[i],DETAIL_NAME))) { f->gronk = gronk; f->ftype = ft; f->nxt = files; files = f; } break; } sprintf(buf,"%s/%s/%s",RADIUS_DIR,argv[i],DETAIL_NAME); f->file = scopy(buf); } f->gronk = gronk; f->ftype = ft; f->nxt = files; files = f; break; case LOGFILELIST: f = bmalloc(sizeof(struct file)); f->file = scopy(argv[i]); f->gronk = gronk; f->ftype = ft; if (files == NULL) files = f; else { for(l=p=files;p;p++) { if (strncmp(p->file,"lastlog",7)) break; if (strncmp(f->file,p->file,14) < 0) break; l=p; } f->nxt = p; if (p == files) files = f; else l->nxt = f; } break; } } } for(xp=ex;xp;xp=xp->nxt) { while ((u = finduser(us,xp->user))) { for(pp=cp=us;cp && cp != u;cp=cp->nxt) pp = cp; if (!cp) break; if (us == cp) us = u->nxt; else pp->nxt = u->nxt; free(u); } } if (start) { if (start[0] == '-' || start[0] == '+') { for(i=1;start[i];i++) if (!isdigit(start[i])) usage(2); sm = atoi(start); if (sm == 0) { if (start[0] == '-') sm = MINUSZERO; else sm = PLUSZERO; } } else sd = getsacdate(start); } if (end) { if (end[0] == '-' || end[0] == '+') { t = time(0); for(i=1;end[i];i++) if (!isdigit(end[i])) usage(2); em = atoi(end); if (em == 0) { if (end[0] == '-') em = MINUSZERO; else em = PLUSZERO; } } else ed = getsacdate(end); } if (back) backtime = (curtime=time(0)) - churn_time(back); if (ignore) { ignoretime = churn_time(ignore); for(u=us;u;u=u->nxt) u->ignore = ignoretime; } if (files == NULL) { files = bmalloc(sizeof(struct file)); files->file = scopy(_PATH_WTMP); files->gronk = gronk; } for(f=files;f;f=f->nxt) { if (!strcmp(f->file,"-")) fd = 0; else { if ((fd = open(f->file,O_RDONLY)) < 0) { fprintf(stderr,"sac: Error opening '%s' for reading. [%d]\n",f->file,errno); continue; } } if (fastseek) try_fastseek(fd,f); f->gronk(fd); cleanup(); if (fd != 0) close(fd); } report(); return 0; } void usage(int n) { switch (n) { case 1: fprintf(stderr,"usage: sac [-w [wtmp-list|-]] [-acdDfFhilmoprtSUX[3|4]] [-s start] [-e end]\n [-b H[:M[:S]]] [[-u] userlist] [-x [userlist]] [-T [ttylist]]\n [-H [hostlist]] [-M hour-range-spec[,hour-range-spec[,...]]]\n [-R [portmaster-list]] [--hms] [--hm] [--hours] [--round]\n [--version] [--help]\n"); break; case 2: fprintf(stderr,"sac: Invalid date. Format: +days | -days | mm/dd/yy\n"); break; case 3: fprintf(stderr,"sac: Invalid time. Format: hours[:minutes[:seconds]]\n"); break; case 4: fprintf(stderr,"usage: sac [-w [wtmp-list|-]] [-acdDfFhilmoprtSUX[3|4]] [-s start] [-e end]\n [-b H[:M[:S]]] [[-u] userlist] [-x [userlist]] [-T [ttylist]]\n [-H [hostlist]] [-M hour-range-spec[,hour-range-spec[,...]]]\n [-R [portmaster-list]] [--hms] [--hm] [--hours] [--round]\n [--version] [--help]\n"); fprintf(stderr," -w [wtmp|-] Read alternate wtmp file(s).\n"); fprintf(stderr," -d Perform daily accounting.\n"); fprintf(stderr," -a Average usage / login.\n"); fprintf(stderr," -t Report usage by tty line.\n"); fprintf(stderr," -U List simultaneous usage levels.\n"); fprintf(stderr," -r Show raw statistics about accounting file(s).\n"); fprintf(stderr," -h Show hourly profile.\n"); fprintf(stderr," -l Show login listing.\n"); fprintf(stderr," -p Per user accounting.\n"); fprintf(stderr," -f Perform accounting for ftp logins too.\n"); fprintf(stderr," -F Perform accounting for ftp logins only.\n"); fprintf(stderr," -m Show min/max # of concurrent logins.\n"); fprintf(stderr," -c Perform login clipping.\n"); fprintf(stderr," -o Read old decrepit wtmp format.\n"); fprintf(stderr," -X[3] Read tacacs 3.4 - 3.5 wtmp format.\n"); fprintf(stderr," -X4 Read tacacs 4.x wtmp format.\n"); fprintf(stderr," -i Include hostname in determining login/logouts.\n"); fprintf(stderr," -R [pmstrs] Read Radius detail files for each portmaster listed.\n"); fprintf(stderr," -D Use @hostname in user@hostname instead of portmaster name.\n"); fprintf(stderr," -P Perform packet and octet accounting.\n"); fprintf(stderr," -s start Display accounting info from `start'.\n"); fprintf(stderr," -e end Display accounting info up to `end'.\n"); fprintf(stderr," -S Seek to start of day specified with -s (MM/DD/YY format).\n"); fprintf(stderr," -M hourspec Select only specific hours to perform accounting on.\n"); fprintf(stderr," -b H:M:S Show accounting info from the last few hours:minutes:seconds.\n"); fprintf(stderr," -I H:M:S Ignore usage before starting accounting (per user only).\n"); fprintf(stderr," -x [users] Does not perform accounting for [users].\n"); fprintf(stderr," -T [ttys] Perform accounting on only those ttys listed.\n"); fprintf(stderr," -H [hosts] Perform accounting only for the hosts listed.\n"); fprintf(stderr," -u [users] Perform accounting only for the users listed.\n"); fprintf(stderr," userlist Perform accounting only for the users listed.\n"); fprintf(stderr," --seconds Display time in seconds.\n"); fprintf(stderr," --hms Display time in hours:minutes:seconds format.\n"); fprintf(stderr," --hm Display time in hours:minutes format.\n"); fprintf(stderr," --hours Display time in hours only format.\n"); fprintf(stderr," --round Round time to nearest minute or hour instead of always down.\n"); fprintf(stderr," --longdate Display dates in long format.\n"); fprintf(stderr," --version Display sac version.\n"); fprintf(stderr," --help Display this help message.\n"); break; case 5: fprintf(stderr,"sac: Invalid hour range. Format: (0-23)[-(0-23)[,...]]\n"); break; case 6: fprintf(stderr,"sac: Malformed wildcard.\n"); break; case 7: fprintf(stderr,"sac: Missing argument.\n"); break; case 8: fprintf(stderr,"sac: Must specify at least one portmaster with -R option \n or specify stdin or detail file with -w.\n"); break; case 9: { char *v = version+12; printf("%.*s\n",(int)strlen(v)-1,v); exit(0); } case 10: fprintf(stderr,"sac: Invalid usage range. Format: [+|-]N[-[+|-]M]\n"); break; } exit(1); } struct file *parseradiusdir(char *base, char *pattern, char *file) { static DIR *dir = NULL; struct dirent *ent; struct file *f; if (!dir) { dir = opendir(base); if (!dir) return NULL; } for(;;) { ent = readdir(dir); if (ent == NULL) { closedir(dir); dir = NULL; return NULL; } if (patmatch(ent->d_name,pattern) != 1) continue; sprintf(buf,"%s/%s/%s",RADIUS_DIR,ent->d_name,DETAIL_NAME); if (access(buf,F_OK)) continue; f = bmalloc(sizeof(struct file)); f->file = scopy(buf); return f; } } /* * Turn "mm/dd/yy" into time in seconds. */ time_t getsacdate(char *s) { struct tm tm; int y; time_t t; /* * * Need a real date parser that can handle different formats and separators */ if (!isdigit(s[0]) || !isdigit(s[1]) || isdigit(s[2])) usage(2); if (!isdigit(s[3]) || !isdigit(s[4]) || isdigit(s[5])) usage(2); if (!isdigit(s[6]) || !isdigit(s[7]) || s[8]) usage(2); tm.tm_mon = (((s[0]-'0')*10) + (s[1]-'0')) -1; tm.tm_mday = (((s[3]-'0')*10) + (s[4]-'0')); y = (((s[6]-'0')*10) + (s[7]-'0')); tm.tm_year = (y < 70? 100 + y : y); tm.tm_isdst = -1; tm.tm_hour = 0; tm.tm_min = tm.tm_sec = 0; t = mktime(&tm); if (t == (time_t)(-1)) { if (verbose) fprintf(stderr,"oops\n"); usage(2); } return t; } time_t churn_time(char *s) { time_t t = 0, mult=3600; u_char nbuf[20], p; for(p=0;*s;s++) { if (*s == ':') { nbuf[p] = (u_char)0; t += atoi(nbuf) * mult; p = 0; if (mult > 1) mult /= 60; else usage(3); } else if (isdigit(*s)) { if (p < 15) nbuf[p++] = (u_char)*s; else usage(3); } else usage(3); } nbuf[p] = (u_char)0; t += atoi(nbuf) * mult; return t; } /* * hourrangespec = [0-23][-[0-23]][,...] */ u_long hourrangespec(char *s) { u_long mask = 0; int i= 0, j = 0, p = 0; while(1) { if (!*s) usage(5); for(i=0;isdigit(*s) && i < 24;s++) i = (i*10) + (*s-'0'); if (i > 23) usage(5); if (*s == '-') { s++; if (!*s) usage(5); for(j=0;isdigit(*s) && j < 24;s++) j = (j*10) + (*s-'0'); if (j > 23) usage(5); for(p=i;p<=j;p++) mask |= (1<nxt) i++; st = malloc(sizeof(struct simuse *) * i); for(i=0,s=simuse; s; s=s->nxt) st[i++] = s; n = i-1; } return NULL; } /* * Adjust the time warp factor by as many days as you think are necessary. * If you have very infrequent logins, a WARP_FACTOR greater than 60 days * would probably be called for. It's probably safe to have a huge warp * factor. Most errors in time are either small or very very large (i.e. * year 2038/1970). */ #define WARP_FACTOR (60*24*60*60) /* * Make sure the day that the wtmp entry corresponds to, exists in our * days list. If day is less than the starting day by more than a day, or * greater than the last day by more than a day, allocate all the days in * between as well. */ int checkday(time_t t) { struct day *d, *p; if (days == NULL) { end = days = mkday(t); } else { if (t < days->start) { if (t < days->start-WARP_FACTOR) return -1; p = d = mkday(t); while (p->stop+1 < days->start) { p->nxt = mkday(p->stop+1); p = p->nxt; } p->nxt = days; days = d; } else if (t > end->stop) { if (t > end->stop+WARP_FACTOR) return -1; p = d = mkday(end->stop+1); while (p->stop < t) { p->nxt = mkday(p->stop+1); p = p->nxt; } end->nxt = d; end = p; } } return 0; } /* * Makes a day entry. We'll assume a day is exactly 24 hours or 86400 * seconds long. I don't know if this is the case or not. I don't think * the time algorithm takes into account leap seconds. * * Oh boy! Daylight savings time screws this all to hell. We can't * assume anything. Check the time at 12:00:00am and 11:59:59pm to get * the time bound for the day. */ struct day *mkday(time_t t) { struct day *dp; struct tm tm, *xtm; int yday; xtm = localtime(&t); memcpy(&tm,xtm,sizeof(tm)); tm.tm_hour = tm.tm_min = tm.tm_sec = 0; yday = tm.tm_yday; dp = bmalloc(sizeof(struct day)); dp->start = mktime(&tm); /* They may not have a 12am on the day they change to daylight savings time. */ while (yday != tm.tm_yday) { tm.tm_hour++; dp->start = mktime(&tm); } tm.tm_isdst = -1; xtm = &tm; xtm->tm_hour = 23; xtm->tm_min = 59; xtm->tm_sec = 59; dp->stop = mktime(xtm); dp->day = xtm->tm_mday; dp->wday = xtm->tm_wday; dp->month = xtm->tm_mon; dp->year = 1900+xtm->tm_year; dp->minlogins = -1; ndays++; /* if (d->stop > (d->start + (24*60*60))) fprintf(stderr,"Created long day: %s %d.\n",month[tm.tm_mon],tm.tm_mday); if (d->start > (d->stop - ((24*60*60)-1))) fprintf(stderr,"Created short day: %s %d.\n",month[tm.tm_mon],tm.tm_mday); */ return dp; } void saclogin(struct sactmp su) { struct user *q; struct day *d; struct usr *u = NULL, *up; int l; /* * If we still have a login on this line, it's logged out for sure now! * Wtmp could be corrupted. */ saclogout(su,FALSE); /* printf ("Logging in %s [%s] <%ld>...\n",u.ut_user,u.ut_line,u.ut_time); */ q = bmalloc(sizeof(struct user)); strncpy(q->line,su.u.ut_line,UT_LINESIZE); strncpy(q->user,su.u.ut_user,UT_NAMESIZE); strncpy(q->host,su.u.ut_host,UT_HOSTSIZE); q->user[UT_NAMESIZE] = q->line[UT_LINESIZE] = q->host[UT_HOSTSIZE] = 0; q->in = su.u.ut_time; q->nxt = usr; usr = q; if ((type & 0x000F) == SIMUSE) { release(q,su.u.ut_time,&su,0,1); return; } if ((type & MINMAX) == MINMAX && !(fix && (finduser(us,q->user)) == NULL) && !(exclude && isexcluded(q->user)) && !(fixtty && !istty(q->line)) && !(fixhost && !ishost(q->host))) { l = loggedin++; maxlogins = max(maxlogins,loggedin); if (minlogins > -1) minlogins = min(minlogins,l); else minlogins = l; if ((type & 0x000F) == USER) { if ((u = finduser(us,q->user)) == NULL) u = adduser(&us,q->user); u->loggedin++; u->maxlogins = max(u->maxlogins,u->loggedin); if (u->minlogins > -1) u->minlogins = min(u->minlogins,u->loggedin-1); else u->minlogins = u->loggedin-1; } if (su.u.ut_time < end->start) { for(d=days;d;d=d->nxt) { if (su.u.ut_time >= d->start && su.u.ut_time <= d->stop) { d->maxlogins = max(d->maxlogins,loggedin); if (d->minlogins > -1) d->minlogins = min(d->minlogins,l); else d->minlogins = l; if ((type & 0x000F) == USER) { if ((up = finduser(d->us,q->user)) == NULL) up = adduser(&d->us,q->user); up->maxlogins = max(up->maxlogins,u->loggedin); if (up->minlogins > -1) up->minlogins = min(up->minlogins,u->loggedin-1); else up->minlogins = u->loggedin-1; } break; } } } else { end->maxlogins = max(end->maxlogins,loggedin); if (end->minlogins > -1) end->minlogins = min(end->minlogins,l); else end->minlogins = l; if ((type & 0x000F) == USER) { if ((up = finduser(end->us,q->user)) == NULL) up = adduser(&end->us,q->user); up->maxlogins = max(up->maxlogins,u->loggedin); if (up->minlogins > -1) up->minlogins = min(up->minlogins,u->loggedin-1); else up->minlogins = u->loggedin-1; } } } } void saclogout(struct sactmp su, int usext) { struct user *p, *q; struct usr *u = NULL, *up; struct day *d; /* time_t t = su.u.ut_time - end->start,h,m,s; char uid[UT_NAMESIZE+2]; int hack; hack = TRUE; */ for(p=q=usr;p;) { if (!strncmp(su.u.ut_line,p->line,UT_LINESIZE) && (hosttoo? !strncmp(su.u.ut_host,p->host,UT_HOSTSIZE) : TRUE)) { /* printf ("Logging out %s [%s] <%ld>...\n",u.ut_user,u.ut_line,u.ut_time); */ release(p,su.u.ut_time,&su,usext,-1); if ((type & MINMAX) == MINMAX && !(fix && (finduser(us,p->user)) == NULL) && !(exclude && isexcluded(p->user)) && !(fixtty && !istty(p->line)) && !(fixhost && !ishost(p->host))) { loggedin = max(0,loggedin-1); if (minlogins > -1) minlogins = min(minlogins,loggedin); else minlogins = loggedin; if ((type & 0x000F) == USER && ((u = finduser(us,p->user)) != NULL)) { u->loggedin = max(0,u->loggedin-1); } if (su.u.ut_time < end->start) { for(d=days;d;d=d->nxt) { if (su.u.ut_time >= d->start && su.u.ut_time <= d->stop) { d->maxlogins = max(d->maxlogins,loggedin+1); if (d->minlogins > -1) d->minlogins = min(d->minlogins,loggedin); else d->minlogins = loggedin; if ((type & 0x000F) == USER) { if ((up = finduser(d->us,p->user)) == NULL) up = adduser(&d->us,p->user); up->maxlogins = max(up->maxlogins,u->loggedin+1); if (up->minlogins > -1) up->minlogins = min(up->minlogins,u->loggedin); else up->minlogins = up->loggedin; } break; } } } else { end->maxlogins = max(end->maxlogins,loggedin+1); if (end->minlogins > -1) end->minlogins = min(end->minlogins,loggedin); else end->minlogins = loggedin; if ((type & 0x000F) == USER) { if ((up = finduser(end->us,p->user)) == NULL) up = adduser(&end->us,p->user); up->maxlogins = max(up->maxlogins,u->loggedin+1); if (up->minlogins > -1) up->minlogins = min(up->minlogins,u->loggedin); else up->minlogins = u->loggedin; } } } if (p == usr) { usr = p->nxt; free(p); p = q = usr; } else { q->nxt = p->nxt; free(p); p = q->nxt; } return; } q = p; p = p->nxt; } /* Vain attempt to reasearch reaping logouts w/ missing logins. if (hack) { if (usext && su.u.ut_type != DEAD_PROCESS); if (!su.u.ut_name[0] && su.u.ut_name[1]) { h=t/3600; m=(t-(h*3600))/60; s=t-(h*3600)-(m*60); memcpy(uid,su.u.ut_name,UT_NAMESIZE); if (!uid[0]) uid[0] = '.'; uid[UT_NAMESIZE] = 0; if (!strcmp("LOGIN", uid) || !strcmp(".OGIN", uid)) return; fprintf(stderr,"sac: no matching login for logout of [%s] at %s %d %2ld:%02ld:%02ld.\n",uid,month[end->month],end->day,h,m,s); } } */ } /* * logout everyone on reboots or crashes. */ void do_reboot(time_t t) { struct user *p; struct day *d; struct usr *u; for(p=usr;usr;p=usr){ release(p,t,NULL,0,-1); usr=usr->nxt; free(p); } simdex = 0; simtime = 0; if ((type & MINMAX) == MINMAX) { loggedin = 0; minlogins = 0; if ((type & 0x000F) == USER) { for(u=us;u;u=u->nxt) { u->loggedin = 0; u->minlogins = 0; } } if (t < end->start) { for(d=days;d;d=d->nxt) if (t >= d->start && t <= d->stop) { d->minlogins = 0; break; } } else end->minlogins = 0; } } /* * A utmp entry denoted with a line of "|" denotes the "old" time before * netdate updated the time. An entry with line of "{" denotes the new time. * The difference in time is applied to the login time of every user still * logged in. */ void changetime(time_t t, char *line) { static time_t old = 0; struct user *p; signed long dif; if (!strcmp("|",line)) { old = t; return; } if (old == 0) return; dif = (signed long)(t - old); for(p=usr;p;p=p->nxt) p->in -= dif; /* I *think* this is the right thing to do */ simtime -= dif; } /* * Apply login time for users who haven't logged out yet (or that the wtmp file * in particular indicates haven't logged out by the EOF) to the days that * are in days list. Login time is not applied to days not listed in the * wtmp file (therefor all the days between the first and last wtmp entries). * The reason for this is that if you're inspecting an old wtmp file, the * wtmp may not indicate all logouts for the last day. It is not possible * to know when the wtmp file really truly ends however, so we apply the * minimum of the current time or the ending time for the last day. */ void cleanup(void) { time_t t = time(0); struct day *lastday; struct user *p; struct usr *u; /* Ooops, the -t option can't just ignore time that isn't in our days list * so we'll just force t to not go beyond the end of the last day in the * wtmp file. An option should be made so you can opt to just use the * last wtmp entry for the end time. */ if (lastent) { lastday = mkday(lastent); if (t > lastday->stop) t = lastday->stop; } /* * Oops, if we're clipping, we have to remove the released time from the * list. This is pretty easily done, by moving usr, instead of using a * temporary pointer. */ for(p=usr;usr;p=usr) { release(usr,t,NULL,0,-1); usr=usr->nxt; } loggedin = 0; minlogins = 0; simdex = 0; if ((type & 0x000F) == USER) { for(u=us;u;u=u->nxt) { u->loggedin = 0; u->minlogins = 0; } } } /* * Release a login entry, applying the login time to the particular day * entries. * A user is logged in on a particular day when: * in >= start && in <= stop || * out >= start && out <= stop || * in < start && out > stop */ void release(struct user *u, time_t t, struct sactmp *su, int usext, int siminc) { struct day *p; struct usr *up; struct user *q; struct tty *tp; struct simuse *s; int i; if ((signed long)((t - u->in) + 1) < 0) return; if (cutoff && ((t - u->in)+1) > cutoff) t = u->in+cutoff; if (discard && ((t - u->in)+1) > discard) return; /* if ((type & 0x000F) == SIMUSE) return; */ /* * Clipping assumes that time moves forward. Only in computers is this * not necessarily a safe assumption. * Clipping rules: * If (login time < all other login times) * reduce logout time (t) to least login time (-1). * else clip entirely. */ if ((type & CLIP) == CLIP && (type & 0x000F) != SIMUSE) { for(q=usr;q;q=q->nxt) { if (q != u && !strncmp(u->user,q->user,UT_NAMESIZE)) { /* throw it out if he's already logged in earlier */ if (q->in <= u->in) return; /* reduce the logout time to the lowest login time. */ if (q->in < t) t = q->in-1; } } } if (backtime) { if (u->in > curtime || t < backtime) return; if (u->in < backtime && t > backtime) u->in = backtime; if (u->in < curtime && t > curtime) t = curtime; } /* Check the user list if it's fixed. */ if (fix && (up = finduser(us,u->user)) == NULL) return; /* Check the exclude user list. */ if (exclude && isexcluded(u->user)) return; /* Check the tty list. */ if (fixtty && !istty(u->line)) return; /* Check the host list. */ if (fixhost && !ishost(u->host)) return; if ((type & 0x000F) == SIMUSE) { /* if (t == simtime && siminc == -1) fprintf(stderr,"mark! [simtime=%ld:%d]\n",simtime,simdex); */ if (siminc != -1) t--; /* if (t < simtime && siminc == 1) fprintf(stderr,"mark! [simtime=%ld:%d]\n",simtime,simdex); */ if (simdex && simtime <= t) { if (simtime < end->start) { for(p=days;p;p=p->nxt) { if (simtime >= p->start && simtime <= p->stop) { s = findsim(&p->simuse,simdex); s->time += (min(t,p->stop) - simtime) + 1; s->count++; if ((type & HOUR) == HOUR) apply_hours(simtime,t,p->start,s->h); } else if (t >= p->start && t <= p->stop) { s = findsim(&p->simuse,simdex); s->time += (t - p->start) + 1; s->count++; if ((type & HOUR) == HOUR) apply_hours(simtime,t,p->start,s->h); } else if (simtime < p->start && t > p->stop) { s = findsim(&p->simuse,simdex); s->time += (p->stop - p->start) + 1; s->count++; if ((type & HOUR) == HOUR) for(i=0;i<24;i++) s->h[i] += 3600; } } if ((type & LOGIN) == LOGIN) { for(p=days;p;p=p->nxt) if (simtime <= p->stop) break; s = findsim(&p->simuse,simdex); addlogin(&s->login,&s->lastlogin,simtime,t,NULL,NULL,NULL); } } else { s = findsim(&end->simuse, simdex); s->time += (t - simtime) + 1; s->count++; if ((type & HOUR) == HOUR) apply_hours(simtime,t,end->start,s->h); if ((type & LOGIN) == LOGIN) addlogin(&s->login,&s->lastlogin,simtime,t,NULL,NULL,NULL); } } simdex = max(0,simdex+siminc); simtime = t+1; return; } /* Only count logins that we actually apply. */ logins++; /* if we're logging usage / tty then apply login time to tty entry. */ if ((type & 0x000F) == TTY || (type & 0x000F) == RAW) { tp=findtty(ttys,u->line); if (tp == NULL) { tp = bmalloc(sizeof(struct tty)); strncpy(tp->line,u->line,UT_LINESIZE); if (ttys == NULL) ttys = tp; else { tp->nxt = ttys; ttys = tp; } } if ((type & LOGIN) == LOGIN) addlogin(&tp->login,&tp->lastlogin,u->in,t,u->user,NULL,u->host); tp->logins++; if (u->in < end->start) { for(p=days;p;p=p->nxt) { if (u->in >= p->start && u->in <= p->stop) tty_apply_hours(u->in,t,p,tp); else if (t >= p->start && t <= p->stop) tty_apply_hours(u->in,t,p,tp); else if (u->in < p->start && t > p->stop) tty_apply_hours(u->in,t,p,tp); } } else tty_apply_hours(u->in,t,end,tp); if (usext && (type & IPACCT) == IPACCT) { tp->inoct += su->inoct; tp->outoct += su->outoct; tp->inpkt += su->inpkt; tp->outpkt += su->outpkt; tp->datarate = tp->logins? ((tp->datarate*(tp->logins-1))+su->datarate)/tp->logins : su->datarate; } if ((type & 0x000F) != RAW) return; } /* if we're logging usage / user then apply login time to user entry. */ if (((type & 0x000F) == USER || (type&0x000F) == RAW) && (((up = finduser(us,u->user)) != NULL) || !fix)) { if (up == NULL) up = adduser(&us,u->user); /* { time_t tt; u_long m,s; tt = (t-u->in)+1; m=tt/60; s=tt-(m*60); printf("Released %5ld:%ld\n",m,s); } */ if ((type & LOGIN) == LOGIN) addlogin(&up->login,&up->lastlogin,u->in,t,NULL,u->line,u->host); up->logins++; if (u->in < end->start) { for(p=days;p;p=p->nxt) { if (u->in >= p->start && u->in <= p->stop) user_apply_hours(u,t,p); else if (t >= p->start && t <= p->stop) user_apply_hours(u,t,p); else if (u->in < p->start && t > p->stop) user_apply_hours(u,t,p); } } else user_apply_hours(u,t,end); if (usext && (type & IPACCT) == IPACCT) { struct usr *ud; double tt; if (u->in < end->start) { for(p=days;p;p=p->nxt) { if ((u->in >= p->start && u->in <= p->stop) || (t >= p->start && t <= p->stop) || (u->in < p->start && t > p->stop)) { if ((ud = finduser(p->us,u->user)) == NULL) ud = adduser(&p->us,u->user); tt = (double)((min(p->stop,t) - max(p->start,u->in)) + 1) / (double)((t - u->in) + 1); ud->inoct += (su->inoct * tt); ud->outoct += (su->outoct * tt); ud->inpkt += (su->inpkt * tt); ud->outpkt += (su->outpkt * tt); ud->datarate = up->logins? ((up->datarate*(up->logins-1))+(su->datarate*tt))/up->logins : (su->datarate*tt); } } } else { if ((ud = finduser(end->us,u->user)) == NULL) ud = adduser(&end->us,u->user); ud->inoct += su->inoct; ud->outoct += su->outoct; ud->inpkt += su->inpkt; ud->outpkt += su->outpkt; ud->datarate = up->logins? ((up->datarate*(up->logins-1))+su->datarate)/up->logins : su->datarate; } } if ((type & 0x000F) != RAW) return; } /* * We go through this for both daily and total. The total will be accumulated * at the end since we don't know the starting and ending dates until we're * done. */ if (u->in < end->start) { /* Ugh, it's probably yesterday, but we've got to start all the way at the * beginning. I wonder if double-linking the list would speed things up. */ for(p=days;p;p=p->nxt) { if (u->in >= p->start && u->in <= p->stop) { p->time += (min(p->stop,t) - u->in) + 1; p->logins++; if (hourmask || (type & HOUR) == HOUR) apply_hours(u->in,t,p->start,p->h); } else if (t >= p->start && t <= p->stop) { p->time += (t - max(p->start,u->in)) + 1; p->logins++; if (hourmask || (type & HOUR) == HOUR) apply_hours(u->in,t,p->start,p->h); } else if (u->in < p->start && t > p->stop) { p->time += 86400; p->logins++; if (hourmask || (type & HOUR) == HOUR) for(i=0;i<24;i++) p->h[i] += 3600; } } if ((type & LOGIN) == LOGIN) { for(p=days;p;p=p->nxt) if (u->in <= p->stop) break; addlogin(&p->login,&p->lastlogin,u->in,t,u->user,u->line,u->host); } /* if ((type & LOGIN) == LOGIN) addlogin(&p->login,&p->lastlogin,u->in,t,u->user,u->line,u->host); if (usext && (type & IPACCT) == IPACCT) { p->inoct += su->inoct; p->outoct += su->outoct; p->inpkt += su->inpkt; p->outpkt += su->outpkt; p->datarate = p->logins? ((p->datarate*(p->logins-1))+su->datarate)/p->logins : su->datarate; } } */ if (usext && (type & IPACCT) == IPACCT) { struct usr *ud; double tt; for(p=days;p;p=p->nxt) { if ((u->in >= p->start && u->in <= p->stop) || (t >= p->start && t <= p->stop) || (u->in < p->start && t > p->stop)) { tt = (double)((min(p->stop,t) - max(p->start,u->in)) + 1) / (double)((t - u->in) + 1); p->inoct += (su->inoct * tt); p->outoct += (su->outoct * tt); p->inpkt += (su->inpkt * tt); p->outpkt += (su->outpkt * tt); p->datarate = p->logins? ((p->datarate*(p->logins-1))+(su->datarate*tt))/p->logins : (su->datarate*tt); } } } } else { end->time += (min(end->stop,t) - max(end->start,u->in)) + 1; end->logins++; if (hourmask || (type & HOUR) == HOUR) apply_hours(u->in,t,end->start,end->h); if ((type & LOGIN) == LOGIN) addlogin(&end->login,&end->lastlogin,u->in,t,u->user,u->line,u->host); if (usext && (type & IPACCT) == IPACCT) { end->inoct += su->inoct; end->outoct += su->outoct; end->inpkt += su->inpkt; end->outpkt += su->outpkt; end->datarate = end->logins? ((end->datarate*(end->logins-1))+su->datarate)/end->logins : su->datarate; } } } void addlogin(struct login **login, struct login **last, time_t in, time_t out, char *name, char *tty, char *host) { struct login *l; l = bmalloc(sizeof(struct login)); l->start = in; l->stop = out; l->name = name? scopy(name) : NULL; l->tty = tty? scopy(tty) : NULL; l->host = host? scopy(host) : NULL; l->nxt = NULL; if (!*last) *last = *login = l; else { (*last)->nxt = l; *last = l; } } void apply_hours(time_t in, time_t out,time_t start, time_t h[24]) { int i; time_t b, e; b = start; e = start + 3599; for(i=0;i<24;i++) { if (in >= b && in <= e) h[i] += (min(e,out) - in) + 1; else if (out >= b && out <= e) h[i] += (out - max(b,in)) + 1; else if (in < b && out > e) h[i] += 3600; b += 3600; e += 3600; } } void user_apply_hours(struct user *u, time_t out, struct day *d) { int i; time_t b, e; struct usr *up; if ((up = finduser(d->us,u->user)) == NULL) up = adduser(&d->us,u->user); up->time += (min(out,d->stop) - max(u->in,d->start)) + 1; if (max(u->in,d->start) == u->in) up->logins++; else up->xlogins++; if (hourmask || ((type & HOUR) == HOUR)) { b = d->start; e = d->start + 3599; for(i=0;i<24;i++) { if (u->in >= b && u->in <= e) up->h[i] += (min(e,out) - u->in) + 1; else if (out >= b && out <= e) up->h[i] += (out - max(b,u->in)) + 1; else if (u->in < b && out > e) up->h[i] += 3600; b += 3600; e += 3600; } } } void tty_apply_hours(time_t in, time_t out, struct day *d, struct tty *t) { int i; time_t b, e; struct tty *tp; if ((tp = findtty(d->tty,t->line)) == NULL) { tp = bmalloc(sizeof(struct tty)); strncpy(tp->line,t->line,UT_LINESIZE); if (d->tty == NULL) d->tty = tp; else { tp->nxt = d->tty; d->tty = tp; } } tp->time += (min(out,d->stop) - max(in,d->start)) + 1; if (max(in,d->start) == in) tp->logins++; else tp->xlogins++; if (hourmask || ((type & HOUR) == HOUR)) { b = d->start; e = d->start + 3599; for(i=0;i<24;i++) { if (in >= b && in <= e) tp->h[i] += (min(e,out) - in) + 1; else if (out >= b && out <= e) tp->h[i] += (out - max(b,in)) + 1; else if (in < b && out > e) tp->h[i] += 3600; b += 3600; e += 3600; } } } char *getwrd(char *s) { char *t; while(*s && isspace(*s)) s++; t = s; while(*s && !isspace(*s)) s++; *s = 0; return t; } struct usr *adduser(struct usr **up, char *s) { struct usr *u; if (*s == '@') { char sbuf[BUFSIZE], *str; FILE *fd; sbuf[BUFSIZE-1] = 0; if ((fd = fopen(s+1,"r")) == NULL) { fprintf(stderr,"sac: error opening file '%s' for reading.\n",s+1); exit(1); } while (fgets(sbuf,BUFSIZE-1,fd) != NULL) { str = getwrd(sbuf); if (!str[0] || str[0] == '#') continue; adduser(up,str); } fclose(fd); return NULL; } u = bmalloc(sizeof(struct usr)); strncpy(u->user,s,UT_NAMESIZE); u->minlogins = -1; u->nxt = *up; *up = u; return *up; } struct usr *finduser(struct usr *up, char *s) { struct usr *u; for(u=up;u;u=u->nxt) if (!strcmp(s,u->user)) return u; return NULL; } void addexclude(char *s) { struct exc *x; if (*s == '@') { char sbuf[BUFSIZE], *str; FILE *fd; sbuf[BUFSIZE-1] = 0; if ((fd = fopen(s+1,"r")) == NULL) { fprintf(stderr,"sac: error opening file '%s' for reading.\n",s+1); exit(1); } while (fgets(sbuf,BUFSIZE-1,fd) != NULL) { str = getwrd(sbuf); if (!str[0] || str[0] == '#') continue; addexclude(str); } fclose(fd); return; } x = bmalloc(sizeof(struct exc)); strncpy(x->user,s,UT_NAMESIZE); x->user[UT_NAMESIZE] = 0; x->nxt = ex; ex = x; return; } int isexcluded(char *s) { struct exc *u; for(u=ex;u;u=u->nxt) if (!strncmp(s,u->user,UT_NAMESIZE)) return TRUE; return FALSE; } struct tty *findtty(struct tty *tt, char *l) { struct tty *t; for(t=tt;t;t=t->nxt) if (!strncmp(t->line,l,UT_LINESIZE)) return t; return NULL; } void addtty(char *s) { struct ttys *t; int ispat; if (*s == '@') { char sbuf[BUFSIZE], *str; FILE *fd; sbuf[BUFSIZE-1] = 0; if ((fd = fopen(s+1,"r")) == NULL) { fprintf(stderr,"sac: error opening file '%s' for reading.\n",s+1); exit(1); } while (fgets(sbuf,BUFSIZE-1,fd) != NULL) { str = getwrd(sbuf); if (!str[0] || str[0] == '#') continue; addtty(str); } fclose(fd); return; } ispat = ispattern(s); if (ispat == -1 || (ispat && patmatch("ttyp0",s) == -1)) usage(6); t = bmalloc(sizeof(struct ttys)); t->line = scopy(s); t->ispat = ispat; t->nxt = tty; tty = t; return; } int istty(char *s) { struct ttys *t; for(t=tty;t;t=t->nxt) { if (t->ispat) { if (patmatch(s,t->line) == 1) return TRUE; continue; } if (!strncmp(s,t->line,UT_LINESIZE)) return TRUE; } return FALSE; } void addhost(char *s) { struct hosts *h; int ispat; if (*s == '@') { char sbuf[BUFSIZE], *str; FILE *fd; sbuf[BUFSIZE-1] = 0; if ((fd = fopen(s+1,"r")) == NULL) { fprintf(stderr,"sac: error opening file '%s' for reading.\n",s+1); exit(1); } while (fgets(sbuf,BUFSIZE-1,fd) != NULL) { str = getwrd(sbuf); if (!str[0] || str[0] == '#') continue; addhost(str); } fclose(fd); return; } ispat = ispattern(s); if (ispat == -1 || (ispat && patmatch("tty",s) == -1)) usage(6); h = bmalloc(sizeof(struct hosts)); h->host = scopy(s); h->ispat = ispat; h->len = strlen(h->host); if (!ispat && ((index(h->host,'.') == NULL) || (h->host[h->len-1] == '.'))) h->ss = TRUE; else h->ss = FALSE; h->nxt = hosts; hosts = h; return; } int ishost(char *s) { struct hosts *h; for(h=hosts;h;h=h->nxt) { if (h->ispat) { if (patmatch(s,h->host) == 1) return TRUE; continue; } if (h->ss) { if (!strncmp(s,h->host,h->len)) return TRUE; } else { if (!strncmp(s,h->host,UT_HOSTSIZE)) return TRUE; } } return FALSE; } struct simuse *findsim(struct simuse **s, int i) { struct simuse *sp, *pp, *n; for(pp=sp=*s;sp;sp=sp->nxt) { if (sp->index == i) return sp; if (sp->index > i) break; pp = sp; } n = bmalloc(sizeof(struct simuse)); n->index = i; n->nxt = sp; if (sp == *s) *s = n; else pp->nxt = n; return n; } /* Tests for *, ?, and [] */ int ispattern(char *s) { for(;*s;s++) { if (*s == '*') return 1; if (*s == '?') return 1; if (*s == '[') { for(;*s;s++) if (*s == ']') return 1; return -1; } } return 0; } /* * Patmatch() code courtesy of Thomas Moore (dark@mama.indstate.edu) * returns: * 1 on a match * 0 on a mismatch * -1 on a syntax error in the pattern */ int patmatch(char *sbuf, char *pat) { int match = 1,m,n; while(*pat && match) { switch(*pat) { case '[': pat++; if(*pat != '^') { n = 1; match = 0; } else { pat++; n = 0; } while(*pat != ']'){ if(*pat == '\\') pat++; if(!*pat) return -1; if(pat[1] == '-'){ m = *pat; pat += 2; if(*pat == '\\' && *pat) pat++; if(*sbuf >= m && *sbuf <= *pat) match = n; if(!*pat) pat--; } else if(*sbuf == *pat) match = n; pat++; } sbuf++; break; case '*': pat++; if(!*pat) return 1; while(*sbuf && !(match = patmatch(sbuf++,pat))); return match; case '?': if(!*sbuf) return 0; sbuf++; break; case '\\': if(*pat) pat++; default: match = (*sbuf++ == *pat); break; } pat++; if(match<1) return match; } if(!*sbuf) return match; return 0; } void *bmalloc(size_t s) { void *m; m = malloc(s); if (m == NULL) { fprintf(stderr,"sac: memory exhausted.\n"); exit(1); } memset(m,0,s); return m; } void report(void) { struct day *d; struct usr *u, *up; struct tty *t, *tp; struct simuse *s, *sp; struct login *l; struct tm in, *out; time_t h[24], tt; double inoct, outoct, inpkt, outpkt; long datarate = 0; int i; /* * Narrow down the days list to the range of days specified by the -s and -e * options. This seems kludged in to me, but I really can't see how else to * do it. */ if (sd || ed || sm || em) { if (!sd) { if (sm == MINUSZERO) sd = end->start; else if (!sm || sm == PLUSZERO) sd = days->start; else if (sm < 0) sd = (end->start + (86400*sm)) + 7200; else if (sm > 0) sd = (days->start + (86400*sm)) + 7200; } if (!ed) { if (em == PLUSZERO) ed = days->start; else if (!em || em == MINUSZERO) ed = end->start; else if (em < 0) ed = (end->start + (86400*em)) + 7200; else if (em > 0) ed = (days->start + (86400*em)) + 7200; } if (sd > ed) { fprintf(stderr,"sac: Start time [%ld] is greater than the end time [%ld]\n",sd,ed); exit(0); } for(d=days;d;d=d->nxt) { if (d->start <= sd && d->stop >= sd) days = d; if (d->start <= ed && d->stop >= ed) { d->nxt = NULL; end = d; } } } /* Produce the reports... */ switch(type & 0x000f) { case TOTAL: inoct = outoct = inpkt = outpkt = 0; datarate = 0; for(ndays=total=0,d=days;d;d=d->nxt) { if (hourmask) { for(i=0;i<24;i++) if (hourmask&(1<h[i]; } else total += d->time; ndays++; if ((type & IPACCT) == IPACCT) { inoct += d->inoct; outoct += d->outoct; inpkt += d->inpkt; outpkt += d->outpkt; datarate = ndays? ((datarate*(ndays-1))+d->datarate)/ndays : d->datarate; } } if (backtime) printf("Total: %s over %s hours.\n", dtime(total,0), back); else printf("Total: %12s over %d days.\n", dtime(total,0), ndays); if ((type & AVERAGE) == AVERAGE) { printf("Average: %10s / day, ",dtime(total,ndays)); printf("%10s / login\n",dtime(total,logins)); } if ((type & MINMAX) == MINMAX) printf("Logins: %11d Min: %3d Max: %3d\n",logins, max(0,minlogins), maxlogins); if ((type & IPACCT) == IPACCT) { printf("Octets: %12.0f in/out: %10.0f/%12.0f\n",inoct+outoct, inoct, outoct); printf("Packets:%12.0f in/out: %10.0f/%12.0f\n",inpkt+outpkt, inpkt, outpkt); if (datarate) printf("\tData rate: %9ld\n",datarate); } if ((type & LOGIN) == LOGIN) { for(d=days;d;d=d->nxt) { for(l=d->login;l;l=l->nxt) { out = localtime(&l->start); memcpy(&in,out,sizeof(struct tm)); out = localtime(&l->stop); tt = (l->stop - l->start) + 1; if (l->stop < days->start || l->start > end->stop) continue; if (longdate) printf(" %s %s %2d %4d",dayabbr[in.tm_wday], month[in.tm_mon], in.tm_mday, 1900+in.tm_year); else printf(" %s %2d", month[in.tm_mon], in.tm_mday); printf(" %02d:%02d:%02d - %02d:%02d:%02d %-8.8s %-6.12s %-16.16s %9s\n", in.tm_hour, in.tm_min, in.tm_sec, out->tm_hour, out->tm_min, out->tm_sec, l->name, l->tty, l->host, dtime(tt,0)); } } } if ((type & HOUR) == HOUR) { for(i=0;i<24;i++) h[i] = 0; for(d=days;d;d=d->nxt) for(i=0;i<24;i++) h[i] += d->h[i]; print_hours(h,total); } break; case DAY: for(d=days;d;d=d->nxt) { if (hourmask) { for(total=i=0;i<24;i++) if (hourmask&(1<h[i]; } else total = d->time; if (longdate) printf("%s %s %2d %4d",dayabbr[d->wday], month[d->month], d->day, d->year); else printf("%s %2d",month[d->month],d->day); printf(" total %10s",dtime(total,0)); if ((type & AVERAGE) == AVERAGE) { if (d->logins) printf(" %5ld logins %8s %s/login",d->logins,dtime(total,d->logins),timefmt==SECONDS?"secs":"hrs"); else { printf(" no logins"); if ((type & MINMAX) == MINMAX) printf(" "); } } if ((type & MINMAX) == MINMAX) { if (d->minlogins == -1) d->maxlogins = d->minlogins = d->logins; printf(" Min: %-3d Max: %3d",d->minlogins, d->maxlogins); } putchar('\n'); if ((type & IPACCT) == IPACCT) { printf("\tOctets: %12.0f in/out: %10.0f/%12.0f\n",d->inoct+d->outoct, d->inoct, d->outoct); printf("\tPackets:%12.0f in/out: %10.0f/%12.0f\n",d->inpkt+d->outpkt, d->inpkt, d->outpkt); if (d->datarate) printf("\tData rate: %9ld\n",d->datarate); } if ((type & LOGIN) == LOGIN) { for(l=d->login;l;l=l->nxt) { out = localtime(&l->start); memcpy(&in,out,sizeof(struct tm)); out = localtime(&l->stop); tt = (l->stop - l->start) + 1; if (l->stop < days->start || l->start > end->stop) continue; printf(" %-8.8s %02d:%02d:%02d - %02d:%02d:%02d port %-6.12s %-16.16s %10s\n", l->name, in.tm_hour, in.tm_min, in.tm_sec, out->tm_hour, out->tm_min, out->tm_sec,l->tty, l->host, dtime(tt,0)); } } if ((type & HOUR) == HOUR) print_hours(d->h,d->time); } break; case USER: for(u=us;u;u=u->nxt) { u->logins = 0; u->minlogins = -1; u->maxlogins = 0; u->ignore = ignoretime; u->inoct = u->outoct = u->inpkt = u->outpkt = 0; u->datarate = 0; for(ndays=total=0,d=days;d;d=d->nxt) { if ((up = finduser(d->us,u->user)) != NULL) { if (u->ignore) { if (hourmask || (type & HOUR) == HOUR) { up->time -= (up->time <= u->ignore) ? up->time : u->ignore; for(i=0;i<24 && u->ignore; i++) if (up->h[i] <= u->ignore) { u->ignore -= up->h[i]; up->h[i] = 0; } else { up->h[i] -= u->ignore; u->ignore = 0; } } else { if (up->time <= u->ignore) { u->ignore -= up->time; up->time = 0; } else { up->time -= u->ignore; u->ignore = 0; } } } if (hourmask) { for(i=0;i<24;i++) if (hourmask&(1<time += up->h[i]; } else u->time += up->time; u->logins += up->logins; if (d == days) u->logins += up->xlogins; if ((type & HOUR) == HOUR) for(i=0;i<24;i++) u->h[i] += up->h[i]; if ((type & MINMAX) == MINMAX) { if (up->minlogins == -1) up->maxlogins = up->minlogins = up->logins; u->maxlogins = max(u->maxlogins,up->maxlogins); if (u->minlogins > -1) u->minlogins = min(u->minlogins,up->minlogins); else u->minlogins = up->minlogins; } if ((type & IPACCT) == IPACCT) { u->inoct += up->inoct; u->outoct += up->outoct; u->inpkt += up->inpkt; u->outpkt += up->outpkt; ndays++; u->datarate = ndays? ((datarate*(ndays-1))+up->datarate)/ndays : up->datarate; } } else { if ((type & MINMAX) == MINMAX) u->minlogins = min(0,u->minlogins); } } printf("%-16s %10s",u->user,dtime(u->time,0)); if ((type & AVERAGE) == AVERAGE) { if (u->logins) printf(" %4ld logins %9s %s/login",u->logins,dtime(u->time,u->logins),timefmt==SECONDS?"secs":"hrs"); else { printf(" no logins"); if ((type & MINMAX) == MINMAX) printf(" "); } } if ((type & MINMAX) == MINMAX) { if (u->minlogins == -1) u->maxlogins = u->minlogins = 0; printf(" Min: %-3d Max: %3d",u->minlogins, u->maxlogins); } putchar('\n'); if ((type & IPACCT) == IPACCT) { printf("\tOctets: %12.0f in/out: %10.0f/%12.0f\n",u->inoct+u->outoct, u->inoct, u->outoct); printf("\tPackets:%12.0f in/out: %10.0f/%12.0f\n",u->inpkt+u->outpkt, u->inpkt, u->outpkt); if (u->datarate) printf("\tData rate: %9ld\n",u->datarate); } if ((type & LOGIN) == LOGIN) { for(l=u->login;l;l=l->nxt) { out = localtime(&l->start); memcpy(&in,out,sizeof(struct tm)); out = localtime(&l->stop); tt = (l->stop - l->start) + 1; if (l->stop < days->start || l->start > end->stop) continue; if (longdate) printf(" %s %s %2d %4d",dayabbr[in.tm_wday], month[in.tm_mon], in.tm_mday, 1900+in.tm_year); else printf(" %s %2d", month[in.tm_mon], in.tm_mday); printf(" %02d:%02d:%02d - %02d:%02d:%02d port %-6.12s %-16.16s %10s\n", in.tm_hour, in.tm_min, in.tm_sec, out->tm_hour, out->tm_min, out->tm_sec, l->tty, l->host, dtime(tt,0)); } } if ((type & HOUR) == HOUR) print_hours(u->h,u->time); } break; case TTY: for(t=ttys;t;t=t->nxt) { t->logins = 0; for (d=days;d;d=d->nxt) { if ((tp = findtty(d->tty,t->line)) != NULL) { if (hourmask) { for(total=i=0;i<24;i++) if (hourmask&(1<time += tp->h[i]; } else t->time += tp->time; t->logins += tp->logins; if (d == days) t->logins += tp->xlogins; if ((type & HOUR) == HOUR) for(i=0;i<24;i++) t->h[i] += tp->h[i]; } } if (t->time == 0) continue; printf(" %-8s %10s",t->line,dtime(t->time,0)); if ((type & AVERAGE) == AVERAGE) printf("\t%5ld logins %10s %s / login",t->logins,dtime(t->time,t->logins),timefmt==SECONDS?"seconds":"hours"); putchar('\n'); if ((type & IPACCT) == IPACCT) { printf("\tOctets: %12.0f in/out: %10.0f/%12.0f\n",t->inoct+t->outoct, t->inoct, t->outoct); printf("\tPackets:%12.0f in/out: %10.0f/%12.0f\n",t->inpkt+t->outpkt, t->inpkt, t->outpkt); if (t->datarate) printf("\tData rate: %9ld\n",t->datarate); } if ((type & LOGIN) == LOGIN) { for(l=t->login;l;l=l->nxt) { out = localtime(&l->start); memcpy(&in,out,sizeof(struct tm)); out = localtime(&l->stop); tt = (l->stop - l->start) + 1; if (l->stop < days->start || l->start > end->stop) continue; if (longdate) printf(" %s %s %2d %4d",dayabbr[in.tm_wday], month[in.tm_mon], in.tm_mday, 1900+in.tm_year); else printf(" %s %2d", month[in.tm_mon], in.tm_mday); printf(" %02d:%02d:%02d - %02d:%02d:%02d %-8.8s %-16.16s %10s\n", in.tm_hour, in.tm_min, in.tm_sec, out->tm_hour, out->tm_min, out->tm_sec, l->name, l->host, dtime(tt,0)); } } if ((type & HOUR) == HOUR) print_hours(t->h,t->time); } break; case SIMUSE: for(d=days;d;d=d->nxt) { for(s=d->simuse;s;s=s->nxt) { sp = findsim(&simuse,s->index); sp->count += s->count; sp->time += s->time; for(i=0;i<24;i++) sp->h[i] += s->h[i]; if (s->login == NULL) continue; if (sp->login == NULL) sp->login = s->login; else sp->lastlogin->nxt = s->login; sp->lastlogin = s->lastlogin; } } for (s=simuse;s;s=s->nxt) { printf(" %3d ttys %10s",s->index,dtime(s->time,0)); printf(" %10s %s usage",dtime(s->time*s->index,0),timefmt==SECONDS?"secs": "hrs"); if ((type & AVERAGE) == AVERAGE) printf(" %5ld times %8s /instance", s->count, dtime(s->time,s->count)); putchar('\n'); if ((type & LOGIN) == LOGIN) { for(l=s->login;l;l=l->nxt) { out = localtime(&l->start); memcpy(&in,out,sizeof(struct tm)); out = localtime(&l->stop); tt = (l->stop - l->start) + 1; if (l->stop < days->start || l->start > end->stop) continue; if (longdate) printf(" %s %s %2d %4d",dayabbr[in.tm_wday], month[in.tm_mon], in.tm_mday, 1900+in.tm_year); else printf(" %s %2d", month[in.tm_mon], in.tm_mday); printf(" %02d:%02d:%02d - %02d:%02d:%02d %10s\n", in.tm_hour, in.tm_min, in.tm_sec, out->tm_hour, out->tm_min, out->tm_sec, dtime(tt,0)); } } if ((type & HOUR) == HOUR) print_hours(s->h,s->time); } break; case RAW: for(i=0;i<24;i++) h[i] = 0; for(ndays=total=0,d=days;d;d=d->nxt) { if (hourmask) { for(i=0;i<24;i++) { if (hourmask&(1<h[i]; h[i] += d->h[i]; } } } else { total += d->time; for(i=0;i<24;i++) h[i] += d->h[i]; } ndays++; for(u=us;u;u=u->nxt) { if ((up = finduser(d->us,u->user)) != NULL) { if (hourmask) { for(i=0;i<24;i++) if (hourmask&(1<time += up->h[i]; } else u->time += up->time; u->logins += up->logins; if (d == days) u->logins += up->xlogins; for(i=0;i<24;i++) u->h[i] += up->h[i]; } } for(t=ttys;t;t=t->nxt) { if ((tp = findtty(d->tty,t->line)) != NULL) { if (hourmask) { for(i=0;i<24;i++) if (hourmask&(1<time += tp->h[i]; } else t->time += tp->time; t->logins += tp->logins; if (d == days) t->logins += tp->xlogins; for(i=0;i<24;i++) t->h[i] += tp->h[i]; } } } if (days) printf("Days in report: %d Start: %02d/%02d/%02d End: %02d/%02d/%02d Hourmask: %06lX\n",ndays,days->month,days->day,days->year,end->month,end->day,end->year,hourmask&0x0FFF); else printf("Days in report: 0 Start: -/-/- End: -/-/- Hourmask: %06lX\n",hourmask&0x0FFF); printf("Total: %lu (%u logins) hours:",total,logins); for(i=0;i<24;i++) printf(" %lu",h[i]); printf("\n\n"); for(i=0,u=us;u;u=u->nxt) i++; printf("Users in report: %d\n",i); for(u=us;u;u=u->nxt) { printf(" %8s total: %-10lu (%5ld logins) hours:",u->user,u->time,u->logins); for(i=0;i<24;i++) printf(" %lu",u->h[i]); printf("\n"); } printf("\n"); for(i=0,t=ttys;t;t=t->nxt) i++; printf("TTY's in report: %d\n",i); for(t=ttys;t;t=t->nxt) { printf(" %8s total: %-10lu (%5ld logins) hours:",t->line,t->time,t->logins); for(i=0;i<24;i++) printf(" %lu",t->h[i]); printf("\n"); } printf("\n"); for(d=days;d;d=d->nxt) { printf("%02d/%02d/%02d total: %-10lu (%5ld logins) hours:",d->month,d->day,d->year,d->time,d->logins); for(i=0;i<24;i++) printf(" %lu",d->h[i]); printf("\n"); for(u=d->us;u;u=u->nxt) { printf(" %8s total: %-10lu (%5ld logins) hours:",u->user,u->time,u->logins+(d==days?u->xlogins:0)); for(i=0;i<24;i++) printf(" %lu",u->h[i]); printf("\n"); } printf("\n"); for(t=d->tty;t;t=t->nxt) { printf(" %8s total: %-10lu (%5ld logins) hours:",t->line,t->time,t->logins+(d==days?t->xlogins:0)); for(i=0;i<24;i++) printf(" %lu",t->h[i]); printf("\n"); } printf("\n"); } } } void print_hours(time_t h[24], time_t total) { static char *bar = "########################################################################"; int i, bl = strlen(bar)-9; float p[24], scale, maxp = 0; if (!total) { for(i=0;i<24;i++) printf("%02d-: %8s\n",i,dtime(0,0)); } else { for(maxp=0,i=0;i<24;i++) { p[i] = (float)h[i] / (float)total; if (p[i] > maxp) maxp = p[i]; } scale = (float)bl / maxp; for(i=0;i<24;i++) { if (hourmask && !(hourmask & (1<= 30) m++; if (m >= 60) {h++; m=0;} } sprintf(tbuf,"%ld:%02ld",h,m); break; case HOURS: if (d) t=(time_t)((float)t/d); h=t/3600; if (rup) { m=(t-(h*3600))/60; s=t-((h*3600)+(m*60)); if (s >= 30) m++; if (m >= 60) {h++; m=0;} if (m >= 30) h++; } sprintf(tbuf,"%ld",h); break; } return tbuf; } sac-1.9/sac.h0100644000000000000000000001432607461273724011571 0ustar rootroot/* $Copyright: $ * Copyright (c) 1995 - 2001 by Steve Baker (ice@mama.indstate.edu) * All Rights reserved * * This software is provided as is without any express or implied * warranties, including, without limitation, the implied warranties * of merchant-ability and fitness for a particular purpose. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utmp.h" #define scopy(s) (strcpy(bmalloc(strlen(s)+1),s)) #define min(a,b) ((a) < (b) ? (a) : (b)) #define max(a,b) ((a) > (b) ? (a) : (b)) /* This will break if someone has an 89 year old wtmp =) */ #define MINUSZERO -32767 /* HACK ALERT!! */ #define PLUSZERO +32767 /* HACK ALERT!! */ #define BUFSIZE 1024 enum { TOTAL = 0x0001, /* Total login time */ DAY = 0x0002, /* Daily average */ USER = 0x0003, /* Per user login time */ TTY = 0x0004, /* Show usage / tty */ RAW = 0x0005, /* Show everything */ SIMUSE = 0x0006, /* Simultaneous tty usage */ AVERAGE = 0x0010, /* Average login time / login */ HOUR = 0x0020, /* Hourly profile */ CLIP = 0x0040, /* Multiple logins during same time count only once */ MINMAX = 0x0080, /* Show min max # logins at one time */ LOGIN = 0x0100, /* Display login breakdown */ IPACCT = 0x0200, /* Display IP accounting/data rate info */ CALLID = 0x0400, /* Display caller ID accounting */ IGNORE = 0x0800 /* Ignore X amount of usage before starting accounting */ }; /* Time formats */ enum { FRAC = 0x00, /* default: %10.2f hours */ HMS = 0x01, HM = 0x02, HOURS = 0x03, SECONDS = 0x04, ROUND = 0x40, }; #ifndef TRUE enum { FALSE=0, TRUE=1 }; #endif enum { USERLIST, EXCLUDELIST, TTYLIST, HOSTLIST, PORTMASTERLIST, LOGFILELIST, FILELIST }; struct day { time_t start, stop; short day, wday, month, year, minlogins, maxlogins; time_t time; time_t h[24]; double inoct, outoct; double inpkt, outpkt; u_long datarate; struct login *login, *lastlogin; u_long logins; struct usr *us; struct tty *tty; struct simuse *simuse; struct day *nxt; }; /* * Keep a pointer at the end of the days list, since we'll usually be * adding crap to it, not to days before. What 'o what do we do about * time changes aka time warps? */ struct usr { char user[UT_NAMESIZE+1]; short loggedin, minlogins, maxlogins; time_t time; time_t ignore; time_t h[24]; double inoct, outoct; double inpkt, outpkt; u_long datarate; struct login *login, *lastlogin; u_long logins, xlogins; struct usr *nxt; }; struct tty { char line[UT_LINESIZE+1]; time_t time, h[24]; double inoct, outoct; double inpkt, outpkt; u_long datarate; struct login *login, *lastlogin; u_long logins, xlogins; struct tty *nxt; }; /* simultaneous usage time */ struct simuse { short index; time_t time; long count; time_t h[24]; struct login *login, *lastlogin; struct simuse *nxt; }; /* excluded users list */ struct exc { char user[UT_NAMESIZE+1]; struct exc *nxt; }; /* ttys list */ struct ttys { char *line; char ispat; /* Is tty a pattern? */ struct ttys *nxt; }; /* hosts list */ struct hosts { char *host; char ispat; /* Is host a pattern? */ char len,ss; /* Is host a substring? */ struct hosts *nxt; }; struct user { char user[UT_NAMESIZE+1]; char line[UT_LINESIZE+1]; char host[UT_HOSTSIZE+1]; time_t in; struct user *nxt; }; enum filetype { ACCT_WTMP, /* Good old linux (sysv) extended utmp format */ ACCT_ANCIENT, /* Bad old BSD style utmp with no ut_type field. */ ACCT_TACACS3, /* same as above (old BSD style) */ ACCT_TACACS4, /* Bizarre utmp w/ ut_comment field. (no ut_type) */ ACCT_RADIUS, /* sucky detail file support */ ACCT_RADIUS_LOGFILE /* probably won't support this. */ }; struct file { char *file; char ftype; void (*gronk)(); struct file *nxt; }; struct login { time_t start, stop; char *name; char *tty; char *host; struct login *nxt; }; #include "proto.h" /* Globals */ #ifdef MAIN # define EXTERN # define INIT(x) = x #else # define EXTERN extern # define INIT(x) #endif EXTERN struct day *days INIT(NULL), *end INIT(NULL); EXTERN struct usr *us INIT(NULL); EXTERN struct ttys *tty INIT(NULL); EXTERN struct exc *ex INIT(NULL); EXTERN struct simuse *simuse INIT(NULL); EXTERN struct tty *ttys INIT(NULL); EXTERN struct hosts *hosts INIT(NULL); EXTERN struct user *usr INIT(NULL); #ifdef MAIN char *month[13] = { "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec", NULL }; char *dayabbr[8] = {"Sun","Mon","Tue","Wed","Thr","Fri","Sat",NULL}; #else extern char *month[13], *dayaddbr[8]; #endif EXTERN char fix INIT(FALSE), exclude INIT(FALSE), fixtty INIT(FALSE), fixhost INIT(FALSE), radhost INIT(FALSE); EXTERN char hosttoo INIT(FALSE), verbose INIT(FALSE), longdate INIT(FALSE); EXTERN char *back INIT(NULL), *ignore INIT(NULL), timefmt INIT(FRAC); EXTERN u_short type INIT(TOTAL); EXTERN time_t total INIT(0), sd INIT(0), ed INIT(0), backtime INIT(0), curtime INIT(0); EXTERN time_t lastent INIT(0), ignoretime INIT(0), cutoff INIT(0), discard INIT(0); EXTERN int ndays INIT(0), logins INIT(0), loggedin INIT(0), minlogins INIT(-1), maxlogins INIT(0); EXTERN signed int sm INIT(0), em INIT(0); EXTERN u_long hourmask INIT(0); EXTERN char *usespec; /* Simultaneous usage globals */ EXTERN int simdex INIT(0); EXTERN time_t simtime INIT(0); EXTERN char buf[BUFSIZE+1]; /* char *month[] = { "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec", NULL }; char *dayabbr[] = {"Sun","Mon","Tue","Wed","Thr","Fri","Sat",NULL }; char fix = FALSE, exclude = FALSE, fixtty = FALSE, fixhost = FALSE, radhost = FALSE; char hosttoo = FALSE, verbose = FALSE, longdate = FALSE; char *back = NULL, *ignore = NULL, timefmt = FRAC; u_short type = TOTAL; time_t total = 0, sd = 0, ed = 0, backtime = 0, curtime = 0; time_t lastent = 0, ignoretime = 0, cutoff = 0, discard = 0; int ndays = 0, logins = 0, loggedin = 0, minlogins = -1, maxlogins = 0; signed int sm = 0, em = 0; u_long hourmask = 0; char *usespec; int simdex = 0; time_t simtime = 0; char buf[BUFSIZE+1]; */ sac-1.9/utmp.h0100644000000000000000000000541507461276124012004 0ustar rootroot #ifdef SOLARIS # include #else # include # include #endif #ifdef BSD # define ut_user ut_name #endif /* Weee... */ #ifdef WTMPX_FILE # define _PATH_WTMP WTMPX_FILE #else # ifndef WTMP_FILE /* FSSTND (now FHS) compliance courtesy of Edward S. Marshall */ # ifndef _PATH_WTMP # define _PATH_WTMP "/var/log/wtmp" /* FSSTND compliant */ # endif # else # ifndef _PATH_WTMP # define _PATH_WTMP WTMP_FILE # endif # endif #endif #ifndef RADIUS_DIR # define RADIUS_DIR "/usr/adm/radacct" #endif /* This is _NOT_ a good way to do this. */ #ifdef SOLARIS # define UT_NAMESIZE 32 # define UT_LINESIZE 32 # define UT_HOSTSIZE 257 # define utmp utmpx # define ut_time ut_tv.tv_sec #endif struct tacacs3_utmp { char ut_line[8]; char ut_user[8]; char ut_host[16]; time_t ut_tactime; }; struct tacacs4_utmp { char ut_line[8]; char ut_user[8]; char ut_host[16]; time_t ut_tactime; char ut_comment[16]; }; #define LIBC6_LINESIZE 32 #define LIBC6_NAMESIZE 32 #define LIBC6_HOSTSIZE 256 struct libc6_utmp { short int ut_type; /* Type of login. */ pid_t ut_pid; /* Process ID of login process. */ char ut_line[LIBC6_LINESIZE]; /* Devicename. */ char ut_id[4]; /* Inittab ID. */ char ut_user[LIBC6_NAMESIZE]; /* Username. */ char ut_host[LIBC6_HOSTSIZE]; /* Hostname for remote login. */ struct libc6_exit_status { short int e_termination; /* Process termination status. */ short int e_exit; /* Process exit status. */ } ut_exit; /* Exit status of a process marked as DEAD_PROCESS. */ long int ut_session; /* Session ID, used for windowing. */ struct timeval ut_tv; /* Time entry was made. */ int32_t ut_addr_v6[4]; /* Internet address of remote host. */ char __unused[20]; /* Reserved for future use. */ }; #define LIBC5_LINESIZE 12 #define LIBC5_NAMESIZE 8 #define LIBC5_HOSTSIZE 16 struct libc5_utmp { short ut_type; /* type of login */ pid_t ut_pid; /* pid of login-process */ char ut_line[LIBC5_LINESIZE]; /* devicename of tty -"/dev/", null-term */ char ut_id[4]; /* abbrev. ttyname, as 01, s1 etc. */ time_t ut_5time; /* logintime */ char ut_user[LIBC5_NAMESIZE]; /* username, not null-term */ char ut_host[LIBC5_HOSTSIZE]; /* hostname for remote login... */ long ut_5addr; /* IP addr of remote host */ }; #define BSD_LINESIZE 8 #define BSD_NAMESIZE 16 #define BSD_HOSTSIZE 16 struct bsd_utmp { char ut_line[BSD_LINESIZE]; char ut_name[BSD_NAMESIZE]; char ut_host[BSD_HOSTSIZE]; time_t ut_bsdtime; }; struct sactmp { struct utmp u; /* detail files have lots of extra fun stuff */ double inoct, outoct; double inpkt, outpkt; u_long datarate; char callid[32]; }; sac-1.9/wcat.c0100644000000000000000000001534607262117300011741 0ustar rootroot/* $Copyright: $ * Copyright (c) 2001 by Steve Baker (ice@mama.indstate.edu) * All Rights reserved * * This software is provided as is without any express or implied * warranties, including, without limitation, the implied warranties * of merchant-ability and fitness for a particular purpose. */ #ifdef SOLARIS #include #else #include #endif #include #include #include #include #include #include #include #include #include #ifdef SOLARIS #define UT_NAMESIZE 32 #define UT_LINESIZE 32 #define UT_HOSTSIZE 257 #define UT_UNKNOWN 0 #define utmp utmpx #define ut_time ut_tv.tv_sec #endif extern int errno; static char *version = "$Version: $ wcat v1.0 (c) 2001 by Steve Baker $"; #ifdef SOLARIS #define _PATH_WTMP WTMPX_FILE #else #ifndef _PATH_WTMP #define _PATH_WTMP "/var/log/wtmp" #endif #endif enum { FALSE=0, TRUE=1 }; struct tacacs3_utmp { char ut_line[8]; char ut_user[8]; char ut_host[16]; time_t ut_tactime; }; struct tacacs4_utmp { char ut_line[8]; char ut_user[8]; char ut_host[16]; time_t ut_tactime; char ut_comment[16]; }; void usage(), doit(), doit_tacacs3(), doit_tacacs4(); time_t getsacdate(), churn_time(); char dflg = FALSE, useaddr = FALSE; char *start = NULL, *end = NULL, *back = NULL; time_t sd = 0, ed = 0, backtime = 0, curtime= 0; int fd; /* * wcat [-w wtmp] [-adX[3|4]] [-s start] [-e end] [-b hours:minutes:seconds] * [--help] [--version] */ int main(int argc, char **argv) { char *file = _PATH_WTMP, tacacs3 = FALSE, tacacs4 = FALSE; int i, j, n; for(n=i=1;i ed) continue; write(1,ut,sizeof(struct utmp)); } } void doit_tacacs3() { struct tacacs3_utmp u; void *ut = &u; int n, m; while (1) { for(n=m=read(fd,ut,sizeof(struct tacacs3_utmp));n < (int)sizeof(struct tacacs3_utmp);n+=m=read(fd,ut+n,sizeof(struct tacacs3_utmp)-n)) { if (m < 0) { perror("rawtmp"); return; } if (!m) return; } if (back && u.ut_tactime < backtime) continue; if (start && u.ut_tactime < sd) continue; if (end && u.ut_tactime > ed) continue; write(1,ut,sizeof(struct tacacs3_utmp)); } } void doit_tacacs4() { struct tacacs4_utmp u; void *ut = &u; int n, m; while (1) { for(n=m=read(fd,ut,sizeof(struct tacacs4_utmp));n < (int)sizeof(struct tacacs4_utmp);n+=m=read(fd,ut+n,sizeof(struct tacacs4_utmp)-n)) { if (m < 0) { perror("rawtmp"); return; } if (!m) return; } if (back && u.ut_tactime < backtime) continue; if (start && u.ut_tactime < sd) continue; if (end && u.ut_tactime > ed) continue; write(1,ut,sizeof(struct tacacs4_utmp)); } } /* * Turn "mm/dd/yy" into time in seconds. */ time_t getsacdate(s) char *s; { struct tm tm; int y; time_t t; /* * * Need a real date parser that can handle different formats and separators */ if (!isdigit(s[0]) || !isdigit(s[1]) || isdigit(s[2])) usage(2); if (!isdigit(s[3]) || !isdigit(s[4]) || isdigit(s[5])) usage(2); if (!isdigit(s[6]) || !isdigit(s[7]) || s[8]) usage(2); tm.tm_mon = atoi(s) -1; tm.tm_mday = atoi(s+3); y = atoi(s+6); tm.tm_year = (y < 70? 100 + y : y); tm.tm_isdst = tm.tm_hour = tm.tm_min = tm.tm_sec = 0; t = mktime(&tm); if (t == (time_t)(-1)) usage(2); return t; } time_t churn_time(s) char *s; { time_t t = 0, mult=3600; u_char nbuf[20], p; for(p=0;*s;s++) { if (*s == ':') { nbuf[p] = (u_char)0; t += atoi(nbuf) * mult; p = 0; if (mult > 1) mult /= 60; else usage(3); } else if (isdigit(*s)) { if (p < 15) nbuf[p++] = (u_char)*s; else usage(3); } else usage(3); } nbuf[p] = (u_char)0; t += atoi(nbuf) * mult; return t; } void usage(n) int n; { switch (n) { case 1: fprintf(stderr,"usage: wcat [-w wtmp|-] [-adX[3|4]] [-s start] [-e end] [-b H[:M[:S]]]\n [--help] [--version]\n"); break; case 2: fprintf(stderr,"wcat: Invalid date. Format: mm/dd/yy\n"); break; case 3: fprintf(stderr,"wcat: Invalid time. Format: hours[:minutes[:seconds]]\n"); break; case 4: fprintf(stderr,"usage: wcat [-w wtmp|-] [-adX[3|4]] [-s start] [-e end] [-b H[:M[:S]]]\n [--help] [--version]\n"); fprintf(stderr," -w wtmp|- Read alternate wtmp file.\n"); fprintf(stderr," -X[3] Read tacacs 3.x wtmp format.\n"); fprintf(stderr," -X4 Read tacacs 4.0 wtmp format.\n"); fprintf(stderr," -d Output time in MMM DD HH:MM:SS date format.\n"); fprintf(stderr," -a Print contents of ut_addr (if it exists) instead of ut_host.\n"); fprintf(stderr," -s start Display accounting info from `start'.\n"); fprintf(stderr," -e end Display accounting info up to `end'.\n"); fprintf(stderr," -b H:M:S Show accounting info from the last few hours/minutes/seconds.\n"); fprintf(stderr," --help Print this help message.\n"); fprintf(stderr," --version Print the version of rawtmp.\n"); break; case 5: { char *v = version+12; printf("%.*s\n",(int)strlen(v)-1,v); exit(0); } } exit(1); } sac-1.9/writetmp.80100644000000000000000000001145407272045233012605 0ustar rootroot.\" $Copyright: $ .\" Copyright (c) 1998 by Steve Baker (ice@mama.indstate.edu) .\" All Rights reserved .\" .\" This software is provided as is without any express or implied .\" warranties, including, without limitation, the implied warranties .\" of merchant-ability and fitness for a particular purpose. .\" ... .V= $Header: writetmp.8 1.0 1998 $ .TH WRITETMP 8 "\*(V)" "UNIX Manual" .SH NAME writetmp \- write special wtmp entries to a wtmp file. .SH SYNOPSIS \fBwritetmp\fP [\fB-w\fP \fIwtmp\fP|\fB-\fP] [\fB-X[3|4]\fP] [\fB-u\fP \fIuser\fP] [\fB-l\fP \fIline\fP] [\fB-h\fP \fIhost\fP] [\fB-i\fP \fIid\fP] [\fB-p\fP \fIpid\fP] [\fB-t\fP \fItype\fP] [\fB-c\fP \fIcomment\fP] [\fB--help\fP] [\fB--version\fP] [\fBentry-type\fP] .br .SH DESCRIPTION \fIWritetmp\fP is a utility to write special entries to a wtmp file. Useful as either a replacement for the functionality of the "\fBhalt -w\fP" or "\fBreboot -w\fP" commands which are normally run at shutdown time or to write special wtmp entries to an alternate wtmp file to which such entries would normally not be written. Under normal conditions radius radtwmp or tacacs accounting logs do not contain shutdown and boottime entries because the access control software is not setup to take into account these events. In the case of a quick shutdown or server crash, the wtmp file(s) will lose coherency. To avoid or minimize the amount of accounting error, it is necessary to write shutdown and boottime entries to such logs. Also changes in time which are made manually with \fIdate\fP or via the network with a program such as \fIrdate\fP are not reflected in the accounting logs, which, if the time difference is severe can improperly account time for logins active during the time change. If an \fIentry-type\fP is specified on the command line, the \fB-u\fP, \fB-l\fP, \fB-h\fP, \fB-i\fP, \fB-p\fP, \fB-t\fP and \fB-c\fP options are ignored as writetmp will fill in the username, line, id, and host entries as required for that particular wtmp entry-type. \fIWritetmp\fP understands the following entry types: .TP \fBshutdown\fP used just prior to a normal system shutdown. Also accepts \fBhalt\fP or \fBreboot\fP as aliases for \fBshutdown\fP. .PP .TP \fBboottime\fP used at system initialization time, to indicate the system is booting. .PP .TP \fBoldtime\fP Indicates the time is about to change. .PP .TP \fBnewtime\fP Indicates the time has changed. The difference in time is determined from the timestamp on the last \fBoldtime\fP entry. .PP .TP \fBrunlevel\fP Indicates a change in runlevel (useless in an accounting sense). .PP .SH OPTIONS \fIWritetmp\fP understands the following command line switches: .TP \fB--help\fP Outputs a verbose usage listing. .PP .TP \fB--version\fP Displays the version of writetmp. .PP .TP \fB-w\fP \fIwtmp\fP Select a different output file instead of the default (\fI/var/log/wtmp\fP). .PP .TP \fB-X[3]\fP Write to a wtmp file maintained by versions 3.3 or 3.4 Tacacs terminal server access control software. .PP .TP \fB-X4\fP Write to a wtmp file maintained by version 4.0 of Tacacs terminal server access control software. .PP .TP \fB-u user\fP Specify the username for the username field. .PP .TP \fB-l line\fP Specify the tty name for the line field. .PP .TP \fB-h host\fP Specify the hostname. .PP .TP \fB-i id\fP Specify the init id name. Not applicable to tacacs wtmp files. .PP .TP \fB-p pid\fP Specify the pid number. Not appliccable to tacacs wtmp files. .PP .TP \fB-t type\fP Specify the type of wtmp entry for the ut_type field, not to be confused with entry-type. May be coded as a number or one of: \fBunknown\fP, \fBrunlevel\fP, \fBboottime\fP, \fBnewtime\fP, \fBoldtime\fP, \fBinit\fP, \fBlogin\fP, \fBuser\fP or \fBdead\fP. .PP .TP \fB-c comment\fP Specify the comment for the tacacs 4 wtmp comment field (16 characters max). .PP .SH EXAMPLES Write a shutdown message to an alternate wtmp log: writetmp -w /var/adm/xtmp shutdown A shell script to update the time in an alternate wtmp file when \fInetdate\fP is run: #!/bin/sh .br writetmp -w /var/adm/xtmp oldtime .br netdate clock.llnl.gov .br writetmp -w /var/adm/xtmp newtime Find out how often and for how long people run a specific program, such as \fIpine\fP: #!/bin/sh .br # /var/adm/cmdtmp must be globally writable. .br cmdtmp=/var/adm/cmdtmp .br .br writetmp -w $cmdtmp -u pine -l cmd$$ -h $USER -t user .br /path/to/real-pine $* .br writetmp -w $cmdtmp -l cmd$$ -t dead .SH FILES /var/log/wtmp login database. .SH AUTHOR Steve Baker (ice@mama.indstate.edu) .SH BUGS Does not lock the wtmp file and does not guarantee a successful write. Could in theory corrupt a log file. \fIRdate\fP and \fInetdate\fP can take seconds to complete, so writing oldtime/newtime records around them may not be entirely accurate. .SH SEE ALSO .BR date (1), .BR last (1), .BR sac (8), .BR netdate (8L), .BR reboot (8) sac-1.9/writetmp.c0100644000000000000000000001730007261702276012661 0ustar rootroot/* $Copyright: $ * Copyright (c) 1998 by Steve Baker (ice@mama.indstate.edu) * All Rights reserved * * This software is provided as is without any express or implied * warranties, including, without limitation, the implied warranties * of merchant-ability and fitness for a particular purpose. */ #include #include #include #ifdef SOLARIS #include #else #include #endif #include #include #include #include #include static char *version = "$Version: $ writetmp v1.0 (c) 1998 by Steve Baker $"; #ifdef SOLARIS #define _PATH_WTMP WTMPX_FILE #else #ifndef _PATH_WTMP #define _PATH_WTMP "/var/log/wtmp" #endif #endif #ifdef SOLARIS #define UT_UNKNOWN 0 #define UT_NAMESIZE 32 #define UT_LINESIZE 32 #define UT_HOSTSIZE 257 #define utmp utmpx #define ut_time ut_tv.tv_sec #endif struct tacacs3_utmp { char ut_line[8]; char ut_user[8]; char ut_host[16]; time_t ut_tactime; }; struct tacacs4_utmp { char ut_line[8]; char ut_user[8]; char ut_host[16]; time_t ut_tactime; char ut_comment[16]; }; struct uttypes { char *name; int val; } uttypes[] = { {"unknown", UT_UNKNOWN}, {"runlevel", RUN_LVL}, {"boottime", BOOT_TIME}, {"newtime", NEW_TIME}, {"oldtime", OLD_TIME}, {"init", INIT_PROCESS}, {"login", LOGIN_PROCESS}, {"user", USER_PROCESS}, {"dead", DEAD_PROCESS}, {NULL, 0} }; enum { TRUE=1, FALSE=0 }; enum { SHUTDOWN=0, HALT, REBOOT, BOOTTIME, RUNLEVEL, OLDTIME, NEWTIME }; struct wtype { char *command; char *user; char *line; char *id; short ut_type; } wtype[] = { {"shutdown", "shutdown", "~", "~~", RUN_LVL}, {"halt", "shutdown", "~", "~~", RUN_LVL}, {"reboot", "shutdown", "~", "~~", RUN_LVL}, {"boottime", "reboot", "~", "~~", BOOT_TIME}, {"runlevel", "runlevel", "~", "~~", RUN_LVL}, {"oldtime", "", "", "|", OLD_TIME}, {"newtime", "", "", "{", NEW_TIME}, {NULL, NULL, NULL, NULL, 0} }; void usage(int); int doit(int, char *, char *, char *, short, char *, pid_t); int doit_tacacs3(int, char *, char *, char *); int doit_tacacs4(int, char *, char *, char *, char *); int fd; /* * writetmp [-w wtmp] [-X[3|4]] [-u user] [-l line] [-h host] [-t type] * [-i id] [-p pid] [-c comment] [entry-type] [--help] [--version] */ int main(int argc, char **argv) { char *file = _PATH_WTMP, tacacs3 = FALSE, tacacs4 = FALSE; char *user = NULL, *line = NULL, *host = NULL, *id = NULL, *comment = NULL; pid_t pid = 0; short type = 0; int i, j, n, r; int command = -1; for(n=i=1;i= 0) { strncpy(u.ut_line,wtype[command].line,UT_LINESIZE); strncpy(u.ut_user,wtype[command].user,UT_NAMESIZE); strncpy(u.ut_id,wtype[command].id,2); u.ut_type = wtype[command].ut_type; } else { if (line) strncpy(u.ut_line, line, UT_LINESIZE); if (host) strncpy(u.ut_host, host, UT_HOSTSIZE); if (user) strncpy(u.ut_user, user, UT_NAMESIZE); if (id) strncpy(u.ut_id, id, 2); u.ut_type = type; u.ut_pid = pid; } u.ut_time = time(0); return write(fd,&u,sizeof(u)) < 0; } int doit_tacacs3(int command, char *line, char *user, char *host) { struct tacacs3_utmp u; memset(&u,0,sizeof(u)); if (command >= 0) { strncpy(u.ut_line,wtype[command].line,UT_LINESIZE); strncpy(u.ut_user,wtype[command].user,UT_NAMESIZE); } else { if (line) strncpy(u.ut_line, line, 8); if (user) strncpy(u.ut_user, user, 8); if (host) strncpy(u.ut_host, host, 16); } u.ut_tactime = time(0); return write(fd,&u,sizeof(u)) < 0; } int doit_tacacs4(int command, char *line, char *user, char *host, char *comment) { struct tacacs4_utmp u; memset(&u,0,sizeof(u)); if (command) { strncpy(u.ut_line,wtype[command].line,UT_LINESIZE); strncpy(u.ut_user,wtype[command].user,UT_NAMESIZE); } else { if (line) strncpy(u.ut_line, line, 8); if (user) strncpy(u.ut_user, user, 8); if (host) strncpy(u.ut_host, host, 16); if (comment) strncpy(u.ut_comment, comment, 16); } u.ut_tactime = time(0); return write(fd,&u,sizeof(u)) < 0; } void usage(n) int n; { switch (n) { case 1: fprintf(stderr,"usage: writetmp [-w wtmp|-] [-X[3|4]] [-u user] [-l line] [-h host] [-i id]\n\t[-t type] [-p pid] [-c comment] [--help] [--version] [entry-type]\n"); break; case 2: fprintf(stderr,"writetmp: Error opening wtmp file for writing.\n"); break; case 4: fprintf(stderr,"usage: writetmp [-w wtmp|-] [-X[3|4]] [-u user] [-l line] [-h host] [-i id]\n\t[-t type] [-p pid] [-c comment] [--help] [--version] [entry-type]\n"); fprintf(stderr," -w wtmp|- Write to alternate wtmp file.\n"); fprintf(stderr," -X[3] Write in tacacs 3.x wtmp format.\n"); fprintf(stderr," -X4 Write in tacacs 4.0 wtmp format.\n"); fprintf(stderr," -u user Specify username.\n"); fprintf(stderr," -l line Specify the line.\n"); fprintf(stderr," -h host Specify the hostname.\n"); fprintf(stderr," -i id Specify the init id.\n"); fprintf(stderr," -t type Specify the type of entry.\n"); fprintf(stderr," -p pid Specify the pid number.\n"); fprintf(stderr," -c comment Specify the comment.\n"); fprintf(stderr," --help Print this help message.\n"); fprintf(stderr," --version Print the version of rawtmp.\n"); fprintf(stderr," Entry-types:\n"); fprintf(stderr," shutdown Indicates the system is shutting down.\n"); fprintf(stderr," reboot Alias for shutdown.\n"); fprintf(stderr," halt Alias for shutdown.\n"); fprintf(stderr," boottime Indicates the system is booting.\n"); fprintf(stderr," oldtime Indicates the time is about to change.\n"); fprintf(stderr," newtime Indicates the time has changed.\n"); fprintf(stderr," runlevel Indicates the runlevel has changed.\n"); break; case 5: { char *v = version+12; printf("%.*s\n",(int)strlen(v)-1,v); exit(0); } } exit(1); } sac-1.9/fixers/Makefile0100644000000000000000000000145106642044753013603 0ustar rootroot# $Copyright: $ # Copyright (c) 1997 by Steve Baker # All Rights reserved # # This software is provided as is without any express or implied # warranties, including, without limitation, the implied warranties # of merchant-ability and fitness for a particular purpose. CC=gcc # For Intel platforms: CFLAGS=-Wall -O2 -fomit-frame-pointer -m486 # For everything else: # CFLAGS=-Wall -O2 # For debugging #CFLAGS=-ggdb # Comment this if doing debugging. LDFLAGS=-s all: fixtmp fixtime fixterm fixtime: fixtime.o fixtime.o: fixtime.c fixterm: fixterm.o fixterm.o: fixterm.c fixtmp: fixtmp.o fixtmp.o: fixtmp.c clean: if [ -e fixtmp ]; then rm fixtmp; fi if [ -e fixtime ]; then rm fixtime; fi if [ -e fixterm ]; then rm fixterm; fi if [ -e fixtmp -o -e fixtime.o -o -e fixterm.o ]; then rm *.o; fi sac-1.9/fixers/fixterm.c0100644000000000000000000000251306435360577013772 0ustar rootroot/* From: Thomas Roessler Subject: sac / xterm's logout entries I run the following program from cron every few minutes here to fix xterm's wtmp entries. Maybe you want to include it with your sac package? */ /* * Fix xterm's messed-up wtmp entries. * * Thomas Roessler , * Thu Aug 29 15:42:37 MET DST 1996 * * Copy and redistribute freely. */ #include #include #include #include #include #include #include #include int main(int argc, const char *argv[]) { struct utmp u; int fd; int status = 0; if((fd = open(argc == 2 ? argv[1] : _PATH_WTMP,O_RDWR)) == -1) { perror("open"); exit(1); } if(flock(fd, LOCK_EX) == -1) { perror("flock"); status = 1; goto bye; } while(read(fd, &u, sizeof(struct utmp)) == sizeof(struct utmp)) { if(u.ut_type == DEAD_PROCESS && *(u.ut_user)) { *(u.ut_user) = '\0'; if(lseek(fd, -sizeof(struct utmp), SEEK_CUR) == -1) { perror("lseek"); status = 1; goto bye; } if(write(fd, &u, sizeof(struct utmp)) != sizeof(struct utmp)) { perror("write"); status = 1; goto bye; } } } bye: if(flock(fd, LOCK_UN) == -1) { perror("flock"); exit(1); } if(close(fd) == -1) { perror("close"); exit(1); } return(status); } sac-1.9/fixers/fixtime.c0100644000000000000000000000520507150112760013742 0ustar rootroot/* $Copyright: $ * Copyright (c) 1997 by Steve Baker (ice@mama.indstate.edu) * All Rights reserved * * This software is provided as is without any express or implied * warranties, including, without limitation, the implied warranties * of merchant-ability and fitness for a particular purpose. */ #include #include #include #include /* Adjust the time quantum if your time corruption is finer grained * or you have infrequent logins */ #define FUDGEFACTOR 60*60*24*30 /* 30 days */ char *prttime(time_t); /* Fixtime: * * Fixes netdate time corruption, by resyncing time entries. * Use: fixtime [ | -] * Use different output file to test if fixtime works for you, * then use the same input and output filename to update the file. * Example: fixtime /var/log/wtmp /var/log/wtmp * * THIS IS NOT A WTMP DE-CORRUPTOR. YOUR WTMP MUST _NOT_ BE CORRUPT. * USE AT YOUR OWN RISK. THIS IS NOT VERY WELL TESTED. * * Compile with: make fixtime */ int main(argc,argv) int argc; char **argv; { struct utmp u; time_t prev = 0, factor = 0; int in, out, n, verbose = 0; if (argc < 3) { fprintf(stderr,"usage: %s [-v] [ | -]\n",argv[0]); exit(1); } n = 1; if (!strcmp(argv[n],"-v")) { verbose = 1; n++; } if ((in = open(argv[n],O_RDONLY)) < 0) { fprintf(stderr,"error opening %s for reading.\n",argv[1]); exit(1); } n++; if (strcmp("-",argv[n])) { if ((out = open(argv[2],O_RDWR|O_CREAT)) < 0) { fprintf(stderr,"error opening %s for writing.\n",argv[1]); exit(1); } } else out = 1; n=read(in,&u,sizeof(struct utmp)); prev = u.ut_time; write(out,&u,sizeof(struct utmp)); while((n=read(in,&u,sizeof(struct utmp))) == sizeof(struct utmp)) { if (u.ut_time > (prev+FUDGEFACTOR) || u.ut_time < (prev-FUDGEFACTOR)) { if (!factor) { factor = u.ut_time - prev; if (verbose) fprintf(stderr,"Adusting time by %s",prttime(factor)); } u.ut_time -= factor; if (verbose) fprintf(stderr,"."); } else { if (factor && verbose) fprintf(stderr,"\n"); factor = 0; } write(out,&u,sizeof(struct utmp)); prev = u.ut_time; } exit(0); } char *prttime(time_t factor) { static char buf[256]; if (factor < 60) { sprintf(buf,"%ld seconds",factor); } if (factor < 3600) { sprintf(buf,"%ld:%02ld minutes",factor/60,factor%60); } if (factor < 86400) { sprintf(buf,"%ld:%ld:%ld hours",factor/3600,(factor%3600)/60,factor%60); } else { sprintf(buf,"%ld days, %ld:%ld:%ld hours",factor/86400,(factor%86400)/3600,(factor%3600)/60,factor%60); } return buf; } sac-1.9/fixers/fixtmp.c0100644000000000000000000000526106756620406013622 0ustar rootroot#include #include #include #include #include #define WARP_FACTOR (60*60*24*120) /* 120 days */ /* Simple fixtmp does this: * - Scan forward until it hits bad utmp. * - Reposition at end of wtmp and scan backwards until it hits bad utmp. * - Ouput the good stuff. * |------> [bad entries] <------| * This works OK with just one corrupted area, not so good with lots of corruption/ * libc5/glib wtmp sharing. */ /* * usage: fixtmp [ | - ] */ int main(int argc, char **argv) { struct utmp u; int n, in, out; time_t lastent; long rpos, lpos, pos; if (argc < 3) { fprintf(stderr,"Usage: %s [ | - ]\n",argv[0]); exit(1); } if ((in = open(argv[1],O_RDONLY,0)) < 0) { fprintf(stderr,"unable to open '%s' for reading.\n",argv[1]); exit(1); } if (!strcmp("-",argv[2])) out = 1; else { if ((out = open(argv[2],O_WRONLY | O_CREAT, 0666)) < 0) { fprintf(stderr,"unable to open '%s' for writing.\n",argv[2]); exit(1); } } lastent = 0; rpos = 0; while((n=read(in,&u,sizeof(struct utmp))) == sizeof(struct utmp)) { if (u.ut_time == 0) continue; if (lastent && (u.ut_time < (lastent-WARP_FACTOR) || u.ut_time > (lastent+WARP_FACTOR))) break; lastent = u.ut_time; if (write(out,&u,sizeof(struct utmp)) < 0) { fprintf(stderr,"error writing output.\n"); exit(1); } rpos += sizeof(struct utmp); } if (n == 0) { fprintf(stderr,"no corruption detected.\n"); exit(0); } if (n < 0) { fprintf(stderr,"error reading input file.\n"); exit(1); } if (n < sizeof(struct utmp)) { fprintf(stderr,"premature EOF, truncating.\n"); exit(0); } fprintf(stderr,"corruption detected, scanning from end.\n"); if ((pos = lseek(in,0,SEEK_END)) < 0) { fprintf(stderr,"error seeking to EOF -- aborting.\n"); exit(1); } lpos = pos - sizeof(struct utmp); lastent = 0; while(lpos > 0) { if ((pos = lseek(in,lpos,SEEK_SET)) < 0) { fprintf(stderr,"error scanning backwards.\n"); exit(1); } if ((n = read(in,&u,sizeof(struct utmp))) != sizeof(struct utmp)) { fprintf(stderr,"error reading input.\n"); exit(1); } if (lastent && (u.ut_time < (lastent-WARP_FACTOR) || u.ut_time > (lastent+WARP_FACTOR))) break; lastent = u.ut_time; lpos -= sizeof(struct utmp); } lpos += sizeof(struct utmp); while((n=read(in,&u,sizeof(struct utmp))) == sizeof(struct utmp)) { if (write(out,&u,sizeof(struct utmp)) < 0) { fprintf(stderr,"error writing output.\n"); exit(1); } } fprintf(stderr,"%ld bytes removed.\n",lpos-rpos); exit(0); } sac-1.9/scripts/check0100755000000000000000000000201506371403600013317 0ustar rootroot#!/bin/sh # # Check checks to see if any of the people who are logged in at the time # have used up their allotment of time (as specified by MAXHOURS) on the # modems (as specified by TTYS). # MAXHOURS=4.25 #TTYS="tty[C-F]*" TTYS="tty[1-6] ttyp[0-4]" hours=`date +%H:%M:%S` users=`users` u=`for x in $users; do echo $x; done | sort | uniq` sac -pb $hours $u -T $TTYS | while read user time do res=`echo "$time <= $MAXHOURS" | bc` if [ $res -eq 1 ]; then echo "$user is OK. ($time)" else echo "$user is over the limit. ($time)" fi done # If you use Sac to implement usage limits, the above can be modified to # "logout" the user by modifing the above if statement to the below: # # if [ `expr $h '<=' $MAXHOURS` -eq 0 ]; then # echo "You're over the time limit bozo! ($h) Bye Bye!" | write $user # ps aux | tail +2 | while read usr PID whatever # do # if [ $usr = $user ]; then kill -9 $PID; fi # done # fi # # Running this script every 5-10 minutes in a cron job wouldn't be a bad idea # either. sac-1.9/scripts/modemhogs0100755000000000000000000000032006371404033014222 0ustar rootroot#!/bin/sh modems="tty[C-F]*" echo "Top 15 modem hogs:" echo "" /usr/local/bin/sac -apT $modems | sort +1 -nr | head -15 echo "" echo "Wtmp begins: `/usr/local/bin/rawtmp -d | head -2 | tail -1 | colrm 13`" sac-1.9/scripts/oldsac.cgi0100755000000000000000000000130107025775157014265 0ustar rootroot#!/bin/sh exec 2>&1 # Change ${HTML_ROOT} to = the root directory for your html area. eval "`${HTML_ROOT}/cgi-bin/cgi-postin`" || exit 1 if [ "x${Name}" = "x" ]; then cat << ERROR Content-Type: text/html Error: No username supplied

ERROR: Must supply a username for accounting.


Please try again. ERROR exit 1 fi cat << HEADER Content-Type: text/html Your daily usage Your daily usage is:

HEADER /usr/local/bin/sac -adm "$Name" cat << FOOTER FOOTER # # That's all folks. # exit 0 sac-1.9/scripts/oldsac.html0100644000000000000000000000060006345327017014455 0ustar rootroot Get your daily usage

Enter your name to see your daily system usage:


Users:

Click this button to see your stats.


sac-1.9/scripts/pretty0100755000000000000000000000104406551445750013606 0ustar rootroot#!/bin/sh # # Use: pretty [] # # A good start on a billing statement generator. IFS=" " sac -apl $* | while read user time logins rest do if [ "$logins" = "no" ]; then logins = 0; fi printf "\n\n\t%-8s %10s %9s %s\n" "$user" "$time" "$logins" "$rest" echo "------------------------------------------------------------------------------" while [ $logins -gt 0 ] do read line echo " $line" logins=$[$logins-1] done echo "------------------------------------------------------------------------------" done sac-1.9/scripts/sac.cgi0100666000000000000000000001141107034700026013551 0ustar rootroot#!/bin/sh # # bob@norcom.net.au created 1998, modified frequently. # a cgi interface for sac.cgi # I use in the following way # Set up a secure realm on your web server, ie a directory # that requires a user to authenticate with a password # see your web server for details # # this authentication is compared to what is input on the form # so either an accounts user can access all details # or a user can only access their own details. # # I currently run on radius server, and manipulate the radwtmp logs # As I roll over the radwtmp logs monthly, I them put 3Months plus # current months data together for a user to see. # # I add extra accounting data into the output page for my clients # # Use all care as it may be insecure! # # plan on adding additional form fields for dates etc... # # Please feel free to modify and send me a copy! # please write comments here # # Please change the html if you use this script # Date=`/bin/date` read postinput Name=`echo $postinput |cut -f2 -d=|cut -f1 -d \&` DATA="/var/adm/OLD/radwtmp_temp" FILE1="/var/adm/radwtmp" FILE2="/var/adm/OLD/radwtmp.0" FILE3="/var/adm/OLD/radwtmp.1" FILE4="/var/adm/OLD/radwtmp.2" radwtmp_temp="/var/adm/OLD/radwtmp_temp" cat $FILE4 > $radwtmp_temp cat $FILE3 >> $radwtmp_temp cat $FILE2 >> $radwtmp_temp CAT $FILE1 >> $radwtmp_temp Trusted="accounts" echo Content-type: text/html echo exec 2>&1 if [ "$Name" != "$REMOTE_USER" ]; then if [ "$Trusted" != "$REMOTE_USER" ]; then cat << ERROR Error: Access Violation

ERROR: You Tried to access Priviledged Information.


You did not enter your username or Your attempt to access account information for someone other than yourself has failed.

This attempt has been logged and the webmaster has been alerted.


Return to Pilbara Systems main website or the Previous Page

Page © Pilbara Systems Internet

ERROR exit 1 else cat << HEADER Your daily usage

Usage Summary

HEADER echo "" echo "Usage summary for " $Name echo "" echo "$FILE1" echo "" /usr/local/bin/sac -w $DATA -ad -u "$Name" echo "" /usr/local/bin/sac -w $DATA -a -u "$Name" cat << FOOTER
Return to Pilbara Systems main website or the Previous Page

Page © Pilbara Systems Internet

FOOTER exit 0 fi cat << ERROR Error: Access Violation

ERROR: You Tried to access Priviledged Information.


You did not enter your username or Your attempt to access account information for someone other than yourself has failed.

This attempt has been logged and the webmaster has been alerted.


Return to Pilbara Systems main website or the Previous Page

Page © Pilbara Systems Internet

ERROR exit 1 else cat << HEADER Your daily usage

Pilbara Systems Account Summary Page

Your daily usage is:

HEADER echo "" echo $Name "At " $Date " " echo "" echo "" /usr/local/bin/sac -w $DATA -ad -u "$Name"; echo "" /usr/local/bin/sac -w $DATA -a -u "$Name"; echo "" echo " Your account details are as follows:" echo "" grep -w $Name /.staff/accounts/account_file echo "" cat << FOOTER Following are some details regarding your account expiry date and type.

The expiry date is in the format YYMMDD (Year, Month, Day) and the symbol ( + ^ ! B ) indicates your account type with the following meaning:
+ is basic
^ is extended basic
! is flat

B is business
P is Remote Premium
C is City Premium
Q is the 100/100
E is 12hrs/mth
M is EMail only

For more information on account types please see our Prices Page


Return to dotnet dotau main website or the Previous Page

Page © dotnet dotau Pty Ltd

FOOTER exit 0 fi sac-1.9/scripts/sac.html0100666000000000000000000000213307034700607013761 0ustar rootroot System usage details

Obtain your System usage stats:


You can only view information about your own account, any attempt to view information about another persons account will be logged and classed as hacking under our policy

The data may take up to a few minutes to display once you press submit, as the data has to be extracted from some large logfiles.

Your Username: Your details.

 


sac-1.9/scripts/usage0100755000000000000000000000014106503370325013350 0ustar rootroot#!/bin/sh # # Shows whos using your system the most. # sac -apm | sort -nr +1 | cat -n | more