/* Program: Extended Last Author: Matty < matty91 at gmail dot com > Current Version: 1.1 Revision History: 1.1 - Fixed bug in add_user Fixed a bug in the add_user function that computes slots Thanks goes out to Steven L for reporting this. 1.0 - First release Last Updated: 5-20-2004 Purpose: lastx is an extension of the last utility shipped with Solaris. It prints all 32-characters of the users utmpx entry, and provides facilities to display last data over a period of days. It also allows the user to print unique logins, and the total # of attempted logins. License: 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, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Usage: lastx { [-d num_days] [-n lines] } [-f filename] [-u] [-h] -d num_days : Print the users who have logged in within the past num_days -f filename : Filename to use -h : Print this screen -n lines : Print the the last N number of lines in the wtmpx file -u : Print the unique entries for each user Example: $ lastx -u -d 10 Username Logins Terminal Host Last Logged in matty 51 pts/2 lappy Sun Jul 24 13:35:36 2005 Installation: $ gcc -o lastx lastx.c && ./lastx -h */ #include #include #include #include #define U_WTMPX_FILE "/var/adm/wtmpx" #define NUMBER_OF_QUEUES 24 #define FILE_SIZE 40 #define SIZEOF_UTMPX sizeof(struct utmpx) /* Data Structures */ typedef struct cache_node { struct cache_node *next; struct utmpx utmp_entry; long nentries; } cache_node; typedef struct cache { long total_users; struct cache_node **nodes; } cache; /* Function definitions */ void usage(); unsigned long hash( char * ); long get_time( int ); int print_user( struct utmpx * ); int print_user_n( struct utmpx *,int ); int cycle_hash_queue( struct cache *); struct cache *init_hq(); void print_heading(int ); /* The meat of the program starts below */ int main(int argc, char **argv) { long nseconds = 0; /* Variable to hold the # of seconds to compare with */ int ndays = 0; /* Number of days to grab -- specified on cmd line */ int lines = 0; /* Number of lines the user wants to see -- specified on cmd line */ int unique = 0; /* Flag to indicate that the userlist should be unique */ int fd = 0; /* Used to store a fd if we cannot use getutxent and company */ int count = 0; /* Used to store an interval so we can cycle from 1 ... lines */ int i = 1; /* Used to calculate offset to lseek() in the wtmpx file */ off_t file_offset; /* Offset within the wtmpx file if we parse it by hand w/o getutxent */ struct utmpx *utx = NULL; /* long int tv_sec = is equivalent to a normal time_t */ char input = '\0'; /* This is used with the arguments to getopt */ char filename[FILE_SIZE] = "\0"; /* wtmp file if /var/tmp/wtmpx doesn't work */ struct cache *hash_queue = NULL; /* Hash queues to store usernames */ /* Figure out what the user would like us to do */ while ((input = getopt(argc, argv, "d:f:hn:u")) != -1) { switch (input) { case 'u': unique = 1; break; case 'n': lines = atoi(optarg); break; case 'd': ndays = atoi(optarg); break; case 'f': strncpy(filename,optarg,FILE_SIZE); break; case 'h': usage(); exit(0); case '?': usage(); exit(1); } } /* Initialize the hash_queue */ hash_queue = init_hq(); /* Get the time of the day and convert it to seconds */ if ( (nseconds = get_time(ndays)) <= 0) { printf("ERROR: Couldn't get the number of seconds\n"); exit(1); } /* Allocate X bytes for the utmpx structure */ if ( ( utx = (struct utmpx *) malloc(sizeof(struct utmpx))) == NULL) { printf("ERROR: Problem allocating memory for a utmpx structure\n"); exit(1); } /* Open the wtmpx file and seek to the end to get the file_offset so we can lseek() around */ if ( ( fd = open( (!strncmp(filename,"",1)) ? U_WTMPX_FILE : filename , O_RDONLY)) == -1) { printf("ERROR: Problems opening the wtmpx file\n"); exit(1); } if ( ( file_offset = lseek(fd, 0, SEEK_END)) == -1) { printf("ERROR: Problems lseek()'ing to the end of the file\n"); } /* Print all users who have WTMPX entries for the last NDAYS */ if (( ndays > 0 ) || (unique == 1)) { if ((ndays > 0) && (unique == 0)) { print_heading(0); while ( ( lseek(fd, ( file_offset - SIZEOF_UTMPX * i) , SEEK_SET)) != -1) { read(fd, utx, SIZEOF_UTMPX); if ( (utx->ut_type == USER_PROCESS) && ( utx->ut_tv.tv_sec > nseconds)) { print_user(utx); } i++; } } else if ((ndays == 0) && (unique == 1)) { print_heading(1); while ( ( lseek(fd, ( file_offset - SIZEOF_UTMPX * i) , SEEK_SET)) != -1) { read(fd, utx, SIZEOF_UTMPX); if ( utx->ut_type == USER_PROCESS ) { add_user(hash_queue, utx); } i++; } cycle_hash_queue( hash_queue ); } else if ((ndays > 0) && (unique == 1)) { print_heading(1); while ( ( lseek(fd, ( file_offset - SIZEOF_UTMPX * i) , SEEK_SET)) != -1) { read(fd, utx, SIZEOF_UTMPX); if ( (utx->ut_type == USER_PROCESS) && ( utx->ut_tv.tv_sec > nseconds)) { add_user(hash_queue, utx); } i++; } cycle_hash_queue( hash_queue ); } } else if ( lines > 0 ) { print_heading(0); while (count < lines ) { lseek(fd, ( file_offset - SIZEOF_UTMPX * i) , SEEK_SET); read(fd, utx, SIZEOF_UTMPX); if (utx->ut_type == USER_PROCESS) { print_user(utx); count++; } i++; } } else { print_heading(0); while ( (lseek(fd, ( file_offset - SIZEOF_UTMPX * i) , SEEK_SET)) != -1) { read(fd, utx, SIZEOF_UTMPX); if (utx->ut_type == USER_PROCESS) { print_user(utx); } i++; } } /* Close the wtmpx file */ close(fd); /* Let the kernel/shell cleanup the memory for now -- to be fixed */ exit(0); } /* Function: print_heading( int ) Purpose: print a heading that associates columns with data */ void print_heading(int n) { if (n == 0) { printf("%-20s %-8s %-20s %-15s\n","Username","Terminal","Host","Logged In"); } else if (n == 1) { printf("%-20s %-6s %-10s %-20s %-15s\n","Username","Logins","Terminal","Host","Last Logged in"); } else { printf("ERROR: there are issues processing the arguments to print_heading\n"); exit(1); } } /* Function: print_users( struct utmpx ) Purpose: Cycle through the array of pointers printing the applicable data from wtmpx/utmpx */ int print_user( struct utmpx *wtmpa ) { printf("%-20s %-8s %-20s %-15s",wtmpa->ut_user, wtmpa->ut_line, wtmpa->ut_host, ctime(&wtmpa->ut_tv.tv_sec)); } /* Function: print_users_n( struct utmpx, int ) Purpose: Cycle through the array of pointers printing the applicable data from wtmpx/utmpx, and the number of times they logged in */ int print_user_n( struct utmpx *wtmpa, int n ) { printf("%-20s %6d %-10s %-20s %-15s",wtmpa->ut_user,n, wtmpa->ut_line, wtmpa->ut_host, ctime(&wtmpa->ut_tv.tv_sec)); } /* Function: get_time(struct timeval, int) Purpose: Calculates an offset from the epoch */ long get_time( int ndays ) { struct timeval current_time; /* Calculate X days ago */ if ( gettimeofday(¤t_time, NULL) == -1) { printf("ERROR: error getting the time of day\n"); exit(1); } else { /* 60 seconds * 60 minutes * 24 hours * 60-days */ return (current_time.tv_sec - 60 * 60 * 24 * ndays); } } /* Function: usage() Purpose: Prints a help screen to assist the user */ void usage() { printf("Usage: lastx { [-d num_days] [-n lines] } [-f filename] [-u] [-h]\n"); printf(" -d num_days : Print the users who have logged in within the past num_days\n"); printf(" -f filename : Filename to use\n"); printf(" -h : Print this screen\n"); printf(" -n lines : Print the the last N number of lines in the wtmpx file\n"); printf(" -u : Print the unique entries for each user\n"); } /* Function: hash(char *str ) Purpose: Generates a 32-bit hash of a string. This was borrowed from the libc code */ unsigned long hash(char *str ) { int i; unsigned long h=0, g; char *p; for (p = str; *p; ++p) { h = ( h << 4 ) + *p; if ( ( g = h & 0xf0000000 ) ) { h = h ^ (g >> 24); h = h ^ g; } } return(h); } /* Function: add_user( struct cache *, struct utmpx * ) Purpose: Add the user passed to the hash_queue. the globa variable NUMBER_OF_QUEUES defines the # of hash queues. */ int add_user( struct cache *hash_queue, struct utmpx *wtmpa ) { int slot = 0; struct cache_node *current, *previous, *cache_entry; /* Where should we stick this user */ slot = ( hash(wtmpa->ut_user) % NUMBER_OF_QUEUES); /* If a hash queue slot does not exist, add one and copy the data */ if ( hash_queue->nodes[slot] == NULL ) { if ( ( hash_queue->nodes[slot] = (struct cache_node *) malloc(sizeof(struct cache_node))) == NULL) { printf("ERROR: Problem allocating memory for a cache_node structure\n"); } else { memcpy(&hash_queue->nodes[slot]->utmp_entry, wtmpa, sizeof(struct utmpx)); hash_queue->nodes[slot]->next=NULL; hash_queue->nodes[slot]->nentries=1; hash_queue->total_users++; } /* A hash queue slot is filled, so we need to add the entry to the end */ } else { current = hash_queue->nodes[slot]; previous = current; while ( current->next != NULL ) { if ( !strcmp(current->utmp_entry.ut_user, wtmpa->ut_user)) { current->nentries++; if (current->utmp_entry.ut_tv.tv_sec > wtmpa->ut_tv.tv_sec) { current->utmp_entry.ut_tv.tv_sec = wtmpa->ut_tv.tv_sec; } return(1); } else { previous = current; current = current->next; } } if ( !strcmp(current->utmp_entry.ut_user, wtmpa->ut_user)) { current->nentries++; } else { if ( ( cache_entry = (struct cache_node *) malloc(sizeof(cache_node))) == NULL) { printf("ERROR: Problem allocating memory for a cache_node structure\n"); } else { memcpy(&cache_entry->utmp_entry, wtmpa, sizeof(struct utmpx)); cache_entry->next=NULL; cache_entry->nentries=1; current->next = cache_entry; hash_queue->total_users++; } } } } /* Function: cycle_hash_queue( struct cache * ) Purpose: Cycle through the hash_queues and print users */ int cycle_hash_queue( struct cache *hash_queue ) { struct cache_node *current, *previous; int i = 0; for ( i = 0; i < NUMBER_OF_QUEUES; i++) { current=hash_queue->nodes[i]; previous=current; while (current != NULL) { print_user_n(¤t->utmp_entry,current->nentries); previous = current; current = previous->next; } } } /* Function: cache *init_hq() Purpose: Initialize the hash_queue */ struct cache *init_hq() { struct cache *hash_queue = NULL; int i = 0; if ( ( hash_queue = (struct cache *) malloc(sizeof(cache))) == NULL) { printf("ERROR: Problem allocating memory\n"); exit(1); } if ( ( hash_queue->nodes = (cache_node **) malloc(NUMBER_OF_QUEUES * (sizeof(cache_node *)))) == NULL) { printf("ERROR: Problem allocating memory\n"); exit(1); } /* Set the total number of unique users to 0 */ hash_queue->total_users = 0; /* Cycle through and NULL everything out */ for ( i = 0; i < NUMBER_OF_QUEUES; i++) { hash_queue->nodes[i] = NULL; } /* set the total # of users to 0 */ hash_queue->total_users = 0; /* Return the address of the initialized hash_queue */ return(hash_queue); }