#! /usr/bin/env python
import os,string, getopt,time,sys,struct
""" usage : wwho [date] [-b secs  (backwards)] [-p precision (sec/m/h/d)] [-l (last)]
            [-a (all last msgs)] [-f wtmp_file] [-d (debug)]
            DATE (2000 May 22 06:12:08)

            wfile : idem, except : [-a (access)] FILE

ça marche. mais la recherche ds le wtmp pour construire _sessions_ est
pas du tout optimale -> lent pour un gros wtmp.
Améliorations à prévoir..
"""
optlist, args = getopt.getopt(sys.argv[1:], "adlb:p:f:")

date=""
emul_last=0
all_last=0
debug=0
datesec=0
precision=2
utmpF="/var/log/wtmp"
if len(args)>0 :
    date=args[0]
    print "cmd_line_date : %s" %date
for i in optlist :
    if i[0] =='-l' : emul_last=1
    if i[0] =='-a' : all_last=1
    if i[0] =='-d' : debug=1
    if i[0] =='-b' : datesec=time.time()-float(i[1])
    if i[0] =='-p' : precision=float(i[1])
    if i[0] =='-f' : utmpF=i[1]
    if debug : print "Option found : "+repr(i)

if debug : print " argv[0] : %s" % sys.argv[0]

if not datesec :
    if date :
        if string.lower(os.path.basename(sys.argv[0]))=="wfile.py" :
            datesec=os.path.getmtime(date)  #modif time
            if all_last : #access time
                datesec=os.stat(date)[7] 
               # datesec=os.path.getatime(date) #BUG ! donne mtime !
            all_last=0
            print " Chosen file Time : %s" \
                  %time.ctime(datesec)
        else :
            t=time.strptime(date,"%Y %b %d %H:%M:%S")
            #we still have to guess the DaylightSsavingTime flag -> -1
            t2=[ t[0],t[1],t[2],t[3],t[4],t[5],t[6],t[7],-1] 
            datesec = time.mktime(t2)
    else : datesec =time.time()

def unpackstr(s, i=0):
    for c in s:
        if c == '\000': break
        i = i + 1
    return s[:i]

def msectime(secs,usecs) :
    t=time.localtime(secs)
    s=time.strftime("%d %b %Y %T",t)
    s=s+ ( ".%06d"%usecs )[0:4]
    #s=s+ time.strftime(" %z %Z",time.localtime(secs))
    return s


if debug : print " Date : %s  // datesec : "% date + repr(datesec)
print "Finding who were here at %s, with precision %fs."\
      % (msectime(datesec,0),precision)

# the C struct declaration  (on AIX 4.2.1)
#   char ut_user[8];
#   char ut_id[14];
#   char ut_line[12];
#   short ut_type;
#   pid_t ut_pid; (int)
#   short ut_exit.e_termination;
#   short ut_exit.e_exit;
#   time_t ut_time; (long)
#   char ut_host[16];
# the format string to represent that structure

""" cf utmp.h , ou man utmp  :
#define UT_LINESIZE     32
#define UT_NAMESIZE     32
#define UT_HOSTSIZE     256

/* The structure describing the status of a terminated process.  This
   type is used in `struct utmp' below.  */
struct exit_status
  {
    short int e_termination;    /* Process termination status.  */
    short int e_exit;           /* Process exit status.  */
  };

/* The structure describing an entry in the user accounting database.  */
struct utmp
{
  short int ut_type;            /* Type of login.  */
  pid_t ut_pid;                 /* Process ID of login process.  */
  char ut_line[UT_LINESIZE=32];    /* Devicename.  */
  char ut_id[4];                /* Inittab ID.  */
  char ut_user[UT_NAMESIZE=32];    /* Username.  */
  char ut_host[UT_HOSTSIZE=256];    /* Hostname for remote login.  */
  struct 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.  */
};
"""
fmt="hi32s4s32s256shhlii4i20s"
#fmt="2s4s32s4s32s256s4s8s8s16s20s"

