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