source: lliurex-statistics/trunk/fuentes/lliurex-statistics.install/usr/sbin/analytics @ 2948

Last change on this file since 2948 was 2948, checked in by mabarracus, 3 years ago

Add debconf, n4d-vars, dialog confirmation logic

  • Property svn:executable set to *
File size: 13.7 KB
Line 
1#!/usr/bin/python
2
3import re
4import os
5import sys
6import signal
7import subprocess
8import threading
9import shlex
10import sqlite3
11from datetime import date
12import string
13import requests
14import json
15import ConfigParser
16import daemon
17import lockfile
18import xmlrpclib
19
20######### END CONFIG ###########
21
22
23#
24# Shows debug data
25#
26def debug(text='',type=1):
27    global DEBUG, daemon_mode, DEBUG_FILE
28    fp_debug=open(DEBUG_FILE,"a")
29    if DEBUG:
30        if type == 1:
31            if daemon_mode != True:
32                print str(text)
33            else:
34                fp_debug.write(str(text)+"\n")
35        else:
36            if daemon_mode != True:
37                sys.stdout.write(text)
38            else:
39                fp_debug.write(str(text))
40        sys.stdout.flush()
41    fp_debug.close()
42#END def debug(text='',type=1):
43
44#
45# Handler to manage interrupt properly, sync data before die when SIGINT
46#
47def sig_handler(sig,frame):
48    global t,LIST
49    debug("Aborting...")
50    try:
51        # Cancel threads
52        t.cancel()
53        # Sync data
54        update_db()
55        # Show stored data to stdout
56        show_list_db()
57    except:
58        sys.exit(1)
59    sys.exit(0)
60#END def sig_handler(sig,frame):
61
62#
63# Forces change file when SIGUSR1
64#
65def sig_handler_change(sig, frame):
66        change_file();
67
68#
69# Forces change file and update server database when SIGUSR1
70#
71def change_file():
72        global LIST,filename,next_filename,LOGPATH
73        # sync in-memory and temporary file data
74        update_db()
75        # clear in-memory list
76        LIST.clear()
77        # Check if permission flag is granted
78        if allow_send():
79            # Send data to master server
80            send_data()
81        # Get new temporary filename
82        next_filename=get_filename(LOGPATH)
83        # Sync data in-memory and temporary file data
84        update_db()
85        # Show curent data to stdout
86        show_list()
87#END def change_file():
88
89def check_server_acknowledge():
90        c = xmlrpclib.ServerProxy("https://server:9779/")
91        return c.get_variable("","VariablesManager","STATS_ENABLED")
92#END def check_server_acknowledge()
93
94def check_local_acknowledge():
95        answer = None
96        if os.path.isfile('/etc/lliurex-analytics/status'):
97            fp = open('/etc/lliurex-analytics/status','r')
98            answer = fp.readline();
99            fp.close()
100        return answer.strip()
101
102#END check_local_acknowledge
103
104def allow_send():
105    answer = check_server_acknowledge()
106    if answer != '1':
107        answer = check_local_acknowledge()
108        try:
109            if answer == "yes":
110                return True
111            else:
112                return False
113        except Exception as e:
114            return False
115    else:
116        return True
117#END def allow_send()
118
119#
120# Sends data to server and clear all
121#
122def clean_and_send():
123        global LIST
124        # sync in-memory and temporary file data
125        update_db()
126        # clear in-memory list
127        LIST.clear()
128        if allow_send(): 
129            # Send data to master server
130            send_data()
131        # Clear database file
132        clear_db()
133        # Sync data in-memory and temporary file data
134        update_db()
135#END def clean_and_send():
136
137#
138# Call show data when SIGINT2
139#
140def sig_handler_show(sig,frame):
141        debug("current filename "+filename)
142        show_list()
143        show_list_db()
144        global TIMEOUT
145        global BDTIMEOUT
146        global ctimeout
147        global btimeout
148        debug('TIMEOUT='+str(TIMEOUT)+',BDTIMEOUT='+str(BDTIMEOUT)+',ctimeout='+str(ctimeout)+',btimeout='+str(btimeout))
149#END def sig_handler_show(sig,frame):
150
151#
152# Global function called every second to run functions guided by timeout
153#
154def runner():
155        global t
156        global ctimeout
157        global btimeout
158        global TIMEOUT
159        global BDTIMEOUT
160
161        # Flush in-memory data to file db when expires timeout
162        if ctimeout > TIMEOUT:
163                update_db()
164                ctimeout=0
165        else:
166                ctimeout+=1
167
168        # Call send-data to server when expires dbtimeout
169        if btimeout > BDTIMEOUT:
170                clean_and_send();
171                btimeout=0
172        else:
173                btimeout+=1
174        debug('.',2)
175
176        # Reload 1 sec timeout
177        t=threading.Timer(1.0,runner)
178#        De-comment to allow stop threads inmediately
179#        t.daemon = True
180        t.start()
181#END def runner():
182
183#
184# Add item to in-memory LIST
185#
186def add_item(item):
187        global LIST,BLACKLIST,INTERPRETERS
188
189        # Split binary and parameters
190        #part=shlex.split(item)       
191        part=item.split(' ')
192        # Check binary name is correct
193        if ('/' in part[0]):
194                part[0]=part[0].split('/')[-1]         
195        # Check if binary name must be recorded
196        if (part[0] in BLACKLIST):
197                return
198        # Check if is a known interpreter
199        if (part[0] in INTERPRETERS):
200                part[0]=part[1]
201                # Check script name is correct
202                if ('/' in part[0]):
203                        part[0]=part[0].split('/')[-1]
204                # Check if it's a parameter
205                i=0
206                novalid=['-',':']
207                while (part[i][0] in novalid and i < len(part)):
208                        i+=1
209                if (i < len(part)):
210                        part[0]=part[i]
211                else:
212                        return
213        if ('/' in part[0]):
214                        part[0]=part[0].split('/')[-1]
215
216        # Check if binary name must be recorded
217        if (part[0] in BLACKLIST):
218                return
219
220        # Add or append to global list
221        if LIST.has_key(part[0]):
222                debug('+',2)
223                LIST[part[0]]+=1
224        else:
225                debug('*',2)
226                LIST[part[0]]=1
227#END def add_item(item):
228
229#
230# Show in-memory LIST and print to stdout
231#
232def show_list():
233        global LIST,daemon_mode,DEBUG_FILE
234        if daemon_mode != True:
235            print "Lista:"
236            print "~~~~~~~~~~"
237            for it in LIST:
238                print it + "->" + str(LIST[it])
239        else:
240            fp_debug=open(DEBUG_FILE,"a")
241            fp_debug.write("Lista:\n~~~~~~~\n");
242            for it in LIST:
243                fp_debug.write(it + "->" + str(LIST[it]) + "\n")
244            fp_debug.close()
245#END def show_list():
246
247#
248# Send data from temporary db to server using http post method
249#
250def send_data():
251    # Calculates mac address
252    f = open('/sys/class/net/eth0/address','r');
253    uid=f.read().strip();
254    f.close();
255
256    global filename;
257   
258    db = sqlite3.connect(filename);
259    cur= db.cursor();
260    try:
261        cur.execute('select * from info order by n DESC limit 100;');
262        json_tmp={};
263        # Dumps temporary database building json object to send
264        for x in cur.fetchall():
265            json_tmp[x[0]]=str(x[1]);
266    except:
267        return;
268    # Get version and flavour
269    p=subprocess.Popen(['lliurex-version','-n'],stdout=subprocess.PIPE)
270    out,err=p.communicate()
271    version=out.strip();
272
273    p=subprocess.Popen(['lliurex-version','-v'],stdout=subprocess.PIPE)
274    out,err=p.communicate()
275    sabor=out.strip();
276
277    # Send json encoded data to server
278    jsonobj=json.dumps(json_tmp);
279    jsonobj=json.dumps({'uid':uid,'vers':version,'sab':sabor, 'stats': jsonobj });
280    payload = {'stats':jsonobj};
281    r = requests.post(url,data=payload,headers=headers);
282    debug("S("+r.text+')',2);
283#END def send_data():
284
285#
286# Show list stored into db file
287#
288def show_list_db():
289    global filename,daemon_mode,DEBUG_FILE
290    db = sqlite3.connect(filename)
291    cur= db.cursor()
292    if daemon_mode != True:
293        print "TOP 10:"
294        print "~~~~~~~~~~"
295        try:
296                cur.execute('select * from info order by n DESC limit 10;')
297                for x in cur.fetchall():
298                        print x[0] + " " + str(x[1]);
299        except:
300                print "No data available"
301    else:
302        fp_debug=open(DEBUG_FILE,"a")
303        fp_debug.write("TOP 10:\n~~~~~~~~~~\n")
304        try:
305                cur.execute('select * from info order by n DESC limit 10;')
306                for x in cur.fetchall():
307                        fp_debug.write(x[0] + " " + str(x[1])+"\n")
308        except:
309                fp_debug.write("No data available\n")
310        fp_debug.close()
311    db.close()
312#END def show_list_db():
313
314#
315# Calculates new name to store temporary database
316#
317def get_filename(LOGPATH=''):
318        global filename
319        l=[]
320        if (LOGPATH != ''):
321            LOGPATH=LOGPATH+'/'
322        #Load all alphabet into list
323        for k in list(string.ascii_lowercase):
324                l.append(k)
325
326        #Load all alphabet into list
327        for k in list(string.ascii_lowercase):
328
329                # Load all alphabet (with two characters) into list
330                for p in list(string.ascii_lowercase):
331                        l.append(k+p)
332        # l = {a,b,c...y,z,aa,ab...zy,zz)
333        # Get current date
334        today=date.today()
335
336        current=LOGPATH+'stats-'+str(today)+'.db'
337        ant=''
338        # Get the next index for apply to file
339        for x in l:
340                if not os.path.isfile(current):
341                        filename=LOGPATH+'stats-'+str(today)+ant+'.db'
342                        return LOGPATH+'stats-'+str(today)+x+'.db'
343                        break;
344                else:
345                        ant=x;
346                current=LOGPATH+'stats-'+str(today)+ant+'.db'
347#END def get_filename():
348
349#
350# Clear file with stored database
351#
352def clear_db():
353        global filename
354        debug('C',2)
355        db = sqlite3.connect(filename)
356        cur = db.cursor()
357        cur.execute('delete from info;')
358        db.commit()
359        db.close()
360#END def clear_db():
361
362#
363# Update temporary database and updates global LIST
364#
365def update_db():
366        debug('U',2)
367        global LIST,filename
368        #debug("updating "+filename);
369        # Temporary list
370        L2={}
371        # Open file to store temporary exec ocurrences
372        db = sqlite3.connect(filename)
373        cur= db.cursor()
374        try:
375                cur.execute('select * from info;')
376        except:
377                cur.execute('create table info (cmd varchar(100) primary key,n int(10));');
378                cur.execute('select * from info;')
379        # Load into L2 all database records
380        for x in cur.fetchall():
381                L2[x[0]]=x[1]
382        # Append to LIST all previously added database records
383        for x in L2:
384                if not LIST.has_key(x):
385                        LIST[x]=L2[x]
386        # Clear database records
387        cur.execute('delete from info;')
388        values=[]
389        # Write all records from LIST into file
390        for x in LIST:
391                values.append((x,str(LIST[x])))
392        cur.executemany('insert into info values (?,?)',values)
393        db.commit()
394        db.close()
395#END def update_db():
396
397def daemonize():
398        debug('Running daemon...')
399        with daemon.DaemonContext(detach_process=True,working_directory='/tmp',umask=002,pidfile=lockfile.FileLock('/var/run/analytics')):
400                start()
401#END def daemonize():
402
403def start():
404        fp = open('/var/run/analytics.pid','w');
405        fp.write(str(os.getpid()))
406        fp.close()
407        # Add handlers for functions, close, show, change file / update db
408        signal.signal(signal.SIGINT,sig_handler)
409        signal.signal(signal.SIGUSR1,sig_handler_change)
410        signal.signal(signal.SIGUSR2,sig_handler_show)
411        # Sync data
412        update_db()
413
414        t=threading.Timer(1.0,runner)
415        # De-comment to allow stop threads inmediately
416        #t.daemon = True
417        t.start()
418
419        p1= subprocess.Popen(["tail","-F",logfilename],stdout=subprocess.PIPE)
420
421
422        while True:
423                line = p1.stdout.readline()
424                if(re.search('type=EXECVE',line)):
425                        m=re.findall('a[0-9]+="([^"]+)"',line)
426                        t=""
427                        for i in m:
428                                t+=i + " "
429                        add_item(t)
430#END def start():
431               
432###################
433###### MAIN #######
434###################
435
436# enable / disable show more running data
437DEBUG=0
438# Global list in mem to store results
439LIST={}
440TIMEOUT=300
441BDTIMEOUT=300
442# Counters for threads going to timeout
443ctimeout=0
444btimeout=0
445
446# Get configuration parameters
447config = ConfigParser.ConfigParser()
448config.read('/etc/lliurex-analytics/agent.cfg')
449
450LOGPATH = config.get('Agent','logpath');
451DEBUG_FILE=LOGPATH+"/analytics-debug.log"
452# Filename for temporally files in disk (sqlite)
453filename=''  #initializes as global when is called get_filename
454next_filename=get_filename(LOGPATH)
455debug("filename="+filename+' next='+next_filename)
456
457server = config.get('Server','server');
458server_path = config.get('Server','server-path');
459
460# Max timeout commiting changes to file
461TIMEOUT = config.getint('Agent','timeout')
462
463# Max timeout commiting changes from file to database
464BDTIMEOUT = config.getint('Agent','bdtimeout')
465
466agent = config.get('Agent','user-agent')
467headers = {'user-agent': agent};
468url="http://"+server+"/"+server_path;
469
470daemon_mode = config.getboolean('Agent','daemon')
471logfilename = config.get('Audit','file')
472
473blacklistname = config.get('Audit','blacklist')
474
475BLACKLIST = [line.rstrip('\n') for line in open(blacklistname)]
476INTERPRETERS = config.get('Audit','interpreters').split(',')
477
478
479
480if __name__ == '__main__':
481        if (logfilename == 'stdin'):
482                if (len(sys.argv) < 2):
483                        sys.stderr.write('Error\n')
484                        sys.exit(1)
485                else:       
486                        logfilename=sys.argv[1]
487        else:
488                if daemon_mode == True:
489                        daemonize()
490                else:
491                        start()
492else:
493        if (logfilename == 'stdin'):
494                sys.stderr.write('file=stdin not valid running as module\n')
495                sys.exit(1)
496
Note: See TracBrowser for help on using the repository browser.