""" constants (for ut_type):
"""
UT_EMPTY =           0
UT_RUN_LVL =         1
UT_BOOT_TIME =       2
UT_NEW_TIME =        3
UT_OLD_TIME =        4
UT_INIT_PROCESS =    5
UT_LOGIN_PROCESS =   6
UT_USER_PROCESS =    7
UT_DEAD_PROCESS =    8
UT_ACCOUNTING =      9
record_size = struct.calcsize(fmt)
# read who is logged on to a UNIX system
file = open(utmpF, 'rb')

sessions={}
suspects=[]

def evdisp(event,zt,rev) :
    ( ut_t,ut_ut,zt_t,zt_ut,u,l,h,p,id,typ) =event
    if not zt :
        if zt_t: zt=msectime(zt_t,zt_ut)
        else : zt=" -stillNow-"
    if rev :
        t=zt;
        zt=msectime(ut_t,ut_ut)
    else :
        t=msectime(ut_t,ut_ut)
    return  '%-8s %-8s %d/%-2d/%-6d %-4s  [%-24s]=>[%-24s] (%s)' % \
              (u, l,  typ,1,p,      id,   t,   zt,   h)

def rewind(sessions,n,criter,event) :
    i=n
    ( ut_time,ut_utime,zt_time,zt_utime,user,line,host,pid,sessid,type) =event
    i0=1
    while i>i0 :
        i=i-1
        closethis=0
        if criter=='all' :
            closethis=1
        if criter =='line':
            closethis=(sessions[i][5] == line)
            if closethis : i0=i

        if closethis :
            ( ut_t,ut_ut,zt_t,zt_ut,u,l,h,p,id,typ)=ev=sessions[i]
            if zt_t !=0 :
                if criter=='all' : continue
                if all_last : 
                    s="-ERR: "
                    s=s+evdisp(ev,"",1)
            else :
                ev=sessions[i]=( ut_t,ut_ut,ut_time,ut_utime,u,l,h,p,id,typ)
                if all_last : 
                    s="-CLOS "
                    s=s+evdisp(ev,"",1)
            if all_last : 
                print s

block = file.read(record_size)
n=1
while block:
    ( type,pid,line,id,user,host,exit_1,exit_2,ut_session,ut_time,ut_utime,\
      ip1,ip2,ip3,ip4,unused) =  \
         struct.unpack(fmt, block)
    user = unpackstr(user) # remove the null characters
    line = unpackstr(line)
    host = unpackstr(host)
    id   = unpackstr(id)

    event = ( ut_time,ut_utime,0,0,user,line,host,pid,id,type)

    if type and all_last :
        s='USER: '
        s=s+evdisp(event,"  -n/a-",0)
        print s
    if user in ['LOGIN','reboot','shutdown'] :
        "Crash & such : everybody logs off.."
        rewind(sessions,n,'all',event)

    if type == 7 and user  :
        sessions[n]=event
        n=n+1

    if type == 8 :
        rewind(sessions,n,'line',event)

    block=file.read(record_size)
file.close()

print "Number of entries : %d" %n

evs=sessions.values()
evs.sort(lambda x, y: cmp(y[0]+y[1]*1e-7,x[0]+x[1]*1e-7) )
i=0
for ev in evs:
    i=i+1
    (ut_t,ut_ut,zt_t,zt_ut,u,l,h,p,id,typ)=ev
    if emul_last : print  evdisp(ev,"",0)
    if datesec <360000 :
        # secret feature : giving a near-epoch date does that :
        if precision >0 and zt_t-ut_t < precision  : continue
        if precision <0 and zt_t-ut_t > -precision : continue
    else :
        if datesec + precision < ut_t : continue
        if zt_t !=0 and zt_t + precision <datesec : continue
    if emul_last : print "----SUSPECT"
    suspects.append(ev)

print "\nAnd Now the \"suspects\" :"

suspects.sort(lambda x, y: cmp(x[2]-x[0], y[2]-y[0]))
for ev in suspects :
    (ut_t,ut_ut,zt_t,zt_ut,u,l,h,p,id,typ)=ev
    print evdisp(ev,"",0)
