Imported Upstream version 2.5.1
[ossec-hids.git] / src / analysisd / decoders / syscheck.c
1 /* @(#) $Id$ */
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 2) 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         lf->data = NULL;
269         return(0);
270     }
271
272
273     /* Reads the integrity file and search for a possible
274      * entry
275      */
276     if(fgetpos(fp, &sdb.init_pos) == -1)
277     {
278         merror("%s: Error handling integrity database (fgetpos).",ARGV0);
279         return(0);
280     }
281     
282     
283     /* Looping the file */
284     while(fgets(sdb.buf, OS_MAXSTR, fp) != NULL)
285     {
286         /* Ignore blank lines and lines with a comment */
287         if(sdb.buf[0] == '\n' || sdb.buf[0] == '#')
288         {
289             fgetpos(fp, &sdb.init_pos); /* getting next location */
290             continue;
291         }
292
293
294         /* Getting name */    
295         saved_name = strchr(sdb.buf, ' ');
296         if(saved_name == NULL)
297         {
298             merror("%s: Invalid integrity message in the database.",ARGV0);
299             fgetpos(fp, &sdb.init_pos); /* getting next location */
300             continue;
301         }
302         *saved_name = '\0';
303         saved_name++;
304         
305         
306         /* New format - with a timestamp */
307         if(*saved_name == '!')
308         {
309             saved_name = strchr(saved_name, ' ');
310             if(saved_name == NULL)
311             {
312                 merror("%s: Invalid integrity message in the database",ARGV0);
313                 fgetpos(fp, &sdb.init_pos); /* getting next location */
314                 continue;
315             }
316             saved_name++;
317         }
318
319
320         /* Removing new line from saved_name */
321         sn_size = strlen(saved_name);
322         sn_size -= 1;
323         if(saved_name[sn_size] == '\n')
324             saved_name[sn_size] = '\0';
325
326
327         /* If name is different, go to next one. */
328         if(strcmp(f_name,saved_name) != 0)
329         {
330             /* Saving currently location */
331             fgetpos(fp, &sdb.init_pos);
332             continue;
333         }
334         
335
336         saved_sum = sdb.buf;
337
338
339         /* First three bytes are for frequency check */
340         saved_sum+=3;
341
342
343         /* checksum match, we can just return and keep going */
344         if(strcmp(saved_sum, c_sum) == 0)
345         {
346             lf->data = NULL;
347             return(0);
348         }
349
350
351         /* If we reached here, the checksum of the file has changed */
352         if(saved_sum[-3] == '!')
353         {
354             p++;
355             if(saved_sum[-2] == '!')
356             {
357                 p++;
358                 if(saved_sum[-1] == '!')    
359                     p++;
360                 else if(saved_sum[-1] == '?')
361                     p+=2;    
362             }
363         }
364
365
366         /* Checking the number of changes */
367         if(!Config.syscheck_auto_ignore)
368         {
369             sdb.syscheck_dec->id = sdb.id1;
370         }
371         else
372         {
373             switch(p)
374             {
375                 case 0:
376                 sdb.syscheck_dec->id = sdb.id1;
377                 break;
378
379                 case 1:
380                 sdb.syscheck_dec->id = sdb.id2;
381                 break;
382
383                 case 2:
384                 sdb.syscheck_dec->id = sdb.id3;
385                 break;
386
387                 default:
388                 lf->data = NULL;
389                 return(0);
390                 break;
391             }
392         }
393
394
395         /* Adding new checksum to the database */
396         /* Commenting the file entry and adding a new one latter */
397         fsetpos(fp, &sdb.init_pos);
398         fputc('#',fp);
399
400
401         /* Adding the new entry at the end of the file */
402         fseek(fp, 0, SEEK_END);
403         fprintf(fp,"%c%c%c%s !%d %s\n",
404                 '!',
405                 p >= 1? '!' : '+',
406                 p == 2? '!' : (p > 2)?'?':'+',
407                 c_sum,
408                 lf->time,
409                 f_name);
410         fflush(fp);
411
412
413         /* File deleted */
414         if(c_sum[0] == '-' && c_sum[1] == '1')
415         {
416             sdb.syscheck_dec->id = sdb.idd;
417             snprintf(sdb.comment, OS_MAXSTR,
418                     "File '%.756s' was deleted. Unable to retrieve "
419                     "checksum.", f_name);
420         }
421         
422         /* If file was re-added, do not compare changes */
423         else if(saved_sum[0] == '-' && saved_sum[1] == '1')
424         {
425             sdb.syscheck_dec->id = sdb.idn;
426             snprintf(sdb.comment, OS_MAXSTR,
427                      "File '%.756s' was re-added.", f_name);
428         }
429
430         else    
431         {
432             int oldperm = 0, newperm = 0;
433             
434             /* Providing more info about the file change */
435             char *oldsize = NULL, *newsize = NULL;
436             char *olduid = NULL, *newuid = NULL;
437             char *c_oldperm = NULL, *c_newperm = NULL;
438             char *oldgid = NULL, *newgid = NULL;
439             char *oldmd5 = NULL, *newmd5 = NULL;
440             char *oldsha1 = NULL, *newsha1 = NULL;
441
442             oldsize = saved_sum;
443             newsize = c_sum;
444
445             c_oldperm = strchr(saved_sum, ':');
446             c_newperm = strchr(c_sum, ':');
447
448             /* Get old/new permissions */
449             if(c_oldperm && c_newperm)
450             {
451                 *c_oldperm = '\0';
452                 c_oldperm++;
453
454                 *c_newperm = '\0';
455                 c_newperm++;
456
457                 /* Get old/new uid/gid */
458                 olduid = strchr(c_oldperm, ':');
459                 newuid = strchr(c_newperm, ':');
460
461                 if(olduid && newuid)
462                 {
463                     *olduid = '\0';
464                     *newuid = '\0';
465
466                     olduid++;
467                     newuid++;
468
469                     oldgid = strchr(olduid, ':');
470                     newgid = strchr(newuid, ':');
471
472                     if(oldgid && newgid)
473                     {
474                         *oldgid = '\0';
475                         *newgid = '\0';
476
477                         oldgid++;
478                         newgid++;
479
480
481                         /* Getting md5 */
482                         oldmd5 = strchr(oldgid, ':');
483                         newmd5 = strchr(newgid, ':');
484
485                         if(oldmd5 && newmd5)
486                         {
487                             *oldmd5 = '\0';
488                             *newmd5 = '\0';
489
490                             oldmd5++;
491                             newmd5++;
492
493                             /* getting sha1 */
494                             oldsha1 = strchr(oldmd5, ':');
495                             newsha1 = strchr(newmd5, ':');
496
497                             if(oldsha1 && newsha1)
498                             {
499                                 *oldsha1 = '\0';
500                                 *newsha1 = '\0';
501
502                                 oldsha1++;
503                                 newsha1++;
504                             }
505                         }
506                     }
507                 }
508             }
509
510             /* Getting integer values */
511             if(c_newperm && c_oldperm)
512             {
513                 newperm = atoi(c_newperm);
514                 oldperm = atoi(c_oldperm);
515             }
516
517             /* Generating size message */
518             if(!oldsize || !newsize || strcmp(oldsize, newsize) == 0)
519             {
520                 sdb.size[0] = '\0';
521             }
522             else
523             {
524                 snprintf(sdb.size, OS_FLSIZE,
525                         "Size changed from '%s' to '%s'\n",
526                         oldsize, newsize);
527
528                 #ifdef PRELUDE
529                 os_strdup(oldsize, lf->size_before);
530                 os_strdup(newsize, lf->size_after);
531                 #endif
532             }
533
534             /* Permission message */
535             if(oldperm == newperm)
536             {
537                 sdb.perm[0] = '\0';
538             }
539             else if(oldperm > 0 && newperm > 0)
540             {
541
542                 snprintf(sdb.perm, OS_FLSIZE, "Permissions changed from "
543                         "'%c%c%c%c%c%c%c%c%c' "
544                         "to '%c%c%c%c%c%c%c%c%c'\n",
545                         (oldperm & S_IRUSR)? 'r' : '-',
546                         (oldperm & S_IWUSR)? 'w' : '-',
547                         
548                         (oldperm & S_ISUID)? 's' :
549                         (oldperm & S_IXUSR)? 'x' : '-',
550                         
551                         (oldperm & S_IRGRP)? 'r' : '-',
552                         (oldperm & S_IWGRP)? 'w' : '-',
553
554                         (oldperm & S_ISGID)? 's' :
555                         (oldperm & S_IXGRP)? 'x' : '-',
556                         
557                         (oldperm & S_IROTH)? 'r' : '-',
558                         (oldperm & S_IWOTH)? 'w' : '-',
559
560                         (oldperm & S_ISVTX)? 't' :
561                         (oldperm & S_IXOTH)? 'x' : '-',
562
563
564
565                         (newperm & S_IRUSR)? 'r' : '-',
566                         (newperm & S_IWUSR)? 'w' : '-',
567
568                         (newperm & S_ISUID)? 's' :
569                         (newperm & S_IXUSR)? 'x' : '-',
570
571                         
572                         (newperm & S_IRGRP)? 'r' : '-',
573                         (newperm & S_IWGRP)? 'w' : '-',
574                         
575                         (newperm & S_ISGID)? 's' :
576                         (newperm & S_IXGRP)? 'x' : '-',
577
578                         (newperm & S_IROTH)? 'r' : '-',
579                         (newperm & S_IWOTH)? 'w' : '-',
580
581                         (newperm & S_ISVTX)? 't' :
582                         (newperm & S_IXOTH)? 'x' : '-');
583
584                 #ifdef PRELUDE
585                 lf->perm_before = oldperm;
586                 lf->perm_after = newperm;
587                 #endif
588             }
589
590             /* Ownership message */
591             if(!newuid || !olduid || strcmp(newuid, olduid) == 0)
592             {
593                 sdb.owner[0] = '\0';
594             }
595             else
596             {
597                 snprintf(sdb.owner, OS_FLSIZE, "Ownership was '%s', "
598                         "now it is '%s'\n",
599                         olduid, newuid);
600
601
602                 #ifdef PRELUDE
603                 os_strdup(olduid, lf->owner_before);
604                 os_strdup(newuid, lf->owner_after);
605                 #endif
606             }    
607
608             /* group ownership message */
609             if(!newgid || !oldgid || strcmp(newgid, oldgid) == 0)
610             {
611                 sdb.gowner[0] = '\0';
612             }
613             else
614             {
615                 snprintf(sdb.gowner, OS_FLSIZE,"Group ownership was '%s', "
616                         "now it is '%s'\n",
617                         oldgid, newgid);
618                 #ifdef PRELUDE
619                 os_strdup(oldgid, lf->gowner_before);
620                 os_strdup(newgid, lf->gowner_after);
621                 #endif
622             }
623
624             /* md5 message */
625             if(!newmd5 || !oldmd5 || strcmp(newmd5, oldmd5) == 0)
626             {
627                 sdb.md5[0] = '\0';
628             }
629             else
630             {
631                 snprintf(sdb.md5, OS_FLSIZE, "Old md5sum was: '%s'\n"
632                         "New md5sum is : '%s'\n",
633                         oldmd5, newmd5);
634                 #ifdef PRELUDE
635                 os_strdup(oldmd5, lf->md5_before);
636                 os_strdup(newmd5, lf->md5_after);
637                 #endif
638             }
639
640             /* sha1 */
641             if(!newsha1 || !oldsha1 || strcmp(newsha1, oldsha1) == 0)
642             {
643                 sdb.sha1[0] = '\0';
644             }
645             else
646             {
647                 snprintf(sdb.sha1, OS_FLSIZE, "Old sha1sum was: '%s'\n"
648                         "New sha1sum is : '%s'\n",
649                         oldsha1, newsha1);
650                 #ifdef PRELUDE
651                 os_strdup(oldsha1, lf->sha1_before);
652                 os_strdup(newsha1, lf->sha1_after);
653                 #endif
654             }
655             #ifdef PRELUDE
656             os_strdup(f_name, lf->filename);
657             #endif
658
659
660             /* Provide information about the file */    
661             snprintf(sdb.comment, OS_MAXSTR, "Integrity checksum changed for: "
662                     "'%.756s'\n"
663                     "%s"
664                     "%s"
665                     "%s"
666                     "%s"
667                     "%s"
668                     "%s"
669                     "%s%s",
670                     f_name, 
671                     sdb.size,
672                     sdb.perm,
673                     sdb.owner,
674                     sdb.gowner,
675                     sdb.md5,
676                     sdb.sha1,
677                     lf->data == NULL?"":"What changed:\n",
678                     lf->data == NULL?"":lf->data
679                     );
680         }
681
682
683         /* Creating a new log message */
684         free(lf->full_log);
685         os_strdup(sdb.comment, lf->full_log);
686         lf->log = lf->full_log;
687         lf->data = NULL;
688
689         
690         /* Setting decoder */
691         lf->decoder_info = sdb.syscheck_dec;
692                         
693
694         return(1); 
695
696     } /* continuiing... */
697
698
699     /* If we reach here, this file is not present on our database */
700     fseek(fp, 0, SEEK_END);
701     
702     fprintf(fp,"+++%s !%d %s\n", c_sum, lf->time, f_name);
703
704
705     /* Alert if configured to notify on new files */
706     if((Config.syscheck_alert_new == 1) && (DB_IsCompleted(agent_id)))
707     {
708         sdb.syscheck_dec->id = sdb.idn;
709
710         /* New file message */
711         snprintf(sdb.comment, OS_MAXSTR,
712                               "New file '%.756s' "
713                               "added to the file system.", f_name);
714         
715
716         /* Creating a new log message */
717         free(lf->full_log);
718         os_strdup(sdb.comment, lf->full_log);
719         lf->log = lf->full_log;
720
721
722         /* Setting decoder */
723         lf->decoder_info = sdb.syscheck_dec;
724         lf->data = NULL;
725
726         return(1);
727     }
728
729     lf->data = NULL;
730     return(0);
731 }
732
733
734 /* Special decoder for syscheck
735  * Not using the default decoding lib for simplicity
736  * and to be less resource intensive
737  */
738 int DecodeSyscheck(Eventinfo *lf)
739 {
740     char *c_sum;
741     char *f_name;
742    
743    
744     /* Every syscheck message must be in the following format:
745      * checksum filename     
746      */
747     f_name = strchr(lf->log, ' ');
748     if(f_name == NULL)
749     {
750         /* If we don't have a valid syscheck message, it may be
751          * a database completed message.
752          */
753         if(strcmp(lf->log, HC_SK_DB_COMPLETED) == 0)
754         {
755             DB_SetCompleted(lf);
756             return(0);
757         }
758          
759         merror(SK_INV_MSG, ARGV0);
760         return(0);
761     }
762
763
764     /* Zeroing to get the check sum */
765     *f_name = '\0';
766     f_name++;
767
768
769     /* Getting diff. */
770     lf->data = strchr(f_name, '\n');
771     if(lf->data)
772     {
773         *lf->data = '\0';
774         lf->data++;
775     }
776     else
777     {
778         lf->data = NULL;
779     }
780     
781    
782
783     /* Checking if file is supposed to be ignored */
784     if(Config.syscheck_ignore)
785     {
786         char **ff_ig = Config.syscheck_ignore;
787         
788         while(*ff_ig)
789         {
790             if(strncasecmp(*ff_ig, f_name, strlen(*ff_ig)) == 0)
791             {
792                 lf->data = NULL;
793                 return(0);
794             }
795             
796             ff_ig++;
797         }
798     }
799     
800     
801     /* Checksum is at the beginning of the log */
802     c_sum = lf->log;
803     
804     
805     /* Searching for file changes */
806     return(DB_Search(f_name, c_sum, lf));
807 }
808
809 /* EOF */