/* $Id: agent_shared.c,v 1.69 2006/05/13 01:12:59 jonz Exp $ */ /* DSPAM COPYRIGHT (C) 2002-2006 JONATHAN A. ZDZIARSKI 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; version 2 of the License. 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. */ /* * agent_shared.c - shared agent-based components * * DESCRIPTION * agent-based components shared between the full dspam agent (dspam) * and the lightweight client agent (dspamc) */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #ifdef HAVE_UNISTD_H_ #include #include #endif #include #include #include #ifdef _WIN32 #include #include #define WIDEXITED(x) 1 #define WEXITSTATUS(x) (x) #include #else #include #include #endif #include "util.h" #include "read_config.h" #ifdef DAEMON #include "daemon.h" #endif #ifdef TIME_WITH_SYS_TIME # include # include #else # ifdef HAVE_SYS_TIME_H # include # else # include # endif #endif #include "agent_shared.h" #include "language.h" #include "buffer.h" /* * initialize_atx(AGENT_CTX *) * * DESCRIPTION * initializes an existing agent context * * INPUT ARGUMENTS * ATX agent context to initialize * * RETURN VALUES * returns 0 on success */ int initialize_atx(AGENT_CTX *ATX) { #if defined(TRUSTED_USER_SECURITY) && \ defined(_REENTRANT) && \ defined(HAVE_GETPWUID_R) char buf[1024]; #endif memset(ATX, 0, sizeof(AGENT_CTX)); ATX->training_mode = DST_DEFAULT; ATX->training_buffer = 0; ATX->train_pristine = 0; ATX->classification = DSR_NONE; ATX->source = DSS_NONE; ATX->operating_mode = DSM_PROCESS; ATX->users = nt_create (NT_CHAR); if (ATX->users == NULL) { LOG(LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } #ifdef TRUSTED_USER_SECURITY /* Cache the current user's passwd entry and establish trust */ #if defined(_REENTRANT) && defined(HAVE_GETPWUID_R) if (getpwuid_r(getuid(), &ATX->pwbuf, buf, sizeof(buf), &ATX->p)) { ATX->p = NULL; } #else ATX->p = getpwuid (getuid()); #endif if (!ATX->p) { LOG(LOG_ERR, ERR_AGENT_RUNTIME_USER); exit(EXIT_FAILURE); } if (ATX->p->pw_uid == 0) ATX->trusted = 1; else ATX->trusted = _ds_match_attribute(agent_config, "Trust", ATX->p->pw_name); if (!ATX->trusted) nt_add (ATX->users, ATX->p->pw_name); #endif return 0; } /* * process_arguments(AGENT_CTX *, int argc, char *argv[]) * * DESCRIPTION * master commandline argument process loop * * INPUT ARGUMENTS * ATX agent context * argc number of arguments provided * argv array of arguments * * RETURN VALUES * returns 0 on success, EINVAL when invalid options specified */ int process_arguments(AGENT_CTX *ATX, int argc, char **argv) { int flag_u = 0, flag_r = 0; int client = (_ds_read_attribute(agent_config, "ClientHost") != NULL); char *ptrptr; int i; #ifdef DEBUG ATX->debug_args[0] = 0; #endif ATX->client_args[0] = 0; for (i=0; idebug_args, argv[i], sizeof (ATX->debug_args)); strlcat (ATX->debug_args, " ", sizeof (ATX->debug_args)); #endif /* Terminate user/rcpt lists */ if ((flag_u || flag_r) && (argv[i][0] == '-' || argv[i][0] == 0 || !strcmp(argv[i], "--"))) { flag_u = flag_r = 0; if (!strcmp(argv[i], "--")) continue; } if (!strcmp (argv[i], "--user")) { flag_u = 1; continue; } if (!strcmp (argv[i], "--rcpt-to")) { if (!ATX->recipients) { ATX->recipients = nt_create(NT_CHAR); if (ATX->recipients == NULL) { LOG(LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } } flag_r = 1; continue; } /* Build arg list to pass to server (when in client/server mode) */ if (client && !flag_u && !flag_r && i>0) { if (argv[i][0] == 0) strlcat(ATX->client_args, "\"", sizeof(ATX->client_args)); strlcat (ATX->client_args, argv[i], sizeof(ATX->client_args)); if (argv[i][0] == 0) strlcat(ATX->client_args, "\"", sizeof(ATX->client_args)); strlcat (ATX->client_args, " ", sizeof(ATX->client_args)); } if (!strcmp (argv[i], "--debug")) { #ifdef DEBUG if (DO_DEBUG == 0) DO_DEBUG = 1; #endif continue; } #if defined(DAEMON) && !defined(_DSPAMC_H) if (!strcmp (argv[i], "--client")) { ATX->client_mode = 1; continue; } #ifdef TRUSTED_USER_SECURITY if (!strcmp (argv[i], "--daemon") && ATX->trusted) #else if (!strcmp (argv[i], "--daemon")) #endif { ATX->operating_mode = DSM_DAEMON; continue; } #endif if (!strncmp (argv[i], "--mode=", 7)) { char *mode = strchr(argv[i], '=')+1; process_mode(ATX, mode); continue; } /* Build RCPT TO list */ if (flag_r) { if (argv[i] != NULL && strlen (argv[i]) < MAX_USERNAME_LENGTH) { char user[MAX_USERNAME_LENGTH]; if (_ds_match_attribute(agent_config, "Broken", "case")) lc(user, argv[i]); else strcpy(user, argv[i]); #ifdef TRUSTED_USER_SECURITY if (!ATX->trusted && strcmp(user, ATX->p->pw_name)) { LOG(LOG_ERR, ERR_TRUSTED_USER, ATX->p->pw_uid, ATX->p->pw_name); return EINVAL; } if (ATX->trusted) #endif nt_add (ATX->recipients, user); } continue; } /* Build process user list */ if (flag_u) { if (argv[i] != NULL && strlen (argv[i]) < MAX_USERNAME_LENGTH) { char user[MAX_USERNAME_LENGTH]; if (_ds_match_attribute(agent_config, "Broken", "case")) lc(user, argv[i]); else strcpy(user, argv[i]); #ifdef TRUSTED_USER_SECURITY if (!ATX->trusted && strcmp(user, ATX->p->pw_name)) { LOG(LOG_ERR, ERR_TRUSTED_USER, ATX->p->pw_uid, ATX->p->pw_name); return EINVAL; } if (ATX->trusted) #endif nt_add (ATX->users, user); } continue; } if (!strncmp (argv[i], "--mail-from=", 12)) { strlcpy(ATX->mailfrom, strchr(argv[i], '=')+1, sizeof(ATX->mailfrom)); LOGDEBUG("MAIL FROM: %s", ATX->mailfrom); continue; } if (!strncmp (argv[i], "--profile=", 10)) { #ifdef TRUSTED_USER_SECURITY if (!ATX->trusted) { LOG(LOG_ERR, ERR_TRUSTED_PRIV, "--profile", ATX->p->pw_uid, ATX->p->pw_name); return EINVAL; } #endif if (!_ds_match_attribute(agent_config, "Profile", argv[i]+10)) { LOG(LOG_ERR,ERR_AGENT_NO_SUCH_PROFILE, argv[i]+10); return EINVAL; } else { _ds_overwrite_attribute(agent_config, "DefaultProfile", argv[i]+10); } continue; } if (!strncmp (argv[i], "--signature=", 12)) { strlcpy(ATX->signature, strchr(argv[i], '=')+1, sizeof(ATX->signature)); continue; } if (!strncmp (argv[i], "--class=", 8)) { char *ptr = strchr(argv[i], '=')+1; char *spam = _ds_read_attribute(agent_config, "ClassAliasSpam"); char *nonspam = _ds_read_attribute(agent_config, "ClassAliasNonspam"); if (!strcmp(ptr, "spam") || (spam && !strcmp(ptr, spam))) { ATX->classification = DSR_ISSPAM; } else if (!strcmp(ptr, "innocent") || !strcmp(ptr, "nonspam") || (nonspam && !strcmp(ptr, nonspam))) { ATX->classification = DSR_ISINNOCENT; } else { LOG(LOG_ERR, ERR_AGENT_NO_SUCH_CLASS, ptr); return EINVAL; } continue; } if (!strncmp (argv[i], "--source=", 9)) { char *ptr = strchr(argv[i], '=')+1; if (!strcmp(ptr, "corpus")) ATX->source = DSS_CORPUS; else if (!strcmp(ptr, "inoculation")) ATX->source = DSS_INOCULATION; else if (!strcmp(ptr, "error")) ATX->source = DSS_ERROR; else { LOG(LOG_ERR, ERR_AGENT_NO_SUCH_SOURCE, ptr); return EINVAL; } continue; } if (!strcmp (argv[i], "--classify")) { ATX->operating_mode = DSM_CLASSIFY; ATX->training_mode = DST_NOTRAIN; continue; } if (!strcmp (argv[i], "--process")) { ATX->operating_mode = DSM_PROCESS; continue; } if (!strncmp (argv[i], "--deliver=", 10)) { char *dup = strdup(strchr(argv[i], '=')+1); char *ptr; if (dup == NULL) { LOG(LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } ptr = strtok_r(dup, ",", &ptrptr); while(ptr != NULL) { if (!strcmp(ptr, "stdout")) { ATX->flags |= DAF_DELIVER_SPAM; ATX->flags |= DAF_DELIVER_INNOCENT; ATX->flags |= DAF_STDOUT; } else if (!strcmp(ptr, "spam")) ATX->flags |= DAF_DELIVER_SPAM; else if (!strcmp(ptr, "innocent") || !strcmp(ptr, "nonspam")) ATX->flags |= DAF_DELIVER_INNOCENT; else if (!strcmp(ptr, "summary")) ATX->flags |= DAF_SUMMARY; else { LOG(LOG_ERR, ERR_AGENT_NO_SUCH_DELIVER, ptr); free(dup); return EINVAL; } ptr = strtok_r(NULL, ",", &ptrptr); } free(dup); continue; } if (!strncmp (argv[i], "--feature=", 10)) { ATX->feature = 1; process_features(ATX, strchr(argv[i], '=')+1); continue; } if (!strcmp (argv[i], "--stdout")) { ATX->flags |= DAF_STDOUT; continue; } if (!strcmp (argv[i], "--help")) { fprintf (stderr, "%s\n", SYNTAX); exit(EXIT_SUCCESS); } if (!strcmp (argv[i], "--version")) { printf ("\nDSPAM Anti-Spam Suite %s (agent/library)\n\n", VERSION); printf ("Copyright (c) 2002-2006 Jonathan A. Zdziarski\n"); printf ("http://dspam.nuclearelephant.com\n\n"); printf ("DSPAM may be copied only under the terms of the GNU " "General Public License,\n"); printf ("a copy of which can be found with the DSPAM distribution " "kit.\n\n"); #ifdef TRUSTED_USER_SECURITY if (ATX->trusted) { #endif printf("Configuration parameters: %s\n\n", CONFIGURE_ARGS); #ifdef TRUSTED_USER_SECURITY } #endif exit (EXIT_SUCCESS); } /* Append all unknown arguments as mailer args */ if (i>0 #ifdef TRUSTED_USER_SECURITY && ATX->trusted #endif ) { if (argv[i][0] == 0) strlcat (ATX->mailer_args, "\"\"", sizeof (ATX->mailer_args)); else strlcat (ATX->mailer_args, argv[i], sizeof (ATX->mailer_args)); strlcat (ATX->mailer_args, " ", sizeof (ATX->mailer_args)); } } return 0; } /* * process_features(AGENT_CTX *, const char *) * * DESCRIPTION * convert --feature= stdin into agent context values * * INPUT ARGUMENTS * ATX agent context * in remainder of --feature= stdin * * RETURN VALUES * returns 0 on success, EINVAL when invalid options specified * */ int process_features(AGENT_CTX *ATX, const char *in) { char *ptr, *dup, *ptrptr; int ret = 0; if (!in || in[0]==0) return 0; dup = strdup(in); if (dup == NULL) { LOG(LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } ptr = strtok_r(dup, ",", &ptrptr); while(ptr != NULL) { if (!strncmp(ptr, "ch",2)) ATX->flags |= DAF_CHAINED; else if (!strncmp(ptr, "sb",2) || !strncmp(ptr, "ma", 2)) ATX->flags |= DAF_SBPH; else if (!strncmp(ptr, "no",2)) ATX->flags |= DAF_NOISE; else if (!strncmp(ptr, "wh", 2)) ATX->flags |= DAF_WHITELIST; else if (!strncmp(ptr, "tb=", 3)) { ATX->training_buffer = atoi(strchr(ptr, '=')+1); if (ATX->training_buffer < 0 || ATX->training_buffer > 10) { LOG(LOG_ERR, ERR_AGENT_TB_INVALID); ret = EINVAL; } } else { LOG(LOG_ERR, ERR_AGENT_NO_SUCH_FEATURE, ptr); ret = EINVAL; } ptr = strtok_r(NULL, ",", &ptrptr); } free(dup); return ret; } /* * process_mode(AGENT_CTX *, const char *) * * DESCRIPTION * convert --mode= stdin into training mode * * INPUT ARGUMENTS * ATX agent context * mode remainder of --mode= stdin * * RETURN VALUES * returns 0 on success, EINVAL when invalid mode specified */ int process_mode(AGENT_CTX *ATX, const char *mode) { if (!mode) return EINVAL; if (!strcmp(mode, "toe")) ATX->training_mode = DST_TOE; else if (!strcmp(mode, "teft")) ATX->training_mode = DST_TEFT; else if (!strcmp(mode, "tum")) ATX->training_mode = DST_TUM; else if (!strcmp(mode, "notrain")) ATX->training_mode = DST_NOTRAIN; else if (!strcmp(mode, "unlearn")) { ATX->training_mode = DST_TEFT; ATX->flags |= DAF_UNLEARN; } else { LOG(LOG_ERR, ERR_AGENT_TR_MODE_INVALID, mode); return EINVAL; } return 0; } /* * apply_defaults(AGENT_CTX *) * * DESCRIPTION * apply default values from dspam.conf in absence of other options * * INPUT ARGUMENTS * ATX agent context * * RETURN VALUES * returns 0 on success */ int apply_defaults(AGENT_CTX *ATX) { /* Training mode */ if (ATX->training_mode == DST_DEFAULT) { char *v = _ds_read_attribute(agent_config, "TrainingMode"); process_mode(ATX, v); } /* Default delivery agent */ if ( ! (ATX->flags & DAF_STDOUT) && ATX->operating_mode != DSM_CLASSIFY && (ATX->flags & DAF_DELIVER_INNOCENT || ATX->flags & DAF_DELIVER_SPAM)) { char key[32]; #ifdef TRUSTED_USER_SECURITY if (!ATX->trusted) strcpy(key, "UntrustedDeliveryAgent"); else #endif strcpy(key, "TrustedDeliveryAgent"); if (_ds_read_attribute(agent_config, key)) { char fmt[sizeof(ATX->mailer_args)]; snprintf(fmt, sizeof(fmt), "%s ", _ds_read_attribute(agent_config, key)); #ifdef TRUSTED_USER_SECURITY if (ATX->trusted) #endif strlcat(fmt, ATX->mailer_args, sizeof(fmt)); strcpy(ATX->mailer_args, fmt); } else if (!_ds_read_attribute(agent_config, "DeliveryHost")) { LOG(LOG_ERR, ERR_AGENT_NO_AGENT, key); return EINVAL; } } /* Default quarantine agent */ if (_ds_read_attribute(agent_config, "QuarantineAgent")) { snprintf(ATX->spam_args, sizeof(ATX->spam_args), "%s ", _ds_read_attribute(agent_config, "QuarantineAgent")); } else { LOGDEBUG("No QuarantineAgent option found. Using standard quarantine."); } /* Features */ if (!ATX->feature && _ds_find_attribute(agent_config, "Feature")) { attribute_t attrib = _ds_find_attribute(agent_config, "Feature"); while(attrib != NULL) { process_features(ATX, attrib->value); attrib = attrib->next; } } return 0; } /* * check_configuration(AGENT_CTX *) * * DESCRIPTION * sanity-check agent configuration * * INPUT ARGUMENTS * ATX agent context * * RETURN VALUES * returns 0 on success, EINVAL on invalid configuration */ int check_configuration(AGENT_CTX *ATX) { if (ATX->classification != DSR_NONE && ATX->operating_mode == DSM_CLASSIFY) { LOG(LOG_ERR, ERR_AGENT_CLASSIFY_CLASS); return EINVAL; } if (ATX->classification != DSR_NONE && ATX->source == DSS_NONE && !(ATX->flags & DAF_UNLEARN)) { LOG(LOG_ERR, ERR_AGENT_NO_SOURCE); return EINVAL; } if (ATX->source != DSS_NONE && ATX->classification == DSR_NONE) { LOG(LOG_ERR, ERR_AGENT_NO_CLASS); return EINVAL; } if (ATX->operating_mode == DSM_NONE) { LOG(LOG_ERR, ERR_AGENT_NO_OP_MODE); return EINVAL; } if (ATX->training_mode == DST_DEFAULT) { LOG(LOG_ERR, ERR_AGENT_NO_TR_MODE); return EINVAL; } if (!_ds_match_attribute(agent_config, "ParseToHeaders", "on")) { if (ATX->users->items == 0) { LOG(LOG_ERR, ERR_AGENT_USER_UNDEFINED); return EINVAL; } } return 0; } /* * read_stdin(AGENT_CTX *) * * DESCRIPTION * read message from stdin and perform any inline configuration * (such as servicing 'ParseToHeaders' functions) * * INPUT ARGUMENTS * ATX agent context * * RETURN VALUES * buffer structure containing the message */ buffer * read_stdin(AGENT_CTX *ATX) { int body = 0, line = 1; char buf[1024]; buffer *msg; msg = buffer_create(NULL); if (msg == NULL) { LOG(LOG_CRIT, ERR_MEM_ALLOC); return NULL; } if (_ds_match_attribute(agent_config, "DataSource", "document")) { buffer_cat(msg, ": \n\n"); body = 1; } /* Only read the message if no signature was provided on commandline */ if (ATX->signature[0] == 0) { while ((fgets (buf, sizeof (buf), stdin)) != NULL) { /* Strip CR/LFs for admittedly broken mail servers */ if (_ds_match_attribute(agent_config, "Broken", "lineStripping")) { size_t len = strlen(buf); while (len>1 && buf[len-2]==13) { buf[len-2] = buf[len-1]; buf[len-1] = 0; len--; } } /* Quote message termination characters, that could truncate messages */ if (buf[0] == '.' && buf[1] < 32) { char x[sizeof(buf)]; snprintf(x, sizeof(x), ".%s", buf); strcpy(buf, x); } /* * Don't include first line of message if it's a quarantine header added * by dspam at time of quarantine */ if (line==1 && !strncmp(buf, "From QUARANTINE", 15)) continue; /* * Parse the "To" headers and adjust the operating mode and user when * an email is sent to spam-* or notspam-* address. Behavior must be * configured in dspam.conf */ if (_ds_match_attribute(agent_config, "ParseToHeaders", "on")) { if (buf[0] == 0) body = 1; if (!body && !strncasecmp(buf, "To: ", 4)) process_parseto(ATX, buf); } if (buffer_cat (msg, buf)) { LOG (LOG_CRIT, ERR_MEM_ALLOC); goto bail; } /* * Use the original user id if we are reversing a false positive * (this is only necessary when using shared,managed groups */ if (!strncasecmp (buf, "X-DSPAM-User: ", 14) && ATX->operating_mode == DSM_PROCESS && ATX->classification == DSR_ISINNOCENT && ATX->source == DSS_ERROR) { char user[MAX_USERNAME_LENGTH]; strlcpy (user, buf + 14, sizeof (user)); chomp (user); nt_destroy (ATX->users); ATX->users = nt_create (NT_CHAR); if (ATX->users == NULL) { LOG(LOG_CRIT, ERR_MEM_ALLOC); goto bail; } LOGDEBUG("found username %s in X-DSPAM-User header", user); nt_add (ATX->users, user); } line++; } } if (!msg->used) { if (ATX->signature[0] != 0) { buffer_cat(msg, "\n\n"); } else { LOG (LOG_INFO, "empty message (no data received)"); goto bail; } } return msg; bail: LOGDEBUG("read_stdin() failure"); buffer_destroy(msg); return NULL; } /* * process_parseto(AGENT_CTX *, const char *) * * DESCRIPTION * processes the To: line of a message to provide parseto services * * INPUT ARGUMENTS * ATX agent context * buf To: line * * RETURN VALUES * returns 0 on success */ int process_parseto(AGENT_CTX *ATX, const char *buf) { char *y = NULL; char *x; if (!buf) return EINVAL; x = strstr(buf, "classification = DSR_ISSPAM; ATX->source = DSS_ERROR; } } else { x = strstr(buf, "= 9) { y = strdup(x+9); if (_ds_match_attribute(agent_config, "ChangeModeOnParse", "on")) { ATX->classification = DSR_ISINNOCENT; ATX->source = DSS_ERROR; } } } if (y && (_ds_match_attribute(agent_config, "ChangeUserOnParse", "on") || _ds_match_attribute(agent_config, "ChangeUserOnParse", "full") || _ds_match_attribute(agent_config, "ChangeUserOnParse", "user"))) { char *ptrptr; char *z; if (_ds_match_attribute(agent_config, "ChangeUserOnParse", "full")) { z = strtok_r(y, "> \n", &ptrptr); } else { if (!strstr(x, "spam@")) z = strtok_r(y, "@", &ptrptr); else z = NULL; } if (z) { nt_destroy(ATX->users); ATX->users = nt_create(NT_CHAR); if (!ATX->users) { LOG(LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } nt_add (ATX->users, z); } free(y); } return 0; }