Imported Upstream version 2.3
[ossec-hids.git] / src / analysisd / decoders / syscheck.c
1 /* @(#) $Id: syscheck.c,v 1.53 2009/11/04 18:45:38 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 /* Syscheck decoder */
14
15 #include "eventinfo.h"
16 #include "os_regex/os_regex.h"
17 #include "config.h"
18 #include "alerts/alerts.h"
19 #include "decoder.h"
20
21
22 typedef struct __sdb
23 {
24     char buf[OS_MAXSTR + 1];
25     char comment[OS_MAXSTR +1];
26
27     char size[OS_FLSIZE +1];
28     char perm[OS_FLSIZE +1];
29     char owner[OS_FLSIZE +1];
30     char gowner[OS_FLSIZE +1];
31     char md5[OS_FLSIZE +1];
32     char sha1[OS_FLSIZE +1];
33
34     char agent_cp[MAX_AGENTS +1][1];
35     char *agent_ips[MAX_AGENTS +1];
36     FILE *agent_fps[MAX_AGENTS +1];
37
38     int db_err;
39
40
41     /* Ids for decoder */
42     int id1;
43     int id2;
44     int id3;
45     int idn;
46     int idd;
47     
48
49     /* Syscheck rule */
50     OSDecoderInfo  *syscheck_dec;
51
52
53     /* File search variables */
54     fpos_t init_pos;
55     
56 }_sdb; /* syscheck db information */
57
58
59 /* Global variable */
60 _sdb sdb;
61
62
63
64 /* SyscheckInit
65  * Initialize the necessary information to process the syscheck information
66  */
67 void SyscheckInit()
68 {
69     int i = 0;
70
71     sdb.db_err = 0;
72     
73     for(;i <= MAX_AGENTS;i++)
74     {
75         sdb.agent_ips[i] = NULL;
76         sdb.agent_fps[i] = NULL;
77         sdb.agent_cp[i][0] = '0';
78     }
79
80     /* Clearing db memory */
81     memset(sdb.buf, '\0', OS_MAXSTR +1);
82     memset(sdb.comment, '\0', OS_MAXSTR +1);
83     
84     memset(sdb.size, '\0', OS_FLSIZE +1);
85     memset(sdb.perm, '\0', OS_FLSIZE +1);
86     memset(sdb.owner, '\0', OS_FLSIZE +1);
87     memset(sdb.gowner, '\0', OS_FLSIZE +1);
88     memset(sdb.md5, '\0', OS_FLSIZE +1);
89     memset(sdb.sha1, '\0', OS_FLSIZE +1);
90
91
92     /* Creating decoder */
93     os_calloc(1, sizeof(OSDecoderInfo), sdb.syscheck_dec);
94     sdb.syscheck_dec->id = getDecoderfromlist(SYSCHECK_MOD);
95     sdb.syscheck_dec->name = SYSCHECK_MOD;
96     sdb.syscheck_dec->type = OSSEC_RL;
97     sdb.syscheck_dec->fts = 0;
98     
99     sdb.id1 = getDecoderfromlist(SYSCHECK_MOD);
100     sdb.id2 = getDecoderfromlist(SYSCHECK_MOD2);
101     sdb.id3 = getDecoderfromlist(SYSCHECK_MOD3);
102     sdb.idn = getDecoderfromlist(SYSCHECK_NEW);
103     sdb.idd = getDecoderfromlist(SYSCHECK_DEL);
104     
105     debug1("%s: SyscheckInit completed.", ARGV0);
106     return;
107 }
108
109 /* DB_IsCompleted
110  * Checks if the db is completed for that specific agent.
111  */
112 #define DB_IsCompleted(x) (sdb.agent_cp[x][0] == '1')?1:0
113
114
115 void __setcompleted(char *agent)
116 {
117     FILE *fp;
118     
119     /* Getting agent file */
120     snprintf(sdb.buf, OS_FLSIZE , "%s/.%s.cpt", SYSCHECK_DIR, agent);
121
122     fp = fopen(sdb.buf,"w");
123     if(fp)
124     {
125         fprintf(fp, "#!X");
126         fclose(fp);
127     }
128 }
129
130
131 int __iscompleted(char *agent)
132 {
133     FILE *fp;
134
135     /* Getting agent file */
136     snprintf(sdb.buf, OS_FLSIZE , "%s/.%s.cpt", SYSCHECK_DIR, agent);
137
138     fp = fopen(sdb.buf,"r");
139     if(fp)
140     {
141         fclose(fp);
142         return(1);
143     }
144     return(0);
145 }
146
147
148 /* void DB_SetCompleted(Eventinfo *lf).
149  * Set the database of a specific agent as completed.
150  */
151 void DB_SetCompleted(Eventinfo *lf)
152 {
153     int i = 0;
154
155     /* Finding file pointer */
156     while(sdb.agent_ips[i] != NULL)
157     {
158         if(strcmp(sdb.agent_ips[i], lf->location) == 0)
159         {
160             /* Return if already set as completed. */
161             if(DB_IsCompleted(i))
162             {
163                 return;
164             }
165             
166             __setcompleted(lf->location);
167
168
169             /* Setting as completed in memory */
170             sdb.agent_cp[i][0] = '1';
171             return;
172         }
173
174         i++;
175     }
176 }
177
178
179 /* DB_File
180  * Return the file pointer to be used to verify the integrity
181  */
182 FILE *DB_File(char *agent, int *agent_id)
183 {
184     int i = 0;
185
186     /* Finding file pointer */
187     while(sdb.agent_ips[i] != NULL)
188     {
189         if(strcmp(sdb.agent_ips[i], agent) == 0)
190         {
191             /* Pointing to the beginning of the file */
192             fseek(sdb.agent_fps[i],0, SEEK_SET);
193             *agent_id = i;
194             return(sdb.agent_fps[i]);
195         }
196         
197         i++;    
198     }
199
200     /* If here, our agent wasn't found */
201     os_strdup(agent, sdb.agent_ips[i]);
202
203
204     /* Getting agent file */
205     snprintf(sdb.buf, OS_FLSIZE , "%s/%s", SYSCHECK_DIR,agent);
206     
207         
208     /* r+ to read and write. Do not truncate */
209     sdb.agent_fps[i] = fopen(sdb.buf,"r+");
210     if(!sdb.agent_fps[i])
211     {
212         /* try opening with a w flag, file probably does not exist */
213         sdb.agent_fps[i] = fopen(sdb.buf, "w");
214         if(sdb.agent_fps[i])
215         {
216             fclose(sdb.agent_fps[i]);
217             sdb.agent_fps[i] = fopen(sdb.buf, "r+");
218         }
219     }
220         
221     /* Checking again */    
222     if(!sdb.agent_fps[i])
223     {
224         merror("%s: Unable to open '%s'",ARGV0, sdb.buf);
225
226         free(sdb.agent_ips[i]);
227         sdb.agent_ips[i] = NULL;
228         return(NULL);
229     }
230
231
232     /* Returning the opened pointer (the beginning of it) */
233     fseek(sdb.agent_fps[i],0, SEEK_SET);
234     *agent_id = i;
235     
236     
237     /* Getting if the agent was completed */
238     if(__iscompleted(agent))
239     {
240         sdb.agent_cp[i][0] = '1';    
241     }
242
243     return(sdb.agent_fps[i]);
244 }
245
246
247 /* DB_Search
248  * Search the DB for any entry related to the file being received
249  */
250 int DB_Search(char *f_name, char *c_sum, Eventinfo *lf)
251 {
252     int p = 0;
253     int sn_size;
254     int agent_id;
255     
256     char *saved_sum;
257     char *saved_name;
258     
259     FILE *fp;
260
261
262     /* Getting db pointer */
263     fp = DB_File(lf->location, &agent_id);
264     if(!fp)
265     {
266         merror("%s: Error handling integrity database.",ARGV0);
267         sdb.db_err++; /* Increment db error */
268         return(0);
269     }
270
271
272     /* Reads the integrity file and search for a possible
273      * entry
274      */
275     if(fgetpos(fp, &sdb.init_pos) == -1)
276     {
277         merror("%s: Error handling integrity database (fgetpos).",ARGV0);
278         return(0);
279     }
280     
281     
282     /* Looping the file */
283     while(fgets(sdb.buf, OS_MAXSTR, fp) != NULL)
284     {
285         /* Ignore blank lines and lines with a comment */
286         if(sdb.buf[0] == '\n' || sdb.buf[0] == '#')
287         {
288             fgetpos(fp, &sdb.init_pos); /* getting next location */
289             continue;
290         }
291
292
293         /* Getting name */    
294         saved_name = strchr(sdb.buf, ' ');
295         if(saved_name == NULL)
296         {
297             merror("%s: Invalid integrity message in the database.",ARGV0);
298             fgetpos(fp, &sdb.init_pos); /* getting next location */
299             continue;
300         }
301         *saved_name = '\0';
302         saved_name++;
303         
304         
305         /* New format - with a timestamp */
306         if(*saved_name == '!')
307         {
308             saved_name = strchr(saved_name, ' ');
309             if(saved_name == NULL)
310             {
311                 merror("%s: Invalid integrity message in the database",ARGV0);
312                 fgetpos(fp, &sdb.init_pos); /* getting next location */
313                 continue;
314             }
315             saved_name++;
316         }
317
318
319         /* Removing new line from saved_name */
320         sn_size = strlen(saved_name);
321         sn_size -= 1;
322         if(saved_name[sn_size] == '\n')
323             saved_name[sn_size] = '\0';
324
325
326         /* If name is different, go to next one. */
327         if(strcmp(f_name,saved_name) != 0)
328         {
329             /* Saving currently location */
330             fgetpos(fp, &sdb.init_pos);
331             continue;
332         }
333         
334
335         saved_sum = sdb.buf;
336
337
338         /* First three bytes are for frequency check */
339         saved_sum+=3;
340
341
342         /* checksum match, we can just return and keep going */
343         if(strcmp(saved_sum, c_sum) == 0)
344             return(0);
345
346
347         /* If we reached here, the checksum of the file has changed */
348         if(saved_sum[-3] == '!')
349         {
350             p++;
351             if(saved_sum[-2] == '!')
352             {
353                 p++;
354                 if(saved_sum[-1] == '!')    
355                     p++;
356                 else if(saved_sum[-1] == '?')
357                     p+=2;    
358             }
359         }
360
361
362         /* Checking the number of changes */
363         if(!Config.syscheck_auto_ignore)
364         {
365             sdb.syscheck_dec->id = sdb.id1;
366         }
367         else
368         {
369             switch(p)
370             {
371                 case 0:
372                 sdb.syscheck_dec->id = sdb.id1;
373                 break;
374
375                 case 1:
376                 sdb.syscheck_dec->id = sdb.id2;
377                 break;
378
379                 case 2:
380                 sdb.syscheck_dec->id = sdb.id3;
381                 break;
382
383                 default:
384                 return(0);
385                 break;
386             }
387         }
388
389
390         /* Adding new checksum to the database */
391         /* Commenting the file entry and adding a new one latter */
392         fsetpos(fp, &sdb.init_pos);
393         fputc('#',fp);
394
395
396         /* Adding the new entry at the end of the file */
397         fseek(fp, 0, SEEK_END);
398         fprintf(fp,"%c%c%c%s !%d %s\n",
399                 '!',
400                 p >= 1? '!' : '+',
401                 p == 2? '!' : (p > 2)?'?':'+',
402                 c_sum,
403                 lf->time,
404                 f_name);
405         fflush(fp);
406
407
408         /* File deleted */
409         if(c_sum[0] == '-' && c_sum[1] == '1')
410         {
411             sdb.syscheck_dec->id = sdb.idd;
412             snprintf(sdb.comment, OS_MAXSTR,
413                     "File '%.756s' was deleted. Unable to retrieve "
414                     "checksum.", f_name);
415         }
416         
417         /* If file was re-added, do not compare changes */
418         else if(saved_sum[0] == '-' && saved_sum[1] == '1')
419         {
420             sdb.syscheck_dec->id = sdb.idn;
421             snprintf(sdb.comment, OS_MAXSTR,
422                      "File '%.756s' was re-added.", f_name);
423         }
424
425         else    
426         {
427             int oldperm = 0, newperm = 0;
428             
429             /* Providing more info about the file change */
430             char *oldsize = NULL, *newsize = NULL;
431             char *olduid = NULL, *newuid = NULL;
432             char *c_oldperm = NULL, *c_newperm = NULL;
433             char *oldgid = NULL, *newgid = NULL;
434             char *oldmd5 = NULL, *newmd5 = NULL;
435             char *oldsha1 = NULL, *newsha1 = NULL;
436
437             oldsize = saved_sum;
438             newsize = c_sum;
439
440             c_oldperm = strchr(saved_sum, ':');
441             c_newperm = strchr(c_sum, ':');
442
443             /* Get old/new permissions */
444             if(c_oldperm && c_newperm)
445             {
446                 *c_oldperm = '\0';
447                 c_oldperm++;
448
449                 *c_newperm = '\0';
450                 c_newperm++;
451
452                 /* Get old/new uid/gid */
453                 olduid = strchr(c_oldperm, ':');
454                 newuid = strchr(c_newperm, ':');
455
456                 if(olduid && newuid)
457                 {
458                     *olduid = '\0';
459                     *newuid = '\0';
460
461                     olduid++;
462                     newuid++;
463
464                     oldgid = strchr(olduid, ':');
465                     newgid = strchr(newuid, ':');
466
467                     if(oldgid && newgid)
468                     {
469                         *oldgid = '\0';
470                         *newgid = '\0';
471
472                         oldgid++;
473                         newgid++;
474
475
476                         /* Getting md5 */
477                         oldmd5 = strchr(oldgid, ':');
478                         newmd5 = strchr(newgid, ':');
479
480                         if(oldmd5 && newmd5)
481                         {
482                             *oldmd5 = '\0';
483                             *newmd5 = '\0';
484
485                             oldmd5++;
486                             newmd5++;
487
488                             /* getting sha1 */
489                             oldsha1 = strchr(oldmd5, ':');
490                             newsha1 = strchr(newmd5, ':');
491
492                             if(oldsha1 && newsha1)
493                             {
494                                 *oldsha1 = '\0';
495                                 *newsha1 = '\0';
496
497                                 oldsha1++;
498                                 newsha1++;
499                             }
500                         }
501                     }
502                 }
503             }
504
505             /* Getting integer values */
506             if(c_newperm && c_oldperm)
507             {
508                 newperm = atoi(c_newperm);
509                 oldperm = atoi(c_oldperm);
510             }
511
512             /* Generating size message */
513             if(!oldsize || !newsize || strcmp(oldsize, newsize) == 0)
514             {
515                 sdb.size[0] = '\0';
516             }
517             else
518             {
519                 snprintf(sdb.size, OS_FLSIZE,
520                         "Size changed from '%s' to '%s'\n",
521                         oldsize, newsize);
522             }
523
524             /* Permission message */
525             if(oldperm == newperm)
526             {
527                 sdb.perm[0] = '\0';
528             }
529             else if(oldperm > 0 && newperm > 0)
530             {
531                 snprintf(sdb.perm, OS_FLSIZE, "Permissions changed from "
532                         "'%c%c%c%c%c%c%c%c%c' "
533                         "to '%c%c%c%c%c%c%c%c%c'\n",
534                         (oldperm & S_IRUSR)? 'r' : '-',
535                         (oldperm & S_IWUSR)? 'w' : '-',
536                         
537                         (oldperm & S_ISUID)? 's' :
538                         (oldperm & S_IXUSR)? 'x' : '-',
539                         
540                         (oldperm & S_IRGRP)? 'r' : '-',
541                         (oldperm & S_IWGRP)? 'w' : '-',
542
543                         (oldperm & S_ISGID)? 's' :
544                         (oldperm & S_IXGRP)? 'x' : '-',
545                         
546                         (oldperm & S_IROTH)? 'r' : '-',
547                         (oldperm & S_IWOTH)? 'w' : '-',
548
549                         (oldperm & S_ISVTX)? 't' :
550                         (oldperm & S_IXOTH)? 'x' : '-',
551
552
553
554                         (newperm & S_IRUSR)? 'r' : '-',
555                         (newperm & S_IWUSR)? 'w' : '-',
556
557                         (newperm & S_ISUID)? 's' :
558                         (newperm & S_IXUSR)? 'x' : '-',
559
560                         
561                         (newperm & S_IRGRP)? 'r' : '-',
562                         (newperm & S_IWGRP)? 'w' : '-',
563                         
564                         (newperm & S_ISGID)? 's' :
565                         (newperm & S_IXGRP)? 'x' : '-',
566
567                         (newperm & S_IROTH)? 'r' : '-',
568                         (newperm & S_IWOTH)? 'w' : '-',
569
570                         (newperm & S_ISVTX)? 't' :
571                         (newperm & S_IXOTH)? 'x' : '-');
572             }
573
574             /* Ownership message */
575             if(!newuid || !olduid || strcmp(newuid, olduid) == 0)
576             {
577                 sdb.owner[0] = '\0';
578             }
579             else
580             {
581                 snprintf(sdb.owner, OS_FLSIZE, "Ownership was '%s', "
582                         "now it is '%s'\n",
583                         olduid, newuid);
584             }    
585
586             /* group ownership message */
587             if(!newgid || !oldgid || strcmp(newgid, oldgid) == 0)
588             {
589                 sdb.gowner[0] = '\0';
590             }
591             else
592             {
593                 snprintf(sdb.gowner, OS_FLSIZE,"Group ownership was '%s', "
594                         "now it is '%s'\n",
595                         oldgid, newgid);
596             }
597
598             /* md5 message */
599             if(!newmd5 || !oldmd5 || strcmp(newmd5, oldmd5) == 0)
600             {
601                 sdb.md5[0] = '\0';
602             }
603             else
604             {
605                 snprintf(sdb.md5, OS_FLSIZE, "Old md5sum was: '%s'\n"
606                         "New md5sum is : '%s'\n",
607                         oldmd5, newmd5);
608             }
609
610             /* sha1 */
611             if(!newsha1 || !oldsha1 || strcmp(newsha1, oldsha1) == 0)
612             {
613                 sdb.sha1[0] = '\0';
614             }
615             else
616             {
617                 snprintf(sdb.sha1, OS_FLSIZE, "Old sha1sum was: '%s'\n"
618                         "New sha1sum is : '%s'\n",
619                         oldsha1, newsha1);
620             }
621
622
623             /* Provide information about the file */    
624             snprintf(sdb.comment, 512, "Integrity checksum changed for: "
625                     "'%.756s'\n"
626                     "%s"
627                     "%s"
628                     "%s"
629                     "%s"
630                     "%s"
631                     "%s",
632                     f_name, 
633                     sdb.size,
634                     sdb.perm,
635                     sdb.owner,
636                     sdb.gowner,
637                     sdb.md5,
638                     sdb.sha1);
639         }
640
641
642         /* Creating a new log message */
643         free(lf->full_log);
644         os_strdup(sdb.comment, lf->full_log);
645         lf->log = lf->full_log;
646
647         
648         /* Setting decoder */
649         lf->decoder_info = sdb.syscheck_dec;
650                         
651
652         return(1); 
653
654     } /* continuiing... */
655
656
657     /* If we reach here, this file is not present on our database */
658     fseek(fp, 0, SEEK_END);
659     
660     fprintf(fp,"+++%s !%d %s\n", c_sum, lf->time, f_name);
661
662
663     /* Alert if configured to notify on new files */
664     if((Config.syscheck_alert_new == 1) && (DB_IsCompleted(agent_id)))
665     {
666         sdb.syscheck_dec->id = sdb.idn;
667
668         /* New file message */
669         snprintf(sdb.comment, OS_MAXSTR,
670                               "New file '%.756s' "
671                               "added to the file system.", f_name);
672         
673
674         /* Creating a new log message */
675         free(lf->full_log);
676         os_strdup(sdb.comment, lf->full_log);
677         lf->log = lf->full_log;
678
679
680         /* Setting decoder */
681         lf->decoder_info = sdb.syscheck_dec;
682
683         return(1);
684     }
685
686     return(0);
687 }
688
689
690 /* Special decoder for syscheck
691  * Not using the default decoding lib for simplicity
692  * and to be less resource intensive
693  */
694 int DecodeSyscheck(Eventinfo *lf)
695 {
696     char *c_sum;
697     char *f_name;
698    
699    
700     /* Every syscheck message must be in the following format:
701      * checksum filename     
702      */
703     f_name = strchr(lf->log, ' ');
704     if(f_name == NULL)
705     {
706         /* If we don't have a valid syscheck message, it may be
707          * a database completed message.
708          */
709         if(strcmp(lf->log, HC_SK_DB_COMPLETED) == 0)
710         {
711             DB_SetCompleted(lf);
712             return(0);
713         }
714          
715         merror(SK_INV_MSG, ARGV0);
716         return(0);
717     }
718     
719     
720     /* Zeroing to get the check sum */
721     *f_name = '\0';
722     f_name++;
723
724
725     /* Checking if file is supposed to be ignored */
726     if(Config.syscheck_ignore)
727     {
728         char **ff_ig = Config.syscheck_ignore;
729         
730         while(*ff_ig)
731         {
732             if(strncasecmp(*ff_ig, f_name, strlen(*ff_ig)) == 0)
733             {
734                 return(0);
735             }
736             
737             ff_ig++;
738         }
739     }
740     
741     
742     /* Checksum is at the beginning of the log */
743     c_sum = lf->log;
744     
745     
746     /* Searching for file changes */
747     return(DB_Search(f_name, c_sum, lf));
748 }
749
750 /* EOF */