clamav-milter/clamav-milter.c
e3aaff8e
 /*
e1cbc270
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
  *  Copyright (C) 2008-2013 Sourcefire, Inc.
e3aaff8e
  *
4c237bcf
  *  Author: aCaB <acab@clamav.net>
e3aaff8e
  *
  *  This program is free software; you can redistribute it and/or modify
4c237bcf
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
e3aaff8e
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
48b7b4a7
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
e3aaff8e
  */
 
7908713f
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
e3aaff8e
 #include <stdio.h>
4c237bcf
 #include <sys/types.h>
 #include <unistd.h>
e004f1c5
 #include <pwd.h>
3aa15b4c
 #include <grp.h>
4c237bcf
 #include <string.h>
f5294bf3
 #include <signal.h>
 #include <pthread.h>
aa633fa0
 #ifdef USE_SYSLOG
 #include <syslog.h>
 #endif
4c237bcf
 #include <time.h>
 #include <libmilter/mfapi.h>
eef726b0
 
4c237bcf
 #include "clamav.h"
e3aaff8e
 
4c237bcf
 #include "shared/output.h"
278dc6b3
 #include "shared/optparser.h"
4c237bcf
 #include "shared/misc.h"
ec34b80f
 #include "libclamav/default.h"
4c237bcf
 
 #include "connpool.h"
 #include "netcode.h"
 #include "clamfi.h"
3eb16511
 #include "whitelist.h"
4c237bcf
 
82d4fa94
 struct smfiDesc descr;
f5294bf3
 struct optstruct *opts;
 
288057e9
 static void milter_exit(int sig)
 {
f5294bf3
     const struct optstruct *opt;
 
     logg("*clamav-milter: milter_exit, signal %d\n", sig);
 
 #ifndef _WIN32
288057e9
     if ((opt = optget(opts, "MilterSocket"))) {
         if (unlink(opt->strarg) == -1)
f5294bf3
             logg("!Can't unlink the socket file %s\n", opt->strarg);
         else
             logg("Socket file removed.\n");
     }
 #endif
 
     logg("clamav-milter: stopped\n");
 
     optfree(opts);
 
     logg_close();
     cpool_free();
     localnets_free();
     whitelist_free();
 }
e004f1c5
 
