/* * Copyright (C) 2004 Baris Simsek, * http://www.enderunix.org/simsek/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * * $Id: sheffd.c,v 1.8 2005/09/21 20:59:09 simsek Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "global.h" #include "lib.h" #include "config.h" #include "smtp.h" #include "loadconfig.h" /* * Sheff filter return values. * These values for logging purposes. * */ #define EX_ERR -1 #define EX_OKAY 0 #define EX_ACCEPT 1 #define EX_VIRUS 5 #define EX_SUBJ 6 #define EX_BODY 7 #define EX_ATTACH 8 #define EX_WBLIST 9 #define EX_CUSTOM 10 #define EX_HEADER 11 #define EX_EMPTY 12 /* * qmail errors * Daemon returns one of the following errors to queue-wrapper. * */ #define EXIT00 0 /* Success */ #define EXIT53 53 /* Write error; e.g., disk full. */ #define EXIT54 54 /* Unable to read the message or envelope. */ #define EXIT56 56 /* Problem making a network connection */ #define EXIT62 62 /* Problem with the queue directory. */ #define EXIT74 74 /* Connection to mail server succeeded, but communication failed. */ #define EXIT31 31 /* Permanent rejection */ #define EXIT71 71 /* Temporary rejection */ extern int errno; static char cfgfile[MAXPATHSIZE]; static char tempdir[MAXPATHSIZE]; static char workdir[MAXPATHSIZE]; static char* tcpremoteip; static int debug_level; static char *mime_cmdarray[10]; static char *virus_cmdarray[10]; static char *custom_cmdarray[10]; static void usage() { fprintf(stdout, "\n"); fprintf(stdout, "EnderUNIX Software Development Team @ Turkey\n"); fprintf(stdout, " http://www.enderunix.org/qsheff/\n"); fprintf(stdout, " (c) Baris Simsek\n\n"); fprintf(stdout, "Usage: sheffd [options]\n"); fprintf(stdout, " -c config file : Specify alternative config file.\n"); fprintf(stdout, " -d debug level : Change debug level.\n"); fprintf(stdout, " -t tempdir : Change temporary directory.\n"); fprintf(stdout, " -w workdir : Change work directory.\n"); fprintf(stdout, " -h : This screen.\n"); fprintf(stdout, " -v : Version.\n\n"); return; } void static log(int level, char* fname) { char errbuf[1024]; snprintf(errbuf, sizeof(errbuf)-1, "%s: %s", fname, strerror(errno)); printf("%s\n", errbuf); syslog(level, errbuf); return; } void static clean_exit() { unlink(SOCKET_FILE); unlink(LOCK_FILE); return; } static int parse_cmdline(char *prog, char *exec_cmdline[10]) { int i = 0; char line[MAXPATHSIZE]; char *part; memset(line, 0x0, sizeof(line)); strncpy(line, prog, sizeof(line)-1); if((part = strtok(line, " ")) == NULL) return -1; if((exec_cmdline[0] = malloc(strlen(part)+1)) == NULL) return -1; strncpy(exec_cmdline[i], part, strlen(part)); i++; while(((part = strtok(NULL, " ")) != NULL) && (i < 10)){ if((exec_cmdline[i] = malloc(strlen(part)+1)) == NULL) return -1; memset(exec_cmdline[i], 0x0, strlen(part)+1); strncpy(exec_cmdline[i], part, strlen(part)); i++; } exec_cmdline[i] = NULL; return 0; } static int init_sheff() { openlog("sheffd", LOG_PID, LOG_MAIL); if(parse_cmdline(MIME_PROG, mime_cmdarray) == -1) { syslog(LOG_ERR, "Couldn't parse mime program command-line."); return -1; } if(enable_virus_prog == 1) { if(parse_cmdline(VIRUS_PROG, virus_cmdarray) == -1) { syslog(LOG_ERR, "Couldn't parse virus program command-line."); return -1; } } if(enable_custom_prog == 1) { if(parse_cmdline(CUSTOM_PROG, custom_cmdarray) == -1) { syslog(LOG_ERR, "Couldn't parse custom program command-line."); return -1; } } return 0; } /* * Sheff daemon calls this function before exit. * Free memory, OS always needs more memory space */ static int stop_sheff() { int i; rulelist* listp; listp = rule_sp; while(listp->next != NULL) { free(listp); listp = listp->next; } for(i=0; i<10; i++) { if(mime_cmdarray[i] != NULL) free(mime_cmdarray[i]); if((enable_virus_prog == 1) && (virus_cmdarray[i] != NULL)) free(virus_cmdarray[i]); if((enable_custom_prog == 1) && (custom_cmdarray[i] != NULL)) free(custom_cmdarray[i]); } return 0; } static void signal_handler(int signo) { int status; switch(signo) { case SIGHUP: syslog(LOG_ERR, "Restarting sheff daemon..."); loadconfig(cfgfile); break; case SIGPIPE: /* close(a_socket); */ break; case SIGCHLD: if ((wait(&status)) == -1) return; /* wait error */ if (WIFSIGNALED(status)) return; /* child has received a signal */ if (WIFSTOPPED(status)) return; /* child is stopped */ if (WIFEXITED(status)) return; /* successfull return */ break; case SIGKILL: stop_sheff(); clean_exit(); /* blah blah, fill here */ break; case SIGTERM: stop_sheff(); clean_exit(); /* blah blah, fill here */ break; default: stop_sheff(); clean_exit(); /* blah blah, fill here */ break; } } static int init_daemon() { int fd; int lockd; char pidstr[10]; struct sigaction act, oact; printf("\n"); printf("Starting daemon.\n"); printf("See mail logs for details.\n\n"); syslog(LOG_INFO, "Starting daemon..."); if(setgid((gid_t) SHEFF_GROUP) == -1) { log(LOG_ERR, "setgid@init_daemon"); return -1; } if(setuid((uid_t) SHEFF_USER) == -1) { log(LOG_ERR, "setuid@init_daemon"); return -1; } if(getppid() == 1) return 0; switch(fork()) { case -1: /* Error */ log(LOG_ERR, "fork@init_daemon"); return -1; case 0: /* Child */ if(setsid() == -1) { log(LOG_ERR, "setsid@init_daemon"); return -1; } for(fd=getdtablesize(); fd >= 0; --fd) close(fd); if((fd = open("/dev/null", O_RDWR)) == -1) { log(LOG_ERR, "open_null@init_daemon"); return -1; } dup(fd); dup(fd); dup(fd); umask(0022); if((lockd = open(LOCK_FILE, O_RDWR|O_CREAT, 0640)) == -1) { log(LOG_ERR, "open_lock@init_daemon"); return -1; } if(lockf(lockd, F_TLOCK, 0) == -1) { log(LOG_ERR, "lockf@init_daemon"); return -1; } snprintf(pidstr, sizeof(pidstr)-1, "%d\n", getpid()); if(write(fd, pidstr, strlen(pidstr)) == -1) { log(LOG_ERR, "write_pidstr@init_daemon"); return -1; } act.sa_handler = signal_handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; if(sigaction(SIGKILL, &act, &oact) < 0) log(LOG_ERR, "SIGKILL@init_daemon"); if(sigaction(SIGHUP, &act, &oact) < 0) log(LOG_ERR, "SIGHUP@init_daemon"); if(sigaction(SIGPIPE, &act, &oact) < 0) log(LOG_ERR, "SIGPIPE@init_daemon"); if(sigaction(SIGTERM, &act, &oact) < 0) log(LOG_ERR, "SIGTERM@init_daemon"); if(sigaction(SIGCHLD, &act, &oact) < 0) log(LOG_ERR, "SIGCHLD@init_daemon"); act.sa_handler = SIG_IGN; sigemptyset(&act.sa_mask); act.sa_flags = 0; if(sigaction(SIGTSTP, &act, &oact) < 0) log(LOG_ERR, "SIGTSTP@init_daemon"); if(sigaction(SIGTTOU, &act, &oact) < 0) log(LOG_ERR, "SIGTTOU@init_daemon"); if(sigaction(SIGTTIN, &act, &oact) < 0) log(LOG_ERR, "SIGTTIN@init_daemon"); return 0; default: /* parent */ exit(0); } return 0; } static int init_socket() { int srv_sock; struct sockaddr_un srv_addr; syslog(LOG_INFO, "Initializing socket."); if((srv_sock = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { log(LOG_ERR, "socket@init_socket"); return -1; } strncpy(srv_addr.sun_path, SOCKET_FILE, sizeof(srv_addr.sun_path)-1); if(bind(srv_sock, (struct sockaddr *)&srv_addr, sizeof(srv_addr.sun_len) + sizeof(srv_addr.sun_family) + strlen(srv_addr.sun_path)) == -1) { log(LOG_ERR, "bind@init_socket"); return -1; } if(listen(srv_sock, LISTEN_QUEUE) != 0) { log(LOG_ERR, "listen@init_socket"); return -1; } return srv_sock; } static rulelist* load_rulelist() { int i; FILE *fd; rulelist *ll, *sp; char ruleline[256]; syslog(LOG_INFO, "Loading rules..."); memset(ruleline, 0x0, sizeof(ruleline)); if((fd = fopen(RULE_FILE, "r")) == NULL) { syslog(LOG_ERR, "load_rulelist: %s", strerror(errno)); return NULL; } if((ll = (rulelist *) malloc(sizeof(rulelist))) == NULL) { syslog(LOG_ERR, "malloc: %s", strerror(errno)); return NULL; } sp = ll; while((fgets(ruleline, sizeof(ruleline), fd) != 0) && (!feof(fd))) { if(((ruleline[0] == 'b') || (ruleline[0] == 's') || (ruleline[0] == 'a')) && (ruleline[1] == ':') && (strlen(ruleline) > 5)) { ll->attr = ruleline[0]; for(i=0;iruleline[i] = ruleline[i+2]; ll->ruleline[i] = '\0'; ll->next = (rulelist *) malloc(sizeof(rulelist)); ll = ll->next; } memset(ruleline, 0x0, sizeof(ruleline)); } ll->next = NULL; fclose(fd); return sp; } static int load_attachlist() { int i; FILE *fd; char pattern[32]; syslog(LOG_INFO, "Loading attach rules..."); i = 0; if((fd = fopen(ATTACH_FILE, "r")) == NULL) { syslog(LOG_ERR, "load_attachlist: %s", strerror(errno)); return -1; } while((i < 32) && (!feof(fd))) { memset(pattern, 0x0, sizeof(pattern)); if((fscanf(fd, "%s", pattern) < 0 ) && (!feof(fd))) { syslog(LOG_ERR, "load_attachlist: %s", strerror(errno)); return -1; } if(strlen(pattern) > 1) { strncpy(attach_list[i], pattern, sizeof(attach_list[i])-1); i++; } } fclose(fd); return 0; } /* * Sheff accepts variable parameters for external programs. * This function replaces variables with their values. * Defined variables: * %tempdir% %workdir% */ static int replace_vars(char* cmdarray[10]) { int i; int len; for(i=0;i<10;i++) { /* Replace %tempdir% */ if(cmdarray[i] != NULL) { len = strlen(cmdarray[i]); if(strncmp(cmdarray[i], "%tempdir%", len) == 0) { realloc(cmdarray[i], len+strlen(tempdir)-8); strncpy(cmdarray[i], tempdir, len+strlen(tempdir)-9); } if(strncmp(cmdarray[i], "%workdir%", len) == 0) { realloc(cmdarray[i], len+strlen(workdir)-8); strncpy(cmdarray[i], workdir, len+strlen(tempdir)-9); } } } return 0; } static int exec_cmd(char *cmdline[]) { pid_t pid; int stat; switch((pid = fork())) { case -1: syslog(LOG_ERR, "fork: %s", strerror(errno)); return -1; break; case 0: if(execve(cmdline[0], cmdline, NULL) == -1) { syslog(LOG_ERR, "execve: %s", strerror(errno)); return -1; } break; default: sleep(1); if (wait(&stat) == -1) { slog(1, LOG_ERR); return 1; } if (WIFSIGNALED(stat)) { syslog(LOG_NOTICE, "%s is signalled", cmdline[0]); return -1; } if (WIFSTOPPED(stat)) { syslog(LOG_NOTICE, "%s is stopped", cmdline[0]); return -1; } if (WIFEXITED(stat)) return WEXITSTATUS(stat); break; } return -11; /* Normally, program never come here */ } static int sheff_proc() { int i; int ret; replace_vars(mime_cmdarray); if(enable_virus_prog == 1) replace_vars(virus_cmdarray); if(enable_custom_prog == 1) replace_vars(custom_cmdarray); ret = exec_cmd(mime_cmdarray); if(ret == MIME_PROG_ERR) return EX_ERR; if(chdir(tempdir) != 0) { syslog(LOG_ERR, "chdir: %s", strerror(errno)); return EX_ERR; } if(parse_header() == -1) return EX_ERR; return EX_OKAY; } int main(int argc, char* argv[]) { int opt; int ret; int socketd, newd; pid_t child; struct sockaddr_un addr; socklen_t addrlen; char buffer[256]; char retval[16]; char* qid; char tempdir_root[MAXPATHSIZE]; addrlen = sizeof(struct sockaddr); strncpy(cfgfile, CONFIG_FILE, sizeof(cfgfile)-1); strncpy(tempdir_root, TEMPDIR_ROOT, sizeof(tempdir_root)-1); while((opt = getopt(argc, argv, "c:d:ht:w:v")) != -1) { switch(opt) { case 'c': strncpy(cfgfile, optarg, sizeof(cfgfile)-1); break; case 'd': debug_level = atoi(optarg); break; case 'h': usage(); exit(0); break; case 't': strncpy(tempdir_root, optarg, sizeof(tempdir_root)-1); break; case 'v': fprintf(stdout, "%s %s - Daemonized Content Filter for qmail\n\n", PROGNAME, VERSION); exit(0); break; } } printf("Starting %s %s\n", PROGNAME, VERSION); syslog(LOG_INFO, "Starting %s %s.", PROGNAME, VERSION); if(init_sheff() == -1) exit(-1); if(init_daemon() == -1) exit(-1); if((socketd = init_socket()) == -1) exit(-1); if((rule_sp = load_rulelist()) == NULL) exit(-1); if(load_attachlist() == -1) exit(-1); while(1) { if((newd = accept(socketd, (struct sockaddr *) &addr, &addrlen)) == -1) { syslog(LOG_ERR, "accept: %s", strerror(errno)); continue; } else { child = fork(); if(child < 0) { /* fork() error */ syslog(LOG_NOTICE, "fork: %s", strerror(errno)); continue; } else if(child == 0) { /* child */ close(socketd); memset(buffer, 0x0, sizeof(buffer)); if(recv(newd, buffer, sizeof(buffer)-1, 0) == -1) { syslog(LOG_ERR, "recv: %s", strerror(errno)); send(newd, "EX_EXIT74", 10, 0); close(newd); exit(-1); } syslog(LOG_INFO, "new job received."); syslog(LOG_INFO, "%s", buffer); qid = strtok(buffer, "|"); tcpremoteip = strtok(NULL, "|"); snprintf(tempdir, sizeof(tempdir)-1, "%s/%s", tempdir_root, qid); snprintf(workdir, sizeof(workdir)-1, "%s/%s", WORKDIR_ROOT, qid); if(chdir(workdir) != 0) { syslog(LOG_ERR, "chdir: %s", strerror(errno)); send(newd, "EX_EXIT62", 10, 0); close(newd); exit(-1); } if(mkdir(tempdir, 0700) == -1) { syslog(LOG_ERR, "mkdir: %s", strerror(errno)); send(newd, "EX_EXIT62", 10, 0); close(newd); exit(-1); } ret = sheff_proc(); if(rrm_dir(tempdir) == -1) syslog(LOG_NOTICE, "rrm_dir: %s", strerror(errno)); memset(retval, 0x0, sizeof(retval)); if(ret < 0) { /* Temporary error. */ strncpy(retval, "EXIT71", sizeof(retval)-1); } else if((ret == 0) || (ret == 1)) { /* Innocenent mail. */ strncpy(retval, "EXIT00", sizeof(retval)-1); } else if(ret > 3) { /* Permanently error. */ strncpy(retval, "EXIT31", sizeof(retval)-1); } syslog(LOG_INFO, "Retval: %s", retval); if(send(newd, retval, strlen(retval), 0)) { syslog(LOG_ERR, "send: %s", strerror(errno)); close(newd); exit(-1); } close(newd); exit(0); } else { /* parent */ close(newd); } } } clean_exit(); return 0; }