#!/usr/bin/python #needs at least python 1.5 #*************************************************************************** # copyright : (C) 2001 by McRee * # email : mcree@freemail.hu * #*************************************************************************** #*************************************************************************** #* * #* 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. * #* * #*************************************************************************** database = "/var/local/muddlestats.db"; muddle_log = "/var/log/muddleftpd.log"; import re; import pprint; import cPickle; from zlib import compress, decompress; from time import *; #print "This is MuddleStats"; # lots of threads are simultaniously logging to the same logfile. we have to separate them by: re_thread_id = re.compile(r': [a-zA-Z0-9]+@[a-zA-Z0-9._-]+\([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\)/[0-9]+ :'); # when the user logs out of a thread all loglist data is cleared for that thread re_logout = re.compile(r'User logged out'); # when the server exits, all loglist data for all threads is killed re_server_exit = re.compile(r'Info - Received SIG.*'); # unix timestamp - must find this in every row - or else: log syntax error re_timestamp = re.compile(r'[A-Z][a-z]{2} [A-Z][a-z]{2} [ 0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} [0-9]{4}'); # hostname(ipnumber) of a user re_host = re.compile(r'@[a-zA-Z0-9._-]+\([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\)'); # username re_user = re.compile(r': [a-zA-Z0-9]+@'); # xxx bytes re_bytes = re.compile(r'[0-9]* bytes'); # transfers can be finished or aborted re_finish = re.compile(r'.* 226 Transfer done. [0-9]+ bytes transferred|.* 426 Transfer aborted. [0-9]+ bytes transferred'); # retrieve command re_retr = re.compile(r'^\+\+.*retrieve'); # store commands re_stor = re.compile(r'^\+\+.*store|^\+\+.*append'); loglist = {}; # must init dictionaries userlist = {};# ... oldstamp = ''; # must init timestamp def updatedb(s, mode = "RETR"): "updates userlist database form the given 's' logline using 'retr' or 'stor' modes" global userlist; # we need this global data # extract some data from the logline user = (re_user.search(s)).group()[2:-1]; bytes = (re_bytes.search(s)).group()[:-5]; ttime = strptime(re_timestamp.search(s).group()); host = (re_host.search(s)).group()[1:]; times = "%02d" % ttime[0] + "-%02d" % ttime[1] + "-%02d" % ttime[2]; # maintain database tree if not userlist.has_key(user): # if user does not exist userlist[user]={host: {}}; if not userlist[user].has_key(host): # if this the never connected from this host userlist[user][host]={times: {"RETR": 0, "STOR": 0}}; if not userlist[user][host].has_key(times): # if the user did not do anything 'today' userlist[user][host][times]={"RETR": 0, "STOR": 0}; # this is in fact the 'real' update: we increment the byte counter userlist[user][host][times][mode]=long(userlist[user][host][times][mode]) + long(bytes); # load database try: dbfile = open (database,"r"); s = dbfile.readline(); l = cPickle.loads(decompress(dbfile.read())); userlist = l[0]; loglist = l[1]; oldstamp = l[2]; dbfile.close(); except IOError: pass; # no panic if file not found log=open(muddle_log,"r"); # open logfile rows=1; s = log.readline(); while s != '': # read and analize all lines from logfile m = re_timestamp.search(s); if not m: # huh! print "Error in logfile at line "+str(rows)+":"; print s; print "Invalid FTPD logfile format! - Aborting MuddleStats\n"; raise IOError; timestamp=strptime(m.group()); m = re_server_exit.search(s); if m: # muddleftpd stopped. all users kicked loglist.clear(); m = re_thread_id.search(s); if m and timestamp>oldstamp: # if we read 'younger' data than the old stamp currt = m.group(); # get current thread if not loglist.has_key(currt): loglist[currt]={"RETRFLAG": 0,"STORFLAG": 0}; m = re_retr.search(s); if m: # we got 'retrieve' ss = m.group(); loglist[currt]["RETRFLAG"]=1; m = re_stor.search(s); if m: # we got 'store' ss = m.group(); loglist[currt]["STORFLAG"]=1; m = re_finish.search(s); if m and loglist[currt]["RETRFLAG"]==1: # we got 'transfer done' after 'retrieve' loglist[currt]["RETRFLAG"]=0; updatedb(s,"RETR"); elif m and loglist[currt]["STORFLAG"]==1: # we got 'transfer done' after 'store' loglist[currt]["STORFLAG"]=0; updatedb(s,"STOR"); m = re_logout.search(s); if m: # user logged out - drop thread del loglist[currt]; elif loglist[currt]["RETRFLAG"]==0 and loglist[currt]["STORFLAG"]==0: del loglist[currt]; # drop inactive tread s = log.readline(); rows = rows+1; #pprint.pprint(loglist); #pprint.pprint(userlist); log.close(); # save database dbfile = open (database,"w"); dbfile.writelines(["MuddleStats datafile v2 - DO NOT MODIFY\n"]); dbfile.write(compress(cPickle.dumps([userlist,loglist,timestamp]))); dbfile.close();