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

Last change on this file was 8320, checked in by mabarracus, 9 months ago

fix double execution detection

  • Property svn:executable set to *
File size: 31.7 KB
Line 
1#!/usr/bin/env python3
2import sys
3import os
4import re
5import signal
6import time
7import subprocess
8import requests
9import json
10try:
11    import configparser
12except:
13    import ConfigParser as configparser
14import daemon
15try:
16    from xmlrpc import client
17except:
18    import xmlrpclib as client
19import lockfile
20import logging.handlers
21from logging import config as cfg
22import ssl
23import psutil
24
25
26oldsignals = {}
27try:
28    for sig in signal.Signals:
29        try:
30            oldsignals.setdefault(sig.name, signal.getsignal(sig))
31            signal.signal(sig.value, signal.SIG_IGN)
32        except:
33            continue
34except:
35    allsignals = [s for s in dir(signal) if s[0:3] == 'SIG']
36    for sig in allsignals:
37        try:
38            oldsignals.setdefault(sig,getattr(signal,sig))
39            signal.signal(getattr(signal,sig), signal.SIG_IGN)
40        except:
41            continue
42
43#
44# START EDITABLE VARS (OVERRIDES VALUES IN CONFIGFILE)
45#
46
47# DEBUG = 1
48
49# MODE = 'PROCEDURAL'
50MODE = 'THREADED'
51
52CONFIGFILE = '/etc/lliurex-analytics/agent.cfg'
53# CONFIGFILE = 'config.txt'
54
55# OVERRIDE_SEND_PERMISSION   = 1
56
57# MIN_LOG_LEVEL = 'debug'
58
59# DAEMON_MODE = 1
60
61# FILELOCK = '/tmp/analytics'
62
63PIDFILE = '/var/run/analytics.pid'
64FILELOCK = '/var/run/analytics.pid'
65
66# PIDFILE = '/tmp/analitics.pid'
67
68# STATUSFILE = '/etc/lliurex-analytics/status'
69
70# TIMEOUT = 1
71
72#
73# END EDITABLE VARS #
74#
75
76
77if MODE == 'PROCEDURAL':
78    from multiprocessing import Process, Manager, get_logger
79    str_formatter = '(%(processName)s)'
80if MODE == 'THREADED':
81    from multiprocessing.dummy import Process, Manager
82    from multiprocessing import get_logger
83    str_formatter = '(%(threadName)s)'
84
85def get_var_value(varname, config=None, mode='string', section='Agent'):
86    value = None
87
88    if config:
89        varname = varname.lower()
90        try:
91            if mode == 'string':
92                value = config.get(section, varname)
93            elif mode == 'bool':
94                value = config.getboolean(section, varname)
95            elif mode == 'int':
96                value = config.getint(section, varname)
97            elif mode == 'float':
98                value = config.getfloat(section, varname)
99        except:
100            pass
101
102    def f(item):
103        if isinstance(item, str) or isinstance(item, bool) or isinstance(item, int) or isinstance(item, float):
104            return True
105        else:
106            return False
107
108    for x in (v for v in globals() if varname.lower() == v.lower() and f(globals()[v])):
109        value = globals()[x]
110
111    if mode == 'string':
112        return str(value)
113    elif mode == 'bool':
114        return bool(value)
115    elif mode == 'int':
116        return int(value)
117    elif mode == 'float':
118        return float(value)
119    else:
120        return value
121
122loglevel = None
123CONFIG = None
124
125def set_loglevel():
126    global loglevel,CONFIG
127    namelevel = False
128    with_debug = False
129    set_level = False
130    set_to_default = False
131
132    if get_var_value('DEBUG',config=CONFIG, mode='bool'):
133        with_debug = True
134        loglevel = get_var_value('MIN_LOG_LEVEL',config=CONFIG)
135        if loglevel:
136            namelevel = loglevel
137            set_level = True
138            set_to_default = False
139            if loglevel == 'debug' or loglevel == logging.DEBUG:
140                loglevel = logging.DEBUG
141            elif loglevel == 'critical' or loglevel == logging.CRITICAL:
142                loglevel = logging.CRITICAL
143            elif loglevel == 'error' or loglevel == logging.ERROR:
144                loglevel = logging.ERROR
145            elif loglevel == 'warning' or loglevel == logging.WARNING:
146                loglevel = logging.WARNING
147            elif loglevel == 'info' or loglevel == logging.INFO:
148                loglevel = logging.INFO
149            else:
150                loglevel = logging.DEBUG
151                set_to_default = True
152        else:
153            set_level = False
154            set_to_default = True
155            loglevel = logging.DEBUG
156    else:
157        with_debug = False
158        namelevel = 'info'
159        set_to_default = True
160        loglevel = logging.INFO
161
162    print('set_loglevel to {}, explicitly {}, debug is {}, default mode {}'.format(namelevel,set_level,with_debug,set_to_default))
163
164set_loglevel()
165
166LOGGING = {
167    'version': 1,
168    'disable_existing_loggers': False,
169    'formatters': {
170        'verbose': {
171#            'format': '%(levelname)s %(module)s (%(pathname)s:%(lineno)d) ' + str_formatter  + ' %(message)s'
172            'format': '%(levelname)s %(module)s ' + str_formatter + ' %(message)s'
173        },
174    },
175    'handlers': {
176        'stdout': {
177            'class': 'logging.StreamHandler',
178            'stream': sys.stdout,
179            'formatter': 'verbose',
180        },
181        'sys-logger6': {
182            'class': 'logging.handlers.SysLogHandler',
183            'address': '/dev/log',
184            'facility': "local6",
185            'formatter': 'verbose',
186        },
187    },
188    'loggers': {
189        'analytics-logger': {
190            'handlers': ['sys-logger6', 'stdout'],
191            'level': loglevel,
192            'propagate': True,
193        },
194    }
195}
196
197
198cfg.dictConfig(LOGGING)
199log = logging.getLogger('analytics-logger')
200
201
202def print_config(config):
203    global log
204
205    try:
206        for sect in config:
207            for key in config[sect]:
208                log.debug("Config Loaded: '{}' '{}' '{}'".format(sect, key, config[sect][key]))
209    except:
210        for sect in config.sections():
211            for key in config.options(sect):
212                log.debug("Config Loaded: '{}' '{}' '{}'".format(sect, key, config.get(sect,key)))
213
214
215def init_config():
216
217    config = configparser.ConfigParser()
218    config.read(CONFIGFILE)
219
220    return config
221
222
223def init_logging(config):
224    global DEBUG, log, loglevel, mpl
225
226    mpl = get_logger()
227    mpl.setLevel(loglevel)
228    for hdl in log.handlers:
229        mpl.addHandler(hdl)
230    mpl.propagate = True
231    set_loglevel()
232    if get_var_value('DEBUG', config):
233        DEBUG = True
234        log.setLevel(loglevel)
235        print_config(config)
236
237
238def bin_to_ascii(value):
239    global log
240
241    try:
242        if isinstance(value, bytes):
243            return value.decode('utf-8')
244        else:
245            return value
246    except Exception as e:
247        log.error('Error bin_to_ascii {}'.format(e))
248        return value
249
250
251def get_llx_version():
252    global log
253    if sys.version_info[0] > 2:
254        output = bin_to_ascii(subprocess.check_output(['lliurex-version','-n']))
255    else:
256        output = bin_to_ascii(subprocess.check_output(['bash','lliurex-version','-n']))
257    full_release = output.strip()
258    release = output[0:2].strip()
259    if release == '15':
260        use = ['lliurex-detect','-f']
261        output = bin_to_ascii(subprocess.check_output(use))
262    else:
263        if sys.version_info[0] > 2:
264            use = ['lliurex-version','-f']
265        else:
266            use = ['bash','lliurex-version','-f']
267        output = bin_to_ascii(subprocess.check_output(use))
268    flavour = output.strip()
269    #log.info("Detected release:'{}' flavour:'{}'".format(release, flavour))
270    return release, flavour, full_release
271
272
273def detect_proxy():
274    global log
275
276    px = subprocess.Popen(["bash", "-c", "source /etc/profile && echo $http_proxy"], stdout=subprocess.PIPE)
277    proxy = bin_to_ascii(px.stdout.readline()).strip()
278    #log.info("Detected proxy: '{}'".format(proxy))
279    return proxy
280
281
282def valid_fd(fd):
283    try:
284        os.fstat(fd)
285        return True
286    except:
287        return False
288
289def daemonize(*args, **kwargs):
290    global glob, log, CONFIG
291
292    log.info('Running daemon mode...')
293    filelock = get_var_value('filelock', CONFIG)
294    if not filelock:
295        filelock = '/var/run/analytics.pid'
296    if os.path.isfile(filelock):
297        try:
298            os.unlink(filelock)
299        except:
300            log.error('Unable to remove old pidfile {}'.format(filelock))
301
302    preserve = []
303    for x in [log.handlers[0].socket.fileno(),get_logger().handlers[0].socket.fileno()]:
304        if valid_fd(x):
305            preserve.append(x)
306    try:
307        with daemon.DaemonContext(detach_process=True,
308                                  working_directory='/tmp',
309                                  umask=0o002,
310                                  pidfile=lockfile.FileLock(filelock),
311                                  files_preserve=preserve):
312            start(**kwargs)
313    except Exception as e:
314        log.critical("Error daemonizing {}".format(e))
315        sys.exit(1)
316
317
318def add_item(item,regexp):
319    global glob, log
320
321    log.debug('Request to add {}'.format(item))
322
323    parts_item = item.split(' ')
324
325    executable = None
326    for i in range(len(parts_item)):
327        executable = parts_item[i].strip()
328        if '/' in executable:
329            executable = executable.split('/')[-1]
330            log.debug('Trimming executable to {}'.format(executable))
331        if not re.match(regexp,executable):
332            log.debug('Skipping malformed executable {}'.format(executable))
333            executable = None
334            continue
335        if executable in glob['INTERPRETERS']:
336            log.debug('Trimming interpreter part {}'.format(executable))
337            executable = None
338            continue
339        else:
340            if executable in glob['BLACKLIST']:
341                log.debug('Skipping add due to blacklisted command {}'.format(executable))
342                return None
343            log.debug('Valid executable {}'.format(executable))
344            break
345
346    the_list = glob['LIST']
347    if executable:
348        if executable in the_list:
349            the_list[executable] = the_list[executable] + 1
350            log.debug('+++ Incrementing {} = {}'.format(executable, the_list[executable]))
351        else:
352            log.debug('*** Adding {} = 1'.format(executable, 1))
353            the_list[executable] = 1
354    glob['LIST'] = the_list
355
356def monitor():
357    global glob, log
358
359    log.info('Start monitor')
360    logfilename = get_var_value('file', glob['config'], section='Audit')
361
362    glob['BLACKLIST'] = get_var_value('blacklist', glob['config'], section='Audit')
363    glob['INTERPRETERS'] = get_var_value('interpreters', glob['config'], section='Audit')
364
365    try:
366        glob['INTERPRETERS'] = [x.strip() for x in glob['INTERPRETERS'].split(',')]
367    except Exception as e:
368        log.error('Malformed interpreters list ,{}'.format(e))
369        glob['INTERPRETERS'] = []
370        return None
371
372    try:
373        with open(glob['BLACKLIST'], 'r') as fp:
374            glob['BLACKLIST'] = [line.strip() for line in fp]
375    except Exception as e:
376        log.error('Unable to read blacklist from {} , {}'.format(glob['BLACKLIST'], e))
377        glob['BLACKLIST'] = []
378        return None
379
380    try:
381        if not (os.path.isfile(logfilename) and os.access(logfilename, os.R_OK)):
382            log.critical('File {} not readable'.format(logfilename))
383            glob['TERMINATE'] = True
384
385        fp = subprocess.Popen(['tail', '-n', '0', '-F', logfilename], stdout=subprocess.PIPE, stderr=open(os.devnull, 'w'))
386        glob['monitor_pid'] = fp.pid
387    except Exception as e:
388        log.critical('Error initializing {} read, {}'.format(logfilename, e))
389        glob['TERMINATE'] = True
390        return None
391
392    try:
393        log.info('Starting monitoring {}'.format(logfilename))
394        regexp = re.compile('^[a-zA-Z][a-zA-Z0-9_.+\-]+$')
395        while not glob['TERMINATE']:
396            if fp.poll() is not None:
397                log.error('Dead subprocess monitoring {}'.format(logfilename))
398                fp = subprocess.Popen(['tail', '-F', logfilename], stdout=subprocess.PIPE, stderr=open(os.devnull, 'w'))
399                glob['monitor_pid'] = fp.pid
400            else:
401                line = bin_to_ascii(fp.stdout.readline()).strip()
402                if re.search('type=EXECVE', line):
403                    m = re.findall('a[0-9]+="([^"]+)"', line)
404                    if m:
405                        captured = ' '.join(m)
406                        add_item(captured,regexp)
407
408    except Exception as e:
409        try:
410            if isinstance(e, ConnectionResetError):
411                log.info('Connection reset exitting monitor thread')
412                glob['TERMINATE'] = True
413                return
414            else:
415                log.error('Error reading file {}, {}'.format(logfilename, e))
416                glob['TERMINATE'] = True
417                return
418        except:
419            if e.errno == 32:
420                log.info('Connection reset exitting monitor thread')
421                glob['TERMINATE'] = True
422                return
423            else:
424                log.error('Error reading file {}, {}'.format(logfilename, e))
425                glob['TERMINATE'] = True
426                return
427
428    log.info('Exitting monitor thread')
429    return
430
431def update_list():
432    global glob, log
433
434    log.info('Start update list')
435    retry = 3
436    done = False
437    while retry > 0:
438        try:
439            list_path = get_var_value('list_path', glob['config'], mode='string', section='Server')
440            server = get_var_value('server', glob['config'], mode='string', section='Server')
441            url = 'http://' + server + '/' + list_path
442            agent = glob['user_agent']
443            headers = {'user-agent': agent}
444            retry = 0
445            done = True
446        except Exception as e:
447            retry -= 1
448            done = e
449
450    if done != True :
451        log.error('Error gettting update list settings {}'.format(done))
452
453    log.debug('List path {}'.format(url))
454
455    tick = 1
456    timeout = 60 * 60 * 12
457    c = 10
458
459    while not glob['TERMINATE']:
460        time.sleep(tick)
461        if c > 0:
462            c = c - tick
463        else:
464            c = timeout
465
466            sent = False
467            rq = None
468
469            try:
470                if glob['use_proxy']:
471                    proxy_obj = dict()
472                    proxy_obj.setdefault('http', glob['proxy'])
473
474                    rq = requests.get(url, headers=headers, proxies=proxy_obj, timeout=5)
475                    sent = True
476                else:
477                    rq = requests.get(url, headers=headers, timeout=5)
478                    sent = True
479            except Exception as e:
480                log.warning('Error getting list from {}, {}'.format(url,e))
481
482            try:
483                blist = glob['BLACKLIST']
484            except Exception as e:
485                log.error('Error loading current blacklist on update_list, {}'.format(e))
486
487            try:
488                the_list = glob['LIST']
489            except Exception as e:
490                log.error('Error loading current applist on update_list, {}'.format(e))
491
492            if sent and rq:
493                result = rq.text
494                try:
495                    json_list = json.loads(result)
496                except Exception as e:
497                    log.warning('Wrong list received {}, {}'.format(result,e))
498                    continue
499
500                try:
501                    for item in json_list:
502                        if item not in blist:
503                            blist.append(item)
504                            log.info("Received item list '{}'".format(item))
505                        if item in the_list:
506                            del the_list[item]
507                            log.info("Removed item from list '{}'".format(item))
508                    glob['BLACKLIST'] = blist
509                    glob['LIST'] = the_list
510                except Exception as e:
511                    log.error('Error updating blacklist, {}'.format(e))
512            else:
513                log.warning('Unable to get list data')
514
515    log.info('Exitting update list thread')
516
517
518def timed_send():
519    global glob, log
520    log.debug('Start timed_send ')
521    try:
522        count = get_var_value('timeout', glob['config'], mode='int')
523        if count < 0:
524            log.warning('Not valid timeout value setting default 300')
525    except Exception as e:
526        log.warning('Unable to read timeout value defaulting to 300, {}'.format(e))
527        count = 300
528
529    log.info('Initialized timed send with value {} seconds'.format(count))
530    c = count
531    tick = 0.2
532    try:
533        while not glob['TERMINATE']:
534            while glob['PRINTING'] == True:
535                time.sleep(1)
536            time.sleep(tick)
537            if c > 0:
538                c = c - tick
539            else:
540                c = count
541                log.debug('Triggering timed send')
542                clean_and_send()
543
544    except Exception as e:
545        try:
546            if isinstance(e, ConnectionResetError):
547                log.info('Connection reset exitting timer thread')
548            else:
549                log.error('Error with timed send, {}'.format(e))
550        except:
551            if e.errno == 32:
552                log.info('Connection reset exitting timer thread')
553            else:
554                log.error('Error with timed send, {}'.format(e))
555
556    log.info('Exitting timer thread')
557    return
558
559
560def start(daemon_mode=False,release='Unknown',flavour='Unknown',proxy=False, **kwargs):
561    global THREADS, oldsignals, log, CONFIG, glob, mgr
562
563    log.info("Starting analytics")
564    log.info('Initialization with release={} flavour={} proxy={}'.format(release,flavour,proxy))
565
566    mgr = Manager()
567    glob = mgr.dict()
568
569    glob['DAEMON_MODE'] = daemon_mode
570    glob['config'] = CONFIG
571
572    glob['release'] = release
573    glob['flavour'] = flavour
574    if proxy:
575        glob['proxy'] = proxy
576        glob['use_proxy'] = True
577    else:
578        glob['proxy'] = False
579        glob['use_proxy'] = False
580
581    pidfile = get_var_value('pidfile', glob['config'])
582
583    try:
584        server = get_var_value('server', glob['config'], section='Server')
585        server_path = get_var_value('server-path', glob['config'], section='Server')
586        if server.strip() == '' or server_path.strip() == '':
587            raise Exception('Empty server or server-path')
588        glob['server'] = server
589        glob['server_path'] = server_path
590    except Exception as e:
591        log.critical('Error getting server url, {}'.format(e))
592
593    try:
594        agent = get_var_value('user-agent', glob['config'])
595        if agent.strip() == '' or agent == 'None':
596            agent = 'lliurex-analytics-agent'
597        glob['user_agent'] = agent
598    except Exception as e:
599        log.warning('Error getting user-agent, {}'.format(e))
600
601        # write pid
602    try:
603        with open(pidfile, 'w') as fp:
604            fp.write(str(os.getpid()))
605    except Exception as e:
606        log.error('Error writting pidfile {}'.format(e))
607
608    glob['TERMINATE'] = False
609    glob['PRINTING'] = False
610    glob['LIST'] = {}
611
612    glob['platform_data'] = get_platform_data()
613
614    signals = {'SIGTERM': interrupt, 'SIGINT': interrupt, 'SIGUSR1': clean_and_send, 'SIGUSR2': show_captured}
615    for sig in oldsignals:
616        if sig in signals:
617            signal.signal(signal.__dict__[sig], signals[sig])
618        else:
619            try:
620                signal.signal(signal.__dict__[sig], oldsignals[sig])
621            except:
622                continue
623
624    THREADS = dict()
625    THREADS['monitor'] = Process(target=monitor, name='monitor')
626    THREADS['monitor'].daemon = glob['DAEMON_MODE']
627
628    THREADS['timed_send'] = Process(target=timed_send, name='timed_send')
629    THREADS['timed_send'].daemon = glob['DAEMON_MODE']
630
631    THREADS['update_list'] = Process(target=update_list, name='update_list')
632    THREADS['update_list'].daemon = glob['DAEMON_MODE']
633
634    THREADS['monitor'].start()
635    THREADS['timed_send'].start()
636    THREADS['update_list'].start()
637
638
639def clean_and_send(*args, **kwargs):
640    global glob, log
641
642    override_send_permission = get_var_value('override_send_permission', glob['config'], mode='bool')
643
644    if allow_send() or override_send_permission:
645        send_data(glob['LIST'])
646    else:
647        log.info('Sending not allowed when try to send results')
648    glob['LIST'] = {}
649
650
651def get_mac():
652    global log
653
654    default_mac = '00:00:00:00:00:00'
655    dirmac = '/sys/class/net'
656    eth = None
657    filemac = 'address'
658    uid = None
659    eths = sorted(os.listdir(dirmac))
660
661    def remove_mac(mac):
662        try:
663            eths.remove(mac)
664        except:
665            pass
666
667    remove_mac('lo')
668    while len(eths):
669        eth = eths[0]
670        f = '{}/{}/{}'.format(dirmac, eth, filemac)
671        try:
672            with open(f, 'r') as fp:
673                uid = str(bin_to_ascii(fp.read()).strip())
674                if uid == default_mac:
675                    raise Exception('Zero MAC')
676                else:
677                    break
678        except Exception as e:
679            log.warning('Exception {} on {}'.format(e,eth))
680            remove_mac(eths[0])
681
682    if not uid:
683        log.error('Unable to read mac address, {}'.format(e))
684        uid = default_mac
685
686    return uid
687
688def get_cpu():
689    global log
690
691    file = '/proc/cpuinfo'
692    cpu = {}
693    try:
694        with open(file, 'r') as fp:
695            for line in fp:
696                if re.search('^processor\s+:\s+([0-9]+)$', line):
697                    m = re.findall('^processor\s+:\s+([0-9]+)', line)
698                    if m and len(m) > 0:
699                        cpu['ncpus'] = int(m[0]) + 1
700                if re.search('^model name\s+:\s+(.+)$', line):
701                    m = re.findall('^model name\s+:\s+(.+)$', line)
702                    if m and len(m) > 0:
703                        cpu['model'] = str(m[0])
704    except Exception as e:
705        log.warning('Unable to read cpuinfo, {}'.format(e))
706        cpu = None
707    return cpu
708
709
710def get_mem():
711    global log
712
713    file = '/proc/meminfo'
714    mem = None
715
716    try:
717        with open(file, 'r') as fp:
718            for line in fp:
719                if re.search('^MemTotal:\s+([0-9]+)\s+\S+$', line):
720                    m = re.findall('^MemTotal:\s+([0-9]+)\s+\S+$', line)
721                    if m and len(m) > 0:
722                        mem = int(m[0])
723                        break
724    except Exception as e:
725        log.warning('Unable to read meminfo, {}'.format(e))
726        mem = None
727    return str(mem)
728
729
730def get_vga():
731    global log
732
733    vga = None
734    try:
735        out = bin_to_ascii(subprocess.check_output(['lspci'])).split('\n')
736        for line in out:
737            line_strip = line.strip()
738            if re.search('VGA', line_strip, re.IGNORECASE):
739                m = re.findall('^\S+\s(.+)$', line_strip)
740                if m and len(m) > 0:
741                    vga = m[0]
742                    break
743    except Exception as e:
744        log.warning('Unable to read pciinfo, {}'.format(e))
745        vga = None
746    return str(vga)
747
748
749def get_arch():
750    global log
751
752    arch = None
753    try:
754        arch = bin_to_ascii(subprocess.check_output(['uname', '-m'])).strip()
755    except Exception as e:
756        log.warning('Unable to read architecture, {}'.format(e))
757        arch = None
758    return str(arch)
759
760def get_subtype():
761    global log
762
763    subtype = { 'LTSP': None , 'MODE': None}
764    try:
765        rel,fla,full_rel = get_llx_version()
766        if rel == '15':
767            outtype = bin_to_ascii(subprocess.check_output(['lliurex-detect -a -e'],shell=True)).strip()
768        else:
769            outtype = bin_to_ascii(subprocess.check_output(['lliurex-version -a -e'],shell=True)).strip()
770        outtype = outtype.split('\n')
771        ltsp = None
772        mode = None
773        for line in outtype:
774            if re.search('FAT=yes', line, re.IGNORECASE):
775                mode = 'FAT'
776            elif re.search('THIN=yes', line, re.IGNORECASE):
777                mode = 'THIN'
778            elif re.search('SEMI=yes', line, re.IGNORECASE):
779                mode = 'SEMI'
780            elif re.search('LTSP=yes', line, re.IGNORECASE):
781                ltsp = True
782        subtype['LTSP'] = ltsp
783        subtype['MODE'] = mode
784    except Exception as e:
785        return subtype
786    return subtype
787
788
789def get_platform_data():
790    global log
791
792    data = {}
793    data.setdefault('mac', get_mac())
794    data.setdefault('cpu', get_cpu())
795    data.setdefault('mem', get_mem())
796    data.setdefault('vga', get_vga())
797    data.setdefault('arch', get_arch())
798    data.setdefault('subtype', get_subtype())
799
800    log.debug("Detected mac='{}' arch='{}' cpu='{}' mem='{}' vga='{}' subtype='{}'".format(data['mac'], data['arch'], data['cpu'], data['mem'], data['vga'], data['subtype']))
801    return data
802
803
804def send_data(data):
805    global log, glob
806
807    log.debug('sending specs {}'.format(glob['platform_data']))
808    log.debug('sending data {}'.format(glob['LIST']))
809
810    agent = glob['user_agent']
811    url = 'http://' + glob['server'] + '/' + glob['server_path']
812    headers = {'user-agent': agent}
813
814    version = glob['release']
815    flavour = glob['flavour']
816
817    list_data = data
818    try:
819        json_list_data = json.dumps(list_data)
820    except Exception as e:
821        log.error('Json error on internal data list')
822        return None
823
824    platform_data = glob['platform_data']
825    uid = platform_data['mac']
826
827    data_to_send = dict()
828    data_to_send.setdefault('uid', uid)
829    data_to_send.setdefault('vers', version)
830    data_to_send.setdefault('sab', flavour)
831    data_to_send.setdefault('specs', platform_data)
832    data_to_send.setdefault('stats', json_list_data)
833
834    try:
835        json_data_to_send = json.dumps(data_to_send)
836    except Exception as e:
837        log.error('Json error on data to send')
838        return None
839
840    payload = {'stats': json_data_to_send}
841    log.debug('Payload to send: {}'.format(payload))
842
843    sent = False
844    rq = None
845    if glob['use_proxy']:
846        proxy_obj = dict()
847        proxy_obj.setdefault('http', glob['proxy'])
848        try:
849            rq = requests.post(url, data=payload, headers=headers, proxies=proxy_obj, timeout=5)
850            sent = True
851        except Exception as e:
852            log.error('Error sending data through proxy, {}'.format(e))
853
854    if not glob['use_proxy'] or sent == False:
855        try:
856            rq = requests.post(url, data=payload, headers=headers, timeout=5)
857            sent = True
858        except Exception as e:
859            log.error('Error sending data, {}'.format(e))
860
861    if sent and rq:
862        result = rq.text
863        result = result.strip().lower()
864        if result == 'ok':
865            log.debug('Sending was success with reply OK ')
866        elif result == 'nok':
867            log.info('Sending was success but reply is NOK ')
868        else:
869            log.warning("Sending was success but reply is unknown '{}'".format(result))
870    else:
871        log.warning('Unable to send data')
872
873
874def interrupt(*args, **kwargs):
875    global glob, log, THREADS, mgr
876
877    log.info('Interrupting analytics')
878    try:
879        clean_and_send()
880        try:
881            glob['TERMINATE'] = True
882            os.kill(glob['monitor_pid'],signal.SIGKILL)
883        except Exception as e:
884            log.error('Requested kill the program {}'.format(e))
885            sys.exit(1)
886        for x in THREADS:
887            THREADS[x].join(1)
888            log.info('Interrupt: Joined {}'.format(x))
889
890    except Exception as e:
891        log.error('Error while interrupting, {}'.format(e))
892
893
894def show_captured(*args, **kwargs):
895    global glob, log
896
897    glob['PRINTING'] = True
898    log.info('Requested to show list')
899
900    list_items = glob['LIST']
901    if not isinstance(list_items, dict):
902        log.warning('Error showing captured items, LIST is not a dictionary')
903
904    listkeys_sorted = sorted(list_items, key=list_items.get, reverse=True)
905
906    if len(listkeys_sorted) > 0:
907        log.info('analytics is showing currently capture list in memory')
908        for i in listkeys_sorted:
909            log.info('{} = {}'.format(i, list_items.get(i)))
910    else:
911        log.info('analytics detect an empty capture list in memory')
912
913    glob['PRINTING'] = False
914
915
916def check_server_acknowledge():
917    global log
918
919    try:
920        c = client.ServerProxy("https://server:9779/",
921                               verbose=False,
922                               use_datetime=True,
923                               context=ssl._create_unverified_context())
924        return c.get_variable("", "VariablesManager", "STATS_ENABLED")
925    except Exception as e:
926        log.error('Error getting variables, {}'.format(e))
927        return None
928
929
930def check_local_acknowledge():
931    global glob, log
932
933    if glob['TERMINATE']:
934        return None
935
936    try:
937        statusfile = get_var_value('statusfile', glob['config'])
938        if str(statusfile) == 'None':
939            statusfile = '/etc/lliurex-analytics/status'
940            log.warning('Warning statusfile not set, defaulting to {}'.format(statusfile))
941    except Exception as e:
942        log.error('Error getting value for statusfile, {}'.format(e))
943
944    answer = None
945    try:
946
947        if os.path.isfile(statusfile):
948            fp = open(statusfile, 'r')
949            answer = fp.readline()
950            fp.close()
951        else:
952            log.error('wrong statusfile {}'.format(statusfile))
953            return None
954
955        return answer.strip()
956    except Exception as e:
957        log.warning('Error reading status file, {}'.format(e))
958        return None
959
960
961def allow_send():
962    global glob, log
963
964    if glob['TERMINATE']:
965        return False
966
967    if glob['flavour'].lower() == 'server':
968        answer = str(check_server_acknowledge())
969        answer = answer.strip()
970        if answer == '1':
971            log.info('Allowed to send stats checking server acknowledge')
972            return True
973        elif answer == '0':
974            log.info('Denied to send stats checking server acknowledge')
975            return False
976        elif answer == 'None':
977            pass
978        else:
979            log.info('Unknown value checking server acknowledge, {}'.format(answer))
980    answer = str(check_local_acknowledge()).lower()
981    answer = answer.strip()
982    if answer == 'yes':
983        log.info('Allowed to send stats checking local acknowledge')
984        return True
985    elif answer == 'no':
986        log.info('Denied to send stats checking local acknowledge')
987        return False
988    elif answer == '':
989        pass
990    else:
991        log.info('Unknown value checking local acknowledge, {}'.format(answer))
992
993    log.info('Denied to send stats by default')
994    return False
995
996
997if __name__ == '__main__':
998    exit = 0
999    keyword='analytics'
1000    if sys.version[0] == '3':
1001        interpreter='python3'
1002    else:
1003        interpreter='python'
1004    for proc in psutil.process_iter():
1005        a=False
1006        b=False
1007        cmd=None
1008        try:
1009            cmd = proc.cmdline()
1010        except:
1011            cmd = proc.cmdline
1012        for argument in cmd:
1013            #print('{} {} {}'.format(cmd,keyword,argument[-len(keyword):]))
1014            if interpreter in argument[-len(interpreter):]:
1015                a = True
1016            if keyword in argument[-len(keyword):]:
1017                b = True
1018            if a and b:
1019                exit = exit +1
1020    if exit > 1:
1021        log.error('Another daemon is running')
1022        sys.exit(1)
1023
1024    try:
1025        log.info('Initializing config')
1026        CONFIG = init_config()
1027    except Exception as e:
1028        log.error('Error initializing config analytics {}'.format(e))
1029        sys.exit(1)
1030    try:
1031        log.info('Initializing final logging')
1032        init_logging(CONFIG)
1033    except Exception as e:
1034        log.error('Error initializing logging analytics {}'.format(e))
1035        sys.exit(1)
1036
1037    THREADS = {}
1038
1039    try:
1040        release, flavour, full_release = get_llx_version()
1041    except Exception as e:
1042        log.error('Error getting llx version {}'.format(e))
1043        release = 'Unknown'
1044        flavour = 'Unknown'
1045        full_release = 'Unknown'
1046
1047    proxy = ''
1048    try:
1049        proxy = detect_proxy()
1050        if proxy == '':
1051            #log.info('Not using proxy')
1052            proxy = False
1053    except Exception as e:
1054        log.warning('Error detecting proxy {}'.format(e))
1055        proxy = False
1056
1057    DAEMON_MODE = get_var_value('DAEMON_MODE', CONFIG, 'bool')
1058
1059    if DAEMON_MODE:
1060        daemonize(daemon_mode=True,flavour=flavour,release=full_release,proxy=proxy)
1061    else:
1062        start(daemon_mode=False,flavour=flavour,release=full_release,proxy=proxy)
1063
1064    log.debug('End main')
1065    ended = False
1066    while not ended:
1067        ended = True
1068        for t in THREADS:
1069            THREADS[t].join(1)
1070            if THREADS[t].exitcode == None:
1071                ended = False
1072            else:
1073                log.info('Joined {}'.format(t))
1074    try:
1075        mgr.shutdown()
1076    except:
1077        pass
1078
1079    log.info('Exitting analytics')
1080    sys.exit(0)
Note: See TracBrowser for help on using the repository browser.