#!/usr/bin/perl # # ph2 by Tikiman, orig version by miff # # this is probably way too much work for what it does, but some of the # subroutines are ready to be used elsewhere - especially addDays(), # putDays(), and getExp(). use Time::Local; $file_of_interest = '/etc/shadow'; $users = loadUsers($file_of_interest); # load a array of users $totusers = scalar(@$users); # number of users $| = 1; # autoflush on print "There are $totusers users on your system.\n"; print "Which user are you interested in? Use Tab to cycle through partial matches:\n"; print "User: "; $buf = ''; # This is the convoluted section for tab-completion BACK: while($key = getone()) { # blocks til it gets a keystroke if (ord($key) == 9) { # its a tab if ($lasttab) { # if the last keystroke was also a tab... for ($t = 0;$t < $totusers;$t++) { # loop in user array $user = $users->[$t]; if ($user eq $buf) { # found the user that we just picked $u = $t + 1; while($users->[$u]) { # shift the next value down $users->[$t] = $users->[$u]; $t++; $u = $t + 1; } $users->[$t] = $user; # append last user to user array } } } else { $oldbuf = $buf; # just hit tab - use new buffer to match } $lasttab = 1; # tag flag for ($t = 0;$t < $totusers;$t++) { # loop through users $user = $users->[$t]; if ($user =~ /^$oldbuf(.*)$/) { # we have a match delLine($buf); # slurp out old buffer $buf = $oldbuf . $1; # keep new buffer print $buf; # display it next BACK; # end loop } } } elsif ((ord($key) == 127) || (ord($key) == 8)) { # backspace of c-H $lasttab = ''; $buf && ($buf = backSp($buf)); # back up if theres room } elsif (ord($key) == 10) { # return $user = $buf; # got the user print "\n"; last BACK; # break out of loop } else { print "$key"; # print the thing $lasttab = 0; $buf .= $key; # add to buffer } } # get the number of days until user exipres ($got_user,$days) = getExp($user,$file_of_interest); if ($got_user) { # got a user print "\n=================================\nUsername $user\n"; if ($days) { print "Expiration date: " . days2date($days) . " \n"; } else { print "Expiration date: None\n"; } print "=================================\n\n"; } else { # no user die "No such user\n"; } dMenu(); # display a menu HUH: # in case bad value is inputed chomp($selection = <>); # get selection number $selection =~ s/^(\d*).*?$/$1/; if ($selection == 1) { # pretty clear section $days || die "Sorry, there was no expiration date to add to\n"; print "How many days would you like to add? "; chomp($ndays = ); ($ndays =~ /^\d*$/) || die "Thats not a number!\n"; addDays($user,$ndays,$file_of_interest); print "You have succufully added $ndays days\n"; print "New expiration date of $user is " . expDate($user,$file_of_interest) . "\n\n"; exit; } elsif ($selection == 2) { # pretty clear section $days || die "Sorry, there was no expiration date to add to\n"; print "How many months would you like to add? "; chomp($nmon = ); ($nmon =~ /^\d*$/) || die "Thats not a number!\n"; addDays($user,$nmon * 30,$file_of_interest); print "You have successfully added $nmon months\n"; ($got_user,$days) = getExp($user,$file_of_interest); print "New expiration date of $user is " . expDate($user,$file_of_interest) . "\n\n"; exit; } elsif ($selection == 3) { # less clear print "Enter the new expiration date in MM-DD-YYYY format:\n"; chomp($ndate = ); foreach $sval (('-',' ','/')) { # determines the best date seperator if (scalar(@splitdate = split($sval,$ndate)) > 1) { @rsplit = @splitdate ;} } $rsplit[2] || die "Sorry, I don't understand that input\n"; (($rsplit[0] >= 1) && ($rsplit[0] <= 12)) || die "Bad Month!\n"; (($rsplit[1] >= 1) && ($rsplit[1] <= 31)) || die "Bad Day!\n"; if (($rsplit[2] > -1) && ($rsplit[2] < 99)) { # prepends 20 to some years $rsplit[2] =~ s/^(\d)$/0$1/g; $rsplit[2] = "20" . $rsplit[2]; } ($rsplit[2] == "99") && ($rsplit[2] = "1999"); # prepends 19 to 99 (($rsplit[2] >= 1999) && ($rsplit[2] <= 2030)) || die "Bad Year!\n"; $rsplit[0]--; $rsplit[2] -= 1900; $newsecs = timelocal(0,0,20,$rsplit[1],$rsplit[0],$rsplit[2]); $newsecs /= 60 * 60 * 24; # calculate new days since 1770 putDays($user,$newsecs,$file_of_interest); print "Set expiration date of $user to " . expDate($user,$file_of_interest) . "\n\n"; exit; } elsif ($selection == 4) { putDays($user,'',$file_of_interest); print "Removed epiration date of $user\n\n"; exit; } elsif ($selection == 5) { print "Ok, no problem\n\n"; exit; } else { print "Huh? \n"; goto HUH; } # returns exp. date of user sub expDate { ($got_user,$days) = getExp($_[0],$_[1]); return(days2date($days)); } # gets expiration date, returns a bool if it found a user even # if there was no date sub getExp { my($user,$file) = @_; open(SHADOW,$file) || die "Can't open\n"; while () { chomp; split ':'; if ($_[0] eq $user) { # we have our row close(SHADOW); $_[7] ? return("1",$_[7]) : return("1") ; } } } # slurps users into array and returns reference to that array sub loadUsers { my($file) = @_; my @users; open(SHADOW,$file) || die "Can't open\n"; while () { chomp; split ':'; push(@users,$_[0]); } return(\@users); } # converts days since 1970 into a date sub days2date { $date = localtime($_[0] * 60 * 60 * 24); $date =~ s/^\S* (.*) .*:.*:.* (.*)$/$1 $2/; return($date); } # displays menu sub dMenu { print "Please select one of the following options:\n"; print "1) Add days to user's account\n"; print "2) Add months to user's account (30 days)\n"; print "3) Set new expiration date\n"; print "4) Remove expiration date\n"; print "5) Exit\n\n"; } # adds days to a users account sub addDays { my($user,$days,$file) = @_; open(SHADOW,$file) || die "Can't open\n"; open(OUTPUT,">/etc/$$.tmp"); while() { if (/^($user:.*?:.*?:.*?:.*?:.*?:.*?):(.*?):(.*?$)/) { $old = $2; $old += $days; print OUTPUT "$1:$old:$3\n"; } else { print OUTPUT $_; } } close(SHADOW); close(OUTPUT); rename("/etc/$$.tmp",$file); chmod 0600, $file; } # sets the days in a users account sub putDays { my($user,$days,$file) = @_; open(SHADOW,$file) || die "Can't open\n"; open(OUTPUT,">/etc/$$.tmp"); while() { if (/^($user:.*?:.*?:.*?:.*?:.*?:.*?):(.*?):(.*?$)/) { print OUTPUT "$1:$days:$3\n"; } else { print OUTPUT $_; } } close(SHADOW); close(OUTPUT); rename("/etc/$$.tmp",$file); chmod 0600, $file; } # section that lets you read 1 character from STDIN - uses term settings # and closures BEGIN { use POSIX qw(:termios_h); my ($term, $oterm, $echo, $noecho, $fd_stdin); $fd_stdin = fileno(STDIN); $term = POSIX::Termios->new(); $term->getattr($fd_stdin); $oterm = $term->getlflag(); $echo = ECHO | ECHOK | ICANON; $noecho = $oterm & ~$echo; sub cbreak { $term->setlflag($noecho); $term->setcc(VTIME, 1); $term->setattr($fd_stdin, TCSANOW); } sub cooked { $term->setlflag($oterm); $term->setcc(VTIME, 0); $term->setattr($fd_stdin, TCSANOW); } sub getone { my $key = ''; cbreak(); sysread(STDIN, $key, 1); cooked(); return $key; } } END { cooked() } sub backSp { my $buf = shift; print chr(8) . " " . chr(8); $buf =~ s/.$//g; return($buf); } sub delLine { my $buf = shift; while($buf) { $buf = backSp($buf); } }