1 /*
2  *  Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3  *  Copyright (C) 2008-2013 Sourcefire, Inc.
4  *
5  *  Author: aCaB <acab@clamav.net>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  *  MA 02110-1301, USA.
20  */
21 
22 #if HAVE_CONFIG_H
23 #include "clamav-config.h"
24 #endif
25 
26 #include <stdio.h>
27 #include <sys/types.h>
28 #include <unistd.h>
29 #include <pwd.h>
30 #include <grp.h>
31 #include <string.h>
32 #include <signal.h>
33 #include <pthread.h>
34 #ifdef USE_SYSLOG
35 #include <syslog.h>
36 #endif
37 #include <time.h>
38 #include <libmilter/mfapi.h>
39 
40 // libclamav
41 #include "clamav.h"
42 #include "default.h"
43 
44 // shared
45 #include "output.h"
46 #include "optparser.h"
47 #include "misc.h"
48 
49 #include "connpool.h"
50 #include "netcode.h"
51 #include "clamfi.h"
52 #include "whitelist.h"
53 
54 #ifndef _WIN32
55 #include <sys/wait.h>
56 #endif
57 
58 struct smfiDesc descr;
59 struct optstruct *opts;
60 
milter_exit(int sig)61 static void milter_exit(int sig)
62 {
63     const struct optstruct *opt;
64 
65     logg("*clamav-milter: milter_exit, signal %d\n", sig);
66 
67 #ifndef _WIN32
68     if ((opt = optget(opts, "MilterSocket"))) {
69         if (unlink(opt->strarg) == -1)
70             logg("!Can't unlink the socket file %s\n", opt->strarg);
71         else
72             logg("Socket file removed.\n");
73     }
74 #endif
75 
76     logg("clamav-milter: stopped\n");
77 
78     optfree(opts);
79 
80     logg_close();
81     cpool_free();
82     localnets_free();
83     whitelist_free();
84 }
85 
main(int argc,char ** argv)86 int main(int argc, char **argv)
87 {
88     char *my_socket, *pt;
89     const struct optstruct *opt;
90     time_t currtime;
91     mode_t umsk;
92     pid_t parentPid = getpid();
93 #ifndef _WIN32
94     int dropPrivRet = 0;
95 #endif /* _WIN32 */
96 
97     sigset_t sigset;
98     struct sigaction act;
99     const char *user_name = NULL;
100 
101     cl_initialize_crypto();
102 
103     memset(&descr, 0, sizeof(struct smfiDesc));
104     descr.xxfi_name    = "ClamAV";         /* filter name */
105     descr.xxfi_version = SMFI_VERSION;     /* milter version */
106     descr.xxfi_flags   = SMFIF_QUARANTINE; /* flags */
107     descr.xxfi_connect = clamfi_connect;   /* connection info filter */
108     descr.xxfi_envfrom = clamfi_envfrom;   /* envelope sender filter */
109     descr.xxfi_envrcpt = clamfi_envrcpt;   /* envelope recipient filter */
110     descr.xxfi_header  = clamfi_header;    /* header filter */
111     descr.xxfi_body    = clamfi_body;      /* body block */
112     descr.xxfi_eom     = clamfi_eom;       /* end of message */
113     descr.xxfi_abort   = clamfi_abort;     /* message aborted */
114 
115     opts = optparse(NULL, argc, argv, 1, OPT_MILTER, 0, NULL);
116     if (!opts) {
117         mprintf("!Can't parse command line options\n");
118         return 1;
119     }
120 
121     if (optget(opts, "help")->enabled) {
122         printf("\n");
123         printf("                       Clam AntiVirus: Milter Mail Scanner %s\n", get_version());
124         printf("           By The ClamAV Team: https://www.clamav.net/about.html#credits\n");
125         printf("           (C) 2022 Cisco Systems, Inc.\n");
126         printf("\n");
127         printf("    %s [-c <config-file>]\n\n", argv[0]);
128         printf("\n");
129         printf("    --help                   -h       Show this help\n");
130         printf("    --version                -V       Show version\n");
131         printf("    --config-file <file>     -c       Read configuration from file\n");
132         printf("\n");
133         optfree(opts);
134         return 0;
135     }
136 
137     if (opts->filename) {
138         int x;
139         for (x = 0; opts->filename[x]; x++)
140             mprintf("^Ignoring option %s\n", opts->filename[x]);
141     }
142 
143     if (optget(opts, "version")->enabled) {
144         printf("clamav-milter %s\n", get_version());
145         optfree(opts);
146         return 0;
147     }
148 
149     pt = strdup(optget(opts, "config-file")->strarg);
150     if (pt == NULL) {
151         printf("Unable to allocate memory for config file\n");
152         return 1;
153     }
154     if ((opts = optparse(pt, 0, NULL, 1, OPT_MILTER, 0, opts)) == NULL) {
155         printf("%s: cannot parse config file %s\n", argv[0], pt);
156         free(pt);
157         return 1;
158     }
159     free(pt);
160 
161     if ((opt = optget(opts, "User"))->enabled) {
162         user_name = opt->strarg;
163     }
164 
165     if ((opt = optget(opts, "Chroot"))->enabled) {
166         if (chdir(opt->strarg) != 0) {
167             logg("!Cannot change directory to %s\n", opt->strarg);
168             return 1;
169         }
170         if (chroot(opt->strarg) != 0) {
171             logg("!chroot to %s failed. Are you root?\n", opt->strarg);
172             return 1;
173         }
174     }
175 
176     pt = optget(opts, "AddHeader")->strarg;
177     if (strcasecmp(pt, "No")) {
178         char myname[255];
179 
180         if (((opt = optget(opts, "ReportHostname"))->enabled &&
181              strncpy(myname, opt->strarg, sizeof(myname))) ||
182             !gethostname(myname, sizeof(myname))) {
183 
184             myname[sizeof(myname) - 1] = '\0';
185             snprintf(xvirushdr, sizeof(xvirushdr), "clamav-milter %s at %s",
186                      get_version(), myname);
187         } else {
188             snprintf(xvirushdr, sizeof(xvirushdr), "clamav-milter %s",
189                      get_version());
190         }
191         xvirushdr[sizeof(xvirushdr) - 1] = '\0';
192 
193         descr.xxfi_flags |= SMFIF_ADDHDRS;
194 
195         if (strcasecmp(pt, "Add")) { /* Replace or Yes */
196             descr.xxfi_flags |= SMFIF_CHGHDRS;
197             addxvirus = 1;
198         } else { /* Add */
199             addxvirus = 2;
200         }
201     }
202 
203     if (!(my_socket = optget(opts, "MilterSocket")->strarg)) {
204         logg("!Please configure the MilterSocket directive\n");
205         logg_close();
206         optfree(opts);
207         return 1;
208     }
209 
210     if (smfi_setconn(my_socket) == MI_FAILURE) {
211         logg("!smfi_setconn failed\n");
212         logg_close();
213         optfree(opts);
214         return 1;
215     }
216     if (smfi_register(descr) == MI_FAILURE) {
217         logg("!smfi_register failed\n");
218         logg_close();
219         optfree(opts);
220         return 1;
221     }
222     opt  = optget(opts, "FixStaleSocket");
223     umsk = umask(0777); /* socket is created with 000 to avoid races */
224     if (smfi_opensocket(opt->enabled) == MI_FAILURE) {
225         logg("!Failed to create socket %s\n", my_socket);
226         logg_close();
227         optfree(opts);
228         return 1;
229     }
230     umask(umsk); /* restore umask */
231     if (strncmp(my_socket, "inet:", 5) && strncmp(my_socket, "inet6:", 6)) {
232         /* set group ownership and perms on the local socket */
233         char *sock_name = my_socket;
234         mode_t sock_mode;
235         if (!strncmp(my_socket, "unix:", 5))
236             sock_name += 5;
237         if (!strncmp(my_socket, "local:", 6))
238             sock_name += 6;
239         if (*my_socket == ':')
240             sock_name++;
241 
242         if (optget(opts, "MilterSocketGroup")->enabled) {
243             char *gname    = optget(opts, "MilterSocketGroup")->strarg, *end;
244             gid_t sock_gid = strtol(gname, &end, 10);
245             if (*end) {
246                 struct group *pgrp = getgrnam(gname);
247                 if (!pgrp) {
248                     logg("!Unknown group %s\n", gname);
249                     logg_close();
250                     optfree(opts);
251                     return 1;
252                 }
253                 sock_gid = pgrp->gr_gid;
254             }
255             if (chown(sock_name, -1, sock_gid)) {
256                 logg("!Failed to change socket ownership to group %s\n", gname);
257                 logg_close();
258                 optfree(opts);
259                 return 1;
260             }
261         }
262 
263         if (NULL != user_name) {
264             struct passwd *user;
265             if ((user = getpwnam(user_name)) == NULL) {
266                 logg("ERROR: Can't get information about user %s.\n",
267                      user_name);
268                 logg_close();
269                 optfree(opts);
270                 return 1;
271             }
272 
273             if (chown(sock_name, user->pw_uid, -1)) {
274                 logg("!Failed to change socket ownership to user %s\n", user->pw_name);
275                 optfree(opts);
276                 logg_close();
277                 return 1;
278             }
279         }
280 
281         if (optget(opts, "MilterSocketMode")->enabled) {
282             char *end;
283             sock_mode = strtol(optget(opts, "MilterSocketMode")->strarg, &end, 8);
284             if (*end) {
285                 logg("!Invalid MilterSocketMode %s\n", optget(opts, "MilterSocketMode")->strarg);
286                 logg_close();
287                 optfree(opts);
288                 return 1;
289             }
290         } else
291             sock_mode = 0777 & ~umsk;
292 
293         if (chmod(sock_name, sock_mode & 0666)) {
294             logg("!Cannot set milter socket permission to %s\n", optget(opts, "MilterSocketMode")->strarg);
295             logg_close();
296             optfree(opts);
297             return 1;
298         }
299     }
300 
301     logg_lock    = !optget(opts, "LogFileUnlock")->enabled;
302     logg_time    = optget(opts, "LogTime")->enabled;
303     logg_size    = optget(opts, "LogFileMaxSize")->numarg;
304     logg_verbose = mprintf_verbose = optget(opts, "LogVerbose")->enabled;
305     if (logg_size)
306         logg_rotate = optget(opts, "LogRotate")->enabled;
307 
308     if ((opt = optget(opts, "LogFile"))->enabled) {
309         logg_file = opt->strarg;
310         if (!cli_is_abspath(logg_file)) {
311             fprintf(stderr, "ERROR: LogFile requires full path.\n");
312             logg_close();
313             optfree(opts);
314             return 1;
315         }
316     } else
317         logg_file = NULL;
318 
319 #if defined(USE_SYSLOG) && !defined(C_AIX)
320     if (optget(opts, "LogSyslog")->enabled) {
321         int fac;
322 
323         opt = optget(opts, "LogFacility");
324         if ((fac = logg_facility(opt->strarg)) == -1) {
325             logg("!LogFacility: %s: No such facility.\n", opt->strarg);
326             logg_close();
327             optfree(opts);
328             return 1;
329         }
330 
331         openlog("clamav-milter", LOG_PID, fac);
332         logg_syslog = 1;
333     }
334 #endif
335 
336     time(&currtime);
337     if (logg("#+++ Started at %s", ctime(&currtime))) {
338         fprintf(stderr, "ERROR: Can't initialize the internal logger\n");
339         logg_close();
340         optfree(opts);
341         return 1;
342     }
343     if ((opt = optget(opts, "TemporaryDirectory"))->enabled)
344         tempdir = opt->strarg;
345 
346     if (localnets_init(opts) || init_actions(opts)) {
347         logg_close();
348         optfree(opts);
349         return 1;
350     }
351 
352     if ((opt = optget(opts, "Whitelist"))->enabled && whitelist_init(opt->strarg)) {
353         localnets_free();
354         logg_close();
355         optfree(opts);
356         return 1;
357     }
358 
359     if ((opt = optget(opts, "SkipAuthenticated"))->enabled && smtpauth_init(opt->strarg)) {
360         localnets_free();
361         whitelist_free();
362         logg_close();
363         optfree(opts);
364         return 1;
365     }
366 
367     multircpt = optget(opts, "SupportMultipleRecipients")->enabled;
368 
369 #ifndef _WIN32
370     if (!optget(opts, "Foreground")->enabled) {
371         if (-1 == daemonize_parent_wait(user_name, logg_file)) {
372             logg("!daemonize() failed\n");
373             localnets_free();
374             whitelist_free();
375             cpool_free();
376             logg_close();
377             optfree(opts);
378             return 1;
379         }
380         if (chdir("/") == -1) {
381             logg("^Can't change current working directory to root\n");
382         }
383     }
384 
385     sigfillset(&sigset);
386     sigdelset(&sigset, SIGUSR1);
387     sigdelset(&sigset, SIGFPE);
388     sigdelset(&sigset, SIGILL);
389     sigdelset(&sigset, SIGSEGV);
390 #ifdef SIGBUS
391     sigdelset(&sigset, SIGBUS);
392 #endif
393     pthread_sigmask(SIG_SETMASK, &sigset, NULL);
394     memset(&act, 0, sizeof(struct sigaction));
395     act.sa_handler = milter_exit;
396     sigfillset(&(act.sa_mask));
397     sigaction(SIGUSR1, &act, NULL);
398     sigaction(SIGSEGV, &act, NULL);
399 
400 #endif /* _WIN32 */
401 
402     maxfilesize = optget(opts, "MaxFileSize")->numarg;
403     if (!maxfilesize) {
404         logg("^Invalid MaxFileSize, using default (%d)\n", CLI_DEFAULT_MAXFILESIZE);
405         maxfilesize = CLI_DEFAULT_MAXFILESIZE;
406     }
407     readtimeout = optget(opts, "ReadTimeout")->numarg;
408 
409     cpool_init(opts);
410     if (!cp) {
411         logg("!Failed to init the socket pool\n");
412         localnets_free();
413         whitelist_free();
414         logg_close();
415         optfree(opts);
416         return 1;
417     }
418 
419     if ((opt = optget(opts, "PidFile"))->enabled) {
420         FILE *fd;
421         mode_t old_umask = umask(0002);
422         int err          = 0;
423 
424         if ((fd = fopen(opt->strarg, "w")) == NULL) {
425             logg("!Can't save PID in file %s\n", opt->strarg);
426             err = 1;
427         } else {
428             if (fprintf(fd, "%u\n", (unsigned int)getpid()) < 0) {
429                 logg("!Can't save PID in file %s\n", opt->strarg);
430                 err = 1;
431             }
432             fclose(fd);
433         }
434         umask(old_umask);
435 
436 #ifndef _WIN32
437         if (0 == err) {
438             /*If the file has already been created by a different user, it will just be
439              * rewritten by us, but not change the ownership, so do that explicitly.
440              */
441             if (0 == geteuid()) {
442                 struct passwd *pw = getpwuid(0);
443                 int ret           = lchown(opt->strarg, pw->pw_uid, pw->pw_gid);
444                 if (ret) {
445                     logg("!Can't change ownership of PID file %s '%s'\n", opt->strarg, strerror(errno));
446                     err = 1;
447                 }
448             }
449         }
450 #endif /*_WIN32*/
451 
452         if (err) {
453             localnets_free();
454             whitelist_free();
455             logg_close();
456             optfree(opts);
457             return 2;
458         }
459     }
460 
461 #ifndef _WIN32
462     dropPrivRet = drop_privileges(user_name, logg_file);
463     if (dropPrivRet) {
464         optfree(opts);
465         return dropPrivRet;
466     }
467 
468     /* We have been daemonized, and initialization is done.  Signal
469      * the parent process so that it can exit cleanly.
470      */
471     if (parentPid != getpid()) { //we have been daemonized
472         daemonize_signal_parent(parentPid);
473     }
474 #endif
475 
476     return smfi_main();
477 }
478 
479 /*
480  * Local Variables:
481  * mode: c
482  * c-basic-offset: 4
483  * tab-width: 8
484  * End:
485  * vim: set cindent smartindent autoindent softtabstop=4 shiftwidth=4 tabstop=8:
486  */
487