spamc/spamc.c (911 lines of code) (raw):

/* <@LICENSE> * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to you under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * </@LICENSE> */ #include "config.h" #include "version.h" #include "libspamc.h" #include "utils.h" #include "spamc.h" #include <stdlib.h> #include <stdio.h> #include <string.h> #include "getopt.h" #ifdef _WIN32 #include <io.h> #include <fcntl.h> #include <process.h> #else #include <syslog.h> #include <unistd.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <arpa/inet.h> #endif #ifdef SPAMC_SSL #include <openssl/crypto.h> #ifndef OPENSSL_VERSION_TEXT #define OPENSSL_VERSION_TEXT "OpenSSL" #endif #endif #ifdef HAVE_SYSEXITS_H #include <sysexits.h> #endif #ifdef HAVE_ERRNO_H #include <errno.h> #endif #ifdef HAVE_SYS_ERRNO_H #include <sys/errno.h> #endif #ifdef HAVE_TIME_H #include <time.h> #endif #ifdef HAVE_SYS_TIME_H #include <sys/time.h> #endif #ifdef HAVE_SIGNAL_H #include <signal.h> #endif #ifdef HAVE_PWD_H #include <pwd.h> #endif /* SunOS 4.1.4 patch from Tom Lipkis <tal@pss.com> */ #if (defined(__sun__) && defined(__sparc__) && !defined(__svr4__)) /* SunOS */ \ || (defined(__sgi)) /* IRIX */ \ || (defined(__osf__)) /* Digital UNIX */ \ || (defined(hpux) || defined(__hpux)) /* HPUX */ \ || (defined(__CYGWIN__)) /* CygWin, Win32 */ extern int spamc_optind; extern char *spamc_optarg; #endif #ifdef _WIN32 char *__progname = "spamc"; #endif /* safe fallback defaults to on now - CRH */ int flags = SPAMC_RAW_MODE | SPAMC_SAFE_FALLBACK | SPAMC_TLSV1; /* global to control whether we should exit(0)/exit(1) on ham/spam */ int use_exit_code = 0; /* Aug 14, 2002 bj: global to hold -e command */ char **exec_argv; static int timeout = 600; static int connect_timeout = 0; /* Sep 8, 2008 mrgus: separate connect timeout */ void check_malloc (void *ptr) { if(ptr == NULL) { libspamc_log(flags, LOG_ERR, "Error allocating memory using malloc\n"); /* this is really quite serious. we can't do anything. die */ exit(EX_OSERR); } } void print_version(void) { printf("%s version %s\n", "SpamAssassin Client", VERSION_STRING); #ifdef SPAMC_SSL printf(" compiled with SSL support (%s)\n", OPENSSL_VERSION_TEXT); #endif } static void usg(char *str) { printf("%s", str); } void print_usage(void) { print_version(); usg("\n"); usg("Usage: spamc [options] [-e command [args]] < message\n"); usg("\n"); usg("Options:\n"); usg(" -d, --dest host[,host2]\n" " Specify one or more hosts to connect to.\n" " [default: localhost]\n"); usg(" -H , --randomize Randomize IP addresses for the looked-up\n" " hostname.\n"); usg(" -p, --port port Specify port for connection to spamd.\n" " [default: 783]\n"); #ifdef SPAMC_SSL usg(" -S, --ssl Use SSL to talk to spamd.\n"); usg(" --ssl-cert cert Authenticate using SSL client certificate.\n"); usg(" --ssl-key key Specify an SSL client key PEM file.\n"); usg(" --ssl-ca-file file Specify the location of the CA PEM file.\n"); usg(" --ssl-ca-path path Specify a directory containin CA files.\n"); #endif #ifndef _WIN32 usg(" -U, --socket path Connect to spamd via UNIX domain sockets.\n"); #endif usg(" -F, --config path Use this configuration file.\n"); usg(" -t, --timeout timeout\n" " Timeout in seconds for communications to\n" " spamd. [default: 600]\n"); usg(" -n, --connect-timeout timeout\n" " Timeout in seconds when opening a connection to\n" " spamd. [default: 600]\n"); usg(" --filter-retries retries\n" " Retry filtering this many times if the spamd\n" " process fails (usually times out) [default: 1]\n"); usg(" --filter-retry-sleep sleep\n" " Sleep for this time between failed filter\n" " attempts, in seconds [default: 1]\n"); usg(" --connect-retries retries\n" " Try connecting to spamd tcp socket this many times\n" " [default: 3]\n"); usg(" --retry-sleep sleep Sleep for this time between attempts to\n" " connect to spamd, in seconds [default: 1]\n"); usg(" -s, --max-size size Specify maximum message size, in bytes.\n" " [default: 500k]\n"); usg(" -u, --username username\n" " User for spamd to process this message under.\n" " [default: current user]\n"); usg(" -L, --learntype learntype\n" " Learn message as spam, ham or forget to\n" " forget or unlearn the message.\n"); usg(" -C, --reporttype reporttype\n" " Report message to collaborative filtering\n" " databases. Report type should be 'report' for\n" " spam or 'revoke' for ham.\n"); usg(" -B, --bsmtp Assume input is a single BSMTP-formatted\n" " message.\n"); usg(" -c, --check Just print the summary line and set an exit\n" " code.\n"); usg(" -y, --tests Just print the names of the tests hit.\n"); usg(" -r, --full-spam Print full report for messages identified as\n" " spam.\n"); usg(" -R, --full Print full report for all messages.\n"); usg(" --headers Rewrite only the message headers.\n"); usg(" -E, --exitcode Filter as normal, and set an exit code.\n"); usg(" -x, --no-safe-fallback\n" " Don't fallback safely.\n"); usg(" -X, --unavailable-tempfail\n" " When using -x, turn 'unavailable' error into\n" " 'tempfail'. This may be useful for an MTAs\n" " to defer emails with a temporary SMTP error\n" " instead of bouncing with a permanent SMTP\n" " error.\n"); usg(" -l, --log-to-stderr Log errors and warnings to stderr.\n"); #ifndef _WIN32 usg(" -e, --pipe-to command [args]\n" " Pipe the output to the given command instead\n" " of stdout. This must be the last option.\n"); #endif usg(" -h, --help Print this help message and exit.\n"); usg(" -V, --version Print spamc version and exit.\n"); usg(" -K Keepalive check of spamd.\n"); #ifdef HAVE_ZLIB_H usg(" -z Compress mail message sent to spamd.\n"); #endif usg(" -f (Now default, ignored.)\n"); usg(" -4 Use IPv4 only for connecting to server.\n"); usg(" -6 Use IPv6 only for connecting to server.\n"); usg("\n"); } /** * Does the command line parsing for argv[]. * * Returns EX_OK or EX_TEMPFAIL if successful. EX_TEMPFAIL is a kludge for * the cases where we want in main to return immediately; we can't exit() * because on Windows WSACleanup() needs to be called. */ int read_args(int argc, char **argv, int *max_size, char **username, int *extratype, struct transport *ptrn) { #ifndef _WIN32 const char *opts = "-BcrR46d:e:fyp:n:t:s:u:L:C:xXzSHU:ElhVKF:0:1:2"; #else const char *opts = "-BcrR46d:fyp:n:t:s:u:L:C:xXzSHElhVKF:0:1:2"; #endif int opt; int ret = EX_OK; int longind = 1; static struct option longoptions[] = { { "dest", required_argument, 0, 'd' }, { "randomize", no_argument, 0, 'H' }, { "port", required_argument, 0, 'p' }, { "ssl", optional_argument, 0, 'S' }, { "ssl-cert", optional_argument, 0, 5 }, { "ssl-key", optional_argument, 0, 6 }, { "ssl-ca-file", optional_argument, 0, 7 }, { "ssl-ca-path", optional_argument, 0, 8 }, { "socket", required_argument, 0, 'U' }, { "config", required_argument, 0, 'F' }, { "timeout", required_argument, 0, 't' }, { "connect-timeout", required_argument, 0, 'n'}, { "connect-retries", required_argument, 0, 0 }, { "retry-sleep", required_argument, 0, 1 }, { "filter-retries", required_argument, 0, 3 }, { "filter-retry-sleep", required_argument, 0, 4 }, { "max-size", required_argument, 0, 's' }, { "username", required_argument, 0, 'u' }, { "learntype", required_argument, 0, 'L' }, { "reporttype", required_argument, 0, 'C' }, { "bsmtp", no_argument, 0, 'B' }, { "check", no_argument, 0, 'c' }, { "tests", no_argument, 0, 'y' }, { "full-spam", no_argument, 0, 'r' }, { "full", no_argument, 0, 'R' }, { "headers", no_argument, 0, 2 }, { "exitcode", no_argument, 0, 'E' }, { "no-safe-fallback", no_argument, 0, 'x' }, { "unavailable-tempfail", no_argument, 0, 'X' }, { "log-to-stderr", no_argument, 0, 'l' }, { "pipe-to", required_argument, 0, 'e' }, { "help", no_argument, 0, 'h' }, { "version", no_argument, 0, 'V' }, { "compress", no_argument, 0, 'z' }, { 0, 0, 0, 0} /* last element _must_ be all zeroes */ }; #ifdef SPAMC_SSL ptrn->ssl_cert_file = 0; ptrn->ssl_key_file = 0; ptrn->ssl_ca_file = 0; ptrn->ssl_ca_path = 0; #endif while ((opt = spamc_getopt_long(argc, argv, opts, longoptions, &longind)) != -1) { switch (opt) { case 'B': { flags = (flags & ~SPAMC_MODE_MASK) | SPAMC_BSMTP_MODE; break; } case 'c': { flags |= SPAMC_CHECK_ONLY; break; } case 'd': { ptrn->type = TRANSPORT_TCP; ptrn->hostname = spamc_optarg; /* fix the ptr to point to this string */ break; } #ifndef _WIN32 case 'e': { int i, j; /* Allocate memory for the necessary pointers needed to * store the remaining arguments. */ exec_argv = malloc(sizeof(*exec_argv) * (argc - spamc_optind + 2)); if (exec_argv == NULL) { return EX_OSERR; } for (i = 0, j = spamc_optind - 1; j < argc; i++, j++) { exec_argv[i] = argv[j]; } exec_argv[i] = NULL; return EX_OK; } #endif case 'f': { /* obsolete, backward compat */ break; } case 'K': { flags |= SPAMC_PING; break; } case 'l': { flags |= SPAMC_LOG_TO_STDERR; break; } case 'H': { flags |= SPAMC_RANDOMIZE_HOSTS; break; } case 'p': { ptrn->port = (unsigned short)atoi(spamc_optarg); break; } case 'r': { flags |= SPAMC_REPORT_IFSPAM; break; } case 'E': { use_exit_code = 1; break; } case 'R': { flags |= SPAMC_REPORT; break; } case 's': { *max_size = atoi(spamc_optarg); break; } #ifdef SPAMC_SSL case 'S': { flags |= SPAMC_USE_SSL; if(spamc_optarg) { libspamc_log(flags, LOG_ERR, "Explicit specification of an SSL/TLS version no longer supported."); ret = EX_USAGE; } break; } #endif case 't': { timeout = atoi(spamc_optarg); if(!connect_timeout) { connect_timeout = timeout; /* Sep 8, 2008 mrgus: default to timeout if not specified */ } break; } case 'n': { connect_timeout = atoi(spamc_optarg); break; } case 'u': { *username = spamc_optarg; break; } case 'L': { flags |= SPAMC_LEARN; if (strcmp(spamc_optarg,"spam") == 0) { *extratype = 0; } else if (strcmp(spamc_optarg,"ham") == 0) { *extratype = 1; } else if (strcmp(spamc_optarg,"forget") == 0) { *extratype = 2; } else { libspamc_log(flags, LOG_ERR, "Please specify a legal learn type"); ret = EX_USAGE; } break; } case 'C': { flags |= SPAMC_REPORT_MSG; if (strcmp(spamc_optarg,"report") == 0) { *extratype = 0; } else if (strcmp(spamc_optarg,"revoke") == 0) { *extratype = 1; } else { libspamc_log(flags, LOG_ERR, "Please specify a legal report type"); ret = EX_USAGE; } break; } #ifndef _WIN32 case 'U': { ptrn->type = TRANSPORT_UNIX; ptrn->socketpath = spamc_optarg; break; } #endif case 'x': { flags &= (~SPAMC_SAFE_FALLBACK); break; } case 'X': { /* Only activates if -x is also used */ if (!(flags & SPAMC_SAFE_FALLBACK)) { flags |= SPAMC_UNAVAIL_TEMPFAIL; } else { libspamc_log(flags, LOG_ERR, "This option is only valid if -x is set first"); } break; } case 'y': { flags |= SPAMC_SYMBOLS; break; } case '?': case ':': { libspamc_log(flags, LOG_ERR, "invalid usage"); ret = EX_USAGE; /* FALLTHROUGH */ } case 'h': { print_usage(); if (ret == EX_OK) ret = EX_TEMPFAIL; return(ret); } case 'V': { print_version(); return(EX_TEMPFAIL); } case 'z': { #ifdef HAVE_ZLIB_H flags |= SPAMC_USE_ZLIB; #else libspamc_log(flags, LOG_ERR, "spamc -z support not available"); ret = EX_USAGE; #endif break; } case '4': { flags |= SPAMC_USE_INET4; flags &= ~SPAMC_USE_INET6; break; } case '6': { flags |= SPAMC_USE_INET6; flags &= ~SPAMC_USE_INET4; break; } case 0: { ptrn->connect_retries = atoi(spamc_optarg); break; } case 1: { ptrn->retry_sleep = atoi(spamc_optarg); break; } case 2: { flags |= SPAMC_HEADERS; break; } case 3: { ptrn->filter_retries = atoi(spamc_optarg); break; } case 4: { ptrn->filter_retry_sleep = atoi(spamc_optarg); break; } #ifdef SPAMC_SSL case 5: { flags |= SPAMC_CLIENT_SSL_CERT; ptrn->ssl_cert_file = spamc_optarg; break; } case 6: { flags |= SPAMC_CLIENT_SSL_CERT; ptrn->ssl_key_file = spamc_optarg; break; } case 7: { ptrn->ssl_ca_file = spamc_optarg; break; } case 8: { ptrn->ssl_ca_path = spamc_optarg; break; } #endif } } #ifdef SPAMC_SSL if ((flags & SPAMC_CLIENT_SSL_CERT) && !(ptrn->ssl_cert_file && ptrn->ssl_key_file)) { libspamc_log(flags, LOG_ERR, "--ssl-cert and --ssl-key must be used together"); ret = EX_USAGE; } #endif if (*max_size > SPAMC_MAX_MESSAGE_LEN) { libspamc_log(flags, LOG_ERR, "-s parameter is beyond max of %d", SPAMC_MAX_MESSAGE_LEN); ret = EX_USAGE; } if ( !(flags & (SPAMC_USE_INET4 | SPAMC_USE_INET6)) ) { /* allow any protocol family (INET or INET6) by default */ flags |= SPAMC_USE_INET4; flags |= SPAMC_USE_INET6; } /* learning action has to block some parameters */ if (flags & SPAMC_LEARN) { if (flags & SPAMC_CHECK_ONLY) { libspamc_log(flags, LOG_ERR, "Learning excludes check only"); ret = EX_USAGE; } if (flags & SPAMC_PING) { libspamc_log(flags, LOG_ERR, "Learning excludes ping"); ret = EX_USAGE; } if (flags & SPAMC_REPORT_IFSPAM) { libspamc_log(flags, LOG_ERR, "Learning excludes report if spam"); ret = EX_USAGE; } if (flags & SPAMC_REPORT) { libspamc_log(flags, LOG_ERR, "Learning excludes report"); ret = EX_USAGE; } if (flags & SPAMC_SYMBOLS) { libspamc_log(flags, LOG_ERR, "Learning excludes symbols"); ret = EX_USAGE; } if (flags & SPAMC_REPORT_MSG) { libspamc_log(flags, LOG_ERR, "Learning excludes reporting to collaborative filtering databases"); ret = EX_USAGE; } } return ret; } /* combine_args() :: parses spamc.conf for options, and combines those * with options passed via command line * * lines beginning with # or blank lines are ignored * * returns EX_OK on success, EX_NOINPUT on absence of a config file (success), * and EX_CONFIG on failure */ int combine_args(char *config_file, int argc, char **argv, int *combo_argc, char **combo_argv) { FILE *config; char option[CONFIG_MAX_LINE_SIZE]; int i, count = 0; char *tok = NULL; int is_user_defined_p = 1; if (config_file == NULL) { config_file = CONFIG_FILE; is_user_defined_p = 0; } if ((config = fopen(config_file, "r")) == NULL) { if (is_user_defined_p == 1) { /* if the config file was user defined we should issue an error */ fprintf(stderr,"Failed to open config file: %s\n", config_file); return EX_CONFIG; } return EX_NOINPUT; } while (!feof(config) && fgets(option, CONFIG_MAX_LINE_SIZE, config)) { int option_l = strlen(option); count++; /* increment the line counter */ if (option_l < 1 || option[0] == '#' || option[0] == '\n') { continue; } if (option[option_l-1] != '\n') { if (option_l < CONFIG_MAX_LINE_SIZE-1) { fprintf(stderr,"Line not terminated with a newline in %s\n", config_file); } else { fprintf(stderr,"Exceeded max line size (%d) in %s\n", CONFIG_MAX_LINE_SIZE-2, config_file); } fclose(config); return EX_CONFIG; } tok = option; while((tok = strtok(tok, " ")) != NULL) { if(tok[0] == '\n') break; for(i=strlen(tok); i>0; i--) { if(tok[i] == '\n') tok[i] = '\0'; } if (*combo_argc >= COMBO_ARGV_SIZE) { fprintf(stderr,"Exceeded max number of arguments (%d) in %s\n", COMBO_ARGV_SIZE, config_file); fclose(config); return EX_CONFIG; } combo_argv[*combo_argc] = strdup(tok); check_malloc(combo_argv[*combo_argc]); /* TODO: leaked. not a big deal since spamc exits quickly */ tok = NULL; *combo_argc+=1; } } fclose(config); /* note: not starting at 0, that's the command name */ for(i=1; i<argc; i++) { if (*combo_argc >= COMBO_ARGV_SIZE) { fprintf(stderr,"Exceeded max number of arguments (%d) in %s\n", COMBO_ARGV_SIZE, config_file); return EX_CONFIG; } combo_argv[*combo_argc] = strdup(argv[i]); check_malloc(combo_argv[*combo_argc]); /* TODO: leaked. not a big deal since spamc exits quickly */ *combo_argc+=1; } return EX_OK; } void get_output_fd(int *fd) { #ifndef _WIN32 int pipe_fds[2]; pid_t pid; #endif if (*fd != -1) return; /* If we aren't told to feed our output to an external app, we simply * write to stdout. */ if (exec_argv == NULL) { *fd = STDOUT_FILENO; return; } #ifndef _WIN32 /* Create a pipe for communication between child and parent. */ if (pipe(pipe_fds)) { libspamc_log(flags, LOG_ERR, "pipe creation failed: %m"); exit(EX_OSERR); } pid = fork(); if (pid < 0) { libspamc_log(flags, LOG_ERR, "fork failed: %m"); exit(EX_OSERR); } else if (pid == 0) { /* This is the child process: * Normally you'd expect the parent process here, however that would * screw up an invoker waiting on the death of the parent. So instead, * we fork a child to feed the data and have the parent exec the new * program. */ close(pipe_fds[0]); *fd = pipe_fds[1]; return; } /* This is the parent process (see above) */ close(pipe_fds[1]); if (dup2(pipe_fds[0], STDIN_FILENO)) { libspamc_log(flags, LOG_ERR, "redirection of stdin failed: %m"); exit(EX_OSERR); } /* No point in leaving extra fds lying around. */ close(pipe_fds[0]); /* Now execute the command specified. */ execv(exec_argv[0], exec_argv); /* Whoa, something failed... */ libspamc_log(flags, LOG_ERR, "exec failed: %m"); #else libspamc_log(flags, LOG_CRIT, "THIS MUST NOT HAPPEN AS -e IS NOT SUPPORTED UNDER WINDOWS."); #endif exit(EX_OSERR); } /** * Determines the username of the uid spamc is running under. * * If the program's caller didn't identify the user to run as, use the * current user for this. Note that we're not talking about UNIX perm- * issions, but giving SpamAssassin a username so it can do per-user * configuration (welcomelists & the like). * * Allocates memory for the username, returns EX_OK if successful. */ int get_current_user(char **username) { #ifndef _WIN32 struct passwd *curr_user; #endif if (*username != NULL) { *username = strdup(*username); if (username == NULL) goto fail; goto pass; } #ifndef _WIN32 /* Get the passwd information for the effective uid spamc is running * under. Setting errno to zero is recommended in the manpage. */ errno = 0; curr_user = getpwuid(geteuid()); if (curr_user == NULL) { perror("getpwuid() failed"); goto fail; } /* Since "curr_user" points to static library data, we don't wish to * risk some other part of the system overwriting it, so we copy the * username to our own buffer -- then this won't arise as a problem. */ *username = strdup(curr_user->pw_name); if (*username == NULL) { goto fail; } #endif pass: return EX_OK; fail: /* FIXME: The handling of SPAMC_CHECK_ONLY should probably be moved to * the end of main() */ if (flags & SPAMC_CHECK_ONLY) { printf("0/0\n"); return EX_NOTSPAM; } return EX_OSERR; } int main(int argc, char *argv[]) { int max_size; char *username; struct transport trans; struct message m; int out_fd = -1; int result = EX_SOFTWARE; int ret = EX_SOFTWARE; int ret_conf = EX_SOFTWARE; int extratype = 0; int islearned = 0; int isreported = 0; /* these are to hold CLI and config options combined, to be passed * to read_args() */ char *combo_argv[COMBO_ARGV_SIZE]; int combo_argc; int i; char *config_file = NULL; transport_init(&trans); #ifdef LIBSPAMC_UNIT_TESTS /* unit test support; divert execution. will not return */ do_libspamc_unit_tests(); #endif #ifndef _WIN32 openlog("spamc", LOG_CONS | LOG_PID, LOG_MAIL); signal(SIGPIPE, SIG_IGN); #endif /* set some defaults */ max_size = 500 * 1024; username = NULL; combo_argc = 1; combo_argv[0] = strdup(argv[0]); check_malloc(combo_argv[0]); /* TODO: leaked. not a big deal since spamc exits quickly */ for(i=0; i<argc; i++) { if(strncmp(argv[i], "-F", 2) == 0) { config_file = argv[i+1]; break; } } ret_conf = combine_args(config_file, argc, argv, &combo_argc, combo_argv); if (ret_conf == EX_OK) { /* Parse the combined arguments of command line and config file */ if ((ret = read_args(combo_argc, combo_argv, &max_size, &username, &extratype, &trans)) != EX_OK) { if (ret == EX_TEMPFAIL) ret = EX_OK; goto finish; } } else if (ret_conf == EX_NOINPUT) { /* no config file read */ /* parse only command line arguments (default behaviour) */ if ((ret = read_args(argc, argv, &max_size, &username, &extratype, &trans)) != EX_OK) { if (ret == EX_TEMPFAIL) ret = EX_OK; goto finish; } } else { /* ret_conf == EX_CONFIG. or some other error */ ret = EX_CONFIG; goto finish; } ret = get_current_user(&username); if (ret != EX_OK) goto finish; if ((flags & SPAMC_RANDOMIZE_HOSTS) != 0) { /* we don't need strong randomness; this is just so we pick * a random host for loadbalancing. */ srand(getpid() ^ (unsigned int)time(NULL)); } /********************************************************************** * SET UP TRANSPORT * * This takes the user parameters and digs up what it can about how * we connect to the spam daemon. Mainly this involves lookup up the * hostname and getting the IP addresses to connect to. */ m.type = MESSAGE_NONE; m.out = NULL; m.outbuf = NULL; m.raw = NULL; m.priv = NULL; m.max_len = max_size; m.timeout = timeout; m.connect_timeout = connect_timeout; /* Sep 8, 2008 mrgus: separate connect timeout */ m.is_spam = EX_NOHOST; /* default err code if can't reach the daemon */ #ifdef _WIN32 setmode(STDIN_FILENO, O_BINARY); setmode(STDOUT_FILENO, O_BINARY); #endif ret = transport_setup(&trans, flags); if (ret == EX_OK) { ret = message_read(STDIN_FILENO, flags, &m); if (ret == EX_OK) { if (flags & SPAMC_LEARN) { int msg_class = 0; unsigned int tellflags = 0; unsigned int didtellflags = 0; if ((extratype == 0) || (extratype == 1)) { if (extratype == 0) { msg_class = SPAMC_MESSAGE_CLASS_SPAM; } else { msg_class = SPAMC_MESSAGE_CLASS_HAM; } tellflags |= SPAMC_SET_LOCAL; } else { tellflags |= SPAMC_REMOVE_LOCAL; } ret = message_tell(&trans, username, flags, &m, msg_class, tellflags, &didtellflags); if (ret == EX_OK) { if ((extratype == 0) || (extratype == 1)) { if (didtellflags & SPAMC_SET_LOCAL) { islearned = 1; } } else { if (didtellflags & SPAMC_REMOVE_LOCAL) { islearned = 1; } } } } else if (flags & SPAMC_REPORT_MSG) { int msg_class = 0; unsigned int tellflags = 0; unsigned int didtellflags = 0; if (extratype == 0) { msg_class = SPAMC_MESSAGE_CLASS_SPAM; tellflags |= SPAMC_SET_REMOTE; tellflags |= SPAMC_SET_LOCAL; } else { msg_class = SPAMC_MESSAGE_CLASS_HAM; tellflags |= SPAMC_SET_LOCAL; tellflags |= SPAMC_REMOVE_REMOTE; } ret = message_tell(&trans, username, flags, &m, msg_class, tellflags, &didtellflags); if (ret == EX_OK) { if (extratype == 0) { if (didtellflags & SPAMC_SET_REMOTE) { isreported = 1; } } else { if (didtellflags & SPAMC_REMOVE_REMOTE) { isreported = 1; } } } } else { ret = message_filter(&trans, username, flags, &m); } free(username); username = NULL; if (ret == EX_OK) { get_output_fd(&out_fd); if (flags & SPAMC_LEARN) { if (islearned == 1) { printf("Message successfully un/learned\n"); } else { printf("Message was already un/learned\n"); } message_cleanup(&m); goto finish; } else if (flags & SPAMC_REPORT_MSG) { if (isreported == 1) { printf("Message successfully reported/revoked\n"); } else { printf("Unable to report/revoke message\n"); } message_cleanup(&m); goto finish; } else if (message_write(out_fd, &m) >= 0) { result = m.is_spam; if ((flags & SPAMC_CHECK_ONLY) && result != EX_TOOBIG) { message_cleanup(&m); ret = result; } else { message_cleanup(&m); if (use_exit_code && result != EX_TOOBIG) { ret = result; } } goto finish; } } } } free(username); /* FAIL: */ result = m.is_spam; if (ret != EX_OK) { result = ret; } if (flags & (SPAMC_LEARN|SPAMC_PING) ) { get_output_fd(&out_fd); message_cleanup(&m); } else { if (flags & (SPAMC_CHECK_ONLY | SPAMC_REPORT | SPAMC_REPORT_IFSPAM)) { get_output_fd(&out_fd); full_write(out_fd, 1, "0/0\n", 4); } else if (flags & SPAMC_SYMBOLS) { /* bug 4991: -y should only output a blank line on connection failure */ get_output_fd(&out_fd); full_write(out_fd, 1, "\n", 1); } else { /* bug 5412: spamc -x should not output the message on error */ if ((flags & SPAMC_SAFE_FALLBACK) || result == EX_TOOBIG) { get_output_fd(&out_fd); message_dump(STDIN_FILENO, out_fd, &m, flags); } /* else, do NOT get_output_fd() (bug 5478) */ } message_cleanup(&m); if (ret == EX_TOOBIG) { ret = EX_OK; /* too big always means exit(0) -- bug 5412 */ } else if (flags & SPAMC_SAFE_FALLBACK) { ret = EX_OK; } else if (use_exit_code) { ret = result; } /* If -x and -X are used, change from EX_UNAVAILABLE TO EX_TEMPFAIL - bug 6717 */ if ((!(flags & SPAMC_SAFE_FALLBACK)) && (flags & SPAMC_UNAVAIL_TEMPFAIL) && (ret == EX_UNAVAILABLE)) { ret = EX_TEMPFAIL; } } finish: #ifdef _WIN32 WSACleanup(); #endif return ret; }