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