root/branch/ggpolo/umitCore/Scheduler.py @ 770

Revision 770, 12.5 kB (checked in by ggpolo, 6 years ago)

Scheduler core is complete for now

Line 
1# Copyright (C) 2007 Insecure.Com LLC.
2#
3# Original author: Adriano Monteiro Marques
4# Now maintained and updated by: Guilherme Polo <ggpolo@gmail.com>
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
20"""
21Job scheduler
22"""
23
24import os
25import sys
26import time
27import warnings
28from tempfile import mktemp
29from ConfigParser import ConfigParser
30
31from umitCore.CronParser import CronParser
32from umitCore.NmapCommand import NmapCommand
33from umitCore.SendMail import send_mail
34from umitCore.Paths import Path
35from umitDB.xmlstore import XMLStore
36
37
38try:
39    umitdb_ng = Path.umitdb_ng
40except:
41    from umitCore.Paths import Path
42    Path.set_umit_conf(join(split(__file__)[0], 'config', 'umit.conf'))
43
44class Scheduler(object):
45    """
46    Schedules schemas to run.
47    """
48
49    def __init__(self, schemas_file):
50        self.schemas_file = schemas_file
51        self.schemas_stat = None
52
53    def parse_schemas(self):
54        """
55        Parse schemas set in file.
56        """
57        self.schemas = [ ]
58
59        self.schema_parser = ConfigParser()
60        self.schema_parser.read(self.schemas_file)
61
62        for sec in self.schema_parser.sections():
63            self.schemas.append(SchedSchema(self.schema_parser, sec))
64
65   
66    def check_for_changes(self):
67        """
68        If schemas file changed since last check, reparse schemas.
69        """
70        try:
71            new_stat = os.stat(self.schemas_file)
72        except OSError: # file was being saved (probably) when this was called
73            time.sleep(.1)
74            new_stat = os.stat(self.schemas_file)
75
76        new_stat = new_stat.st_mtime
77
78        if new_stat != self.schemas_stat:
79            print "Schemas file changed since last check"
80            self.schemas_stat = new_stat
81            self.parse_schemas()
82
83
84    def _get_schemas_stat(self):
85        """
86        Get latest os.stat for schemas file
87        """
88        return self.__schemas_stat
89
90
91    def _set_schemas_stat(self, stat):
92        """
93        Set current os.stat for schemas file
94        """
95        self.__schemas_stat = stat
96
97
98    def _get_schemas_file(self):
99        """
100        Get schemas file.
101        """
102        return self.__schemasf
103
104
105    def _set_schemas_file(self, file):
106        """
107        Set schemas file.
108        """
109        try:
110            os.stat(file)
111        except OSError, e:
112            print e
113            sys.exit(0)
114
115        self.__schemasf = file
116
117
118    # Properties
119    schemas_file = property(_get_schemas_file, _set_schemas_file)
120    schemas_last_stat = property(_get_schemas_stat, _set_schemas_stat)
121
122
123class SchedSchema(object):
124    """
125    Parse scheduled schemas.
126    """
127
128    def __init__(self, schema_parser, schema_name):
129        self.options = { 
130            # Task execution time
131            'hour':self._set_hour,
132            'minute':self._set_minute,
133            'month':self._set_month,
134            'day':self._set_day,
135            'weekday':self._set_weekday,
136            # Task command
137            'command':self._set_command,
138            # Task options
139            'enabled':self._set_enabled,
140            'addtoinv':self._set_addtoinv,
141            'saveto':self._set_saveto,
142            'mailto':self._set_mailto
143        }
144
145        self.__schema_name = schema_name
146        self.schema_parser = schema_parser
147        self.last_check = None
148        self.cron_parser = CronParser()
149        self.setup_schema(self.schema_name)
150
151
152    def job_to_run(self):
153        """
154        Check if there is a scheduled job for now and if there is,
155        return True, otherwise False.
156        """
157        cur_time = time.localtime()
158
159        # check if we should check for job or not
160        if self.last_check and cur_time[1] == self.last_check[1] and \
161            cur_time[2] == self.last_check[2] and \
162            cur_time[3] == self.last_check[3] and \
163            cur_time[4] == self.last_check[4] and \
164            cur_time[6] == self.last_check[6]:
165
166            return False # too early to run a job again!
167
168        if cur_time[1] in self.month and cur_time[2] in self.day \
169            and cur_time[3] in self.hour and cur_time[4] in self.minute \
170            and cur_time[6] in self.weekday:
171           
172            self.last_check = cur_time
173            return True # there is a job to run!
174
175
176    def setup_schema(self, name):
177        """
178        Setup schema.
179        """
180        if self.schema_parser.has_section(name):
181            for opt in self.schema_parser.options(name):
182                if opt in self.options.keys():
183                    self.options[opt](self.schema_parser.get(name, opt))
184
185
186    def _get_schema_name(self):
187        """
188        Return schema name set.
189        """
190        return self.__schema_name
191
192
193    def _get_last_check(self):
194        """
195        Return when last check happened.
196        """
197        return self.__lcheck
198
199
200    def _set_last_check(self, when):
201        """
202        Set last check time.
203        """
204        self.__lcheck = when
205
206
207    # Job time
208    def _get_month(self):
209        return self.__month
210
211
212    def _set_month(self, month):
213        """
214        Parse and set month.
215        """
216        self.__month = self.cron_parser.parse_month(month)
217
218
219    def _get_day(self): 
220        return self.__day
221
222
223    def _set_day(self, day):
224        """
225        Parse and set day.
226        """
227        self.__day = self.cron_parser.parse_day(day)
228
229
230    def _get_weekday(self): 
231        return self.__weekday
232
233
234    def _set_weekday(self, weekday):
235        """
236        Parse and set weekday.
237        """
238        self.__weekday = self.cron_parser.parse_weekday(weekday)
239
240
241    def _get_hour(self): 
242        return self.__hour
243
244
245    def _set_hour(self, hour):
246        """
247        Parse and set hour.
248        """
249        self.__hour = self.cron_parser.parse_hour(hour)
250
251
252    def _get_minute(self): 
253        return self.__minute
254
255
256    def _set_minute(self, minute):
257        """
258        Parse and set minute.
259        """
260        self.__minute = self.cron_parser.parse_minute(minute)
261
262
263    # Job Command
264    def _get_command(self):
265        """
266        Get job command.
267        """
268        return self.__command
269
270
271    def _set_command(self, command):
272        """
273        Set command for job.
274        """
275        self.__command = command
276
277
278    # Job Options
279    def _get_enabled(self):
280        """
281        Returns True if job should run, otherwise, False.
282        """
283        return self.__enabled
284
285
286    def _set_enabled(self, enable):
287        """
288        Set if job should run or not.
289        """
290        self.__enabled = enable
291
292
293    def _get_addtoinv(self):
294        """
295        Returns True if job result should be stored in Inventory, otherwise,
296        False.
297        """
298        return self.__addtoinv
299
300
301    def _set_addtoinv(self, add):
302        """
303        Sets job result to be added to Inventory, or not.
304        """
305        self.__addtoinv = add
306
307
308    def _get_saveto(self):
309        """
310        Get file that stores job results.
311        """
312        return self.__savefile
313
314
315    def _set_saveto(self, file):
316        """
317        Set a file to store job results.
318        """
319        self.__savefile = file
320       
321
322    def _get_mailto(self):
323        """
324        Get email set to receive job results.
325        """
326        return self.__mail
327
328
329    def _set_mailto(self, mail): 
330        """
331        Set an email to receive job results.
332        """
333        self.__mail = mail
334
335
336    # Properties
337    month = property(_get_month, _set_month)
338    day = property(_get_day, _set_day)
339    weekday = property(_get_weekday, _set_weekday)
340    hour = property(_get_hour, _set_hour)
341    minute = property(_get_minute, _set_minute)
342    command = property(_get_command, _set_command)
343    enabled = property(_get_enabled, _set_enabled)
344    addtoinv = property(_get_addtoinv, _set_addtoinv)
345    saveto = property(_get_saveto, _set_saveto)
346    mailto = property(_get_mailto, _set_mailto)
347
348    last_check = property(_get_last_check, _set_last_check)
349    schema_name = property(_get_schema_name)
350
351
352def decide_ouput(file):
353    """
354    Choose a better or the same output path.
355    """
356    output = None
357    err = True
358    file_path = os.path.abspath(file)
359
360    if os.path.exists(os.path.split(file_path)[0]): # path exists at least
361        try: # try to open file in read mode
362            f = open(file, 'r')
363            f.close()
364            # if we are still here, file exists, this is bad ;/
365            count = 1
366            orig = file
367            while os.path.isfile(file):
368                file = ''
369                file = orig + "_%d" % count # try appending extra extension
370                count += 1
371            err = False
372            warnings.warn("File will be saved as %s" % file, Warning)
373           
374        except IOError: # file didnt exist and path exists, this is good!
375            err = False
376           
377        output = file
378       
379    return output
380
381
382def calc_next_time():
383    """
384    Calculate next time to check for changes and jobs.
385    """
386    tt = time.localtime(time.time() + 60)
387    tt = tt[:5] + (0,) + tt[6:]
388    return time.mktime(tt)
389
390
391running_scans = { }
392
393def stop():
394    """
395    Clean temp files used by running scans.
396    """   
397    for running, opts in running_scans.items():
398        print "Cleaning up scan \"%s\"" % opts[3]
399        scan = opts[0]
400        scan.close() # delete temporary files
401       
402
403def run(schema_file=None):
404    """
405    Run scheduler forever.
406    """
407    if not schema_file:
408        print "Get file from umitCore.Path"
409        print "Not working yet"
410        sys.exit(0)
411
412    next_time = calc_next_time()
413
414    s = Scheduler(schema_file)
415
416    scount = 0
417    while 1: # run forever and ever ;)
418        current_time = time.time()
419
420        if current_time < next_time:
421           time.sleep(next_time - current_time + .1)
422
423        # check if time has changed by more than two minutes (clock changes)
424        if abs(time.time() - next_time) > 120:
425            # reset timer
426            next_time = calc_next_time()
427
428        s.check_for_changes()
429
430        for schema in s.schemas:
431            if schema.job_to_run():
432                if not int(schema.enabled): # schema disabled, neeexxt!
433                    continue
434
435                print "To run:", schema.schema_name
436
437                scan = NmapCommand(schema.command)
438                scan.run_scan()
439
440                running_scans[scount] = (scan, schema.saveto, schema.mailto,
441                                         schema.schema_name, schema.addtoinv)
442                scount += 1
443       
444        for running, opts in running_scans.items():
445            scan = opts[0]
446            if not scan.scan_state(): # scan finished
447                print "Scan finished:", opts[3]
448           
449                if opts[1]: # save xml output
450                    saveto = decide_ouput(opts[1])
451                    f = open(saveto, 'w')
452                    f.write(open(scan.get_xml_output_file(), 'r').read())
453                    f.close()
454                    print "Saved as:", saveto
455               
456                if opts[2]: # mail output to
457                    recipients = opts[2].split(',')
458                    print "Mail to:", recipients
459                    send_mail("sendmail", recipients,
460                              _("UMIT: Status Report for scheduled schema \
461\"%s\"" % opts[3]),
462                              _("There was a scheduled job that finished \
463now: %s\nFollows an attachment of job output." % time.ctime()),
464                              scan.get_xml_output_file(), 
465                             
466                          )
467               
468                if int(opts[4]): # add to inventory
469                    print "Results being added to Inventory %s" % opts[3]
470                    XMLStore(Path.umitdb_ng, scan.get_xml_output_file(), 
471                             inventory=opts[3])
472                    print "Finished insertion on Inventory %s" % opts[3]
473                   
474                scan.close() # delete temporary files
475                scount -= 1
476                del running_scans[running]
477
478        next_time += 60
479
480
481if __name__ == "__main__":
482    try:
483        run('schema-sample.conf')
484    except KeyboardInterrupt:
485        stop()
486
Note: See TracBrowser for help on using the browser.