/* N E T L O G Version 1.02 Accumulate hourly machine usage table from Novell NetWare 2.1x Paudit log. Composes reports for monthly usage statistics. Accomodates multiple months. Written by Joe Doupnik and Craig Jeske, Oct 1988 Department of Electrical Engineering Utah State University Logan, Utah 84322-4105 (801) 750-2982 Email: jrd@usu.Bitnet, jrd@cc.usu.edu Copyright (C) 1988 Utah State University, all rights reserved. May be distributed free with this copyright notice intact. Generate the source log file by PAUDIT > filename (Paudit reads from NetWare server binary file NET$ACCT.DAT.) Then run this program by NETLOG filename > outputfile Requires input filename on command line or via prompt. Writes results to stdout. Written in Lattice C, version 3.21 (works with other ANSI C compilers). Build by Lattice commands - lcs netlog links netlog Last edit: 16 Aug 1989 Typical Novell Netware 2.1x log entries from Paudit: (units are mm/dd/yy hh:mm:ss) 9/1/88 11:15:44 File Server EE-CEE-SERVER NOTE: about User SUPERVISOR during File Server services. Login from address 0000FFFA:0000C0B18B06. 9/1/88 11:32:48 File Server EE-CEE-SERVER NOTE: about User SUPERVISOR during File Server services. Logout from address 0000FFFA:0000C0B18B06. 9/27/88 22:26:02 File Server EE-CEE-SERVER NOTE: about (Client account has been deleted) during File Server services. Logout from address 0000FFFA:0000C0EB9206. 9/28/88 19:52:06 File Server EE-CEE-SERVER NOTE: about File Server EE-CEE-SERVER during File Server services. System time changed to 1988-09-28 8:50:00. General comments. - Up to MAXMACH (128) machines can be tracked in each month. Enlarge MAXMACH to suit local conditions. If the number of machines exceeds this limit new arrivals are assigned to the last machine slot and a warning message is issued. In such a case regard the usage statistics with suspicion. - The input to this program is the ascii log file produced by the Novell NetWare utility PAUDIT, examples of which are listed above. This program searches for groups of three lines of text starting with a date/time line. The program is driven by occurrence of events in that log file. - Output of the program lists the server name, warning messages from inconsistencies in the log and system time changes, a list of machine names and the number of times logged in that month, and the table of the number of machines active each hour of the month. This output can be redirected at the DOS command line to any legal DOS destination. - If an interval of six or more hours elapses without log activity stations are changed to a state of UNKNOWN to compensate for gaps in logging. IT IS RECOMMENDED THAT ALL USERS LOGOUT BEFORE DOWNING THE SERVER. Subsequently, a station logging out from a state of UNKNOWN will produce a message saying "Logged out with a missing login." Similarly, a station logging in with a current state of LOGIN will produce a message saying "Logged in again with missing logout." - Stations logged in across month boundaries will not have their presence extended forward to the end of the month nor backward from logout to the beginning of the month. However, logging out will register the stations as active in the hours of login (and up to the time of the last message in the month) and the hour of logout. This again is to compensate for gaps in logging and variable lengths of months. - Changes of the system time of day clock can cause major disruptions in the log analysis process. In such cases station activity is not back dated from the time of logout; some activity will be lost. Hand editing of the Paudit log is suggested to rectify gross blunders in the time or date. - Server rebooting leaves no messages in the log. Instead, stations will be noted as logging out with no previous login, and vice versa. The output of this program notes such situations, as well as system time changes. - Hourly usage values from this program are peak values within an hour. Further, the hourly values are always shown for 31 days/month to ease the task of follow-on software. - Example warning messages from this program are as follows - Error: Unable to open input file. Note: at 10/5/88 11:21:40 System time changed to 1988-01-24 11:21:39. Note: machine 0000FFFA:0000C0EB9206 user GUEST at 10/6/88 7:47:45 Logged in again with missing logout. Note: machine 0000FFFA:0000C0028806 user GUEST at 10/7/88 11:47:31 Logged out with missing login. Warning: more machines present than table space. Reusing last machine for new machine. */ #include #include #include /* length of log line */ #define LINELEN 132 /* states of individual machines: most recent operation */ #define UNKNOWN 0 #define LOGIN 1 #define LOGOUT 2 /* maximum number of machines */ #define MAXMACH 128 struct { /* information about each machine */ char addr[22]; /* network board address */ char linout; /* login/logout flag */ unsigned cntin; /* login count for the month*/ unsigned cntout; } machine[MAXMACH]; char stat[31 * 24]; /* array for days/month x hours/day = 31 X 24 */ char line[LINELEN]; /* a log line buffer */ char date[9], time[9], addr[22], user[15], log[9], server[46]; char olddate[9], firstdate[9]; /* to annotate report */ char filename[65]; char prompt[] = "\n Enter name of PAUDIT output file: "; char badopen[] = "\nError: Unable to open input file."; int hour, prevhour; /* hour of month == (day - 1) * 24 + hour of day */ int nelems; /* number of machines encountered in the input file */ FILE *fpin; /* input file pointer */ main(argc, argv) int argc; char *argv[]; { int i, j; int readtd(), readuser(), readadlog(), search(); void count(), report(); printf("\n\n NETLOG Version 1.02\n"); printf(" Hourly machine usage counts from"); printf(" Novell NetWare 2.1x PAUDIT output file.\n"); printf(" Copyright (C) 1988 Joe Doupnik and Craig Jeske, Utah"); printf(" State University.\n"); if (argc > 1) /* get input filename */ strcpy(filename, argv[1]); else { fwrite(prompt, strlen(prompt), 1, stderr); /* visible prompt*/ if (gets(filename) == NULL) exit(1); } if ((fpin = fopen(filename, "r")) == NULL) { fwrite(badopen, strlen(badopen), 1, stderr); exit(1); } fputs("\n working...\n", stderr); firstdate[0] = '\0'; /* init first date */ printf("\n =="); for (i = 1; i < 24; i++) printf("==="); printf("\n"); while (readtd() != EOF) /* get time and date line */ { if (firstdate[0] == '\0') /* init starting date/hour */ { strcat(firstdate, date); /* remember starting date */ prevhour = hour; /* history starts now */ printf("\n Server: %s\n", server); } if (readuser() == 0) /* get username */ continue; /* none, skip entry */ if (readadlog() == 0) /* get Login/Logout line */ continue; /* none, skip entry */ i = search(); /* get structure index for machine */ if (strcmp(log, "Login") == 0) /* check LOG line */ { /* have "Login" */ machine[i].cntin++; /* another Login */ if (machine[i].linout == LOGIN) { printf( " Note: machine %s user %s at %s %s\n", machine[i].addr, user, date, time); printf( " Logged in again with missing logout.\n"); prevhour = hour - 100; /* logout old stuff */ machine[i].cntout++; /* forced logout */ } /* empty period or long gap */ if (hour - prevhour > 6 ) { prevhour = hour; for (j = 0; j < nelems; j++) machine[j].linout = UNKNOWN; } machine[i].linout = LOGIN; /* this machine */ count(); /* do statistics */ continue; } if (strcmp(log, "Logout") == 0) /* check LOG line */ { machine[i].cntout++; /* another Logout */ if (machine[i].linout == LOGOUT || machine[i].linout == UNKNOWN) /* Uh Oh */ { printf( " Note: machine %s user %s at %s %s\n", machine[i].addr, user, date, time); printf(" Logged out with missing login.\n"); machine[i].linout = LOGIN; /* for count() */ } count(); /* count current machine */ machine[i].linout = LOGOUT; /* now log it out */ } if (hour - prevhour > 6 ) /* empty period or long gap */ { prevhour = hour; for (j = 0; j < nelems; j++) { if (machine[j].linout == LOGIN) /* forced */ machine[j].cntout++; /* logout */ machine[j].linout = UNKNOWN; /* clear all*/ } } } /* end while not EOF */ report(); /* output monthly report */ fclose(fpin); return (0); } /* Display monthly report figures */ void report() { int i; unsigned totalmach = 0, totalin = 0, totalout = 0; printf("\n Workstations:\n"); for (i = 0; i < nelems; i++) /* list known machines */ { if (machine[i].cntin == 0 && machine[i].cntout == 0) continue; printf(" %s %#4u logins %#4u logouts\n", machine[i].addr, machine[i].cntin, machine[i].cntout); totalin += machine[i].cntin; totalout += machine[i].cntout; totalmach++; } printf(" Total of \%d machines, with %u logins and %u logouts.\n", totalmach, totalin, totalout); printf("\n Machines in use, %s - %s,", firstdate, date); printf(" row = day of month, column = hour\n "); for (i = 0; i < 24; i++) printf("%#3d", i); printf("\n --"); for (i = 1; i < 24; i++) printf("---"); for (i = 0; i < 31 * 24; i++) /* usage in hourly bins */ { if ((i % 24) == 0) printf("\n%#3u", 1+(i / 24)); /* start new day-line */ printf("%#3u", stat[i] & 0xff); } printf("\n\n =="); for (i = 1; i < 24; i++) printf("==="); printf("\n"); } /* Count logins to a particular machine and total to this hour. */ void count() { int i, mcnt; /* count machines Logged in at this hour */ for (i = 0, mcnt = 0; i < nelems; i++) if (machine[i].linout == LOGIN) /* machine state = Login */ mcnt++; /* number logged in now */ /* peak # machines in use this hour */ if (stat[hour] < mcnt) stat[hour] = mcnt; /* consider previous hours if they have not been updated */ /* yet, but don't back count a new login. */ if (strcmp(log, "Login") == 0 && mcnt) mcnt--; /* skip new login */ while (prevhour < hour) stat[prevhour++] = mcnt; /* in back count */ } /* Read lines from source file and decode time and date until both exist. Return 0 if success. Return EOF for EOF. Invokes report() and reinits system if data span monthly boundaries. */ int readtd() { int i, j; char *pos, *getstr(), *strsch(); void report(); while (fgets(line, LINELEN, fpin) != NULL) { if (line[0] == ' ') continue; /* not date line */ olddate[0] = '\0'; /* clear last date */ strcat(olddate,date); /* remember here */ if ((pos = getstr(date, line)) == NULL) continue; /* no date */ /* detect crossing month boundary */ if ((olddate[0] != date[0] || olddate[1] != date[1]) && firstdate[0] != '\0') /* if new month */ { date[0] = '\0'; strcat(date,olddate); /* set last date */ count(); /* finish month */ report(); /* dump month's rpt */ /* reinit arrays for this new month */ /* retain machine login/out state */ for (i = 0; i < 31 * 24; i++) stat[i] = 0; for (i = 0; i < nelems; i++) machine[i].cntin = machine[i].cntout = 0; /* retain logged in machines, repack struct machine */ /* find first unused machine */ j = 0; while (j < nelems) if (machine[j++].linout != LOGIN) break; j--; for (i = j; i < nelems; i++) /* copy down used mach */ if (machine[i].linout == LOGIN) { machine[j].addr[0] = '\0'; strcat(machine[j].addr, machine[i].addr); machine[j].cntin = 1; machine[j++].linout = LOGIN; machine[i].linout = UNKNOWN; machine[i].addr[0] = '\0'; } nelems = j; /* new active count */ firstdate[0] = '\0'; pos = getstr(date, line); /* reread date line */ } if ((pos = getstr(time, pos)) != NULL) /* get time */ { if (date[1] == '/') i = 2; /* single month digit*/ else i = 3; /* double month digit*/ /* hour = 24 * (day number - 1) + tod hours digits */ hour = (atoi(&date[i]) - 1) * 24 + atoi(time); if (server[0] == '\0') { /* pickup server name */ pos = strsch(pos, "Server"); /* field ident */ pos += 7; /* skip "Server " */ strcat(server, pos); /* copy server name */ } /* and trailing \n */ return (1); /* got time */ } } /* end while*/ return (EOF); /*return EOF*/ } /* Read NOTE line, locate User tag, and copy out user name. Return 1 if success, 0 if wrong line type. */ int readuser() { char *pos, *u = user; char *strsch(); if (fgets(line, LINELEN, fpin) == NULL) return (0); if ((pos = strsch(line, "NOTE")) == NULL) /* NOTE type line? */ return (0); /* no, skip entry */ if (pos = strsch(pos, "User")) { pos += 4; /* add 4 to get past "User" */ while (*pos++ == ' ') ; /* skip whitespace */ pos--; } else if (pos = strsch(line, "about File")) /* File Server message */ { pos += 10; /* add 4 to get past "File" */ while (*pos++ == ' ') ; /* skip whitespace */ pos--; } else /* (a deleted user name?) */ if ((pos = strsch(line, "deleted")) == NULL) return (0); while (isalpha(*pos)) *u++ = *pos++; /* copy username */ *u = '\0'; /* terminator */ return (1); /* return success */ } /* Read the Login/Logout tag and machine address from buffer line. Store Login/Logout/System word in array log. Return 0 if failure, non-zero if success. */ int readadlog() { char *ln = line, *ad = addr, *lg = log; char *strsch(); if (fgets(line, LINELEN, fpin) == NULL) return (0); log[0] = '\0'; if (ln = strsch(line, "Log")) { while (isalpha(*ln)) *lg++ = *ln++; /* Login/Logout msg */ *lg = '\0'; /* get machine address */ while (! isdigit(*ln)) ln++; /* search for digits*/ while (*ln != '.' && *ln > ' ') *ad++ = *ln++; *ad = '\0'; return (1); } /* Look for system message such as System time changed to 1988-09-28 8:50:00. */ if (ln = strsch(line, "System")) { while (isalpha(*ln)) *lg++ = *ln++; /* log = "System" */ *lg = '\0'; printf(" Note: at %s %s\n", date, time); printf(" %s", line); /* show message w/CRLF */ return (1); } return (0); /* else return failure */ } /* Search structure machine for address string. Return structure member index, creating a new one if not found. */ int search() { int i; for (i = 0; i < nelems; i++) /* search existing elements */ if (!strcmp(machine[i].addr, addr)) /* if address is known */ return (i); /* return structure index */ strcpy(machine[nelems].addr, addr); /* init new element address */ machine[nelems].cntin = 0; /* new login count */ machine[nelems].cntout = 0; /* new logout count */ machine[nelems].linout = UNKNOWN; /* login/out state is unkwn */ if (nelems >= MAXMACH) { printf( " Warning: more machines present than table space.\n"); printf(" Reusing last machine for new machine.\n"); nelems = MAXMACH - 2; } nelems++; /* another element exists */ return (nelems - 1); /* return new element index */ } /* Read time and date. Fill buffer pout from source buffer pin. Return 1 if success, else 0. Output buffer is null terminated. */ char * getstr(pout, pin) char *pout, *pin; { while (*pin == ' ' || *pin == '\t') /* skip leading whitespace */ pin++; if (! isdigit(*pin)) /* fields start with digit */ return (NULL); /* not a digit return */ while (isdigit(*pin) || *pin == '/' || *pin == ':') /* while in {0-9, /, or :} */ *pout++ = *pin++; /* store in output field */ *pout = '\0'; /* output string terminator */ if (*pin != ' ') /* expected break character?*/ return (NULL); /* no, illegal terminator */ return (pin); /* success */ } /* Search string pin for null terminated pattern string. Return pointer to start of match if found, else NULL. */ char * strsch(pin, pattern) char *pin, *pattern; { int i; do { i = 0; /* char position in pattern */ while (pattern[i] != '\0' && pin[i] == pattern[i]) i++; /* while matched, repeat*/ if (pattern[i] == '\0') return (pin); /* completed match */ pin++; /* else start on next char */ } while (*pin != '\0'); return (NULL); /* pattern not present */ } /* End of NETLOG.C */