26350f3cdb44c2b014bd0f5ee8f0a264085fbf13
[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                 os_strdup(oldsize, lf->size_before);
536                 os_strdup(newsize, lf->size_after);
537             }
538
539             /* Permission message */
540             if(oldperm == newperm)
541             {
542                 sdb.perm[0] = '\0';
543             }
544             else if(oldperm > 0 && newperm > 0)
545             {
546
547                 snprintf(sdb.perm, OS_FLSIZE, "Permissions changed from "
548                         "'%c%c%c%c%c%c%c%c%c' "
549                         "to '%c%c%c%c%c%c%c%c%c'\n",
550                         (oldperm & S_IRUSR)? 'r' : '-',
551                         (oldperm & S_IWUSR)? 'w' : '-',
552
553                         (oldperm & S_ISUID)? 's' :
554                         (oldperm & S_IXUSR)? 'x' : '-',
555
556                         (oldperm & S_IRGRP)? 'r' : '-',
557                         (oldperm & S_IWGRP)? 'w' : '-',
558
559                         (oldperm & S_ISGID)? 's' :
560                         (oldperm & S_IXGRP)? 'x' : '-',
561
562                         (oldperm & S_IROTH)? 'r' : '-',
563                         (oldperm & S_IWOTH)? 'w' : '-',
564
565                         (oldperm & S_ISVTX)? 't' :
566                         (oldperm & S_IXOTH)? 'x' : '-',
567
568
569
570                         (newperm & S_IRUSR)? 'r' : '-',
571                         (newperm & S_IWUSR)? 'w' : '-',
572
573                         (newperm & S_ISUID)? 's' :
574                         (newperm & S_IXUSR)? 'x' : '-',
575
576
577                         (newperm & S_IRGRP)? 'r' : '-',
578                         (newperm & S_IWGRP)? 'w' : '-',
579
580                         (newperm & S_ISGID)? 's' :
581                         (newperm & S_IXGRP)? 'x' : '-',
582
583                         (newperm & S_IROTH)? 'r' : '-',
584                         (newperm & S_IWOTH)? 'w' : '-',
585
586                         (newperm & S_ISVTX)? 't' :
587                         (newperm & S_IXOTH)? 'x' : '-');
588
589                 lf->perm_before = oldperm;
590                 lf->perm_after = newperm;
591             }
592
593             /* Ownership message */
594             if(!newuid || !olduid || strcmp(newuid, olduid) == 0)
595             {
596                 sdb.owner[0] = '\0';
597             }
598             else
599             {
600                 snprintf(sdb.owner, OS_FLSIZE, "Ownership was '%s', "
601                         "now it is '%s'\n",
602                         olduid, newuid);
603
604
605                 os_strdup(olduid, lf->owner_before);
606                 os_strdup(newuid, lf->owner_after);
607             }
608
609             /* group ownership message */
610             if(!newgid || !oldgid || strcmp(newgid, oldgid) == 0)
611             {
612                 sdb.gowner[0] = '\0';
613             }
614             else
615             {
616                 snprintf(sdb.gowner, OS_FLSIZE,"Group ownership was '%s', "
617                         "now it is '%s'\n",
618                         oldgid, newgid);
619                 os_strdup(oldgid, lf->gowner_before);
620                 os_strdup(newgid, lf->gowner_after);
621             }
622
623             /* md5 message */
624             if(!newmd5 || !oldmd5 || strcmp(newmd5, oldmd5) == 0)
625             {
626                 sdb.md5[0] = '\0';
627             }
628             else
629             {
630                 snprintf(sdb.md5, OS_FLSIZE, "Old md5sum was: '%s'\n"
631                         "New md5sum is : '%s'\n",
632                         oldmd5, newmd5);
633                 os_strdup(oldmd5, lf->md5_before);
634                 os_strdup(newmd5, lf->md5_after);
635             }
636
637             /* sha1 */
638             if(!newsha1 || !oldsha1 || strcmp(newsha1, oldsha1) == 0)
639             {
640                 sdb.sha1[0] = '\0';
641             }
642             else
643             {
644                 snprintf(sdb.sha1, OS_FLSIZE, "Old sha1sum was: '%s'\n"
645                         "New sha1sum is : '%s'\n",
646                         oldsha1, newsha1);
647                 os_strdup(oldsha1, lf->sha1_before);
648                 os_strdup(newsha1, lf->sha1_after);
649             }
650             os_strdup(f_name, lf->filename);
651
652
653             /* Provide information about the file */
654             snprintf(sdb.comment, OS_MAXSTR, "Integrity checksum changed for: "
655                     "'%.756s'\n"
656                     "%s"
657                     "%s"
658                     "%s"
659                     "%s"
660                     "%s"
661                     "%s"
662                     "%s%s",
663                     f_name,
664                     sdb.size,
665                     sdb.perm,
666                     sdb.owner,
667                     sdb.gowner,
668                     sdb.md5,
669                     sdb.sha1,
670                     lf->data == NULL?"":"What changed:\n",
671                     lf->data == NULL?"":lf->data
672                     );
673         }
674
675
676         /* Creating a new log message */
677         free(lf->full_log);
678         os_strdup(sdb.comment, lf->full_log);
679         lf->log = lf->full_log;
680         lf->data = NULL;
681
682
683         /* Setting decoder */
684         lf->decoder_info = sdb.syscheck_dec;
685
686
687         return(1);
688
689     } /* continuiing... */
690
691
692     /* If we reach here, this file is not present on our database */
693     fseek(fp, 0, SEEK_END);
694
695     fprintf(fp,"+++%s !%d %s\n", c_sum, lf->time, f_name);
696
697     fflush(fp);
698
699     /* Alert if configured to notify on new files */
700     if((Config.syscheck_alert_new == 1) && (DB_IsCompleted(agent_id)))
701     {
702         sdb.syscheck_dec->id = sdb.idn;
703
704         /* New file message */
705         snprintf(sdb.comment, OS_MAXSTR,
706                               "New file '%.756s' "
707                               "added to the file system.", f_name);
708
709
710         /* Creating a new log message */
711         free(lf->full_log);
712         os_strdup(sdb.comment, lf->full_log);
713         lf->log = lf->full_log;
714
715
716         /* Setting decoder */
717         lf->decoder_info = sdb.syscheck_dec;
718         lf->data = NULL;
719
720         return(1);
721     }
722
723     lf->data = NULL;
724     return(0);
725 }
726
727
728 /* Special decoder for syscheck
729  * Not using the default decoding lib for simplicity
730  * and to be less resource intensive
731  */
732 int DecodeSyscheck(Eventinfo *lf)
733 {
734     char *c_sum;
735     char *f_name;
736
737
738     /* Every syscheck message must be in the following format:
739      * checksum filename
740      */
741     f_name = strchr(lf->log, ' ');
742     if(f_name == NULL)
743     {
744         /* If we don't have a valid syscheck message, it may be
745          * a database completed message.
746          */
747         if(strcmp(lf->log, HC_SK_DB_COMPLETED) == 0)
748         {
749             DB_SetCompleted(lf);
750             return(0);
751         }
752
753         merror(SK_INV_MSG, ARGV0);
754         return(0);
755     }
756
757
758     /* Zeroing to get the check sum */
759     *f_name = '\0';
760     f_name++;
761
762
763     /* Getting diff. */
764     lf->data = strchr(f_name, '\n');
765     if(lf->data)
766     {
767         *lf->data = '\0';
768         lf->data++;
769     }
770     else
771     {
772         lf->data = NULL;
773     }
774
775
776
777     /* Checking if file is supposed to be ignored */
778     if(Config.syscheck_ignore)
779     {
780         char **ff_ig = Config.syscheck_ignore;
781
782         while(*ff_ig)
783         {
784             if(strncasecmp(*ff_ig, f_name, strlen(*ff_ig)) == 0)
785             {
786                 lf->data = NULL;
787                 return(0);
788             }
789
790             ff_ig++;
791         }
792     }
793
794
795     /* Checksum is at the beginning of the log */
796     c_sum = lf->log;
797
798
799     /* Searching for file changes */
800     return(DB_Search(f_name, c_sum, lf));
801 }
802
803 /* EOF */