MATCH_LIST *match_list_init(int flags, const char *patterns, int match_count,...) { MATCH_LIST *list; char *saved_patterns; va_list ap; int i; if (flags & ~MATCH_FLAG_ALL) msg_panic("match_list_init: bad flags 0x%x", flags); list = (MATCH_LIST *) mymalloc(sizeof(*list)); list->flags = flags; list->match_count = match_count; list->match_func = (MATCH_LIST_FN *) mymalloc(match_count * sizeof(MATCH_LIST_FN)); list->match_args = (const char **) mymalloc(match_count * sizeof(const char *)); va_start(ap, match_count); for (i = 0; i < match_count; i++) list->match_func[i] = va_arg(ap, MATCH_LIST_FN); va_end(ap); list->error = 0; #define DO_MATCH 1 saved_patterns = mystrdup(patterns); list->patterns = match_list_parse(argv_alloc(1), saved_patterns, DO_MATCH); argv_terminate(list->patterns); myfree(saved_patterns); return (list); }
ARGV *dict_mapnames() { HTABLE_INFO **ht_info; HTABLE_INFO **ht; DICT_OPEN_INFO *dp; ARGV *mapnames; #ifndef NO_DYNAMIC_MAPS DLINFO *dlp; #endif if (dict_open_hash == 0) dict_open_init(); mapnames = argv_alloc(dict_open_hash->used + 1); for (ht_info = ht = htable_list(dict_open_hash); *ht; ht++) { dp = (DICT_OPEN_INFO *) ht[0]->value; argv_add(mapnames, dp->type, ARGV_END); } #ifndef NO_DYNAMIC_MAPS if (!dict_dlinfo) msg_fatal("dlinfo==NULL"); for (dlp=dict_dlinfo; dlp->pattern; dlp++) { argv_add(mapnames, dlp->pattern, ARGV_END); } #endif qsort((void *) mapnames->argv, mapnames->argc, sizeof(mapnames->argv[0]), dict_sort_alpha_cpp); myfree((char *) ht_info); argv_terminate(mapnames); return mapnames; }
void smtp_chat_notify(SMTP_SESSION *session) { const char *myname = "smtp_chat_notify"; VSTREAM *notice; char **cpp; /* * Sanity checks. */ if (session->history == 0) msg_panic("%s: no conversation history", myname); if (msg_verbose) msg_info("%s: notify postmaster", myname); /* * Construct a message for the postmaster, explaining what this is all * about. This is junk mail: don't send it when the mail posting service * is unavailable, and use the double bounce sender address, to prevent * mail bounce wars. Always prepend one space to message content that we * generate from untrusted data. */ #define NULL_TRACE_FLAGS 0 #define NO_QUEUE_ID ((VSTRING *) 0) #define LENGTH 78 #define INDENT 4 notice = post_mail_fopen_nowait(mail_addr_double_bounce(), var_error_rcpt, MAIL_SRC_MASK_NOTIFY, NULL_TRACE_FLAGS, SMTPUTF8_FLAG_NONE, NO_QUEUE_ID); if (notice == 0) { msg_warn("postmaster notify: %m"); return; } post_mail_fprintf(notice, "From: %s (Mail Delivery System)", mail_addr_mail_daemon()); post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt); post_mail_fprintf(notice, "Subject: %s %s client: errors from %s", var_mail_name, smtp_mode ? "SMTP" : "LMTP", session->namaddrport); post_mail_fputs(notice, ""); post_mail_fprintf(notice, "Unexpected response from %s.", session->namaddrport); post_mail_fputs(notice, ""); post_mail_fputs(notice, "Transcript of session follows."); post_mail_fputs(notice, ""); argv_terminate(session->history); for (cpp = session->history->argv; *cpp; cpp++) line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line, (void *) notice); post_mail_fputs(notice, ""); post_mail_fprintf(notice, "For other details, see the local mail logfile"); (void) post_mail_fclose(notice); }
ARGV *mbox_lock_names(void) { const NAME_MASK *np; ARGV *argv; argv = argv_alloc(2); for (np = mbox_mask; np->name != 0; np++) argv_add(argv, np->name, ARGV_END); argv_terminate(argv); return (argv); }
ARGV *match_service_init_argv(char **patterns) { ARGV *list = argv_alloc(1); char **cpp; for (cpp = patterns; *cpp; cpp++) argv_add(list, *cpp, (char *) 0); argv_terminate(list); match_service_compat(list); return (list); }
ARGV *argv_split_append(ARGV *argvp, const char *string, const char *delim) { char *saved_string = mystrdup(string); char *bp = saved_string; char *arg; while ((arg = mystrtok(&bp, delim)) != 0) argv_add(argvp, arg, (char *) 0); argv_terminate(argvp); myfree(saved_string); return (argvp); }
ARGV *match_service_init(const char *patterns) { const char *delim = " ,\t\r\n"; ARGV *list = argv_alloc(1); char *saved_patterns = mystrdup(patterns); char *bp = saved_patterns; const char *item; while ((item = mystrtok(&bp, delim)) != 0) argv_add(list, item, (char *) 0); argv_terminate(list); myfree(saved_patterns); return (list); }
void smtpd_chat_notify(SMTPD_STATE *state) { char *myname = "smtpd_chat_notify"; VSTREAM *notice; char **cpp; /* * Sanity checks. */ if (state->history == 0) msg_panic("%s: no conversation history", myname); if (msg_verbose) msg_info("%s: notify postmaster", myname); /* * Construct a message for the postmaster, explaining what this is all * about. This is junk mail: don't send it when the mail posting service * is unavailable, and use the double bounce sender address to prevent * mail bounce wars. Always prepend one space to message content that we * generate from untrusted data. */ #define NULL_TRACE_FLAGS 0 #define LENGTH 78 #define INDENT 4 notice = post_mail_fopen_nowait(mail_addr_double_bounce(), var_error_rcpt, CLEANUP_FLAG_MASK_INTERNAL, NULL_TRACE_FLAGS); if (notice == 0) { msg_warn("postmaster notify: %m"); return; } post_mail_fprintf(notice, "From: %s (Mail Delivery System)", mail_addr_mail_daemon()); post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt); post_mail_fprintf(notice, "Subject: %s SMTP server: errors from %s[%s]", var_mail_name, state->name, state->addr); post_mail_fputs(notice, ""); post_mail_fputs(notice, "Transcript of session follows."); post_mail_fputs(notice, ""); argv_terminate(state->history); for (cpp = state->history->argv; *cpp; cpp++) line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line, (char *) notice); post_mail_fputs(notice, ""); if (state->reason) post_mail_fprintf(notice, "Session aborted, reason: %s", state->reason); (void) post_mail_fclose(notice); }
ARGV *mail_addr_crunch(const char *string, const char *extension) { VSTRING *extern_addr = vstring_alloc(100); VSTRING *canon_addr = vstring_alloc(100); ARGV *argv = argv_alloc(1); TOK822 *tree; TOK822 **addr_list; TOK822 **tpp; char *ratsign; ssize_t extlen; if (extension) extlen = strlen(extension); #define STR(x) vstring_str(x) /* * Parse the string, rewrite each address to canonical form, and convert * the result to external (quoted) form. Optionally apply the extension * to each address found. * * XXX Workaround for the null address. This works for envelopes but * produces ugly results for message headers. */ if (*string == 0 || strcmp(string, "<>") == 0) string = "\"\""; tree = tok822_parse(string); addr_list = tok822_grep(tree, TOK822_ADDR); for (tpp = addr_list; *tpp; tpp++) { tok822_externalize(extern_addr, tpp[0]->head, TOK822_STR_DEFL); canon_addr_external(canon_addr, STR(extern_addr)); if (extension) { VSTRING_SPACE(canon_addr, extlen + 1); if ((ratsign = strrchr(STR(canon_addr), '@')) == 0) { vstring_strcat(canon_addr, extension); } else { memmove(ratsign + extlen, ratsign, strlen(ratsign) + 1); memcpy(ratsign, extension, extlen); VSTRING_SKIP(canon_addr); } } argv_add(argv, STR(canon_addr), ARGV_END); } argv_terminate(argv); myfree((char *) addr_list); tok822_free_tree(tree); vstring_free(canon_addr); vstring_free(extern_addr); return (argv); }
ARGV *argv_split_count(const char *string, const char *delim, ssize_t count) { ARGV *argvp = argv_alloc(1); char *saved_string = mystrdup(string); char *bp = saved_string; char *arg; if (count < 1) msg_panic("argv_split_count: bad count: %ld", (long) count); while (count-- > 1 && (arg = mystrtok(&bp, delim)) != 0) argv_add(argvp, arg, (char *) 0); if (*bp) bp += strspn(bp, delim); if (*bp) argv_add(argvp, bp, (char *) 0); argv_terminate(argvp); myfree(saved_string); return (argvp); }
ARGV *dict_mapnames() { HTABLE_INFO **ht_info; HTABLE_INFO **ht; DICT_OPEN_INFO *dp; ARGV *mapnames; if (dict_open_hash == 0) dict_open_init(); mapnames = argv_alloc(dict_open_hash->used + 1); for (ht_info = ht = htable_list(dict_open_hash); *ht; ht++) { dp = (DICT_OPEN_INFO *) ht[0]->value; argv_add(mapnames, dp->type, ARGV_END); } qsort((void *) mapnames->argv, mapnames->argc, sizeof(mapnames->argv[0]), dict_sort_alpha_cpp); myfree((char *) ht_info); argv_terminate(mapnames); return mapnames; }
SERVER_ACL *server_acl_parse(const char *extern_acl, const char *origin) { char *saved_acl = mystrdup(extern_acl); SERVER_ACL *intern_acl = argv_alloc(1); char *bp = saved_acl; char *acl; #define STREQ(x,y) ((*x) == (*y) && strcasecmp((x), (y)) == 0) #define STRNE(x,y) ((*x) != (*y) || strcasecmp((x), (y)) != 0) /* * Nested tables are not allowed. Tables are opened before entering the * chroot jail, while access lists are evaluated after entering the * chroot jail. */ while ((acl = mystrtok(&bp, SERVER_ACL_SEPARATORS)) != 0) { if (strchr(acl, ':') != 0) { if (strchr(origin, ':') != 0) { msg_warn("table %s: lookup result \"%s\" is not allowed" " -- ignoring remainder of access list", origin, acl); argv_add(intern_acl, SERVER_ACL_NAME_DUNNO, (char *) 0); break; } else { if (dict_handle(acl) == 0) dict_register(acl, dict_open(acl, O_RDONLY, DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX)); } } argv_add(intern_acl, acl, (char *) 0); } argv_terminate(intern_acl); /* * Cleanup. */ myfree(saved_acl); return (intern_acl); }
static void show_queue(void) { const char *errstr; char buf[VSTREAM_BUFSIZE]; VSTREAM *showq; int n; uid_t uid = getuid(); if (uid != 0 && uid != var_owner_uid && (errstr = check_user_acl_byuid(VAR_SHOWQ_ACL, var_showq_acl, uid)) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to view the mail queue", errstr, (long) uid); /* * Connect to the show queue service. Terminate silently when piping into * a program that terminates early. */ if ((showq = mail_connect(MAIL_CLASS_PUBLIC, var_showq_service, BLOCKING)) != 0) { while ((n = vstream_fread(showq, buf, sizeof(buf))) > 0) { if (vstream_fwrite(VSTREAM_OUT, buf, n) != n || vstream_fflush(VSTREAM_OUT) != 0) { if (errno == EPIPE) break; msg_fatal("write error: %m"); } } if (vstream_fclose(showq) && errno != EPIPE) msg_warn("close: %m"); } /* * Don't assume that the mail system is down when the user has * insufficient permission to access the showq socket. */ else if (errno == EACCES) { msg_fatal_status(EX_SOFTWARE, "Connect to the %s %s service: %m", var_mail_name, var_showq_service); } /* * When the mail system is down, the superuser can still access the queue * directly. Just run the showq program in stand-alone mode. */ else if (geteuid() == 0) { ARGV *argv; int stat; msg_warn("Mail system is down -- accessing queue directly"); argv = argv_alloc(6); argv_add(argv, var_showq_service, "-u", "-S", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(argv, "-v", (char *) 0); argv_terminate(argv); stat = mail_run_foreground(var_daemon_dir, argv->argv); argv_free(argv); if (stat != 0) msg_fatal_status(stat < 0 ? EX_OSERR : EX_SOFTWARE, "Error running %s/%s", var_daemon_dir, argv->argv[0]); } /* * When the mail system is down, unprivileged users are stuck, because by * design the mail system contains no set_uid programs. The only way for * an unprivileged user to cross protection boundaries is to talk to the * showq daemon. */ else { msg_fatal_status(EX_UNAVAILABLE, "Queue report unavailable - mail system is down"); } }
int deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, const char *command) { const char *myname = "deliver_command"; DSN_BUF *why = state.msg_attr.why; int cmd_status; int deliver_status; ARGV *env; int copy_flags; char **cpp; char *cp; ARGV *export_env; VSTRING *exec_dir; int expand_status; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE ELIMINATION * * Skip this command if it was already delivered to as this user. */ if (been_here(state.dup_filter, "command %s:%ld %s", state.msg_attr.user, (long) usr_attr.uid, command)) return (0); /* * Don't deliver a trace-only request. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to command: %s", command); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * DELIVERY RIGHTS * * Choose a default uid and gid when none have been selected (i.e. values * are still zero). */ if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0) msg_panic("privileged default user id"); if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0) msg_panic("privileged default group id"); /* * Deliver. */ copy_flags = MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH | MAIL_COPY_ORIG_RCPT; if (local_deliver_hdr_mask & DELIVER_HDR_CMD) copy_flags |= MAIL_COPY_DELIVERED; if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("%s: seek queue file %s: %m", myname, VSTREAM_PATH(state.msg_attr.fp)); /* * Pass additional environment information. XXX This should be * configurable. However, passing untrusted information via environment * parameters opens up a whole can of worms. Lesson from web servers: * don't let any network data even near a shell. It causes trouble. */ env = argv_alloc(1); if (usr_attr.home) argv_add(env, "HOME", usr_attr.home, ARGV_END); argv_add(env, "LOGNAME", state.msg_attr.user, "USER", state.msg_attr.user, "SENDER", state.msg_attr.sender, "RECIPIENT", state.msg_attr.rcpt.address, "LOCAL", state.msg_attr.local, ARGV_END); if (usr_attr.shell) argv_add(env, "SHELL", usr_attr.shell, ARGV_END); if (state.msg_attr.domain) argv_add(env, "DOMAIN", state.msg_attr.domain, ARGV_END); if (state.msg_attr.extension) argv_add(env, "EXTENSION", state.msg_attr.extension, ARGV_END); if (state.msg_attr.rcpt.orig_addr && state.msg_attr.rcpt.orig_addr[0]) argv_add(env, "ORIGINAL_RECIPIENT", state.msg_attr.rcpt.orig_addr, ARGV_END); #define EXPORT_REQUEST(name, value) \ if ((value)[0]) argv_add(env, (name), (value), ARGV_END); EXPORT_REQUEST("CLIENT_HOSTNAME", state.msg_attr.request->client_name); EXPORT_REQUEST("CLIENT_ADDRESS", state.msg_attr.request->client_addr); EXPORT_REQUEST("CLIENT_HELO", state.msg_attr.request->client_helo); EXPORT_REQUEST("CLIENT_PROTOCOL", state.msg_attr.request->client_proto); EXPORT_REQUEST("SASL_METHOD", state.msg_attr.request->sasl_method); EXPORT_REQUEST("SASL_SENDER", state.msg_attr.request->sasl_sender); EXPORT_REQUEST("SASL_USERNAME", state.msg_attr.request->sasl_username); argv_terminate(env); /* * Censor out undesirable characters from exported data. */ for (cpp = env->argv; *cpp; cpp += 2) for (cp = cpp[1]; *(cp += strspn(cp, var_cmd_exp_filter)) != 0;) *cp++ = '_'; /* * Evaluate the command execution directory. Defer delivery if expansion * fails. */ export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ); exec_dir = vstring_alloc(10); expand_status = local_expand(exec_dir, var_exec_directory, &state, &usr_attr, var_exec_exp_filter); if (expand_status & MAC_PARSE_ERROR) { cmd_status = PIPE_STAT_DEFER; dsb_simple(why, "4.3.5", "mail system configuration error"); msg_warn("bad parameter value syntax for %s: %s", VAR_EXEC_DIRECTORY, var_exec_directory); } else { cmd_status = pipe_command(state.msg_attr.fp, why, PIPE_CMD_UID, usr_attr.uid, PIPE_CMD_GID, usr_attr.gid, PIPE_CMD_COMMAND, command, PIPE_CMD_COPY_FLAGS, copy_flags, PIPE_CMD_SENDER, state.msg_attr.sender, PIPE_CMD_ORIG_RCPT, state.msg_attr.rcpt.orig_addr, PIPE_CMD_DELIVERED, state.msg_attr.delivered, PIPE_CMD_TIME_LIMIT, var_command_maxtime, PIPE_CMD_ENV, env->argv, PIPE_CMD_EXPORT, export_env->argv, PIPE_CMD_SHELL, var_local_cmd_shell, PIPE_CMD_CWD, *STR(exec_dir) ? STR(exec_dir) : (char *) 0, PIPE_CMD_END); } vstring_free(exec_dir); argv_free(export_env); argv_free(env); /* * Depending on the result, bounce or defer the message. */ switch (cmd_status) { case PIPE_STAT_OK: dsb_simple(why, "2.0.0", "delivered to command: %s", command); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); break; case PIPE_STAT_BOUNCE: case PIPE_STAT_DEFER: /* Account for possible owner- sender address override. */ deliver_status = bounce_workaround(state); break; case PIPE_STAT_CORRUPT: deliver_status = DEL_STAT_DEFER; break; default: msg_panic("%s: bad status %d", myname, cmd_status); /* NOTREACHED */ } return (deliver_status); }
ARGV *cleanup_map1n_internal(CLEANUP_STATE *state, const char *addr, MAPS *maps, int propagate) { ARGV *argv; ARGV *lookup; int count; int i; int arg; BH_TABLE *been_here; char *saved_lhs; /* * Initialize. */ argv = argv_alloc(1); argv_add(argv, addr, ARGV_END); argv_terminate(argv); been_here = been_here_init(0, BH_FLAG_FOLD); /* * Rewrite the address vector in place. With each map lookup result, * split it into separate addresses, then rewrite and flatten each * address, and repeat the process. Beware: argv is being changed, so we * must index the array explicitly, instead of running along it with a * pointer. */ #define UPDATE(ptr,new) do { \ if (ptr) myfree(ptr); ptr = mystrdup(new); \ } while (0) #define STR vstring_str #define RETURN(x) do { \ been_here_free(been_here); return (x); \ } while (0) #define UNEXPAND(argv, addr) do { \ argv_truncate((argv), 0); argv_add((argv), (addr), (char *) 0); \ } while (0) for (arg = 0; arg < argv->argc; arg++) { if (argv->argc > var_virt_expan_limit) { msg_warn("%s: unreasonable %s map expansion size for %s -- " "message not accepted, try again later", state->queue_id, maps->title, addr); state->errs |= CLEANUP_STAT_DEFER; UPDATE(state->reason, "4.6.0 Alias expansion error"); UNEXPAND(argv, addr); RETURN(argv); } for (count = 0; /* void */ ; count++) { /* * Don't expand an address that already expanded into itself. */ if (been_here_check_fixed(been_here, argv->argv[arg]) != 0) break; if (count >= var_virt_recur_limit) { msg_warn("%s: unreasonable %s map nesting for %s -- " "message not accepted, try again later", state->queue_id, maps->title, addr); state->errs |= CLEANUP_STAT_DEFER; UPDATE(state->reason, "4.6.0 Alias expansion error"); UNEXPAND(argv, addr); RETURN(argv); } quote_822_local(state->temp1, argv->argv[arg]); if ((lookup = mail_addr_map(maps, STR(state->temp1), propagate)) != 0) { saved_lhs = mystrdup(argv->argv[arg]); for (i = 0; i < lookup->argc; i++) { unquote_822_local(state->temp1, lookup->argv[i]); if (i == 0) { UPDATE(argv->argv[arg], STR(state->temp1)); } else { argv_add(argv, STR(state->temp1), ARGV_END); argv_terminate(argv); } /* * Allow an address to expand into itself once. */ if (strcasecmp(saved_lhs, STR(state->temp1)) == 0) been_here_fixed(been_here, saved_lhs); } myfree(saved_lhs); argv_free(lookup); } else if (maps->error != 0) { msg_warn("%s: %s map lookup problem for %s -- " "message not accepted, try again later", state->queue_id, maps->title, addr); state->errs |= CLEANUP_STAT_WRITE; UPDATE(state->reason, "4.6.0 Alias expansion error"); UNEXPAND(argv, addr); RETURN(argv); } else { break; } } } RETURN(argv); }
int main(int argc, char **argv) { static char *full_name = 0; /* sendmail -F */ struct stat st; char *slash; char *sender = 0; /* sendmail -f */ int c; int fd; int mode; ARGV *ext_argv; int debug_me = 0; int err; int n; int flags = SM_FLAG_DEFAULT; char *site_to_flush = 0; char *id_to_flush = 0; char *encoding = 0; char *qtime = 0; const char *errstr; uid_t uid; const char *rewrite_context = MAIL_ATTR_RWR_LOCAL; int dsn_notify = 0; int dsn_ret = 0; const char *dsn_envid = 0; int saved_optind; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * Be consistent with file permissions. */ umask(022); /* * To minimize confusion, make sure that the standard file descriptors * are open before opening anything else. XXX Work around for 44BSD where * fstat can return EBADF on an open file descriptor. */ for (fd = 0; fd < 3; fd++) if (fstat(fd, &st) == -1 && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) msg_fatal_status(EX_OSERR, "open /dev/null: %m"); /* * The CDE desktop calendar manager leaks a parent file descriptor into * the child process. For the sake of sendmail compatibility we have to * close the file descriptor otherwise mail notification will hang. */ for ( /* void */ ; fd < 100; fd++) (void) close(fd); /* * Process environment options as early as we can. We might be called * from a set-uid (set-gid) program, so be careful with importing * environment variables. */ if (safe_getenv(CONF_ENV_VERB)) msg_verbose = 1; if (safe_getenv(CONF_ENV_DEBUG)) debug_me = 1; /* * Initialize. Set up logging, read the global configuration file and * extract configuration information. Set up signal handlers so that we * can clean up incomplete output. */ if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) argv[0] = slash + 1; msg_vstream_init(argv[0], VSTREAM_ERR); msg_cleanup(tempfail); msg_syslog_init(mail_task("sendmail"), LOG_PID, LOG_FACILITY); set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * Some sites mistakenly install Postfix sendmail as set-uid root. Drop * set-uid privileges only when root, otherwise some systems will not * reset the saved set-userid, which would be a security vulnerability. */ if (geteuid() == 0 && getuid() != 0) { msg_warn("the Postfix sendmail command has set-uid root file permissions"); msg_warn("or the command is run from a set-uid root process"); msg_warn("the Postfix sendmail command must be installed without set-uid root file permissions"); set_ugid(getuid(), getgid()); } /* * Further initialization. Load main.cf first, so that command-line * options can override main.cf settings. Pre-scan the argument list so * that we load the right main.cf file. */ #define GETOPT_LIST "A:B:C:F:GIL:N:O:R:UV:X:b:ce:f:h:imno:p:r:q:tvx" saved_optind = optind; while (argv[OPTIND] != 0) { if (strcmp(argv[OPTIND], "-q") == 0) { /* not getopt compatible */ optind++; continue; } if ((c = GETOPT(argc, argv, GETOPT_LIST)) <= 0) break; if (c == 'C') { VSTRING *buf = vstring_alloc(1); if (setenv(CONF_ENV_PATH, strcmp(sane_basename(buf, optarg), MAIN_CONF_FILE) == 0 ? sane_dirname(buf, optarg) : optarg, 1) < 0) msg_fatal_status(EX_UNAVAILABLE, "out of memory"); vstring_free(buf); } } optind = saved_optind; mail_conf_read(); /* Re-evaluate mail_task() after reading main.cf. */ msg_syslog_init(mail_task("sendmail"), LOG_PID, LOG_FACILITY); get_mail_conf_str_table(str_table); if (chdir(var_queue_dir)) msg_fatal_status(EX_UNAVAILABLE, "chdir %s: %m", var_queue_dir); signal(SIGPIPE, SIG_IGN); /* * Optionally start the debugger on ourself. This must be done after * reading the global configuration file, because that file specifies * what debugger command to execute. */ if (debug_me) debug_process(); /* * The default mode of operation is determined by the process name. It * can, however, be changed via command-line options (for example, * "newaliases -bp" will show the mail queue). */ if (strcmp(argv[0], "mailq") == 0) { mode = SM_MODE_MAILQ; } else if (strcmp(argv[0], "newaliases") == 0) { mode = SM_MODE_NEWALIAS; } else if (strcmp(argv[0], "smtpd") == 0) { mode = SM_MODE_DAEMON; } else { mode = SM_MODE_ENQUEUE; } /* * Parse JCL. Sendmail has been around for a long time, and has acquired * a large number of options in the course of time. Some options such as * -q are not parsable with GETOPT() and get special treatment. */ #define OPTIND (optind > 0 ? optind : 1) while (argv[OPTIND] != 0) { if (strcmp(argv[OPTIND], "-q") == 0) { if (mode == SM_MODE_DAEMON) msg_warn("ignoring -q option in daemon mode"); else mode = SM_MODE_FLUSHQ; optind++; continue; } if (strcmp(argv[OPTIND], "-V") == 0 && argv[OPTIND + 1] != 0 && strlen(argv[OPTIND + 1]) == 2) { msg_warn("option -V is deprecated with Postfix 2.3; " "specify -XV instead"); argv[OPTIND] = "-XV"; } if (strncmp(argv[OPTIND], "-V", 2) == 0 && strlen(argv[OPTIND]) == 4) { msg_warn("option %s is deprecated with Postfix 2.3; " "specify -X%s instead", argv[OPTIND], argv[OPTIND] + 1); argv[OPTIND] = concatenate("-X", argv[OPTIND] + 1, (char *) 0); } if (strcmp(argv[OPTIND], "-XV") == 0) { verp_delims = var_verp_delims; optind++; continue; } if ((c = GETOPT(argc, argv, GETOPT_LIST)) <= 0) break; switch (c) { default: if (msg_verbose) msg_info("-%c option ignored", c); break; case 'n': msg_fatal_status(EX_USAGE, "-%c option not supported", c); case 'B': if (strcmp(optarg, "8BITMIME") == 0)/* RFC 1652 */ encoding = MAIL_ATTR_ENC_8BIT; else if (strcmp(optarg, "7BIT") == 0) /* RFC 1652 */ encoding = MAIL_ATTR_ENC_7BIT; else msg_fatal_status(EX_USAGE, "-B option needs 8BITMIME or 7BIT"); break; case 'F': /* full name */ full_name = optarg; break; case 'G': /* gateway submission */ rewrite_context = MAIL_ATTR_RWR_REMOTE; break; case 'I': /* newaliases */ mode = SM_MODE_NEWALIAS; break; case 'N': if ((dsn_notify = dsn_notify_mask(optarg)) == 0) msg_warn("bad -N option value -- ignored"); break; case 'R': if ((dsn_ret = dsn_ret_code(optarg)) == 0) msg_warn("bad -R option value -- ignored"); break; case 'V': /* DSN, was: VERP */ if (strlen(optarg) > 100) msg_warn("too long -V option value -- ignored"); else if (!allprint(optarg)) msg_warn("bad syntax in -V option value -- ignored"); else dsn_envid = optarg; break; case 'X': switch (*optarg) { default: msg_fatal_status(EX_USAGE, "unsupported: -%c%c", c, *optarg); case 'V': /* VERP */ if (verp_delims_verify(optarg + 1) != 0) msg_fatal_status(EX_USAGE, "-V requires two characters from %s", var_verp_filter); verp_delims = optarg + 1; break; } break; case 'b': switch (*optarg) { default: msg_fatal_status(EX_USAGE, "unsupported: -%c%c", c, *optarg); case 'd': /* daemon mode */ case 'l': /* daemon mode */ if (mode == SM_MODE_FLUSHQ) msg_warn("ignoring -q option in daemon mode"); mode = SM_MODE_DAEMON; break; case 'h': /* print host status */ case 'H': /* flush host status */ mode = SM_MODE_IGNORE; break; case 'i': /* newaliases */ mode = SM_MODE_NEWALIAS; break; case 'm': /* deliver mail */ mode = SM_MODE_ENQUEUE; break; case 'p': /* mailq */ mode = SM_MODE_MAILQ; break; case 's': /* stand-alone mode */ mode = SM_MODE_USER; break; case 'v': /* expand recipients */ flags |= DEL_REQ_FLAG_USR_VRFY; break; } break; case 'f': sender = optarg; break; case 'i': flags &= ~SM_FLAG_AEOF; break; case 'o': switch (*optarg) { default: if (msg_verbose) msg_info("-%c%c option ignored", c, *optarg); break; case 'A': if (optarg[1] == 0) msg_fatal_status(EX_USAGE, "-oA requires pathname"); myfree(var_alias_db_map); var_alias_db_map = mystrdup(optarg + 1); set_mail_conf_str(VAR_ALIAS_DB_MAP, var_alias_db_map); break; case '7': case '8': break; case 'i': flags &= ~SM_FLAG_AEOF; break; case 'm': break; } break; case 'r': /* obsoleted by -f */ sender = optarg; break; case 'q': if (ISDIGIT(optarg[0])) { qtime = optarg; } else if (optarg[0] == 'R') { site_to_flush = optarg + 1; if (*site_to_flush == 0) msg_fatal_status(EX_USAGE, "specify: -qRsitename"); } else if (optarg[0] == 'I') { id_to_flush = optarg + 1; if (*id_to_flush == 0) msg_fatal_status(EX_USAGE, "specify: -qIqueueid"); } else { msg_fatal_status(EX_USAGE, "-q%c is not implemented", optarg[0]); } break; case 't': flags |= SM_FLAG_XRCPT; break; case 'v': msg_verbose++; break; case '?': msg_fatal_status(EX_USAGE, "usage: %s [options]", argv[0]); } } /* * Look for conflicting options and arguments. */ if ((flags & SM_FLAG_XRCPT) && mode != SM_MODE_ENQUEUE) msg_fatal_status(EX_USAGE, "-t can be used only in delivery mode"); if (site_to_flush && mode != SM_MODE_ENQUEUE) msg_fatal_status(EX_USAGE, "-qR can be used only in delivery mode"); if (id_to_flush && mode != SM_MODE_ENQUEUE) msg_fatal_status(EX_USAGE, "-qI can be used only in delivery mode"); if (flags & DEL_REQ_FLAG_USR_VRFY) { if (flags & SM_FLAG_XRCPT) msg_fatal_status(EX_USAGE, "-t option cannot be used with -bv"); if (dsn_notify) msg_fatal_status(EX_USAGE, "-N option cannot be used with -bv"); if (dsn_ret) msg_fatal_status(EX_USAGE, "-R option cannot be used with -bv"); if (msg_verbose == 1) msg_fatal_status(EX_USAGE, "-v option cannot be used with -bv"); } /* * The -v option plays double duty. One requests verbose delivery, more * than one requests verbose logging. */ if (msg_verbose == 1 && mode == SM_MODE_ENQUEUE) { msg_verbose = 0; flags |= DEL_REQ_FLAG_RECORD; } /* * Start processing. Everything is delegated to external commands. */ if (qtime && mode != SM_MODE_DAEMON) exit(0); switch (mode) { default: msg_panic("unknown operation mode: %d", mode); /* NOTREACHED */ case SM_MODE_ENQUEUE: if (site_to_flush) { if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "flush site requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postqueue", "-s", site_to_flush, (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ } else if (id_to_flush) { if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "flush queue_id requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postqueue", "-i", id_to_flush, (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ } else { enqueue(flags, encoding, dsn_envid, dsn_ret, dsn_notify, rewrite_context, sender, full_name, argv + OPTIND); exit(0); /* NOTREACHED */ } break; case SM_MODE_MAILQ: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "display queue mode requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postqueue", "-p", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ case SM_MODE_FLUSHQ: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "flush queue mode requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postqueue", "-f", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ case SM_MODE_DAEMON: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "daemon mode requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postfix", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_add(ext_argv, "start", (char *) 0); argv_terminate(ext_argv); err = (mail_run_background(var_command_dir, ext_argv->argv) < 0); argv_free(ext_argv); exit(err); break; case SM_MODE_NEWALIAS: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "alias initialization mode requires no recipient"); if (*var_alias_db_map == 0) return (0); ext_argv = argv_alloc(2); argv_add(ext_argv, "postalias", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_split_append(ext_argv, var_alias_db_map, CHARS_COMMA_SP); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ case SM_MODE_USER: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "stand-alone mode requires no recipient"); /* The actual enforcement happens in the postdrop command. */ if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl, uid = getuid())) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to submit mail", errstr, (long) uid); ext_argv = argv_alloc(2); argv_add(ext_argv, "smtpd", "-S", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_daemon_dir, ext_argv->argv); /* NOTREACHED */ case SM_MODE_IGNORE: exit(0); /* NOTREACHED */ } }
/* mysqlname_parse - parse mysql configuration file */ static MYSQL_NAME *mysqlname_parse(const char *mysqlcf) { const char *myname = "mysqlname_parse"; int i; char *hosts; MYSQL_NAME *name = (MYSQL_NAME *) mymalloc(sizeof(MYSQL_NAME)); ARGV *hosts_argv; /* parser */ name->parser = cfg_parser_alloc(mysqlcf); /* username */ name->username = cfg_get_str(name->parser, "user", "", 0, 0); /* password */ name->password = cfg_get_str(name->parser, "password", "", 0, 0); /* database name */ name->dbname = cfg_get_str(name->parser, "dbname", "", 1, 0); /* table name */ name->table = cfg_get_str(name->parser, "table", "", 1, 0); /* select field */ name->select_field = cfg_get_str(name->parser, "select_field", "", 1, 0); /* where field */ name->where_field = cfg_get_str(name->parser, "where_field", "", 1, 0); /* additional conditions */ name->additional_conditions = cfg_get_str(name->parser, "additional_conditions", "", 0, 0); /* mysql server hosts */ hosts = cfg_get_str(name->parser, "hosts", "", 0, 0); /* coo argv interface */ hosts_argv = argv_split(hosts, " ,\t\r\n"); if (hosts_argv->argc == 0) { /* no hosts specified, * default to 'localhost' */ if (msg_verbose) msg_info("%s: %s: no hostnames specified, defaulting to 'localhost'", myname, mysqlcf); argv_add(hosts_argv, "localhost", ARGV_END); argv_terminate(hosts_argv); } name->len_hosts = hosts_argv->argc; name->hostnames = (char **) mymalloc((sizeof(char *)) * name->len_hosts); i = 0; for (i = 0; hosts_argv->argv[i] != NULL; i++) { name->hostnames[i] = mystrdup(hosts_argv->argv[i]); if (msg_verbose) msg_info("%s: %s: adding host '%s' to list of mysql server hosts", myname, mysqlcf, name->hostnames[i]); } myfree(hosts); argv_free(hosts_argv); return name; }
static ARGV *expand_argv(const char *service, char **argv, RECIPIENT_LIST *rcpt_list, int flags) { VSTRING *buf = vstring_alloc(100); ARGV *result; char **cpp; PIPE_STATE state; int i; char *ext; char *dom; /* * This appears to be simple operation (replace $name by its expansion). * However, it becomes complex because a command-line argument that * references $recipient must expand to as many command-line arguments as * there are recipients (that's wat programs called by sendmail expect). * So we parse each command-line argument, and depending on what we find, * we either expand the argument just once, or we expand it once for each * recipient. In either case we end up parsing the command-line argument * twice. The amount of CPU time wasted will be negligible. * * Note: we can't use recursive macro expansion here, because recursion * would screw up mail addresses that contain $ characters. */ #define NO 0 #define EARLY_RETURN(x) { argv_free(result); vstring_free(buf); return (x); } result = argv_alloc(1); for (cpp = argv; *cpp; cpp++) { state.service = service; state.expand_flag = 0; if (mac_parse(*cpp, parse_callback, (char *) &state) & MAC_PARSE_ERROR) EARLY_RETURN(0); if (state.expand_flag == 0) { /* no $recipient etc. */ argv_add(result, dict_eval(PIPE_DICT_TABLE, *cpp, NO), ARGV_END); } else { /* contains $recipient etc. */ for (i = 0; i < rcpt_list->len; i++) { /* * This argument contains $recipient. */ if (state.expand_flag & PIPE_FLAG_RCPT) { morph_recipient(buf, rcpt_list->info[i].address, flags); dict_update(PIPE_DICT_TABLE, PIPE_DICT_RCPT, STR(buf)); } /* * This argument contains $original_recipient. */ if (state.expand_flag & PIPE_FLAG_ORIG_RCPT) { morph_recipient(buf, rcpt_list->info[i].orig_addr, flags); dict_update(PIPE_DICT_TABLE, PIPE_DICT_ORIG_RCPT, STR(buf)); } /* * This argument contains $user. Extract the plain user name. * Either anything to the left of the extension delimiter or, * in absence of the latter, anything to the left of the * rightmost @. * * Beware: if the user name is blank (e.g. +user@host), the * argument is suppressed. This is necessary to allow for * cyrus bulletin-board (global mailbox) delivery. XXX But, * skipping empty user parts will also prevent other * expansions of this specific command-line argument. */ if (state.expand_flag & PIPE_FLAG_USER) { morph_recipient(buf, rcpt_list->info[i].address, flags & PIPE_OPT_FOLD_ALL); if (split_at_right(STR(buf), '@') == 0) msg_warn("no @ in recipient address: %s", rcpt_list->info[i].address); if (*var_rcpt_delim) split_addr(STR(buf), var_rcpt_delim); if (*STR(buf) == 0) continue; dict_update(PIPE_DICT_TABLE, PIPE_DICT_USER, STR(buf)); } /* * This argument contains $extension. Extract the recipient * extension: anything between the leftmost extension * delimiter and the rightmost @. The extension may be blank. */ if (state.expand_flag & PIPE_FLAG_EXTENSION) { morph_recipient(buf, rcpt_list->info[i].address, flags & PIPE_OPT_FOLD_ALL); if (split_at_right(STR(buf), '@') == 0) msg_warn("no @ in recipient address: %s", rcpt_list->info[i].address); if (*var_rcpt_delim == 0 || (ext = split_addr(STR(buf), var_rcpt_delim)) == 0) ext = ""; /* insert null arg */ dict_update(PIPE_DICT_TABLE, PIPE_DICT_EXTENSION, ext); } /* * This argument contains $mailbox. Extract the mailbox name: * anything to the left of the rightmost @. */ if (state.expand_flag & PIPE_FLAG_MAILBOX) { morph_recipient(buf, rcpt_list->info[i].address, flags & PIPE_OPT_FOLD_ALL); if (split_at_right(STR(buf), '@') == 0) msg_warn("no @ in recipient address: %s", rcpt_list->info[i].address); dict_update(PIPE_DICT_TABLE, PIPE_DICT_MAILBOX, STR(buf)); } /* * This argument contains $domain. Extract the domain name: * anything to the right of the rightmost @. */ if (state.expand_flag & PIPE_FLAG_DOMAIN) { morph_recipient(buf, rcpt_list->info[i].address, flags & PIPE_OPT_FOLD_ALL); dom = split_at_right(STR(buf), '@'); if (dom == 0) { msg_warn("no @ in recipient address: %s", rcpt_list->info[i].address); dom = ""; /* insert null arg */ } dict_update(PIPE_DICT_TABLE, PIPE_DICT_DOMAIN, dom); } /* * Done. */ argv_add(result, dict_eval(PIPE_DICT_TABLE, *cpp, NO), ARGV_END); } } } argv_terminate(result); vstring_free(buf); return (result); }
static void pgsql_parse_config(DICT_PGSQL *dict_pgsql, const char *pgsqlcf) { const char *myname = "pgsql_parse_config"; CFG_PARSER *p = dict_pgsql->parser; char *hosts; VSTRING *query; char *select_function; dict_pgsql->username = cfg_get_str(p, "user", "", 0, 0); dict_pgsql->password = cfg_get_str(p, "password", "", 0, 0); dict_pgsql->dbname = cfg_get_str(p, "dbname", "", 1, 0); dict_pgsql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0); /* * XXX: The default should be non-zero for safety, but that is not * backwards compatible. */ dict_pgsql->expansion_limit = cfg_get_int(dict_pgsql->parser, "expansion_limit", 0, 0, 0); if ((dict_pgsql->query = cfg_get_str(p, "query", 0, 0, 0)) == 0) { /* * No query specified -- fallback to building it from components ( * old style "select %s from %s where %s" ) */ query = vstring_alloc(64); select_function = cfg_get_str(p, "select_function", 0, 0, 0); if (select_function != 0) { vstring_sprintf(query, "SELECT %s('%%s')", select_function); myfree(select_function); } else db_common_sql_build_query(query, p); dict_pgsql->query = vstring_export(query); } /* * Must parse all templates before we can use db_common_expand() */ dict_pgsql->ctx = 0; (void) db_common_parse(&dict_pgsql->dict, &dict_pgsql->ctx, dict_pgsql->query, 1); (void) db_common_parse(0, &dict_pgsql->ctx, dict_pgsql->result_format, 0); db_common_parse_domain(p, dict_pgsql->ctx); /* * Maps that use substring keys should only be used with the full input * key. */ if (db_common_dict_partial(dict_pgsql->ctx)) dict_pgsql->dict.flags |= DICT_FLAG_PATTERN; else dict_pgsql->dict.flags |= DICT_FLAG_FIXED; if (dict_pgsql->dict.flags & DICT_FLAG_FOLD_FIX) dict_pgsql->dict.fold_buf = vstring_alloc(10); hosts = cfg_get_str(p, "hosts", "", 0, 0); dict_pgsql->hosts = argv_split(hosts, " ,\t\r\n"); if (dict_pgsql->hosts->argc == 0) { argv_add(dict_pgsql->hosts, "localhost", ARGV_END); argv_terminate(dict_pgsql->hosts); if (msg_verbose) msg_info("%s: %s: no hostnames specified, defaulting to '%s'", myname, pgsqlcf, dict_pgsql->hosts->argv[0]); } myfree(hosts); }
static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf) { const char *myname = "mysql_parse_config"; CFG_PARSER *p = dict_mysql->parser; VSTRING *buf; char *hosts; dict_mysql->username = cfg_get_str(p, "user", "", 0, 0); dict_mysql->password = cfg_get_str(p, "password", "", 0, 0); dict_mysql->dbname = cfg_get_str(p, "dbname", "", 1, 0); dict_mysql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0); dict_mysql->option_file = cfg_get_str(p, "option_file", NULL, 0, 0); dict_mysql->option_group = cfg_get_str(p, "option_group", NULL, 0, 0); #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 dict_mysql->tls_key_file = cfg_get_str(p, "tls_key_file", NULL, 0, 0); dict_mysql->tls_cert_file = cfg_get_str(p, "tls_cert_file", NULL, 0, 0); dict_mysql->tls_CAfile = cfg_get_str(p, "tls_CAfile", NULL, 0, 0); dict_mysql->tls_CApath = cfg_get_str(p, "tls_CApath", NULL, 0, 0); dict_mysql->tls_ciphers = cfg_get_str(p, "tls_ciphers", NULL, 0, 0); #if MYSQL_VERSION_ID >= 50023 dict_mysql->tls_verify_cert = cfg_get_bool(p, "tls_verify_cert", -1); #endif #endif /* * XXX: The default should be non-zero for safety, but that is not * backwards compatible. */ dict_mysql->expansion_limit = cfg_get_int(dict_mysql->parser, "expansion_limit", 0, 0, 0); if ((dict_mysql->query = cfg_get_str(p, "query", NULL, 0, 0)) == 0) { /* * No query specified -- fallback to building it from components (old * style "select %s from %s where %s") */ buf = vstring_alloc(64); db_common_sql_build_query(buf, p); dict_mysql->query = vstring_export(buf); } /* * Must parse all templates before we can use db_common_expand() */ dict_mysql->ctx = 0; (void) db_common_parse(&dict_mysql->dict, &dict_mysql->ctx, dict_mysql->query, 1); (void) db_common_parse(0, &dict_mysql->ctx, dict_mysql->result_format, 0); db_common_parse_domain(p, dict_mysql->ctx); /* * Maps that use substring keys should only be used with the full input * key. */ if (db_common_dict_partial(dict_mysql->ctx)) dict_mysql->dict.flags |= DICT_FLAG_PATTERN; else dict_mysql->dict.flags |= DICT_FLAG_FIXED; if (dict_mysql->dict.flags & DICT_FLAG_FOLD_FIX) dict_mysql->dict.fold_buf = vstring_alloc(10); hosts = cfg_get_str(p, "hosts", "", 0, 0); dict_mysql->hosts = argv_split(hosts, " ,\t\r\n"); if (dict_mysql->hosts->argc == 0) { argv_add(dict_mysql->hosts, "localhost", ARGV_END); argv_terminate(dict_mysql->hosts); if (msg_verbose) msg_info("%s: %s: no hostnames specified, defaulting to '%s'", myname, mysqlcf, dict_mysql->hosts->argv[0]); } myfree(hosts); }