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

Last change on this file since 2773 was 2773, checked in by mabarracus, 4 years ago

Ported code to xenial

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