288057e9
 int main(int argc, char **argv)
 {
278dc6b3
     char *my_socket, *pt;
     const struct optstruct *opt;
e9c4dd09
     time_t currtime;
e394c513
     mode_t umsk;
4c237bcf
     int ret;
 
f5294bf3
     sigset_t sigset;
     struct sigaction act;
 
     sigfillset(&sigset);
     sigdelset(&sigset, SIGUSR1);
     sigdelset(&sigset, SIGFPE);
     sigdelset(&sigset, SIGILL);
     sigdelset(&sigset, SIGSEGV);
 #ifdef SIGBUS
     sigdelset(&sigset, SIGBUS);
 #endif
     pthread_sigmask(SIG_SETMASK, &sigset, NULL);
     memset(&act, 0, sizeof(struct sigaction));
     act.sa_handler = milter_exit;
     sigfillset(&(act.sa_mask));
     sigaction(SIGUSR1, &act, NULL);
     sigaction(SIGSEGV, &act, NULL);
 
336ba3e5
     cl_initialize_crypto();
 
82d4fa94
     memset(&descr, 0, sizeof(struct smfiDesc));
288057e9
     descr.xxfi_name    = "ClamAV";         /* filter name */
     descr.xxfi_version = SMFI_VERSION;     /* milter version */
     descr.xxfi_flags   = SMFIF_QUARANTINE; /* flags */
     descr.xxfi_connect = clamfi_connect;   /* connection info filter */
     descr.xxfi_envfrom = clamfi_envfrom;   /* envelope sender filter */
     descr.xxfi_envrcpt = clamfi_envrcpt;   /* envelope recipient filter */
     descr.xxfi_header  = clamfi_header;    /* header filter */
     descr.xxfi_body    = clamfi_body;      /* body block */
     descr.xxfi_eom     = clamfi_eom;       /* end of message */
     descr.xxfi_abort   = clamfi_abort;     /* message aborted */
82d4fa94
 
278dc6b3
     opts = optparse(NULL, argc, argv, 1, OPT_MILTER, 0, NULL);
     if (!opts) {
288057e9
         mprintf("!Can't parse command line options\n");
         return 1;
4c237bcf
     }
 
288057e9
     if (optget(opts, "help")->enabled) {
         printf("\n");
         printf("                       Clam AntiVirus: Milter Mail Scanner %s\n", get_version());
         printf("           By The ClamAV Team: https://www.clamav.net/about.html#credits\n");
e1cbc270
         printf("           (C) 2019 Cisco Systems, Inc.\n");
288057e9
         printf("\n");
         printf("    %s [-c <config-file>]\n\n", argv[0]);
         printf("\n");
         printf("    --help                   -h       Show this help\n");
         printf("    --version                -V       Show version\n");
         printf("    --config-file <file>     -c       Read configuration from file\n");
         printf("\n");
         optfree(opts);
         return 0;
4c237bcf
     }
19c17946
 
288057e9
     if (opts->filename) {
         int x;
         for (x = 0; opts->filename[x]; x++)
             mprintf("^Ignoring option %s\n", opts->filename[x]);
19c17946
     }
4c237bcf
 
288057e9
     if (optget(opts, "version")->enabled) {
         printf("clamav-milter %s\n", get_version());
         optfree(opts);
         return 0;
4c237bcf
     }
44ba5c0e
 
278dc6b3
     pt = strdup(optget(opts, "config-file")->strarg);
637d2461
     if (pt == NULL) {
288057e9
         printf("Unable to allocate memory for config file\n");
         return 1;
637d2461
     }
288057e9
     if ((opts = optparse(pt, 0, NULL, 1, OPT_MILTER, 0, opts)) == NULL) {
         printf("%s: cannot parse config file %s\n", argv[0], pt);
         free(pt);
         return 1;
4c237bcf
     }
278dc6b3
     free(pt);
684d3122
 
288057e9
     if ((opt = optget(opts, "Chroot"))->enabled) {
         if (chdir(opt->strarg) != 0) {
             logg("!Cannot change directory to %s\n", opt->strarg);
             return 1;
         }
         if (chroot(opt->strarg) != 0) {
             logg("!chroot to %s failed. Are you root?\n", opt->strarg);
             return 1;
         }
f7203529
     }
 
a3fef40e
     pt = optget(opts, "AddHeader")->strarg;
     if (strcasecmp(pt, "No")) {
288057e9
         char myname[255];
 
         if (((opt = optget(opts, "ReportHostname"))->enabled &&
              strncpy(myname, opt->strarg, sizeof(myname))) ||
             !gethostname(myname, sizeof(myname))) {
 
             myname[sizeof(myname) - 1] = '\0';
             snprintf(xvirushdr, sizeof(xvirushdr), "clamav-milter %s at %s",
                      get_version(), myname);
         } else {
             snprintf(xvirushdr, sizeof(xvirushdr), "clamav-milter %s",
                      get_version());
         }
         xvirushdr[sizeof(xvirushdr) - 1] = '\0';
 
         descr.xxfi_flags |= SMFIF_ADDHDRS;
 
         if (strcasecmp(pt, "Add")) { /* Replace or Yes */
             descr.xxfi_flags |= SMFIF_CHGHDRS;
             addxvirus = 1;
         } else { /* Add */
             addxvirus = 2;
         }
a3fef40e
     }
 
288057e9
     if (!(my_socket = optget(opts, "MilterSocket")->strarg)) {
         logg("!Please configure the MilterSocket directive\n");
         logg_close();
         optfree(opts);
         return 1;
a3fef40e
     }
 
288057e9
     if (smfi_setconn(my_socket) == MI_FAILURE) {
         logg("!smfi_setconn failed\n");
         logg_close();
         optfree(opts);
         return 1;
a3fef40e
     }
288057e9
     if (smfi_register(descr) == MI_FAILURE) {
         logg("!smfi_register failed\n");
         logg_close();
         optfree(opts);
         return 1;
a3fef40e
     }
288057e9
     opt  = optget(opts, "FixStaleSocket");
a3fef40e
     umsk = umask(0777); /* socket is created with 000 to avoid races */
288057e9
     if (smfi_opensocket(opt->enabled) == MI_FAILURE) {
         logg("!Failed to create socket %s\n", my_socket);
         logg_close();
         optfree(opts);
         return 1;
a3fef40e
     }
     umask(umsk); /* restore umask */
288057e9
     if (strncmp(my_socket, "inet:", 5) && strncmp(my_socket, "inet6:", 6)) {
         /* set group ownership and perms on the local socket */
         char *sock_name = my_socket;
         mode_t sock_mode;
         if (!strncmp(my_socket, "unix:", 5))
             sock_name += 5;
         if (!strncmp(my_socket, "local:", 6))
             sock_name += 6;
         if (*my_socket == ':')
             sock_name++;
 
         if (optget(opts, "MilterSocketGroup")->enabled) {
             char *gname    = optget(opts, "MilterSocketGroup")->strarg, *end;
             gid_t sock_gid = strtol(gname, &end, 10);
             if (*end) {
                 struct group *pgrp = getgrnam(gname);
                 if (!pgrp) {
                     logg("!Unknown group %s\n", gname);
                     logg_close();
                     optfree(opts);
                     return 1;
                 }
                 sock_gid = pgrp->gr_gid;
             }
             if (chown(sock_name, -1, sock_gid)) {
                 logg("!Failed to change socket ownership to group %s\n", gname);
                 logg_close();
                 optfree(opts);
                 return 1;
             }
         }
 
         if ((opt = optget(opts, "User"))->enabled) {
             struct passwd *user;
             if ((user = getpwnam(opt->strarg)) == NULL) {
                 logg("ERROR: Can't get information about user %s.\n",
                      opt->strarg);
                 logg_close();
                 optfree(opts);
                 return 1;
             }
 
             if (chown(sock_name, user->pw_uid, -1)) {
                 logg("!Failed to change socket ownership to user %s\n", user->pw_name);
                 optfree(opts);
                 logg_close();
                 return 1;
             }
         }
 
         if (optget(opts, "MilterSocketMode")->enabled) {
             char *end;
             sock_mode = strtol(optget(opts, "MilterSocketMode")->strarg, &end, 8);
             if (*end) {
                 logg("!Invalid MilterSocketMode %s\n", optget(opts, "MilterSocketMode")->strarg);
                 logg_close();
                 optfree(opts);
                 return 1;
             }
         } else
             sock_mode = 0777 & ~umsk;
 
         if (chmod(sock_name, sock_mode & 0666)) {
             logg("!Cannot set milter socket permission to %s\n", optget(opts, "MilterSocketMode")->strarg);
             logg_close();
             optfree(opts);
             return 1;
         }
a3fef40e
     }
 
288057e9
     if (geteuid() == 0 && (opt = optget(opts, "User"))->enabled) {
4c237bcf
         struct passwd *user = NULL;
288057e9
         if ((user = getpwnam(opt->strarg)) == NULL) {
             fprintf(stderr, "ERROR: Can't get information about user %s.\n", opt->strarg);
             optfree(opts);
             return 1;
         }
28071296
 
4c237bcf
 #ifdef HAVE_INITGROUPS
288057e9
         if (initgroups(opt->strarg, user->pw_gid)) {
             fprintf(stderr, "ERROR: initgroups() failed.\n");
             optfree(opts);
             return 1;
         }
2ea4230d
 #elif HAVE_SETGROUPS
288057e9
         if (setgroups(1, &user->pw_gid)) {
             fprintf(stderr, "ERROR: setgroups() failed.\n");
             optfree(opts);
             return 1;
         }
2ea4230d
 #endif
288057e9
         if (setgid(user->pw_gid)) {
             fprintf(stderr, "ERROR: setgid(%d) failed.\n", (int)user->pw_gid);
             optfree(opts);
             return 1;
         }
 
         if (setuid(user->pw_uid)) {
             fprintf(stderr, "ERROR: setuid(%d) failed.\n", (int)user->pw_uid);
             optfree(opts);
             return 1;
         }
4c237bcf
     }
ebc4ec8f
 
288057e9
     logg_lock    = !optget(opts, "LogFileUnlock")->enabled;
     logg_time    = optget(opts, "LogTime")->enabled;
     logg_size    = optget(opts, "LogFileMaxSize")->numarg;
278dc6b3
     logg_verbose = mprintf_verbose = optget(opts, "LogVerbose")->enabled;
42ccf9c2
     if (logg_size)
         logg_rotate = optget(opts, "LogRotate")->enabled;
fe3d8be8
 
288057e9
     if ((opt = optget(opts, "LogFile"))->enabled) {
         logg_file = opt->strarg;
         if (!cli_is_abspath(logg_file)) {
             fprintf(stderr, "ERROR: LogFile requires full path.\n");
             logg_close();
             optfree(opts);
             return 1;
         }
4c237bcf
     } else
288057e9
         logg_file = NULL;
fe3d8be8
 
4c237bcf
 #if defined(USE_SYSLOG) && !defined(C_AIX)
288057e9
     if (optget(opts, "LogSyslog")->enabled) {
         int fac;
 
         opt = optget(opts, "LogFacility");
         if ((fac = logg_facility(opt->strarg)) == -1) {
             logg("!LogFacility: %s: No such facility.\n", opt->strarg);
             logg_close();
             optfree(opts);
             return 1;
         }
 
         openlog("clamav-milter", LOG_PID, fac);
         logg_syslog = 1;
4c237bcf
     }
06bfd678
 #endif
7089d180
 
e9c4dd09
     time(&currtime);
288057e9
     if (logg("#+++ Started at %s", ctime(&currtime))) {
         fprintf(stderr, "ERROR: Can't initialize the internal logger\n");
         logg_close();
         optfree(opts);
         return 1;
e9c4dd09
     }
288057e9
     if ((opt = optget(opts, "TemporaryDirectory"))->enabled)
         tempdir = opt->strarg;
87620def
 
288057e9
     if (localnets_init(opts) || init_actions(opts)) {
         logg_close();
         optfree(opts);
         return 1;
6840d862
     }
 
288057e9
     if ((opt = optget(opts, "Whitelist"))->enabled && whitelist_init(opt->strarg)) {
         localnets_free();
         logg_close();
         optfree(opts);
         return 1;
3eb16511
     }
ce34c246
 
288057e9
     if ((opt = optget(opts, "SkipAuthenticated"))->enabled && smtpauth_init(opt->strarg)) {
         localnets_free();
         whitelist_free();
         logg_close();
         optfree(opts);
         return 1;
57aa0269
     }
 
1672b517
     multircpt = optget(opts, "SupportMultipleRecipients")->enabled;
288057e9
 
     if (!optget(opts, "Foreground")->enabled) {
         if (daemonize() == -1) {
             logg("!daemonize() failed\n");
             localnets_free();
             whitelist_free();
             cpool_free();
             logg_close();
             optfree(opts);
             return 1;
         }
         if (chdir("/") == -1)
             logg("^Can't change current working directory to root\n");
41f5623e
     }
 
278dc6b3
     maxfilesize = optget(opts, "MaxFileSize")->numarg;
288057e9
     if (!maxfilesize) {
         logg("^Invalid MaxFileSize, using default (%d)\n", CLI_DEFAULT_MAXFILESIZE);
         maxfilesize = CLI_DEFAULT_MAXFILESIZE;
ec34b80f
     }
278dc6b3
     readtimeout = optget(opts, "ReadTimeout")->numarg;
36f79c60
 
278dc6b3
     cpool_init(opts);
4c237bcf
     if (!cp) {
288057e9
         logg("!Failed to init the socket pool\n");
         localnets_free();
         whitelist_free();
         logg_close();
         optfree(opts);
         return 1;
     }
 
     if ((opt = optget(opts, "PidFile"))->enabled) {
         FILE *fd;
         mode_t old_umask = umask(0002);
 
         if ((fd = fopen(opt->strarg, "w")) == NULL) {
             logg("!Can't save PID in file %s\n", opt->strarg);
         } else {
             if (fprintf(fd, "%u\n", (unsigned int)getpid()) < 0) {
                 logg("!Can't save PID in file %s\n", opt->strarg);
             }
             fclose(fd);
         }
         umask(old_umask);
87620def
     }
530999cb
 
f5294bf3
     return smfi_main();
93928eab
 }
3eb16511
 
9fe789f8
 /*
4c237bcf
  * Local Variables:
  * mode: c
  * c-basic-offset: 4
  * tab-width: 8
  * End: 
  * vim: set cindent smartindent autoindent softtabstop=4 shiftwidth=4 tabstop=8: 
9fe789f8
  */