| /* <@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; |
| |
| /* 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"); |
| #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' }, |
| { "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 */ |
| }; |
| |
| 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; |
| } |
| } |
| } |
| |
| 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); |
| } |
| 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); |
| 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 (whitelists & 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; |
| } |