Imported Upstream version 2.3
[ossec-hids.git] / src / syscheckd / run_check.c
1 /* @(#) $Id: run_check.c,v 1.49 2009/11/05 15:15:14 dcid Exp $ */
2
3 /* Copyright (C) 2009 Trend Micro Inc.
4  * All right reserved.
5  *
6  * This program is a free software; you can redistribute it
7  * and/or modify it under the terms of the GNU General Public
8  * License (version 3) as published by the FSF - Free Software
9  * Foundation
10  */
11
12
13 /* SCHED_BATCH is Linux specific and is only picked up with _GNU_SOURCE */
14 #ifdef __linux__
15         #define _GNU_SOURCE
16         #include <sched.h>
17 #endif
18
19 #include "shared.h"
20 #include "syscheck.h"
21 #include "os_crypto/md5/md5_op.h"
22 #include "os_crypto/sha1/sha1_op.h"
23 #include "os_crypto/md5_sha1/md5_sha1_op.h"
24
25 #include "rootcheck/rootcheck.h"
26
27
28 /** Prototypes **/
29 int c_read_file(char *file_name, char *oldsum, char *newsum);
30
31
32 /* Send syscheck message.
33  * Send a message related to syscheck change/addition.
34  */
35 int send_syscheck_msg(char *msg)
36 {
37     if(SendMSG(syscheck.queue, msg, SYSCHECK, SYSCHECK_MQ) < 0)
38     {
39         merror(QUEUE_SEND, ARGV0);
40
41         if((syscheck.queue = StartMQ(DEFAULTQPATH,WRITE)) < 0)
42         {
43             ErrorExit(QUEUE_FATAL, ARGV0, DEFAULTQPATH);
44         }
45
46         /* If we reach here, we can try to send it again */
47         SendMSG(syscheck.queue, msg, SYSCHECK, SYSCHECK_MQ);
48     }
49
50     return(0);
51 }
52
53
54
55 /* Send rootcheck message.
56  * Send a message related to rootcheck change/addition.
57  */
58 int send_rootcheck_msg(char *msg)
59 {
60     if(SendMSG(syscheck.queue, msg, ROOTCHECK, ROOTCHECK_MQ) < 0)
61     {
62         merror(QUEUE_SEND, ARGV0);
63
64         if((syscheck.queue = StartMQ(DEFAULTQPATH,WRITE)) < 0)
65         {
66             ErrorExit(QUEUE_FATAL, ARGV0, DEFAULTQPATH);
67         }
68
69         /* If we reach here, we can try to send it again */
70         SendMSG(syscheck.queue, msg, ROOTCHECK, ROOTCHECK_MQ);
71     }
72
73     return(0);
74 }
75
76
77 /* Sends syscheck db to the server.
78  */
79 void send_sk_db()
80 {
81     char buf[MAX_LINE +1];
82     int file_count = 0;
83
84     buf[MAX_LINE] = '\0';
85
86     if(fseek(syscheck.fp, 0, SEEK_SET) == -1)
87     {
88         ErrorExit(FSEEK_ERROR, ARGV0, "syscheck_db");
89     }
90
91
92     /* Sending scan start message */
93     if(syscheck.dir[0])
94     {
95         merror("%s: INFO: Starting syscheck scan (forwarding database).", ARGV0);
96         send_rootcheck_msg("Starting syscheck scan.");
97     }
98     else
99     {
100         sleep(syscheck.tsleep +10);
101         return;
102     }
103         
104
105
106     while(fgets(buf,MAX_LINE, syscheck.fp) != NULL)
107     {
108         if((buf[0] != '#') && (buf[0] != ' ') && (buf[0] != '\n'))
109         {
110             char *n_buf;
111
112             /* Removing the \n before sending to the analysis server */
113             n_buf = strchr(buf,'\n');
114             if(n_buf == NULL)
115                 continue;
116
117             *n_buf = '\0';
118
119
120             /* First 6 characters are for internal use */
121             n_buf = buf;
122             n_buf+=6;
123
124             send_syscheck_msg(n_buf);
125
126
127             /* A count and a sleep to avoid flooding the server.
128              * Time or speed are not requirements in here
129              */
130             file_count++;
131
132
133             /* sleep X every Y files */
134             if(file_count >= syscheck.sleep_after)
135             {
136                 sleep(syscheck.tsleep);
137                 file_count = 0;
138             }
139         }
140     }
141
142
143     /* Sending scan ending message */
144     sleep(syscheck.tsleep +10);
145
146     if(syscheck.dir[0])
147     {
148         merror("%s: INFO: Ending syscheck scan (forwarding database).", ARGV0);
149         send_rootcheck_msg("Ending syscheck scan.");
150     }
151 }
152      
153
154  
155 /* start_daemon
156  * Run periodicaly the integrity checking 
157  */
158 void start_daemon()
159 {
160     int day_scanned = 0;
161     int curr_day = 0;
162     
163     time_t curr_time = 0;
164     
165     time_t prev_time_rk = 0;
166     time_t prev_time_sk = 0;
167
168     char curr_hour[12];
169
170     struct tm *p;
171    
172
173     /* To be used by select. */
174     #ifdef USEINOTIFY
175     struct timeval selecttime;
176     fd_set rfds;
177     #endif
178
179     
180     /*
181      * SCHED_BATCH forces the kernel to assume this is a cpu intensive 
182      * process
183      * and gives it a lower priority. This keeps ossec-syscheckd 
184      * from reducing
185      * the interactity of an ssh session when checksumming large files.
186      * This is available in kernel flavors >= 2.6.16
187      */
188     #ifdef SCHED_BATCH
189     struct sched_param pri;
190     int status;
191     
192     pri.sched_priority = 0;
193     status = sched_setscheduler(0, SCHED_BATCH, &pri);
194     
195     debug1("%s: Setting SCHED_BATCH returned: %d", ARGV0, status);
196     #endif
197     
198             
199     #ifdef DEBUG
200     verbose("%s: Starting daemon ..",ARGV0);
201     #endif
202   
203   
204     
205     /* Some time to settle */
206     memset(curr_hour, '\0', 12);
207     sleep(syscheck.tsleep * 10);
208
209
210
211     /* If the scan time/day is set, reset the 
212      * syscheck.time/rootcheck.time 
213      */
214     if(syscheck.scan_time || syscheck.scan_day)
215     {
216         /* At least once a week. */
217         syscheck.time = 604800;
218         rootcheck.time = 604800;
219     }
220
221
222     /* Will create the db to store syscheck data */
223     if(syscheck.scan_on_start)
224     {
225         create_db(1);
226         fflush(syscheck.fp);
227
228         sleep(syscheck.tsleep * 60);
229         send_sk_db();
230     }
231     else
232     {
233         prev_time_rk = time(0);
234     }
235                
236
237     
238     /* Before entering in daemon mode itself */
239     prev_time_sk = time(0);
240     sleep(syscheck.tsleep * 10);
241     
242
243     /* If the scan_time or scan_day is set, we need to handle the
244      * current day/time on the loop.
245      */
246     if(syscheck.scan_time || syscheck.scan_day)
247     {
248         curr_time = time(0); 
249         p = localtime(&curr_time);
250
251
252         /* Assign hour/min/sec values */
253         snprintf(curr_hour, 9, "%02d:%02d:%02d",
254                 p->tm_hour,
255                 p->tm_min,
256                 p->tm_sec);
257
258
259         curr_day = p->tm_mday;
260
261
262         
263         if(syscheck.scan_time && syscheck.scan_day)
264         {
265             if((OS_IsAfterTime(curr_hour, syscheck.scan_time)) &&
266                (OS_IsonDay(p->tm_wday, syscheck.scan_day)))
267             {
268                 day_scanned = 1;
269             }
270         }
271
272         else if(syscheck.scan_time)
273         {
274             if(OS_IsAfterTime(curr_hour, syscheck.scan_time))
275             {
276                 day_scanned = 1;
277             }
278         }
279         else if(syscheck.scan_day)
280         {
281             if(OS_IsonDay(p->tm_wday, syscheck.scan_day))
282             {
283                 day_scanned = 1;
284             }
285         }
286     }
287
288     
289     #if defined (USEINOTIFY) || defined (WIN32)
290     if(syscheck.realtime && (syscheck.realtime->fd >= 0))
291         verbose("%s: INFO: Starting real time file monitoring.", ARGV0);
292     #endif
293     
294
295     /* Checking every SYSCHECK_WAIT */    
296     while(1)
297     {
298         int run_now = 0;
299         curr_time = time(0);
300         
301
302         /* Checking if syscheck should be restarted, */
303         run_now = os_check_restart_syscheck();
304
305         
306         /* Checking if a day_time or scan_time is set. */
307         if(syscheck.scan_time || syscheck.scan_day)
308         {
309             p = localtime(&curr_time);
310
311
312             /* Day changed. */
313             if(curr_day != p->tm_mday)
314             {
315                 day_scanned = 0;
316                 curr_day = p->tm_mday;
317             }
318             
319             
320             /* Checking for the time of the scan. */
321             if(!day_scanned && syscheck.scan_time && syscheck.scan_day)
322             {
323                 if((OS_IsAfterTime(curr_hour, syscheck.scan_time)) &&
324                    (OS_IsonDay(p->tm_wday, syscheck.scan_day)))
325                 {
326                     day_scanned = 1;
327                     run_now = 1;
328                 }
329             }
330             
331             else if(!day_scanned && syscheck.scan_time)
332             {
333                 /* Assign hour/min/sec values */
334                 snprintf(curr_hour, 9, "%02d:%02d:%02d", 
335                                     p->tm_hour, p->tm_min, p->tm_sec);
336
337                 if(OS_IsAfterTime(curr_hour, syscheck.scan_time))
338                 {
339                     run_now = 1;
340                     day_scanned = 1;
341                 }
342             }
343
344             /* Checking for the day of the scan. */
345             else if(!day_scanned && syscheck.scan_day)
346             {
347                 if(OS_IsonDay(p->tm_wday, syscheck.scan_day))
348                 {
349                     run_now = 1;
350                     day_scanned = 1;
351                 }
352             }
353         }
354         
355         
356
357         /* If time elapsed is higher than the rootcheck_time,
358          * run it.
359          */
360         if(syscheck.rootcheck)
361         {
362             if(((curr_time - prev_time_rk) > rootcheck.time) || run_now)
363             {
364                 run_rk_check();
365                 prev_time_rk = time(0);
366             }
367         }
368
369         
370         /* If time elapsed is higher than the syscheck time,
371          * run syscheck time.
372          */
373         if(((curr_time - prev_time_sk) > syscheck.time) || run_now)
374         {
375             /* We need to create the db, if scan on start is not set. */
376             if(syscheck.scan_on_start == 0)
377             {
378                 create_db(1);
379                 fflush(syscheck.fp);
380
381                 sleep(syscheck.tsleep * 10);
382                 send_sk_db();
383                 sleep(syscheck.tsleep * 10);
384
385                 syscheck.scan_on_start = 1;
386             }
387             
388             
389             else
390             {
391                 /* Sending scan start message */
392                 if(syscheck.dir[0])
393                 {
394                     merror("%s: INFO: Starting syscheck scan.", ARGV0);
395                     send_rootcheck_msg("Starting syscheck scan.");
396                 }
397
398
399                 #ifdef WIN32
400                 /* Checking for registry changes on Windows */
401                 os_winreg_check();
402                 #endif
403
404
405                 check_db();
406
407
408                 /* Set syscheck.fp to the begining of the file */
409                 fseek(syscheck.fp, 0, SEEK_SET);
410
411
412                 /* Checking for changes */
413                 run_check();
414             }
415
416             
417             /* Sending scan ending message */
418             sleep(syscheck.tsleep + 20);
419             if(syscheck.dir[0])
420             {
421                 merror("%s: INFO: Ending syscheck scan.", ARGV0);
422                 send_rootcheck_msg("Ending syscheck scan.");
423             }
424                 
425
426
427             /* Sending database completed message */
428             send_syscheck_msg(HC_SK_DB_COMPLETED);
429             debug2("%s: DEBUG: Sending database completed message.", ARGV0);
430
431             
432             prev_time_sk = time(0);
433         } 
434
435
436         #ifdef USEINOTIFY
437         if(syscheck.realtime && (syscheck.realtime->fd >= 0))
438         {
439             selecttime.tv_sec = SYSCHECK_WAIT;
440             selecttime.tv_usec = 0;
441
442             /* zero-out the fd_set */
443             FD_ZERO (&rfds);
444
445             FD_SET(syscheck.realtime->fd, &rfds);
446
447             run_now = select(syscheck.realtime->fd + 1, &rfds, 
448                              NULL, NULL, &selecttime);
449             if(run_now < 0)
450             {
451                 merror("%s: ERROR: Select failed (for realtime fim).", ARGV0);
452                 sleep(SYSCHECK_WAIT);
453             }
454             else if(run_now == 0)
455             {
456                 /* Timeout. */
457             }
458             else if (FD_ISSET (syscheck.realtime->fd, &rfds))
459             {
460                 realtime_process();
461             }
462         }
463         else
464         {
465             sleep(SYSCHECK_WAIT);
466         }
467
468         #elif WIN32
469         if(syscheck.realtime && (syscheck.realtime->fd >= 0))
470         {
471             run_now = WaitForSingleObjectEx(syscheck.realtime->evt, SYSCHECK_WAIT * 1000, TRUE);
472             if(run_now == WAIT_FAILED)
473             {
474                 merror("%s: ERROR: WaitForSingleObjectEx failed (for realtime fim).", ARGV0);
475                 sleep(SYSCHECK_WAIT);
476             }
477             else
478             {
479                 sleep(1);
480             }
481         }
482         else
483         {
484             sleep(SYSCHECK_WAIT);
485         }
486
487
488         #else
489         sleep(SYSCHECK_WAIT);
490         #endif
491     }
492 }
493
494
495 /* run_check: v0.1
496  * Read the database and check if the binary has changed
497  */
498 void run_check()
499 {
500     char c_sum[256 +2];
501     char alert_msg[912 +2];
502     char buf[MAX_LINE +2];
503     int file_count = 0;
504
505
506     /* Cleaning buffer */
507     memset(buf, '\0', MAX_LINE +1);
508     memset(alert_msg, '\0', 912 +1);
509     memset(c_sum, '\0', 256 +1);
510
511     /* fgets garantee the null termination */
512     while(fgets(buf, MAX_LINE, syscheck.fp) != NULL)
513     {
514         /* Buf should be in the following format:
515          * header checksum file_name (checksum space filename)
516          */
517         char *n_file; /* file read from the db */
518         char *n_sum;  /* md5sum read from the db */
519         char *tmp_c;  /* tmp_char */
520         
521         
522         /* Avoiding wrong formats in the database. Alert about them */
523         if(buf[0] == '#' || buf[0] == ' ' || buf[0] == '\n')
524         {
525             merror("%s: Invalid entry in the integrity database: '%s'",
526                                                             ARGV0, buf);
527             continue;
528         }
529         
530         /* Adding a sleep in here -- avoid floods and extreme CPU usage
531          * on the client side -- speed not necessary
532          */
533          file_count++;
534          if(file_count >= (syscheck.sleep_after))
535          {
536              sleep(syscheck.tsleep);
537              file_count = 0;
538          }
539         
540          
541         /* Finding the file name */
542         n_file = strchr(buf, ' ');
543         if(n_file == NULL)
544         {
545             merror("%s: Invalid entry in the integrity check database.",ARGV0);
546             continue;
547         }
548
549         /* Zeroing the ' ' and messing up with buf */
550         *n_file = '\0';
551
552
553         /* Setting n_file to the begining of the file name */
554         n_file++;
555
556
557         /* Removing the '\n' if present and setting it to \0 */
558         tmp_c = strchr(n_file,'\n');
559         if(tmp_c)
560         {
561             *tmp_c = '\0';
562         }
563         
564         
565         /* Setting n_sum to the begining of buf */
566         n_sum = buf;
567
568
569         /* Cleaning up c_sum */
570         memset(c_sum, '\0', 16);        
571         c_sum[255] = '\0';
572         
573
574         /* If it returns < 0, we will already have alerted. */
575         if(c_read_file(n_file, n_sum, c_sum) < 0)
576             continue;
577
578
579         if(strcmp(c_sum, n_sum+6) != 0)
580         {
581             /* Sending the new checksum to the analysis server */
582             alert_msg[912 +1] = '\0';
583             snprintf(alert_msg, 912, "%s %s", c_sum, n_file);
584             send_syscheck_msg(alert_msg);
585
586             continue;
587         }
588
589         /* FILE OK if reached here */
590     }
591 }
592
593
594 /* c_read_file
595  * Read file information and return a pointer
596  * to the checksum
597  */
598 int c_read_file(char *file_name, char *oldsum, char *newsum)
599 {
600     int size = 0, perm = 0, owner = 0, group = 0, md5sum = 0, sha1sum = 0;
601     
602     struct stat statbuf;
603
604     os_md5 mf_sum;
605     os_sha1 sf_sum;
606
607
608     /* Cleaning sums */
609     strncpy(mf_sum, "xxx", 4);
610     strncpy(sf_sum, "xxx", 4);
611                     
612     
613
614     /* Stating the file */
615     #ifdef WIN32
616     if(stat(file_name, &statbuf) < 0)
617     #else
618     if(lstat(file_name, &statbuf) < 0)
619     #endif
620     {
621         char alert_msg[912 +2];
622
623         alert_msg[912 +1] = '\0';
624         snprintf(alert_msg, 912,"-1 %s", file_name);
625         send_syscheck_msg(alert_msg);
626
627         return(-1);
628     }
629
630     /* Getting the old sum values */
631
632     /* size */
633     if(oldsum[0] == '+')
634         size = 1;
635
636     /* perm */
637     if(oldsum[1] == '+')
638         perm = 1;
639
640     /* owner */
641     if(oldsum[2] == '+')
642         owner = 1;     
643     
644     /* group */
645     if(oldsum[3] == '+')
646         group = 1;
647         
648     /* md5 sum */
649     if(oldsum[4] == '+')
650         md5sum = 1;
651
652     /* sha1 sum */
653     if(oldsum[5] == '+')
654         sha1sum = 1;
655     
656     
657     /* Generating new checksum */
658     #ifdef WIN32
659     if(S_ISREG(statbuf.st_mode))
660     #else
661     if(S_ISREG(statbuf.st_mode))
662     #endif
663     {
664         if(sha1sum || md5sum)
665         {
666             /* Generating checksums of the file. */
667             if(OS_MD5_SHA1_File(file_name, mf_sum, sf_sum) < 0)
668             {
669                 strncpy(sf_sum, "xxx", 4);
670                 strncpy(mf_sum, "xxx", 4);
671             }
672         }
673     }
674     #ifndef WIN32
675     /* If it is a link, we need to check if the actual file is valid. */
676     else if(S_ISLNK(statbuf.st_mode))
677     {
678         struct stat statbuf_lnk;
679         if(stat(file_name, &statbuf_lnk) == 0)
680         {
681             if(S_ISREG(statbuf_lnk.st_mode))
682             {
683                 if(sha1sum || md5sum)
684                 {
685                     /* Generating checksums of the file. */
686                     if(OS_MD5_SHA1_File(file_name, mf_sum, sf_sum) < 0)
687                     {
688                         strncpy(sf_sum, "xxx", 4);
689                         strncpy(mf_sum, "xxx", 4);
690                     }
691                 }
692             }
693         }
694     }
695     #endif
696     
697     newsum[0] = '\0';
698     newsum[255] = '\0';
699     snprintf(newsum,255,"%d:%d:%d:%d:%s:%s",
700             size == 0?0:(int)statbuf.st_size,
701             perm == 0?0:(int)statbuf.st_mode,
702             owner== 0?0:(int)statbuf.st_uid,
703             group== 0?0:(int)statbuf.st_gid,
704             md5sum   == 0?"xxx":mf_sum,
705             sha1sum  == 0?"xxx":sf_sum);
706
707     return(0);
708 }
709
710 /* EOF */