/* * topuser.c * kreator, 1998. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. */ /* Safely undefine all */ #undef FANCY_UTMP_CODE #undef IGN_USERS #undef OLD_UTMP #undef REQ_GID #undef REQ_MONTH #undef REQ_TYPE #undef VERSION #undef REQ_TIME /* If defined, uses old utmp/wtmp structures */ /* #define OLD_UTMP */ /* Output and calculate only current month */ #define REQ_MONTH /* Use new (fancy) utmp/wtmp reading code */ #define FANCY_UTMP_CODE /* List users that should be ingnored in output, currently obsolete by * REQ_GID, but still possible to use */ /* #define IGN_USERS "www-data backup dos operator games man lp mail news uucp proxy majordom postgres www-data backup dos operator list irc gnats alias qmaild qmails qmailr qmailq qmaill qmailp nobody daemon bin sys sync" */ /* Require specific GID */ #define REQ_GID 100 /* Version string. B indicates Beta. */ #define VERSION "1.14B Solaris" /* Require specific device, still _under development_ */ /* #define REQ_TYPE "pty tty" */ /* List only users with mins or hours or days nonzero */ #define REQ_TIME #include #include #include #include #include #include #include #include #include #include /* Structures and defines required by program itself */ #ifndef OLD_UTMP_H #define OLD_UTMP_H #define OLD_LINESIZE 32 #define OLD_NAMESIZE 32 #define OLD_HOSTSIZE 257 struct oldutmp { short ut_type; int ut_pid; char ut_line[OLD_LINESIZE]; char ut_id[4]; long ut_oldtime; char ut_user[OLD_NAMESIZE]; char ut_host[OLD_HOSTSIZE]; long ut_oldaddr; }; #endif /* Defines required on some systems */ #ifndef SHUTDOWN_TIME # define SHUTDOWN_TIME 254 #endif #define UCHUNKSIZE 16384 #define R_CRASH 1 #define R_DOWN 2 #define R_NORMAL 3 #define R_NOW 4 #define R_REBOOT 5 /* Solaris compatibility hacks */ #define ut_time ut_tv.tv_sec #define UT_NAMESIZE 32 /* Some more structures and defines ;) */ struct utmpxlist { struct utmpx ut; struct utmpxlist *next; struct utmpxlist *prev; }; struct utmpxlist *utmplist=NULL; int user_counter; #ifdef REQ_MONTH int tmp_month; #endif struct users { char pw_name[UT_NAMESIZE], pw_gecos[42+1]; time_t secs; int logins; } *users; time_t lastdate, begintime, tmp_time; typedef int (*sort_func) (const void *, const void *); /* Fatal handler */ void fatal(const char *msg) { fprintf(stderr, "%s\n", msg); exit(EXIT_FAILURE); } /* Count users, allocate users db and fill with passwd data */ void getallusers(void) { int i; struct passwd *temp; char *pos; setpwent(); for (user_counter=0; (temp=getpwent())!=NULL;) #ifdef REQ_GID if (temp->pw_gid==REQ_GID) #endif ++user_counter; endpwent(); if (user_counter) { users=calloc(user_counter, sizeof(struct users)); if (users==NULL) fatal("out of memory"); setpwent(); for (i=0; ipw_gid!=REQ_GID) continue; #endif memcpy((users+i)->pw_name, temp->pw_name, UT_NAMESIZE); /* Quick and dirty hack to get rid of extra data */ if ((pos=strstr(temp->pw_gecos, ",")) && *pos) *pos=0; memcpy((users+i)->pw_gecos, temp->pw_gecos, 42); ++i; } endpwent(); } else fatal("no users in passwd file"); } /* Convert structures from old to new utmp/wtmp format */ void uconv(struct oldutmp *oldut, struct utmpx *utn) { memset(utn, 0, sizeof(struct utmpx)); utn->ut_type=oldut->ut_type; utn->ut_pid=oldut->ut_pid; utn->ut_time=oldut->ut_oldtime; /* Solaris again */ /* utn->ut_addr=oldut->ut_oldaddr; */ strncpy(utn->ut_line, oldut->ut_line, OLD_LINESIZE); strncpy(utn->ut_user, oldut->ut_user, OLD_NAMESIZE); strncpy(utn->ut_host, oldut->ut_host, OLD_HOSTSIZE); } /* Read utmp/wtmp file and return data (new utmp code) */ #ifdef FANCY_UTMP_CODE int uread(FILE *fp, struct utmpx *u, int *quit) { static int utsize; static char buf[UCHUNKSIZE]; char tmp[1024]; static off_t fpos; static int bpos; #ifdef OLD_UTMP struct oldutmp uto; #endif int r; off_t o; if (quit==NULL && u!=NULL) { #ifdef OLD_UTMP r=fread(&uto, sizeof(uto), 1, fp); uconv(&uto, u); #else r=fread(u, sizeof(struct utmpx), 1, fp); #endif return r; } if (u==NULL) { #ifdef OLD_UTMP utsize=sizeof(uto); #else utsize=sizeof(struct utmpx); #endif fseek(fp, 0L, SEEK_END); fpos=ftell(fp); if (fpos==0) return 0; o=((fpos-1)/UCHUNKSIZE)*UCHUNKSIZE; if (fseek(fp, o, SEEK_SET)<0) { /* If this is run from crontab, it will happily * mail stderr to $USER, so there is no need to do * it explicitly */ fprintf(stderr, "seek failed!\n"); return 0; } bpos=fpos-o; if (fread(buf, bpos, 1, fp)!=1) { /* Same here. If you get it, it means.. rotate * your wtmp(x)! */ fprintf(stderr, "read failed!\n"); return 0; } fpos=o; return 1; } bpos-=utsize; if (bpos>=0) { #ifdef OLD_UTMP uconv((struct oldutmp *)(buf+bpos), u); #else memcpy(u, buf+bpos, sizeof(struct utmpx)); #endif return 1; } fpos-=UCHUNKSIZE; if (fpos<0) return 0; memcpy(tmp+(-bpos), buf, utsize+bpos); if (fseek(fp, fpos, SEEK_SET)<0) { /* Yap. Same here. */ perror("fseek"); return 0; } if (fread(buf, UCHUNKSIZE, 1, fp)!=1) { /* And here! I have been thinking of calling fatal() * though.. */ perror("fread"); return 0; } memcpy(tmp, buf+UCHUNKSIZE+bpos, -bpos); bpos+=UCHUNKSIZE; #ifdef OLD_UTMP uconv((struct oldutmp *)tmp, u); #else memcpy(u, tmp, sizeof(struct utmpx)); #endif return 1; } #else /* Read utmp/wtmp file and return data (old utmp code) */ int uread(FILE *fp, struct utmpx *u, int *quit) { struct oldutmp uto; int r; if (u==NULL) { #ifdef OLD_UTMP r=sizeof(struct oldutmp); #else r=sizeof(struct utmpx); #endif fseek(fp, -1L*r, SEEK_END); return 1; } #ifndef OLD_UTMP r=fread(u, sizeof(struct utmpx), 1, fp); if (r==1) { if (fseek(fp, -2L*sizeof(struct utmpx), SEEK_CUR)<0) if (quit) *quit=1; } return r; #else r=fread(&uto, sizeof(struct oldutmp), 1, fp); if (r==1) { if (fseek(fp, -2L*sizeof(struct oldutmp), SEEK_CUR)<0) if (quit) *quit=1; uconv(&uto, u); } return r; #endif } #endif /* Trace user, optionally check in/out time and enter data */ int find_user(const char *name, time_t secs, char *utline) { int i=0; struct users *tmp_user; /* This should be done _much_ faster! I am thinking of using IDs, * but it would eat to much memory. * Aargh! No UIDs possible: char ut_user[UT_NAMESIZE]; * and I was thinking about hashing method :( */ for(; ipw_name, name)==0)) { #ifdef REQ_TYPE /* Still unusable, I guess */ utline[3]=0; if (strstr(REQ_TYPE, utline)==NULL) break; #endif /* Here used to reside pile of code, now it is a bit * optimised, can more be done? */ tmp_user=users+i; tmp_user->secs+=secs; tmp_user->logins++; return 0; } /* I still don't do anything with return values here. Should I? */ return 1; } /* User is found! Prepare userdata for trace and call tracer */ int list(struct utmpx *p, time_t t, int what) { time_t secs; char utline[12]; #ifdef REQ_MONTH time_t tmp; tmp=(time_t)p->ut_time; #endif strncpy(utline, p->ut_line, sizeof(utline)); /* Old compatibility hack */ if (strncmp(utline, "ftp", 3)==0) utline[3]=0; if (strncmp(utline, "uucp", 4)==0) utline[4]=0; /* Hey! Be more precise than counting with last! */ secs=t-p->ut_time; if (secs) { #ifdef REQ_MONTH if ((localtime(&tmp)->tm_mon!=tmp_month) && /*logintime */ (localtime(&t)->tm_mon!=tmp_month)) /* logouttime */ return 1; #endif find_user(p->ut_name, secs, utline); } return 0; } /* Scanning code */ void scan_wtmp(void) { struct utmpx ut; struct utmpx oldut; struct utmpxlist *p; struct utmpxlist *next; FILE *fp; time_t lastboot=0, lastrch=0, lastdown; int whydown=0, c, x, quit=0, down=0, lastb=0; struct stat st; time(&lastdown); lastrch=lastdown; lastdate=lastdown; if ((fp=fopen(WTMPX_FILE,"r"))==NULL) fatal("cannot open wtmp file"); setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE); if (uread(fp, &ut, NULL)==1) begintime=ut.ut_time; else { fstat(fileno(fp), &st); begintime=st.st_ctime; quit=1; } uread(fp, NULL, NULL); while(!quit) { if (uread(fp, &ut, &quit)!=1) break; if (memcmp(&ut, &oldut, sizeof(struct utmpx))==0) continue; memcpy(&oldut, &ut, sizeof(struct utmpx)); lastdate=ut.ut_time; if (lastb) { quit=list(&ut, ut.ut_time, R_NORMAL); continue; } if (!strncmp(ut.ut_line, "~", 1)) { if (strncmp(ut.ut_user, "shutdown", 8)==0) ut.ut_type=SHUTDOWN_TIME; else if (strncmp(ut.ut_user, "reboot", 6)==0) ut.ut_type=BOOT_TIME; else if (strncmp(ut.ut_user, "runlevel", 7)==0) ut.ut_type=RUN_LVL; } else { if (ut.ut_type!=DEAD_PROCESS && ut.ut_name[0] && ut.ut_line[0] && strcmp(ut.ut_name, "LOGIN")!=0) ut.ut_type=USER_PROCESS; if (ut.ut_name[0]==0) ut.ut_type=DEAD_PROCESS; } switch (ut.ut_type) { case SHUTDOWN_TIME: lastdown=lastrch=ut.ut_time; down=1; break; case BOOT_TIME: strcpy(ut.ut_line, "system boot"); quit=list(&ut, lastdown, R_REBOOT); down=1; break; case RUN_LVL: x=ut.ut_pid & 255; if (x=='0' || x=='6') { lastdown=ut.ut_time; down=1; } lastrch=ut.ut_time; break; case USER_PROCESS: c=0; for (p=utmplist; p; p=next) { next=p->next; if (strncmp(p->ut.ut_line, ut.ut_line, 12)==0) { if (c==0) { quit=list(&ut, p->ut.ut_time, R_NORMAL); c=1; } if (p->next) p->next->prev=p->prev; if (p->prev) p->prev->next=p->next; else utmplist=p->next; free(p); } } if (c==0) { if (lastboot==0) quit=list(&ut, time(NULL), R_NOW); else quit=list(&ut, lastboot, whydown); } case DEAD_PROCESS: if (ut.ut_line[0]==0) break; if ((p=malloc(sizeof(struct utmpxlist)))==NULL) fatal("out of memory"); memcpy(&p->ut, &ut, sizeof(struct utmpx)); p->next=utmplist; p->prev=NULL; if (utmplist) utmplist->prev=p; utmplist=p; break; } if (down) { lastboot=ut.ut_time; whydown=(ut.ut_type==SHUTDOWN_TIME)?R_DOWN:R_CRASH; for (p=utmplist; p; p=next) { next=p->next; free(p); } utmplist=NULL; down=0; } } fclose(fp); } /* Qsort compare function */ int users_compare(struct users *a, struct users *b) { /* Can it be faster? */ return (b->secs-a->secs); } /* Output all data we have collected */ void output(void) { int i=0, days, mins, hours; struct users *tmp; struct utsname buf; /* I don't even think of implementing it myself */ qsort(users, user_counter, sizeof(struct users), (sort_func)users_compare); if (uname(&buf)==-1) fprintf(stderr, "Cannot determine host data!\n"); else printf("Running on %s %s %s %s %s\n", buf.sysname, buf.nodename, buf.release, buf.version, buf.machine); #ifndef REQ_MONTH printf("Statistics since %s", ctime(&begintime)); #else printf("Logfile begins on %sStatistics for current month\n", ctime(&begintime)); #endif printf( "Scanning time: %ld secs\nScanned on %s\n%5s %-10s %-42s %9s - %-6s\n", time(NULL)-tmp_time, ctime(&tmp_time), "No.", "Username", "Realname", "ddd:hh:mm", "logins"); for(; i<79; printf("-"), ++i); puts(""); for(i=0; ipw_name)!=NULL) continue; #endif /* It resided in find_user(), but this is more precise and * more powerful. Here is right. */ mins=(tmp->secs/60)%60; hours=(tmp->secs/3600)%24; days=tmp->secs/86400; /* Request by mioc@fly.cc.fer.hr */ #ifdef REQ_TIME if (mins || hours || days) #endif printf("%4d. %-10s %-42s %03d:%02d:%02d - %6d\n", i+1, tmp->pw_name, tmp->pw_gecos, days, hours, mins, tmp->logins); } printf("\nProgramming by kreator, 1999\nCompile date %s\nVersion %s\n", __DATE__, VERSION); } /* Root of all */ int main(void) { tmp_time=time(NULL); #ifdef REQ_MONTH /* I am thinking of making some rough magic. Subtracting some * seconds instead.. */ tmp_month=localtime(&tmp_time)->tm_mon; #endif getallusers(); scan_wtmp(); output(); free(users); return(EXIT_SUCCESS); }