int smtp_sasl_passwd_lookup(SMTP_SESSION *session) { const char *myname = "smtp_sasl_passwd_lookup"; SMTP_STATE *state = session->state; SMTP_ITERATOR *iter = session->iterator; const char *value; char *passwd; /* * Sanity check. */ if (smtp_sasl_passwd_map == 0) msg_panic("%s: passwd map not initialized", myname); /* * Look up the per-server password information. Try the hostname first, * then try the destination. * * XXX Instead of using nexthop (the intended destination) we use dest * (either the intended destination, or a fall-back destination). * * XXX SASL authentication currently depends on the host/domain but not on * the TCP port. If the port is not :25, we should append it to the table * lookup key. Code for this was briefly introduced into 2.2 snapshots, * but didn't canonicalize the TCP port, and did not append the port to * the MX hostname. */ smtp_sasl_passwd_map->error = 0; if ((smtp_mode && var_smtp_sender_auth && state->request->sender[0] && (value = mail_addr_find(smtp_sasl_passwd_map, state->request->sender, (char **) 0)) != 0) || (smtp_sasl_passwd_map->error == 0 && (value = maps_find(smtp_sasl_passwd_map, STR(iter->host), 0)) != 0) || (smtp_sasl_passwd_map->error == 0 && (value = maps_find(smtp_sasl_passwd_map, STR(iter->dest), 0)) != 0)) { if (session->sasl_username) myfree(session->sasl_username); session->sasl_username = mystrdup(value); passwd = split_at(session->sasl_username, ':'); if (session->sasl_passwd) myfree(session->sasl_passwd); session->sasl_passwd = mystrdup(passwd ? passwd : ""); if (msg_verbose) msg_info("%s: host `%s' user `%s' pass `%s'", myname, STR(iter->host), session->sasl_username, session->sasl_passwd); return (1); } else if (smtp_sasl_passwd_map->error) { msg_warn("%s: %s lookup error", state->request->queue_id, smtp_sasl_passwd_map->title); vstream_longjmp(session->stream, SMTP_ERR_DATA); } else { if (msg_verbose) msg_info("%s: no auth info found (sender=`%s', host=`%s')", myname, state->request->sender, STR(iter->host)); return (0); } }
void edit_parameters(int mode, int argc, char **argv) { char *path; EDIT_FILE *ep; VSTREAM *src; VSTREAM *dst; VSTRING *buf = vstring_alloc(100); VSTRING *key = vstring_alloc(10); char *cp; char *edit_key; char *edit_val; HTABLE *table; struct cvalue { char *value; int found; }; struct cvalue *cvalue; HTABLE_INFO **ht_info; HTABLE_INFO **ht; int interesting; const char *err; /* * Store command-line parameters for quick lookup. */ table = htable_create(argc); while ((cp = *argv++) != 0) { if (strchr(cp, '\n') != 0) msg_fatal("-e or -# accepts no multi-line input"); while (ISSPACE(*cp)) cp++; if (*cp == '#') msg_fatal("-e or -# accepts no comment input"); if (mode & EDIT_MAIN) { if ((err = split_nameval(cp, &edit_key, &edit_val)) != 0) msg_fatal("%s: \"%s\"", err, cp); } else if (mode & COMMENT_OUT) { if (*cp == 0) msg_fatal("-# requires non-blank parameter names"); if (strchr(cp, '=') != 0) msg_fatal("-# requires parameter names only"); edit_key = mystrdup(cp); trimblanks(edit_key, 0); edit_val = 0; } else { msg_panic("edit_parameters: unknown mode %d", mode); } cvalue = (struct cvalue *) mymalloc(sizeof(*cvalue)); cvalue->value = edit_val; cvalue->found = 0; htable_enter(table, edit_key, (char *) cvalue); } /* * Open a temp file for the result. This uses a deterministic name so we * don't leave behind thrash with random names. */ set_config_dir(); path = concatenate(var_config_dir, "/", MAIN_CONF_FILE, (char *) 0); if ((ep = edit_file_open(path, O_CREAT | O_WRONLY, 0644)) == 0) msg_fatal("open %s%s: %m", path, EDIT_FILE_SUFFIX); dst = ep->tmp_fp; /* * Open the original file for input. */ if ((src = vstream_fopen(path, O_RDONLY, 0)) == 0) { /* OK to delete, since we control the temp file name exclusively. */ (void) unlink(ep->tmp_path); msg_fatal("open %s for reading: %m", path); } /* * Copy original file to temp file, while replacing parameters on the * fly. Issue warnings for names found multiple times. */ #define STR(x) vstring_str(x) interesting = 0; while (vstring_get(buf, src) != VSTREAM_EOF) { for (cp = STR(buf); ISSPACE(*cp) /* including newline */ ; cp++) /* void */ ; /* Copy comment, all-whitespace, or empty line. */ if (*cp == '#' || *cp == 0) { vstream_fputs(STR(buf), dst); } /* Copy, skip or replace continued text. */ else if (cp > STR(buf)) { if (interesting == 0) vstream_fputs(STR(buf), dst); else if (mode & COMMENT_OUT) vstream_fprintf(dst, "#%s", STR(buf)); } /* Copy or replace start of logical line. */ else { vstring_strncpy(key, cp, strcspn(cp, " \t\r\n=")); cvalue = (struct cvalue *) htable_find(table, STR(key)); if ((interesting = !!cvalue) != 0) { if (cvalue->found++ == 1) msg_warn("%s: multiple entries for \"%s\"", path, STR(key)); if (mode & EDIT_MAIN) vstream_fprintf(dst, "%s = %s\n", STR(key), cvalue->value); else if (mode & COMMENT_OUT) vstream_fprintf(dst, "#%s", cp); else msg_panic("edit_parameters: unknown mode %d", mode); } else { vstream_fputs(STR(buf), dst); } } } /* * Generate new entries for parameters that were not found. */ if (mode & EDIT_MAIN) { for (ht_info = ht = htable_list(table); *ht; ht++) { cvalue = (struct cvalue *) ht[0]->value; if (cvalue->found == 0) vstream_fprintf(dst, "%s = %s\n", ht[0]->key, cvalue->value); } myfree((char *) ht_info); } /* * When all is well, rename the temp file to the original one. */ if (vstream_fclose(src)) msg_fatal("read %s: %m", path); if (edit_file_close(ep) != 0) msg_fatal("close %s%s: %m", path, EDIT_FILE_SUFFIX); /* * Cleanup. */ myfree(path); vstring_free(buf); vstring_free(key); htable_free(table, myfree); }
void master_listen_init(MASTER_SERV *serv) { const char *myname = "master_listen_init"; char *end_point; int n; MAI_HOSTADDR_STR hostaddr; struct sockaddr *sa; /* * Find out what transport we should use, then create one or more * listener sockets. Make the listener sockets non-blocking, so that * child processes don't block in accept() when multiple processes are * selecting on the same socket and only one of them gets the connection. */ switch (serv->type) { /* * UNIX-domain or stream listener endpoints always come as singlets. */ case MASTER_SERV_TYPE_UNIX: set_eugid(var_owner_uid, var_owner_gid); serv->listen_fd[0] = LOCAL_LISTEN(serv->name, serv->max_proc > var_proc_limit ? serv->max_proc : var_proc_limit, NON_BLOCKING); close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC); set_ugid(getuid(), getgid()); break; /* * FIFO listener endpoints always come as singlets. */ case MASTER_SERV_TYPE_FIFO: set_eugid(var_owner_uid, var_owner_gid); serv->listen_fd[0] = fifo_listen(serv->name, 0622, NON_BLOCKING); close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC); set_ugid(getuid(), getgid()); break; /* * INET-domain listener endpoints can be wildcarded (the default) or * bound to specific interface addresses. * * With dual-stack IPv4/6 systems it does not matter, we have to specify * the addresses anyway, either explicit or wild-card. */ case MASTER_SERV_TYPE_INET: for (n = 0; n < serv->listen_fd_count; n++) { sa = SOCK_ADDR_PTR(MASTER_INET_ADDRLIST(serv)->addrs + n); SOCKADDR_TO_HOSTADDR(sa, SOCK_ADDR_LEN(sa), &hostaddr, (MAI_SERVPORT_STR *) 0, 0); end_point = concatenate(hostaddr.buf, ":", MASTER_INET_PORT(serv), (char *) 0); serv->listen_fd[n] = inet_listen(end_point, serv->max_proc > var_proc_limit ? serv->max_proc : var_proc_limit, NON_BLOCKING); close_on_exec(serv->listen_fd[n], CLOSE_ON_EXEC); myfree(end_point); } break; /* * Descriptor passing endpoints always come as singlets. */ #ifdef MASTER_SERV_TYPE_PASS case MASTER_SERV_TYPE_PASS: set_eugid(var_owner_uid, var_owner_gid); serv->listen_fd[0] = PASS_LISTEN(serv->name, serv->max_proc > var_proc_limit ? serv->max_proc : var_proc_limit, NON_BLOCKING); close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC); set_ugid(getuid(), getgid()); break; #endif default: msg_panic("%s: unknown service type: %d", myname, serv->type); } }
char *tls_serverid_digest(const TLS_CLIENT_START_PROPS *props, long protomask, const char *ciphers) { EVP_MD_CTX *mdctx; const EVP_MD *md; const char *mdalg; unsigned char md_buf[EVP_MAX_MD_SIZE]; unsigned int md_len; int ok = 1; int i; long sslversion; VSTRING *result; /* * Try to use sha256: our serverid choice should be strong enough to * resist 2nd-preimage attacks with a difficulty comparable to that of * DANE TLSA digests. Failing that, we compute serverid digests with the * default digest, but DANE requires sha256 and sha512, so if we must * fall back to our default digest, DANE support won't be available. We * panic if the fallback algorithm is not available, as it was verified * available in tls_client_init() and must not simply vanish. */ if ((md = EVP_get_digestbyname(mdalg = "sha256")) == 0 && (md = EVP_get_digestbyname(mdalg = props->mdalg)) == 0) msg_panic("digest algorithm \"%s\" not found", mdalg); /* Salt the session lookup key with the OpenSSL runtime version. */ sslversion = OpenSSL_version_num(); mdctx = EVP_MD_CTX_create(); checkok(EVP_DigestInit_ex(mdctx, md, NULL)); digest_string(props->helo ? props->helo : ""); digest_object(&sslversion); digest_object(&protomask); digest_string(ciphers); /* * All we get from the session cache is a single bit telling us whether * the certificate is trusted or not, but we need to know whether the * trust is CA-based (in that case we must do name checks) or whether it * is a direct end-point match. We mustn't confuse the two, so it is * best to process only TA trust in the verify callback and check the EE * trust after. This works since re-used sessions always have access to * the leaf certificate, while only the original session has the leaf and * the full trust chain. * * Only the trust anchor matchlist is hashed into the session key. The end * entity certs are not used to determine whether a certificate is * trusted or not, rather these are rechecked against the leaf cert * outside the verification callback, each time a session is created or * reused. * * Therefore, the security context of the session does not depend on the EE * matching data, which is checked separately each time. So we exclude * the EE part of the DANE structure from the serverid digest. * * If the security level is "dane", we send SNI information to the peer. * This may cause it to respond with a non-default certificate. Since * certificates for sessions with no or different SNI data may not match, * we must include the SNI name in the session id. */ if (props->dane) { digest_dane(props->dane, ta); #if 0 digest_dane(props->dane, ee); /* See above */ #endif digest_string(TLS_DANE_BASED(props->tls_level) ? props->host : ""); } checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len)); EVP_MD_CTX_destroy(mdctx); if (!ok) msg_fatal("error computing %s message digest", mdalg); /* Check for OpenSSL contract violation */ if (md_len > EVP_MAX_MD_SIZE) msg_panic("unexpectedly large %s digest size: %u", mdalg, md_len); /* * Append the digest to the serverid. We don't compare this digest to * any user-specified fingerprints. Therefore, we don't need to use a * colon-separated format, which saves space in the TLS session cache and * makes logging of session cache lookup keys more readable. * * This does however duplicate a few lines of code from the digest encoder * for colon-separated cert and pkey fingerprints. If that is a * compelling reason to consolidate, we could use that and append the * result. */ result = vstring_alloc(strlen(props->serverid) + 1 + 2 * md_len); vstring_strcpy(result, props->serverid); VSTRING_ADDCH(result, '&'); for (i = 0; i < md_len; i++) { VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0xf0) >> 4U]); VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0x0f)]); } VSTRING_TERMINATE(result); return (vstring_export(result)); }
unsigned long safe_strtoul(const char *start, char **end, int base) { const char *myname = "safe_strtoul"; static unsigned char *char_map = 0; unsigned char *cp; unsigned long sum; unsigned long div_limit; unsigned long mod_limit; int char_val; /* * Sanity check. */ if (base < SAFE_MIN_BASE || base > SAFE_MAX_BASE) msg_panic("%s: bad base: %d", myname, base); /* * One-time initialization. Assume 8-bit bytes. */ if (char_map == 0) { char_map = (unsigned char *) mymalloc(256); for (char_val = 0; char_val < 256; char_val++) char_map[char_val] = SAFE_MAX_BASE; for (char_val = 0; char_val < SAFE_MAX_BASE; char_val++) char_map[safe_chars[char_val]] = char_val; } /* * Per-call initialization. */ sum = 0; div_limit = ULONG_MAX / base; mod_limit = ULONG_MAX % base; /* * Skip leading whitespace. We don't implement sign/base prefixes. */ while (ISSPACE(*start)) ++start; /* * Start the conversion. */ errno = 0; for (cp = (unsigned char *) start; *cp; cp++) { /* Return (0, EINVAL) if no conversion was made. */ if ((char_val = char_map[*cp]) >= base) { if (cp == (unsigned char *) start) errno = EINVAL; break; } /* Return (ULONG_MAX, ERANGE) if the result is too large. */ if (sum > div_limit || (sum == div_limit && char_val > mod_limit)) { sum = ULONG_MAX; errno = ERANGE; /* Skip "valid" characters, per the strtoul() spec. */ while (char_map[*++cp] < base) /* void */ ; break; } sum = sum * base + char_val; } if (end) *end = (char *) cp; return (sum); }
void pcf_edit_master_param(PCF_MASTER_ENT *masterp, int mode, const char *param_name, const char *param_value) { const char *myname = "pcf_edit_master_param"; ARGV *argv = masterp->argv; const char *arg; const char *aval; int param_match = 0; int name_len = strlen(param_name); int field; for (field = PCF_MASTER_MIN_FIELDS; argv->argv[field] != 0; field++) { arg = argv->argv[field]; /* * Stop at the first non-option argument or end-of-list. */ if (arg[0] != '-' || strcmp(arg, "--") == 0) { break; } /* * Zoom in on command-line options with a value. */ else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0 && (aval = argv->argv[field + 1]) != 0) { /* * Zoom in on "-o parameter=value". */ if (strcmp(arg, "-o") == 0) { if (strncmp(aval, param_name, name_len) == 0 && aval[name_len] == '=') { param_match = 1; switch (mode & (PCF_EDIT_CONF | PCF_EDIT_EXCL)) { /* * Update parameter=value. */ case PCF_EDIT_CONF: aval = concatenate(param_name, "=", param_value, (char *) 0); argv_replace_one(argv, field + 1, aval); myfree((void *) aval); if (masterp->all_params) dict_put(masterp->all_params, param_name, param_value); /* XXX Update parameter "used/defined" status. */ break; /* * Delete parameter=value. */ case PCF_EDIT_EXCL: argv_delete(argv, field, 2); if (masterp->all_params) dict_del(masterp->all_params, param_name); /* XXX Update parameter "used/defined" status. */ field -= 2; break; default: msg_panic("%s: unexpected mode: %d", myname, mode); } } } /* * Skip over the command-line option value. */ field += 1; } } /* * Add unmatched parameter. */ if ((mode & PCF_EDIT_CONF) && param_match == 0) { /* XXX Generalize: argv_insert(argv, where, list...) */ argv_insert_one(argv, field, "-o"); aval = concatenate(param_name, "=", param_value, (char *) 0); argv_insert_one(argv, field + 1, aval); if (masterp->all_params) dict_put(masterp->all_params, param_name, param_value); /* XXX May affect parameter "used/defined" status. */ myfree((void *) aval); param_match = 1; } }
void rewrite_tree(RWR_CONTEXT *context, TOK822 *tree) { TOK822 *colon; TOK822 *domain; TOK822 *bang; TOK822 *local; VSTRING *vstringval; /* * XXX If you change this module, quote_822_local.c, or tok822_parse.c, * be sure to re-run the tests under "make rewrite_clnt_test" and "make * resolve_clnt_test" in the global directory. */ /* * Sanity check. */ if (tree->head == 0) msg_panic("rewrite_tree: empty tree"); /* * An empty address is a special case. */ if (tree->head == tree->tail && tree->tail->type == TOK822_QSTRING && VSTRING_LEN(tree->tail->vstr) == 0) return; /* * Treat a lone @ as if it were an empty address. */ if (tree->head == tree->tail && tree->tail->type == '@') { tok822_free_tree(tok822_sub_keep_before(tree, tree->tail)); tok822_sub_append(tree, tok822_alloc(TOK822_QSTRING, "")); return; } /* * Strip source route. */ if (tree->head->type == '@' && (colon = tok822_find_type(tree->head, ':')) != 0 && colon != tree->tail) tok822_free_tree(tok822_sub_keep_after(tree, colon)); /* * Optionally, transform address forms without @. */ if ((domain = tok822_rfind_type(tree->tail, '@')) == 0) { /* * Swap domain!user to user@domain. */ if (var_swap_bangpath != 0 && (bang = tok822_find_type(tree->head, '!')) != 0) { tok822_sub_keep_before(tree, bang); local = tok822_cut_after(bang); tok822_free(bang); tok822_sub_prepend(tree, tok822_alloc('@', (char *) 0)); if (local) tok822_sub_prepend(tree, local); } /* * Promote user%domain to user@domain. */ else if (var_percent_hack != 0 && (domain = tok822_rfind_type(tree->tail, '%')) != 0) { domain->type = '@'; } /* * Append missing @origin */ else if (var_append_at_myorigin != 0 && REW_PARAM_VALUE(context->origin) != 0 && REW_PARAM_VALUE(context->origin)[0] != 0) { domain = tok822_sub_append(tree, tok822_alloc('@', (char *) 0)); tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->origin), (TOK822 **) 0)); } } /* * Append missing .domain, but leave broken forms ending in @ alone. This * merely makes diagnostics more accurate by leaving bogus addresses * alone. * * Backwards-compatibility warning: warn for "user@localhost" when there is * no "localhost" in mydestination or in any other address class with an * explicit domain list. */ if (var_append_dot_mydomain != 0 && REW_PARAM_VALUE(context->domain) != 0 && REW_PARAM_VALUE(context->domain)[0] != 0 && (domain = tok822_rfind_type(tree->tail, '@')) != 0 && domain != tree->tail && tok822_find_type(domain, TOK822_DOMLIT) == 0 && tok822_find_type(domain, '.') == 0) { if (warn_compat_break_app_dot_mydomain && (vstringval = domain->next->vstr) != 0) { if (strcasecmp(vstring_str(vstringval), "localhost") != 0) { msg_info("using backwards-compatible default setting " VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to " "\"%s.%s\"", vstring_str(vstringval), vstring_str(vstringval), var_mydomain); } else if (resolve_class("localhost") == RESOLVE_CLASS_DEFAULT) { msg_info("using backwards-compatible default setting " VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to " "\"%s.%s\"; please add \"localhost\" to " "mydestination or other address class", vstring_str(vstringval), vstring_str(vstringval), var_mydomain); } } tok822_sub_append(tree, tok822_alloc('.', (char *) 0)); tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->domain), (TOK822 **) 0)); } /* * Strip trailing dot at end of domain, but not dot-dot or @-dot. This * merely makes diagnostics more accurate by leaving bogus addresses * alone. */ if (tree->tail->type == '.' && tree->tail->prev && tree->tail->prev->type != '.' && tree->tail->prev->type != '@') tok822_free_tree(tok822_sub_keep_before(tree, tree->tail)); }
static void post_jail_init(char *unused_name, char **unused_argv) { const NAME_CODE actions[] = { PSC_NAME_ACT_DROP, PSC_ACT_DROP, PSC_NAME_ACT_ENFORCE, PSC_ACT_ENFORCE, PSC_NAME_ACT_IGNORE, PSC_ACT_IGNORE, PSC_NAME_ACT_CONT, PSC_ACT_IGNORE, /* compatibility */ 0, -1, }; int cache_flags; const char *tmp; /* * This routine runs after the skeleton code has entered the chroot jail. * Prevent automatic process suicide after a limited number of client * requests. It is OK to terminate after a limited amount of idle time. */ var_use_limit = 0; /* * Workaround for parameters whose values may contain "$", and that have * a default of "$parametername". Not sure if it would be a good idea to * always to this in the mail_conf_raw(3) module. */ if (*var_psc_rej_footer == '$' && mail_conf_lookup(var_psc_rej_footer + 1)) { tmp = mail_conf_eval_once(var_psc_rej_footer); myfree(var_psc_rej_footer); var_psc_rej_footer = mystrdup(tmp); } if (*var_psc_exp_filter == '$' && mail_conf_lookup(var_psc_exp_filter + 1)) { tmp = mail_conf_eval_once(var_psc_exp_filter); myfree(var_psc_exp_filter); var_psc_exp_filter = mystrdup(tmp); } /* * Other one-time initialization. */ psc_temp = vstring_alloc(10); vstring_sprintf(psc_temp, "%s/%s", MAIL_CLASS_PRIVATE, var_smtpd_service); psc_smtpd_service_name = mystrdup(STR(psc_temp)); psc_dnsbl_init(); psc_early_init(); psc_smtpd_init(); if ((psc_blist_action = name_code(actions, NAME_CODE_FLAG_NONE, var_psc_blist_action)) < 0) msg_fatal("bad %s value: %s", VAR_PSC_BLIST_ACTION, var_psc_blist_action); if ((psc_dnsbl_action = name_code(actions, NAME_CODE_FLAG_NONE, var_psc_dnsbl_action)) < 0) msg_fatal("bad %s value: %s", VAR_PSC_DNSBL_ACTION, var_psc_dnsbl_action); if ((psc_pregr_action = name_code(actions, NAME_CODE_FLAG_NONE, var_psc_pregr_action)) < 0) msg_fatal("bad %s value: %s", VAR_PSC_PREGR_ACTION, var_psc_pregr_action); if ((psc_pipel_action = name_code(actions, NAME_CODE_FLAG_NONE, var_psc_pipel_action)) < 0) msg_fatal("bad %s value: %s", VAR_PSC_PIPEL_ACTION, var_psc_pipel_action); if ((psc_nsmtp_action = name_code(actions, NAME_CODE_FLAG_NONE, var_psc_nsmtp_action)) < 0) msg_fatal("bad %s value: %s", VAR_PSC_NSMTP_ACTION, var_psc_nsmtp_action); if ((psc_barlf_action = name_code(actions, NAME_CODE_FLAG_NONE, var_psc_barlf_action)) < 0) msg_fatal("bad %s value: %s", VAR_PSC_BARLF_ACTION, var_psc_barlf_action); /* Fail "closed" on error. */ psc_wlist_if = addr_match_list_init(MATCH_FLAG_RETURN, var_psc_wlist_if); /* * Start the cache maintenance pseudo thread last. Early cleanup makes * verbose logging more informative (we get positive confirmation that * the cleanup thread runs). */ cache_flags = DICT_CACHE_FLAG_STATISTICS; if (msg_verbose > 1) cache_flags |= DICT_CACHE_FLAG_VERBOSE; if (psc_cache_map != 0 && var_psc_cache_scan > 0) dict_cache_control(psc_cache_map, DICT_CACHE_CTL_FLAGS, cache_flags, DICT_CACHE_CTL_INTERVAL, var_psc_cache_scan, DICT_CACHE_CTL_VALIDATOR, psc_cache_validator, DICT_CACHE_CTL_CONTEXT, (char *) 0, DICT_CACHE_CTL_END); /* * Pre-compute the minimal and maximal TTL. */ psc_min_ttl = PSC_MIN(PSC_MIN(var_psc_pregr_ttl, var_psc_dnsbl_ttl), PSC_MIN(PSC_MIN(var_psc_pipel_ttl, var_psc_nsmtp_ttl), var_psc_barlf_ttl)); psc_max_ttl = PSC_MAX(PSC_MAX(var_psc_pregr_ttl, var_psc_dnsbl_ttl), PSC_MAX(PSC_MAX(var_psc_pipel_ttl, var_psc_nsmtp_ttl), var_psc_barlf_ttl)); /* * Pre-compute the stress and normal command time limits. */ mail_conf_update(VAR_STRESS, "yes"); psc_stress_cmd_time_limit = get_mail_conf_time(VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, 1, 0); psc_stress_greet_wait = get_mail_conf_time(VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, 1, 0); mail_conf_update(VAR_STRESS, ""); psc_normal_cmd_time_limit = get_mail_conf_time(VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, 1, 0); psc_normal_greet_wait = get_mail_conf_time(VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, 1, 0); psc_lowat_check_queue_length = .7 * var_psc_pre_queue_limit; psc_hiwat_check_queue_length = .9 * var_psc_pre_queue_limit; if (msg_verbose) msg_info(VAR_PSC_CMD_TIME ": stress=%d normal=%d lowat=%d hiwat=%d", psc_stress_cmd_time_limit, psc_normal_cmd_time_limit, psc_lowat_check_queue_length, psc_hiwat_check_queue_length); if (psc_lowat_check_queue_length == 0) msg_panic("compiler error: 0.7 * %d = %d", var_psc_pre_queue_limit, psc_lowat_check_queue_length); if (psc_hiwat_check_queue_length == 0) msg_panic("compiler error: 0.9 * %d = %d", var_psc_pre_queue_limit, psc_hiwat_check_queue_length); /* * Per-client concurrency. */ psc_client_concurrency = htable_create(var_psc_pre_queue_limit); }
void pcf_edit_master(int mode, int argc, char **argv) { const char *myname = "pcf_edit_master"; char *path; EDIT_FILE *ep; VSTREAM *src; VSTREAM *dst; VSTRING *line_buf = vstring_alloc(100); VSTRING *parse_buf = vstring_alloc(100); int lineno; PCF_MASTER_ENT *new_entry; VSTRING *full_entry_buf = vstring_alloc(100); char *cp; char *pattern; int service_name_type_matched; const char *err; PCF_MASTER_EDIT_REQ *edit_reqs; PCF_MASTER_EDIT_REQ *req; int num_reqs = argc; const char *edit_opts = "-Me, -Fe, -Pe, -X, or -#"; char *service_name; char *service_type; /* * Sanity check. */ if (num_reqs <= 0) msg_panic("%s: empty argument list", myname); /* * Preprocessing: split pattern=value, then split the pattern components. */ edit_reqs = (PCF_MASTER_EDIT_REQ *) mymalloc(sizeof(*edit_reqs) * num_reqs); for (req = edit_reqs; *argv != 0; req++, argv++) { req->match_count = 0; req->raw_text = *argv; cp = req->parsed_text = mystrdup(req->raw_text); if (strchr(cp, '\n') != 0) msg_fatal("%s accept no multi-line input", edit_opts); while (ISSPACE(*cp)) cp++; if (*cp == '#') msg_fatal("%s accept no comment input", edit_opts); /* Separate the pattern from the value. */ if (mode & PCF_EDIT_CONF) { if ((err = split_nameval(cp, &pattern, &req->edit_value)) != 0) msg_fatal("%s: \"%s\"", err, req->raw_text); if ((mode & PCF_MASTER_PARAM) && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)]) msg_fatal("whitespace in parameter value: \"%s\"", req->raw_text); } else if (mode & (PCF_COMMENT_OUT | PCF_EDIT_EXCL)) { if (strchr(cp, '=') != 0) msg_fatal("-X or -# requires names without value"); pattern = cp; trimblanks(pattern, 0); req->edit_value = 0; } else { msg_panic("%s: unknown mode %d", myname, mode); } #define PCF_MASTER_MASK (PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM) /* * Split name/type or name/type/whatever pattern into components. */ switch (mode & PCF_MASTER_MASK) { case PCF_MASTER_ENTRY: if ((req->service_pattern = pcf_parse_service_pattern(pattern, 2, 2)) == 0) msg_fatal("-Me, -MX or -M# requires service_name/type"); break; case PCF_MASTER_FLD: if ((req->service_pattern = pcf_parse_service_pattern(pattern, 3, 3)) == 0) msg_fatal("-Fe or -FX requires service_name/type/field_name"); req->field_number = pcf_parse_field_pattern(req->service_pattern->argv[2]); if (pcf_is_magic_field_pattern(req->field_number)) msg_fatal("-Fe does not accept wild-card field name"); if ((mode & PCF_EDIT_CONF) && req->field_number < PCF_MASTER_FLD_CMD && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)]) msg_fatal("-Fe does not accept whitespace in non-command field"); break; case PCF_MASTER_PARAM: if ((req->service_pattern = pcf_parse_service_pattern(pattern, 3, 3)) == 0) msg_fatal("-Pe or -PX requires service_name/type/parameter"); req->param_pattern = req->service_pattern->argv[2]; if (PCF_IS_MAGIC_PARAM_PATTERN(req->param_pattern)) msg_fatal("-Pe does not accept wild-card parameter name"); if ((mode & PCF_EDIT_CONF) && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)]) msg_fatal("-Pe does not accept whitespace in parameter value"); break; default: msg_panic("%s: unknown edit mode %d", myname, mode); } } /* * Open a temp file for the result. This uses a deterministic name so we * don't leave behind thrash with random names. */ pcf_set_config_dir(); path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (char *) 0); if ((ep = edit_file_open(path, O_CREAT | O_WRONLY, 0644)) == 0) msg_fatal("open %s%s: %m", path, EDIT_FILE_SUFFIX); dst = ep->tmp_fp; /* * Open the original file for input. */ if ((src = vstream_fopen(path, O_RDONLY, 0)) == 0) { /* OK to delete, since we control the temp file name exclusively. */ (void) unlink(ep->tmp_path); msg_fatal("open %s for reading: %m", path); } /* * Copy original file to temp file, while replacing service entries on * the fly. */ service_name_type_matched = 0; new_entry = 0; lineno = 0; while ((cp = pcf_next_cf_line(parse_buf, src, dst, &lineno)) != 0) { vstring_strcpy(line_buf, STR(parse_buf)); /* * Copy, skip or replace continued text. */ if (cp > STR(parse_buf)) { if (service_name_type_matched == 0) vstream_fputs(STR(line_buf), dst); else if (mode & PCF_COMMENT_OUT) vstream_fprintf(dst, "#%s", STR(line_buf)); } /* * Copy or replace (start of) logical line. */ else { service_name_type_matched = 0; /* * Parse out the service name and type. */ if ((service_name = mystrtok(&cp, PCF_MASTER_BLANKS)) == 0 || (service_type = mystrtok(&cp, PCF_MASTER_BLANKS)) == 0) msg_fatal("file %s: line %d: specify service name and type " "on the same line", path, lineno); if (strchr(service_name, '=')) msg_fatal("file %s: line %d: service name syntax \"%s\" is " "unsupported with %s", path, lineno, service_name, edit_opts); if (service_type[strcspn(service_type, "=/")] != 0) msg_fatal("file %s: line %d: " "service type syntax \"%s\" is unsupported with %s", path, lineno, service_type, edit_opts); /* * Match each service pattern. */ for (req = edit_reqs; req < edit_reqs + num_reqs; req++) { if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, service_name, service_type)) { service_name_type_matched = 1; /* Sticky flag */ req->match_count += 1; /* * Generate replacement master.cf entries. */ if ((mode & PCF_EDIT_CONF) || ((mode & PCF_MASTER_PARAM) && (mode & PCF_EDIT_EXCL))) { switch (mode & PCF_MASTER_MASK) { /* * Replace master.cf entry field or parameter * value. */ case PCF_MASTER_FLD: case PCF_MASTER_PARAM: if (new_entry == 0) { /* Gobble up any continuation lines. */ pcf_gobble_cf_line(full_entry_buf, line_buf, src, dst, &lineno); new_entry = (PCF_MASTER_ENT *) mymalloc(sizeof(*new_entry)); if ((err = pcf_parse_master_entry(new_entry, STR(full_entry_buf))) != 0) msg_fatal("file %s: line %d: %s", path, lineno, err); } if (mode & PCF_MASTER_FLD) { pcf_edit_master_field(new_entry, req->field_number, req->edit_value); } else { pcf_edit_master_param(new_entry, mode, req->param_pattern, req->edit_value); } break; /* * Replace entire master.cf entry. */ case PCF_MASTER_ENTRY: if (new_entry != 0) pcf_free_master_entry(new_entry); new_entry = (PCF_MASTER_ENT *) mymalloc(sizeof(*new_entry)); if ((err = pcf_parse_master_entry(new_entry, req->edit_value)) != 0) msg_fatal("%s: \"%s\"", err, req->raw_text); break; default: msg_panic("%s: unknown edit mode %d", myname, mode); } } } } /* * Pass through or replace the current input line. */ if (new_entry) { pcf_print_master_entry(dst, PCF_FOLD_LINE, new_entry); pcf_free_master_entry(new_entry); new_entry = 0; } else if (service_name_type_matched == 0) { vstream_fputs(STR(line_buf), dst); } else if (mode & PCF_COMMENT_OUT) { vstream_fprintf(dst, "#%s", STR(line_buf)); } } } /* * Postprocessing: when editing entire service entries, generate new * entries for services not found. Otherwise (editing fields or * parameters), "service not found" is a fatal error. */ for (req = edit_reqs; req < edit_reqs + num_reqs; req++) { if (req->match_count == 0) { if ((mode & PCF_MASTER_ENTRY) && (mode & PCF_EDIT_CONF)) { new_entry = (PCF_MASTER_ENT *) mymalloc(sizeof(*new_entry)); if ((err = pcf_parse_master_entry(new_entry, req->edit_value)) != 0) msg_fatal("%s: \"%s\"", err, req->raw_text); pcf_print_master_entry(dst, PCF_FOLD_LINE, new_entry); pcf_free_master_entry(new_entry); } else if ((mode & PCF_MASTER_ENTRY) == 0) { msg_warn("unmatched service_name/type: \"%s\"", req->raw_text); } } } /* * When all is well, rename the temp file to the original one. */ if (vstream_fclose(src)) msg_fatal("read %s: %m", path); if (edit_file_close(ep) != 0) msg_fatal("close %s%s: %m", path, EDIT_FILE_SUFFIX); /* * Cleanup. */ myfree(path); vstring_free(line_buf); vstring_free(parse_buf); vstring_free(full_entry_buf); for (req = edit_reqs; req < edit_reqs + num_reqs; req++) { argv_free(req->service_pattern); myfree(req->parsed_text); } myfree((char *) edit_reqs); }
static QMGR_JOB *qmgr_job_preempt(QMGR_JOB *current) { const char *myname = "qmgr_job_preempt"; QMGR_TRANSPORT *transport = current->transport; QMGR_JOB *job, *prev; int expected_slots; int rcpt_slots; /* * Suppress preempting completely if the current job is not big enough to * accumulate even the minimal number of slots required. * * Also, don't look for better job candidate if there are no available slots * yet (the count can get negative due to the slot loans below). */ if (current->slots_available <= 0 || MAX_ENTRIES(current) < transport->min_slots * transport->slot_cost) return (current); /* * Find best candidate for preempting the current job. * * Note that the function also takes care that the candidate fits within the * number of delivery slots which the current job is still able to * accumulate. */ if ((job = qmgr_job_candidate(current)) == 0) return (current); /* * Sanity checks. */ if (job == current) msg_panic("%s: attempt to preempt itself", myname); if (job->stack_children.next != 0) msg_panic("%s: already on the job stack (%d)", myname, job->stack_level); if (job->stack_level < 0) msg_panic("%s: not on the job list (%d)", myname, job->stack_level); /* * Check if there is enough available delivery slots accumulated to * preempt the current job. * * The slot loaning scheme improves the average message response time. Note * that the loan only allows the preemption happen earlier, though. It * doesn't affect how many slots have to be "paid" - in either case the * full number of slots required has to be accumulated later before the * current job can be preempted again. */ expected_slots = MAX_ENTRIES(job) - job->selected_entries; if (current->slots_available / transport->slot_cost + transport->slot_loan < expected_slots * transport->slot_loan_factor / 100.0) return (current); /* * Preempt the current job. * * This involves placing the selected candidate in front of the current job * on the job list and updating the stack parent/child/sibling pointers * appropriately. But first we need to make sure that the candidate is * taken from its previous job stack which it might be top of. */ if (job->stack_level > 0) qmgr_job_pop(job); QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers); prev = current->transport_peers.prev; QMGR_LIST_LINK(transport->job_list, prev, job, current, transport_peers); job->stack_parent = current; QMGR_LIST_APPEND(current->stack_children, job, stack_siblings); job->stack_level = current->stack_level + 1; /* * Update the current job pointer and explicitly reset the candidate * cache. */ transport->job_current = job; RESET_CANDIDATE_CACHE(transport); /* * Since the single job can be preempted by several jobs at the same * time, we have to adjust the available slot count now to prevent using * the same slots multiple times. To do that we subtract the number of * slots the preempting job will supposedly use. This number will be * corrected later when that job is popped from the stack to reflect the * number of slots really used. * * As long as we don't need to keep track of how many slots were really * used, we can (ab)use the slots_used counter for counting the * difference between the real and expected amounts instead of the * absolute amount. */ current->slots_available -= expected_slots * transport->slot_cost; job->slots_used = -expected_slots; /* * Add part of extra recipient slots reserved for preempting jobs to the * new current job if necessary. * * Note that transport->rcpt_unused is within <-rcpt_per_stack,0> in such * case. */ if (job->message->rcpt_offset != 0) { rcpt_slots = (transport->rcpt_per_stack + transport->rcpt_unused + 1) / 2; job->rcpt_limit += rcpt_slots; job->message->rcpt_limit += rcpt_slots; transport->rcpt_unused -= rcpt_slots; } if (msg_verbose) msg_info("%s: %s by %s, level %d", myname, current->message->queue_id, job->message->queue_id, job->stack_level); return (job); }
static void psc_endpt_lookup_done(int endpt_status, VSTREAM *smtp_client_stream, MAI_HOSTADDR_STR *smtp_client_addr, MAI_SERVPORT_STR *smtp_client_port, MAI_HOSTADDR_STR *smtp_server_addr, MAI_SERVPORT_STR *smtp_server_port) { const char *myname = "psc_endpt_lookup_done"; PSC_STATE *state; const char *stamp_str; int saved_flags; /* * Best effort - if this non-blocking write(2) fails, so be it. */ if (endpt_status < 0) { (void) write(vstream_fileno(smtp_client_stream), "421 4.3.2 No system resources\r\n", sizeof("421 4.3.2 No system resources\r\n") - 1); event_server_disconnect(smtp_client_stream); return; } if (msg_verbose > 1) msg_info("%s: sq=%d cq=%d connect from [%s]:%s", myname, psc_post_queue_length, psc_check_queue_length, smtp_client_addr->buf, smtp_client_port->buf); msg_info("CONNECT from [%s]:%s to [%s]:%s", smtp_client_addr->buf, smtp_client_port->buf, smtp_server_addr->buf, smtp_server_port->buf); /* * Bundle up all the loose session pieces. This zeroes all flags and time * stamps. */ state = psc_new_session_state(smtp_client_stream, smtp_client_addr->buf, smtp_client_port->buf, smtp_server_addr->buf, smtp_server_port->buf); /* * Reply with 421 when the client has too many open connections. */ if (var_psc_cconn_limit > 0 && state->client_concurrency > var_psc_cconn_limit) { msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: too many connections", state->smtp_client_addr, state->smtp_client_port); PSC_DROP_SESSION_STATE(state, "421 4.7.0 Error: too many connections\r\n"); return; } /* * Reply with 421 when we can't forward more connections. */ if (var_psc_post_queue_limit > 0 && psc_post_queue_length >= var_psc_post_queue_limit) { msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: all server ports busy", state->smtp_client_addr, state->smtp_client_port); PSC_DROP_SESSION_STATE(state, "421 4.3.2 All server ports are busy\r\n"); return; } /* * The permanent white/blacklist has highest precedence. */ if (psc_acl != 0) { switch (psc_acl_eval(state, psc_acl, VAR_PSC_ACL)) { /* * Permanently blacklisted. */ case PSC_ACL_ACT_BLACKLIST: msg_info("BLACKLISTED [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_BLIST_FAIL); switch (psc_blist_action) { case PSC_ACT_DROP: PSC_DROP_SESSION_STATE(state, "521 5.3.2 Service currently unavailable\r\n"); return; case PSC_ACT_ENFORCE: PSC_ENFORCE_SESSION_STATE(state, "550 5.3.2 Service currently unavailable\r\n"); break; case PSC_ACT_IGNORE: PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_BLIST_FAIL); /* * Not: PSC_PASS_SESSION_STATE. Repeat this test the next * time. */ break; default: msg_panic("%s: unknown blacklist action value %d", myname, psc_blist_action); } break; /* * Permanently whitelisted. */ case PSC_ACL_ACT_WHITELIST: msg_info("WHITELISTED [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); psc_conclude(state); return; /* * Other: dunno (don't know) or error. */ default: break; } } /* * The temporary whitelist (i.e. the postscreen cache) has the lowest * precedence. This cache contains information about the results of prior * tests. Whitelist the client when all enabled test results are still * valid. */ if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0 && psc_cache_map != 0 && (stamp_str = psc_cache_lookup(psc_cache_map, state->smtp_client_addr)) != 0) { saved_flags = state->flags; psc_parse_tests(state, stamp_str, event_time()); state->flags |= saved_flags; if (msg_verbose) msg_info("%s: cached + recent flags: %s", myname, psc_print_state_flags(state->flags, myname)); if ((state->flags & PSC_STATE_MASK_ANY_TODO_FAIL) == 0) { msg_info("PASS OLD [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); psc_conclude(state); return; } } else { saved_flags = state->flags; psc_new_tests(state); state->flags |= saved_flags; if (msg_verbose) msg_info("%s: new + recent flags: %s", myname, psc_print_state_flags(state->flags, myname)); } /* * Don't whitelist clients that connect to backup MX addresses. Fail * "closed" on error. */ if (addr_match_list_match(psc_wlist_if, smtp_server_addr->buf) == 0) { state->flags |= (PSC_STATE_FLAG_WLIST_FAIL | PSC_STATE_FLAG_NOFORWARD); msg_info("WHITELIST VETO [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); } /* * Reply with 421 when we can't analyze more connections. That also means * no deep protocol tests when the noforward flag is raised. */ if (var_psc_pre_queue_limit > 0 && psc_check_queue_length - psc_post_queue_length >= var_psc_pre_queue_limit) { msg_info("reject: connect from [%s]:%s: all screening ports busy", state->smtp_client_addr, state->smtp_client_port); PSC_DROP_SESSION_STATE(state, "421 4.3.2 All screening ports are busy\r\n"); return; } /* * If the client has no up-to-date results for some tests, do those tests * first. Otherwise, skip the tests and hand off the connection. */ if (state->flags & PSC_STATE_MASK_EARLY_TODO) psc_early_tests(state); else if (state->flags & (PSC_STATE_MASK_SMTPD_TODO | PSC_STATE_FLAG_NOFORWARD)) psc_smtpd_tests(state); else psc_conclude(state); }
static void qmgr_job_link(QMGR_JOB *job) { QMGR_TRANSPORT *transport = job->transport; QMGR_MESSAGE *message = job->message; QMGR_JOB *prev, *next, *list_prev, *list_next, *unread, *current; int delay; /* * Sanity checks. */ if (job->stack_level >= 0) msg_panic("qmgr_job_link: already on the job lists (%d)", job->stack_level); /* * Traverse the time list and the scheduler list from the end and stop * when we found job older than the one being linked. * * During the traversals keep track if we have come across either the * current job or the first unread job on the job list. If this is the * case, these pointers will be adjusted below as required. * * Although both lists are exactly the same when only jobs on the stack * level zero are considered, it's easier to traverse them separately. * Otherwise it's impossible to keep track of the current job pointer * effectively. * * This may look inefficient but under normal operation it is expected that * the loops will stop right away, resulting in normal list appends * below. However, this code is necessary for reviving retired jobs and * for jobs which are created long after the first chunk of recipients * was read in-core (either of these can happen only for multi-transport * messages). * * XXX Note that we test stack_parent rather than stack_level below. This * subtle difference allows us to enqueue the job in correct time order * with respect to orphaned children even after their original parent on * level zero is gone. Consequently, the early loop stop in candidate * selection works reliably, too. These are the reasons why we care to * bother with children adoption at all. */ current = transport->job_current; for (next = 0, prev = transport->job_list.prev; prev; next = prev, prev = prev->transport_peers.prev) { if (prev->stack_parent == 0) { delay = message->queued_time - prev->message->queued_time; if (delay >= 0) break; } if (current == prev) current = 0; } list_prev = prev; list_next = next; unread = transport->job_next_unread; for (next = 0, prev = transport->job_bytime.prev; prev; next = prev, prev = prev->time_peers.prev) { delay = message->queued_time - prev->message->queued_time; if (delay >= 0) break; if (unread == prev) unread = 0; } /* * Link the job into the proper place on the job lists and mark it so we * know it has been linked. */ job->stack_level = 0; QMGR_LIST_LINK(transport->job_list, list_prev, job, list_next, transport_peers); QMGR_LIST_LINK(transport->job_bytime, prev, job, next, time_peers); /* * Update the current job pointer if necessary. */ if (current == 0) transport->job_current = job; /* * Update the pointer to the first unread job on the job list and steal * the unused recipient slots from the old one. */ if (unread == 0) { unread = transport->job_next_unread; transport->job_next_unread = job; if (unread != 0) qmgr_job_move_limits(unread); } /* * Get as much recipient slots as possible. The excess will be returned * to the transport pool as soon as the exact amount required is known * (which is usually after all recipients have been read in core). */ if (transport->rcpt_unused > 0) { job->rcpt_limit += transport->rcpt_unused; message->rcpt_limit += transport->rcpt_unused; transport->rcpt_unused = 0; } }
static int smtpd_peer_sockaddr_to_hostaddr(SMTPD_STATE *state) { const char *myname = "smtpd_peer_sockaddr_to_hostaddr"; struct sockaddr *sa = (struct sockaddr *) &(state->sockaddr); SOCKADDR_SIZE sa_length = state->sockaddr_len; /* * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd, * while Postfix IPv6 (or IPv4) support is turned off, don't (skip to the * final else clause, pretend the origin is localhost[127.0.0.1], and * become an open relay). */ if (sa->sa_family == AF_INET #ifdef AF_INET6 || sa->sa_family == AF_INET6 #endif ) { MAI_HOSTADDR_STR client_addr; MAI_SERVPORT_STR client_port; MAI_HOSTADDR_STR server_addr; MAI_SERVPORT_STR server_port; int aierr; char *colonp; /* * Sanity check: we can't use sockets that we're not configured for. */ if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0) msg_fatal("cannot handle socket type %s with \"%s = %s\"", #ifdef AF_INET6 sa->sa_family == AF_INET6 ? "AF_INET6" : #endif sa->sa_family == AF_INET ? "AF_INET" : "other", VAR_INET_PROTOCOLS, var_inet_protocols); /* * Sorry, but there are some things that we just cannot do while * connected to the network. */ if (geteuid() != var_owner_uid || getuid() != var_owner_uid) { msg_error("incorrect SMTP server privileges: uid=%lu euid=%lu", (unsigned long) getuid(), (unsigned long) geteuid()); msg_fatal("the Postfix SMTP server must run with $%s privileges", VAR_MAIL_OWNER); } /* * Convert the client address to printable form. */ if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr, &client_port, 0)) != 0) msg_fatal("%s: cannot convert client address/port to string: %s", myname, MAI_STRERROR(aierr)); state->port = mystrdup(client_port.buf); /* * XXX Require that the infrastructure strips off the IPv6 datalink * suffix to avoid false alarms with strict address syntax checks. */ #ifdef HAS_IPV6 if (strchr(client_addr.buf, '%') != 0) msg_panic("%s: address %s has datalink suffix", myname, client_addr.buf); #endif /* * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on, * but only if IPv4 support is enabled (why would anyone want to turn * it off)? With IPv4 support enabled we have no need for the IPv6 * form in logging, hostname verification and access checks. */ #ifdef HAS_IPV6 if (sa->sa_family == AF_INET6) { if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0 && IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa)) && (colonp = strrchr(client_addr.buf, ':')) != 0) { struct addrinfo *res0; if (msg_verbose > 1) msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"", myname, client_addr.buf, colonp + 1); state->addr = mystrdup(colonp + 1); state->rfc_addr = mystrdup(colonp + 1); state->addr_family = AF_INET; aierr = hostaddr_to_sockaddr(state->addr, (char *) 0, 0, &res0); if (aierr) msg_fatal("%s: cannot convert %s from string to binary: %s", myname, state->addr, MAI_STRERROR(aierr)); sa_length = res0->ai_addrlen; if (sa_length > sizeof(state->sockaddr)) sa_length = sizeof(state->sockaddr); memcpy((void *) sa, res0->ai_addr, sa_length); freeaddrinfo(res0); /* 200412 */ } /* * Following RFC 2821 section 4.1.3, an IPv6 address literal gets * a prefix of 'IPv6:'. We do this consistently for all IPv6 * addresses that that appear in headers or envelopes. The fact * that valid_mailhost_addr() enforces the form helps of course. * We use the form without IPV6: prefix when doing access * control, or when accessing the connection cache. */ else { state->addr = mystrdup(client_addr.buf); state->rfc_addr = concatenate(IPV6_COL, client_addr.buf, (char *) 0); state->addr_family = sa->sa_family; } } /* * An IPv4 address is in dotted quad decimal form. */ else #endif { state->addr = mystrdup(client_addr.buf); state->rfc_addr = mystrdup(client_addr.buf); state->addr_family = sa->sa_family; } /* * Convert the server address/port to printable form. */ if ((aierr = sockaddr_to_hostaddr((struct sockaddr *) &state->dest_sockaddr, state->dest_sockaddr_len, &server_addr, &server_port, 0)) != 0) msg_fatal("%s: cannot convert server address/port to string: %s", myname, MAI_STRERROR(aierr)); /* TODO: convert IPv4-in-IPv6 to IPv4 form. */ state->dest_addr = mystrdup(server_addr.buf); state->dest_port = mystrdup(server_port.buf); return (0); } /* * It's not Internet. */ else { return (-1); } }
int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why) { const char *myname = "smtp_sasl_authenticate"; SMTP_ITERATOR *iter = session->iterator; SMTP_RESP *resp; const char *mechanism; int result; char *line; int steps = 0; /* * Sanity check. */ if (session->sasl_mechanism_list == 0) msg_panic("%s: no mechanism list", myname); if (msg_verbose) msg_info("%s: %s: SASL mechanisms %s", myname, session->namaddrport, session->sasl_mechanism_list); /* * Avoid repeated login failures after a recent 535 error. */ #ifdef HAVE_SASL_AUTH_CACHE if (smtp_sasl_auth_cache && smtp_sasl_auth_cache_find(smtp_sasl_auth_cache, session)) { char *resp_dsn = smtp_sasl_auth_cache_dsn(smtp_sasl_auth_cache); char *resp_str = smtp_sasl_auth_cache_text(smtp_sasl_auth_cache); if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5') resp_dsn[0] = '4'; dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS, STR(iter->host), var_procname, resp_str, "SASL [CACHED] authentication failed; server %s said: %s", STR(iter->host), resp_str); return (0); } #endif /* * Start the client side authentication protocol. */ result = xsasl_client_first(session->sasl_client, session->sasl_mechanism_list, session->sasl_username, session->sasl_passwd, &mechanism, session->sasl_reply); if (result != XSASL_AUTH_OK) { dsb_update(why, "4.7.0", DSB_DEF_ACTION, DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply), "SASL authentication failed; " "cannot authenticate to server %s: %s", session->namaddr, STR(session->sasl_reply)); return (-1); } /* * Send the AUTH command and the optional initial client response. * sasl_encode64() produces four bytes for each complete or incomplete * triple of input bytes. Allocate an extra byte for string termination. */ if (LEN(session->sasl_reply) > 0) { smtp_chat_cmd(session, "AUTH %s %s", mechanism, STR(session->sasl_reply)); } else { smtp_chat_cmd(session, "AUTH %s", mechanism); } /* * Step through the authentication protocol until the server tells us * that we are done. */ while ((resp = smtp_chat_resp(session))->code / 100 == 3) { /* * Sanity check. */ if (++steps > 100) { dsb_simple(why, "4.3.0", "SASL authentication failed; " "authentication protocol loop with server %s", session->namaddr); return (-1); } /* * Process a server challenge. */ line = resp->str; (void) mystrtok(&line, "- \t\n"); /* skip over result code */ result = xsasl_client_next(session->sasl_client, line, session->sasl_reply); if (result != XSASL_AUTH_OK) { dsb_update(why, "4.7.0", DSB_DEF_ACTION, /* Fix 200512 */ DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply), "SASL authentication failed; " "cannot authenticate to server %s: %s", session->namaddr, STR(session->sasl_reply)); return (-1); /* Fix 200512 */ } /* * Send a client response. */ smtp_chat_cmd(session, "%s", STR(session->sasl_reply)); } /* * We completed the authentication protocol. */ if (resp->code / 100 != 2) { #ifdef HAVE_SASL_AUTH_CACHE /* Update the 535 authentication failure cache. */ if (smtp_sasl_auth_cache && resp->code == 535) smtp_sasl_auth_cache_store(smtp_sasl_auth_cache, session, resp); #endif if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5) STR(resp->dsn_buf)[0] = '4'; dsb_update(why, resp->dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS, STR(iter->host), var_procname, resp->str, "SASL authentication failed; server %s said: %s", session->namaddr, resp->str); return (0); } return (1); }
int smtpd_peer_from_haproxy(SMTPD_STATE *state) { const char *myname = "smtpd_peer_from_haproxy"; MAI_HOSTADDR_STR smtp_client_addr; MAI_SERVPORT_STR smtp_client_port; MAI_HOSTADDR_STR smtp_server_addr; MAI_SERVPORT_STR smtp_server_port; const char *proxy_err; int io_err; VSTRING *escape_buf; /* * While reading HAProxy handshake information, don't buffer input beyond * the end-of-line. That would break the TLS wrappermode handshake. */ vstream_control(state->client, VSTREAM_CTL_BUFSIZE, 1, VSTREAM_CTL_END); /* * Note: the haproxy_srvr_parse() routine performs address protocol * checks, address and port syntax checks, and converts IPv4-in-IPv6 * address string syntax (:ffff::1.2.3.4) to IPv4 syntax where permitted * by the main.cf:inet_protocols setting, but logs no warnings. */ #define ENABLE_DEADLINE 1 smtp_stream_setup(state->client, var_smtpd_uproxy_tmout, ENABLE_DEADLINE); switch (io_err = vstream_setjmp(state->client)) { default: msg_panic("%s: unhandled I/O error %d", myname, io_err); case SMTP_ERR_EOF: msg_warn("haproxy read: unexpected EOF"); return (-1); case SMTP_ERR_TIME: msg_warn("haproxy read: timeout error"); return (-1); case 0: if (smtp_get(state->buffer, state->client, HAPROXY_MAX_LEN, SMTP_GET_FLAG_NONE) != '\n') { msg_warn("haproxy read: line > %d characters", HAPROXY_MAX_LEN); return (-1); } if ((proxy_err = haproxy_srvr_parse(STR(state->buffer), &smtp_client_addr, &smtp_client_port, &smtp_server_addr, &smtp_server_port)) != 0) { escape_buf = vstring_alloc(HAPROXY_MAX_LEN + 2); escape(escape_buf, STR(state->buffer), LEN(state->buffer)); msg_warn("haproxy read: %s: %s", proxy_err, STR(escape_buf)); vstring_free(escape_buf); return (-1); } state->addr = mystrdup(smtp_client_addr.buf); if (strrchr(state->addr, ':') != 0) { state->rfc_addr = concatenate(IPV6_COL, state->addr, (char *) 0); state->addr_family = AF_INET6; } else { state->rfc_addr = mystrdup(state->addr); state->addr_family = AF_INET; } state->port = mystrdup(smtp_client_port.buf); /* * Avoid surprises in the Dovecot authentication server. */ state->dest_addr = mystrdup(smtp_server_addr.buf); /* * Enable normal buffering. */ vstream_control(state->client, VSTREAM_CTL_BUFSIZE, VSTREAM_BUFSIZE, VSTREAM_CTL_END); return (0); } }
static void master_status_event(int event, char *context) { const char *myname = "master_status_event"; MASTER_SERV *serv = (MASTER_SERV *) context; MASTER_STATUS stat; MASTER_PROC *proc; MASTER_PID pid; int n; if (event == 0) /* XXX Can this happen? */ return; /* * We always keep the child end of the status pipe open, so an EOF read * condition means that we're seriously confused. We use non-blocking * reads so that we don't get stuck when someone sends a partial message. * Messages are short, so a partial read means someone wrote less than a * whole status message. Hopefully the next read will be in sync again... * We use a global child process status table because when a child dies * only its pid is known - we do not know what service it came from. */ switch (n = read(serv->status_fd[0], (char *) &stat, sizeof(stat))) { case -1: msg_warn("%s: read: %m", myname); return; case 0: msg_panic("%s: read EOF status", myname); /* NOTREACHED */ default: msg_warn("service %s(%s): child (pid %d) sent partial status update (%d bytes)", serv->ext_name, serv->name, stat.pid, n); return; case sizeof(stat): pid = stat.pid; if (msg_verbose) msg_info("%s: pid %d gen %u avail %d", myname, stat.pid, stat.gen, stat.avail); } /* * Sanity checks. Do not freak out when the child sends garbage because * it is confused or for other reasons. However, be sure to freak out * when our own data structures are inconsistent. A process not found * condition can happen when we reap a process before receiving its * status update, so this is not an error. */ if ((proc = (MASTER_PROC *) binhash_find(master_child_table, (char *) &pid, sizeof(pid))) == 0) { if (msg_verbose) msg_info("%s: process id not found: %d", myname, stat.pid); return; } if (proc->gen != stat.gen) { msg_info("ignoring status update from child pid %d generation %u", pid, stat.gen); return; } if (proc->serv != serv) msg_panic("%s: pointer corruption: %p != %p", myname, (void *) proc->serv, (void *) serv); /* * Update our idea of the child process status. Allow redundant status * updates, because different types of events may be processed out of * order. Otherwise, warn about weird status updates but do not take * action. It's all gossip after all. */ if (proc->avail == stat.avail) return; switch (stat.avail) { case MASTER_STAT_AVAIL: proc->use_count++; master_avail_more(serv, proc); break; case MASTER_STAT_TAKEN: master_avail_less(serv, proc); break; default: msg_warn("%s: ignoring unknown status: %d allegedly from pid: %d", myname, stat.pid, stat.avail); break; } }
void pcf_register_service_parameters(void) { const char *myname = "pcf_register_service_parameters"; static const PCF_STRING_NV pipe_params[] = { /* suffix, default parameter name */ _MAXTIME, VAR_COMMAND_MAXTIME, #define service_params (pipe_params + 1) _XPORT_RCPT_LIMIT, VAR_XPORT_RCPT_LIMIT, _STACK_RCPT_LIMIT, VAR_STACK_RCPT_LIMIT, _XPORT_REFILL_LIMIT, VAR_XPORT_REFILL_LIMIT, _XPORT_REFILL_DELAY, VAR_XPORT_REFILL_DELAY, _DELIVERY_SLOT_COST, VAR_DELIVERY_SLOT_COST, _DELIVERY_SLOT_LOAN, VAR_DELIVERY_SLOT_LOAN, _DELIVERY_SLOT_DISCOUNT, VAR_DELIVERY_SLOT_DISCOUNT, _MIN_DELIVERY_SLOTS, VAR_MIN_DELIVERY_SLOTS, _INIT_DEST_CON, VAR_INIT_DEST_CON, _DEST_CON_LIMIT, VAR_DEST_CON_LIMIT, _DEST_RCPT_LIMIT, VAR_DEST_RCPT_LIMIT, _CONC_POS_FDBACK, VAR_CONC_POS_FDBACK, _CONC_NEG_FDBACK, VAR_CONC_NEG_FDBACK, _CONC_COHORT_LIM, VAR_CONC_COHORT_LIM, _DEST_RATE_DELAY, VAR_DEST_RATE_DELAY, 0, }; static const PCF_STRING_NV spawn_params[] = { /* suffix, default parameter name */ _MAXTIME, VAR_COMMAND_MAXTIME, 0, }; typedef struct { const char *progname; const PCF_STRING_NV *params; } PCF_SERVICE_DEF; static const PCF_SERVICE_DEF service_defs[] = { MAIL_PROGRAM_LOCAL, service_params, MAIL_PROGRAM_ERROR, service_params, MAIL_PROGRAM_VIRTUAL, service_params, MAIL_PROGRAM_SMTP, service_params, MAIL_PROGRAM_LMTP, service_params, MAIL_PROGRAM_PIPE, pipe_params, MAIL_PROGRAM_SPAWN, spawn_params, 0, }; const PCF_STRING_NV *sp; const char *progname; const char *service; PCF_MASTER_ENT *masterp; ARGV *argv; const PCF_SERVICE_DEF *sd; /* * Sanity checks. */ if (pcf_param_table == 0) msg_panic("%s: global parameter table is not initialized", myname); if (pcf_master_table == 0) msg_panic("%s: master table is not initialized", myname); /* * Extract service names from master.cf and generate service parameter * information. */ for (masterp = pcf_master_table; (argv = masterp->argv) != 0; masterp++) { /* * Add service parameters for message delivery transports or spawn * programs. */ progname = argv->argv[7]; for (sd = service_defs; sd->progname; sd++) { if (strcmp(sd->progname, progname) == 0) { service = argv->argv[0]; for (sp = sd->params; sp->name; sp++) pcf_register_service_parameter(service, sp->name, sp->value); break; } } } }
int myflock(int fd, int lock_style, int operation) { int status; /* * Sanity check. */ if ((operation & (MYFLOCK_OP_BITS)) != operation) msg_panic("myflock: improper operation type: 0x%x", operation); switch (lock_style) { /* * flock() does exactly what we need. Too bad it is not standard. */ #ifdef HAS_FLOCK_LOCK case MYFLOCK_STYLE_FLOCK: { static int lock_ops[] = { LOCK_UN, LOCK_SH, LOCK_EX, -1, -1, LOCK_SH | LOCK_NB, LOCK_EX | LOCK_NB, -1 }; while ((status = flock(fd, lock_ops[operation])) < 0 && errno == EINTR) sleep(1); break; } #endif /* * fcntl() is standard and does more than we need, but we can handle * it. */ #ifdef HAS_FCNTL_LOCK case MYFLOCK_STYLE_FCNTL: { struct flock lock; int request; static int lock_ops[] = { F_UNLCK, F_RDLCK, F_WRLCK }; memset((char *) &lock, 0, sizeof(lock)); lock.l_type = lock_ops[operation & ~MYFLOCK_OP_NOWAIT]; request = (operation & MYFLOCK_OP_NOWAIT) ? F_SETLK : F_SETLKW; while ((status = fcntl(fd, request, &lock)) < 0 && errno == EINTR) sleep(1); break; } #endif default: msg_panic("myflock: unsupported lock style: 0x%x", lock_style); } /* * Return a consistent result. Some systems return EACCES when a lock is * taken by someone else, and that would complicate error processing. */ if (status < 0 && (operation & MYFLOCK_OP_NOWAIT) != 0) if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EACCES) errno = EAGAIN; return (status); }
void pcf_show_master_fields(VSTREAM *fp, int mode, int argc, char **argv) { const char *myname = "pcf_show_master_fields"; PCF_MASTER_ENT *masterp; PCF_MASTER_FLD_REQ *field_reqs; PCF_MASTER_FLD_REQ *req; int field; /* * Parse the filter expressions. */ if (argc > 0) { field_reqs = (PCF_MASTER_FLD_REQ *) mymalloc(sizeof(*field_reqs) * argc); for (req = field_reqs; req < field_reqs + argc; req++) { req->match_count = 0; req->raw_text = *argv++; req->service_pattern = pcf_parse_service_pattern(req->raw_text, 1, 3); if (req->service_pattern == 0) msg_fatal("-F option requires service_name[/type[/field]]"); field = req->field_pattern = pcf_parse_field_pattern(req->service_pattern->argv[2]); if (pcf_is_magic_field_pattern(field) == 0 && (field < 0 || field > PCF_MASTER_FLD_CMD)) msg_panic("%s: bad attribute field index: %d", myname, field); } } /* * Iterate over the master table. */ for (masterp = pcf_master_table; masterp->argv != 0; masterp++) { if (argc > 0) { for (req = field_reqs; req < field_reqs + argc; req++) { if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, masterp->argv->argv[0], masterp->argv->argv[1])) { req->match_count++; field = req->field_pattern; if (pcf_is_magic_field_pattern(field)) { for (field = 0; field <= PCF_MASTER_FLD_CMD; field++) pcf_print_master_field(fp, mode, masterp, field); } else { pcf_print_master_field(fp, mode, masterp, field); } } } } else { for (field = 0; field <= PCF_MASTER_FLD_CMD; field++) pcf_print_master_field(fp, mode, masterp, field); } } /* * Cleanup. */ if (argc > 0) { for (req = field_reqs; req < field_reqs + argc; req++) { if (req->match_count == 0) msg_warn("unmatched request: \"%s\"", req->raw_text); argv_free(req->service_pattern); } myfree((void *) field_reqs); } }
DNS_RR *smtp_domain_addr(const char *name, DNS_RR **mxrr, int misc_flags, DSN_BUF *why, int *found_myself) { DNS_RR *mx_names; DNS_RR *addr_list = 0; DNS_RR *self = 0; unsigned best_pref; unsigned best_found; int r = 0; /* Resolver flags */ const char *aname; dsb_reset(why); /* Paranoia */ /* * Preferences from DNS use 0..32767, fall-backs use 32768+. */ #define IMPOSSIBLE_PREFERENCE (~0) /* * Sanity check. */ if (smtp_dns_support == SMTP_DNS_DISABLED) msg_panic("smtp_domain_addr: DNS lookup is disabled"); if (smtp_dns_support == SMTP_DNS_DNSSEC) r |= RES_USE_DNSSEC; /* * IDNA support. */ #ifndef NO_EAI if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) { if (msg_verbose) msg_info("%s asciified to %s", name, aname); } else #endif aname = name; /* * Look up the mail exchanger hosts listed for this name. Sort the * results by preference. Look up the corresponding host addresses, and * truncate the list so that it contains only hosts that are more * preferred than myself. When no MX resource records exist, look up the * addresses listed for this name. * * According to RFC 974: "It is possible that the list of MXs in the * response to the query will be empty. This is a special case. If the * list is empty, mailers should treat it as if it contained one RR, an * MX RR with a preference value of 0, and a host name of REMOTE. (I.e., * REMOTE is its only MX). In addition, the mailer should do no further * processing on the list, but should attempt to deliver the message to * REMOTE." * * Normally it is OK if an MX host cannot be found in the DNS; we'll just * use a backup one, and silently ignore the better MX host. However, if * the best backup that we can find in the DNS is the local machine, then * we must remember that the local machine is not the primary MX host, or * else we will claim that mail loops back. * * XXX Optionally do A lookups even when the MX lookup didn't complete. * Unfortunately with some DNS servers this is not a transient problem. * * XXX Ideally we would perform A lookups only as far as needed. But as long * as we're looking up all the hosts, it would be better to look up the * least preferred host first, so that DNS lookup error messages make * more sense. * * XXX 2821: RFC 2821 says that the sender must shuffle equal-preference MX * hosts, whereas multiple A records per hostname must be used in the * order as received. They make the bogus assumption that a hostname with * multiple A records corresponds to one machine with multiple network * interfaces. * * XXX 2821: Postfix recognizes the local machine by looking for its own IP * address in the list of mail exchangers. RFC 2821 says one has to look * at the mail exchanger hostname as well, making the bogus assumption * that an IP address is listed only under one hostname. However, looking * at hostnames provides a partial solution for MX hosts behind a NAT * gateway. */ switch (dns_lookup(aname, T_MX, r, &mx_names, (VSTRING *) 0, why->reason)) { default: dsb_status(why, "4.4.3"); if (var_ign_mx_lookup_err) addr_list = smtp_host_addr(aname, misc_flags, why); break; case DNS_INVAL: dsb_status(why, "5.4.4"); if (var_ign_mx_lookup_err) addr_list = smtp_host_addr(aname, misc_flags, why); break; case DNS_NULLMX: dsb_status(why, "5.1.0"); break; case DNS_POLICY: dsb_status(why, "4.7.0"); break; case DNS_FAIL: dsb_status(why, "5.4.3"); if (var_ign_mx_lookup_err) addr_list = smtp_host_addr(aname, misc_flags, why); break; case DNS_OK: mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any); best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE); addr_list = smtp_addr_list(mx_names, why); if (mxrr) *mxrr = dns_rr_copy(mx_names); /* copies one record! */ dns_rr_free(mx_names); if (addr_list == 0) { /* Text does not change. */ if (var_smtp_defer_mxaddr) { /* Don't clobber the null terminator. */ if (SMTP_HAS_HARD_DSN(why)) SMTP_SET_SOFT_DSN(why); /* XXX */ /* Require some error status. */ else if (!SMTP_HAS_SOFT_DSN(why)) msg_panic("smtp_domain_addr: bad status"); } msg_warn("no MX host for %s has a valid address record", name); break; } best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE); if (msg_verbose) smtp_print_addr(name, addr_list); if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) && (self = smtp_find_self(addr_list)) != 0) { addr_list = smtp_truncate_self(addr_list, self->pref); if (addr_list == 0) { if (best_pref != best_found) { dsb_simple(why, "4.4.4", "unable to find primary relay for %s", name); } else { dsb_simple(why, "5.4.6", "mail for %s loops back to myself", name); } } } #define SMTP_COMPARE_ADDR(flags) \ (((flags) & SMTP_MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \ ((flags) & SMTP_MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \ dns_rr_compare_pref_any) if (addr_list && addr_list->next && var_smtp_rand_addr) { addr_list = dns_rr_shuffle(addr_list); addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); } break; case DNS_NOTFOUND: addr_list = smtp_host_addr(aname, misc_flags, why); break; } /* * Clean up. */ *found_myself |= (self != 0); return (addr_list); }
int main(int unused_argc, char **unused_argv) { char *extension; char *stripped; char *delim = "+-"; #define NO_DELIM "" /* * Incredible. This function takes only three arguments, and the tests * already take more lines of code than the code being tested. */ stripped = strip_addr_internal("foo", (char **) 0, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 1"); stripped = strip_addr_internal("foo", &extension, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 2"); if (extension != 0) msg_panic("strip_addr botch 3"); stripped = strip_addr_internal("foo", (char **) 0, delim); if (stripped != 0) msg_panic("strip_addr botch 4"); stripped = strip_addr_internal("foo", &extension, delim); if (stripped != 0) msg_panic("strip_addr botch 5"); if (extension != 0) msg_panic("strip_addr botch 6"); stripped = strip_addr_internal("foo@bar", (char **) 0, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 7"); stripped = strip_addr_internal("foo@bar", &extension, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 8"); if (extension != 0) msg_panic("strip_addr botch 9"); stripped = strip_addr_internal("foo@bar", (char **) 0, delim); if (stripped != 0) msg_panic("strip_addr botch 10"); stripped = strip_addr_internal("foo@bar", &extension, delim); if (stripped != 0) msg_panic("strip_addr botch 11"); if (extension != 0) msg_panic("strip_addr botch 12"); stripped = strip_addr_internal("foo-ext", (char **) 0, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 13"); stripped = strip_addr_internal("foo-ext", &extension, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 14"); if (extension != 0) msg_panic("strip_addr botch 15"); stripped = strip_addr_internal("foo-ext", (char **) 0, delim); if (stripped == 0) msg_panic("strip_addr botch 16"); msg_info("wanted: foo-ext -> %s", "foo"); msg_info("strip_addr foo-ext -> %s", stripped); myfree(stripped); stripped = strip_addr_internal("foo-ext", &extension, delim); if (stripped == 0) msg_panic("strip_addr botch 17"); if (extension == 0) msg_panic("strip_addr botch 18"); msg_info("wanted: foo-ext -> %s %s", "foo", "-ext"); msg_info("strip_addr foo-ext -> %s %s", stripped, extension); myfree(stripped); myfree(extension); stripped = strip_addr_internal("foo-ext@bar", (char **) 0, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 19"); stripped = strip_addr_internal("foo-ext@bar", &extension, NO_DELIM); if (stripped != 0) msg_panic("strip_addr botch 20"); if (extension != 0) msg_panic("strip_addr botch 21"); stripped = strip_addr_internal("foo-ext@bar", (char **) 0, delim); if (stripped == 0) msg_panic("strip_addr botch 22"); msg_info("wanted: foo-ext@bar -> %s", "foo@bar"); msg_info("strip_addr foo-ext@bar -> %s", stripped); myfree(stripped); stripped = strip_addr_internal("foo-ext@bar", &extension, delim); if (stripped == 0) msg_panic("strip_addr botch 23"); if (extension == 0) msg_panic("strip_addr botch 24"); msg_info("wanted: foo-ext@bar -> %s %s", "foo@bar", "-ext"); msg_info("strip_addr foo-ext@bar -> %s %s", stripped, extension); myfree(stripped); myfree(extension); stripped = strip_addr_internal("foo+ext@bar", &extension, delim); if (stripped == 0) msg_panic("strip_addr botch 25"); if (extension == 0) msg_panic("strip_addr botch 26"); msg_info("wanted: foo+ext@bar -> %s %s", "foo@bar", "+ext"); msg_info("strip_addr foo+ext@bar -> %s %s", stripped, extension); myfree(stripped); myfree(extension); stripped = strip_addr_internal("foo bar+ext", &extension, delim); if (stripped == 0) msg_panic("strip_addr botch 27"); if (extension == 0) msg_panic("strip_addr botch 28"); msg_info("wanted: foo bar+ext -> %s %s", "foo bar", "+ext"); msg_info("strip_addr foo bar+ext -> %s %s", stripped, extension); myfree(stripped); myfree(extension); return (0); }
int attr_vscan0(VSTREAM *fp, int flags, va_list ap) { const char *myname = "attr_scan0"; static VSTRING *str_buf = 0; static VSTRING *name_buf = 0; int wanted_type = -1; char *wanted_name; unsigned int *number; unsigned long *long_number; VSTRING *string; HTABLE *hash_table; int ch; int conversions; ATTR_SCAN_SLAVE_FN scan_fn; void *scan_arg; /* * Sanity check. */ if (flags & ~ATTR_FLAG_ALL) msg_panic("%s: bad flags: 0x%x", myname, flags); /* * EOF check. */ if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF) return (0); vstream_ungetc(fp, ch); /* * Initialize. */ if (str_buf == 0) { str_buf = vstring_alloc(10); name_buf = vstring_alloc(10); } /* * Iterate over all (type, name, value) triples. */ for (conversions = 0; /* void */ ; conversions++) { /* * Determine the next attribute type and attribute name on the * caller's wish list. * * If we're reading into a hash table, we already know that the * attribute value is string-valued, and we get the attribute name * from the input stream instead. This is secure only when the * resulting table is queried with known to be good attribute names. */ if (wanted_type != ATTR_TYPE_HASH) { wanted_type = va_arg(ap, int); if (wanted_type == ATTR_TYPE_END) { if ((flags & ATTR_FLAG_MORE) != 0) return (conversions); wanted_name = "(list terminator)"; } else if (wanted_type == ATTR_TYPE_HASH) { wanted_name = "(any attribute name or list terminator)"; hash_table = va_arg(ap, HTABLE *); if (va_arg(ap, int) != ATTR_TYPE_END) msg_panic("%s: ATTR_TYPE_HASH not followed by ATTR_TYPE_END", myname); } else if (wanted_type != ATTR_TYPE_FUNC) {
static void qmqpd_proto(QMQPD_STATE *state) { int status; netstring_setup(state->client, var_qmqpd_timeout); switch (status = vstream_setjmp(state->client)) { default: msg_panic("qmqpd_proto: unknown status %d", status); case NETSTRING_ERR_EOF: state->reason = "lost connection"; break; case NETSTRING_ERR_TIME: state->reason = "read/write timeout"; break; case NETSTRING_ERR_FORMAT: state->reason = "netstring format error"; if (vstream_setjmp(state->client) == 0) if (state->reason && state->where) qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, "%s while %s", state->reason, state->where); break; case NETSTRING_ERR_SIZE: state->reason = "netstring length exceeds storage limit"; if (vstream_setjmp(state->client) == 0) if (state->reason && state->where) qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, "%s while %s", state->reason, state->where); break; case 0: /* * See if we want to talk to this client at all. */ if (namadr_list_match(qmqpd_clients, state->name, state->addr) != 0) { qmqpd_receive(state); } else if (qmqpd_clients->error == 0) { qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, "Error: %s is not authorized to use this service", state->namaddr); } else { qmqpd_reply(state, DONT_LOG, QMQPD_STAT_RETRY, "Error: server configuration error"); } break; } /* * Log abnormal session termination. Indicate the last recognized state * before things went wrong. */ if (state->reason && state->where) msg_info("%s: %s: %s while %s", state->queue_id ? state->queue_id : "NOQUEUE", state->namaddr, state->reason, state->where); }
static int mac_expand_callback(int type, VSTRING *buf, void *ptr) { static const char myname[] = "mac_expand_callback"; MAC_EXP_CONTEXT *mc = (MAC_EXP_CONTEXT *) ptr; int lookup_mode; const char *lookup; char *cp; int ch; ssize_t res_len; ssize_t tmp_len; const char *res_iftrue; const char *res_iffalse; /* * Sanity check. */ if (mc->level++ > 100) mac_exp_parse_error(mc, "unreasonable macro call nesting: \"%s\"", vstring_str(buf)); if (mc->status & MAC_PARSE_ERROR) return (mc->status); /* * Named parameter or relational expression. In case of a syntax error, * return without doing damage, and issue a warning instead. */ if (type == MAC_PARSE_EXPR) { cp = vstring_str(buf); /* * Relational expression. If recursion is disabled, perform only one * level of $name expansion. */ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { if (mac_exp_parse_relational(mc, &lookup, &cp) != 0) return (mc->status); /* * Look for the ? or : operator. */ if ((ch = *cp) != 0) { if (ch != '?' && ch != ':') MAC_EXP_ERR_RETURN(mc, "\"?\" or \":\" expected at: " "\"...}>>>%.20s\"", cp); cp++; } } /* * Named parameter. */ else { /* * Look for the ? or : operator. In case of a syntax error, * return without doing damage, and issue a warning instead. */ for ( /* void */ ; /* void */ ; cp++) { if ((ch = *cp) == 0) { lookup_mode = MAC_EXP_MODE_USE; break; } if (ch == '?' || ch == ':') { *cp++ = 0; lookup_mode = MAC_EXP_MODE_TEST; break; } if (!ISALNUM(ch) && ch != '_') { MAC_EXP_ERR_RETURN(mc, "attribute name syntax error at: " "\"...%.*s>>>%.20s\"", (int) (cp - vstring_str(buf)), vstring_str(buf), cp); } } /* * Look up the named parameter. Todo: allow the lookup function * to specify if the result is safe for $name expanson. */ lookup = mc->lookup(vstring_str(buf), lookup_mode, mc->context); } /* * Return the requested result. After parsing the result operand * following ?, we fall through to parse the result operand following * :. This is necessary with the ternary ?: operator: first, with * MAC_EXP_FLAG_SCAN to parse both result operands with mac_parse(), * and second, to find garbage after any result operand. Without * MAC_EXP_FLAG_SCAN the content of only one of the ?: result * operands will be parsed with mac_parse(); syntax errors in the * other operand will be missed. */ switch (ch) { case '?': if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { if ((res_iftrue = mac_exp_extract_curly_payload(mc, &cp)) == 0) return (mc->status); } else { res_iftrue = cp; cp = ""; /* no left-over text */ } if ((lookup != 0 && *lookup != 0) || (mc->flags & MAC_EXP_FLAG_SCAN)) mc->status |= mac_parse(res_iftrue, mac_expand_callback, (void *) mc); if (*cp == 0) /* end of input, OK */ break; if (*cp != ':') /* garbage */ MAC_EXP_ERR_RETURN(mc, "\":\" expected at: " "\"...%s}>>>%.20s\"", res_iftrue, cp); cp += 1; /* FALLTHROUGH: do not remove, see comment above. */ case ':': if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { if ((res_iffalse = mac_exp_extract_curly_payload(mc, &cp)) == 0) return (mc->status); } else { res_iffalse = cp; cp = ""; /* no left-over text */ } if (lookup == 0 || *lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) mc->status |= mac_parse(res_iffalse, mac_expand_callback, (void *) mc); if (*cp != 0) /* garbage */ MAC_EXP_ERR_RETURN(mc, "unexpected input at: " "\"...%s}>>>%.20s\"", res_iffalse, cp); break; case 0: if (lookup == 0) { mc->status |= MAC_PARSE_UNDEF; } else if (*lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) { /* void */ ; } else if (mc->flags & MAC_EXP_FLAG_RECURSE) { vstring_strcpy(buf, lookup); mc->status |= mac_parse(vstring_str(buf), mac_expand_callback, (void *) mc); } else { res_len = VSTRING_LEN(mc->result); vstring_strcat(mc->result, lookup); if (mc->flags & MAC_EXP_FLAG_PRINTABLE) { printable(vstring_str(mc->result) + res_len, '_'); } else if (mc->filter) { cp = vstring_str(mc->result) + res_len; while (*(cp += strspn(cp, mc->filter))) *cp++ = '_'; } } break; default: msg_panic("%s: unknown operator code %d", myname, ch); } } /* * Literal text. */ else if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0) { vstring_strcat(mc->result, vstring_str(buf)); } mc->level--; return (mc->status); }
NORETURN trigger_server_main(int argc, char **argv, TRIGGER_SERVER_FN service,...) { const char *myname = "trigger_server_main"; char *root_dir = 0; char *user_name = 0; int debug_me = 0; int daemon_mode = 1; char *service_name = basename(argv[0]); VSTREAM *stream = 0; int delay; int c; int socket_count = 1; int fd; va_list ap; MAIL_SERVER_INIT_FN pre_init = 0; MAIL_SERVER_INIT_FN post_init = 0; MAIL_SERVER_LOOP_FN loop = 0; int key; char buf[TRIGGER_BUF_SIZE]; int len; char *transport = 0; char *lock_path; VSTRING *why; int alone = 0; int zerolimit = 0; WATCHDOG *watchdog; char *oname_val; char *oname; char *oval; const char *err; char *generation; int msg_vstream_needed = 0; int redo_syslog_init = 0; /* * Process environment options as early as we can. */ if (getenv(CONF_ENV_VERB)) msg_verbose = 1; if (getenv(CONF_ENV_DEBUG)) debug_me = 1; /* * Don't die when a process goes away unexpectedly. */ signal(SIGPIPE, SIG_IGN); /* * Don't die for frivolous reasons. */ #ifdef SIGXFSZ signal(SIGXFSZ, SIG_IGN); #endif /* * May need this every now and then. */ var_procname = mystrdup(basename(argv[0])); set_mail_conf_str(VAR_PROCNAME, var_procname); /* * Initialize logging and exit handler. Do the syslog first, so that its * initialization completes before we enter the optional chroot jail. */ msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); if (msg_verbose) msg_info("daemon started"); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * Initialize from the configuration file. Allow command-line options to * override compiled-in defaults or configured parameter values. */ mail_conf_suck(); /* * Register dictionaries that use higher-level interfaces and protocols. */ mail_dict_init(); /* * After database open error, continue execution with reduced * functionality. */ dict_allow_surrogate = 1; /* * Pick up policy settings from master process. Shut up error messages to * stderr, because no-one is going to see them. */ opterr = 0; while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) { switch (c) { case 'c': root_dir = "setme"; break; case 'd': daemon_mode = 0; break; case 'D': debug_me = 1; break; case 'i': mail_conf_update(VAR_MAX_IDLE, optarg); break; case 'l': alone = 1; break; case 'm': mail_conf_update(VAR_MAX_USE, optarg); break; case 'n': service_name = optarg; break; case 'o': oname_val = mystrdup(optarg); if ((err = split_nameval(oname_val, &oname, &oval)) != 0) msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); mail_conf_update(oname, oval); if (strcmp(oname, VAR_SYSLOG_NAME) == 0) redo_syslog_init = 1; myfree(oname_val); break; case 's': if ((socket_count = atoi(optarg)) <= 0) msg_fatal("invalid socket_count: %s", optarg); break; case 'S': stream = VSTREAM_IN; break; case 't': transport = optarg; break; case 'u': user_name = "setme"; break; case 'v': msg_verbose++; break; case 'V': if (++msg_vstream_needed == 1) msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); break; case 'z': zerolimit = 1; break; default: msg_fatal("invalid option: %c", c); break; } } /* * Initialize generic parameters. */ mail_params_init(); if (redo_syslog_init) msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); /* * If not connected to stdin, stdin must not be a terminal. */ if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) { msg_vstream_init(var_procname, VSTREAM_ERR); msg_fatal("do not run this command by hand"); } /* * Application-specific initialization. */ va_start(ap, service); while ((key = va_arg(ap, int)) != 0) { switch (key) { case MAIL_SERVER_INT_TABLE: get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); break; case MAIL_SERVER_LONG_TABLE: get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); break; case MAIL_SERVER_STR_TABLE: get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); break; case MAIL_SERVER_BOOL_TABLE: get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); break; case MAIL_SERVER_TIME_TABLE: get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); break; case MAIL_SERVER_RAW_TABLE: get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); break; case MAIL_SERVER_NINT_TABLE: get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); break; case MAIL_SERVER_NBOOL_TABLE: get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); break; case MAIL_SERVER_PRE_INIT: pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_POST_INIT: post_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_LOOP: loop = va_arg(ap, MAIL_SERVER_LOOP_FN); break; case MAIL_SERVER_EXIT: trigger_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); break; case MAIL_SERVER_PRE_ACCEPT: trigger_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); break; case MAIL_SERVER_IN_FLOW_DELAY: trigger_server_in_flow_delay = 1; break; case MAIL_SERVER_SOLITARY: if (stream == 0 && !alone) msg_fatal("service %s requires a process limit of 1", service_name); break; case MAIL_SERVER_UNLIMITED: if (stream == 0 && !zerolimit) msg_fatal("service %s requires a process limit of 0", service_name); break; case MAIL_SERVER_PRIVILEGED: if (user_name) msg_fatal("service %s requires privileged operation", service_name); break; case MAIL_SERVER_WATCHDOG: trigger_server_watchdog = *va_arg(ap, int *); break; default: msg_panic("%s: unknown argument type: %d", myname, key); } } va_end(ap); if (root_dir) root_dir = var_queue_dir; if (user_name) user_name = var_mail_owner; /* * Can options be required? * * XXX Initially this code was implemented with UNIX-domain sockets, but * Solaris <= 2.5 UNIX-domain sockets misbehave hopelessly when the * client disconnects before the server has accepted the connection. * Symptom: the server accept() fails with EPIPE or EPROTO, but the * socket stays readable, so that the program goes into a wasteful loop. * * The initial fix was to use FIFOs, but those turn out to have their own * problems, witness the workarounds in the fifo_listen() routine. * Therefore we support both FIFOs and UNIX-domain sockets, so that the * user can choose whatever works best. * * Well, I give up. Solaris UNIX-domain sockets still don't work properly, * so it will have to limp along with a streams-specific alternative. */ if (stream == 0) { if (transport == 0) msg_fatal("no transport type specified"); if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) trigger_server_accept = trigger_server_accept_local; else if (strcasecmp(transport, MASTER_XPORT_NAME_FIFO) == 0) trigger_server_accept = trigger_server_accept_fifo; #ifdef MASTER_XPORT_NAME_PASS else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0) trigger_server_accept = trigger_server_accept_pass; #endif else msg_fatal("unsupported transport type: %s", transport); } /* * Retrieve process generation from environment. */ if ((generation = getenv(MASTER_GEN_NAME)) != 0) { if (!alldig(generation)) msg_fatal("bad generation: %s", generation); OCTAL_TO_UNSIGNED(trigger_server_generation, generation); if (msg_verbose) msg_info("process generation: %s (%o)", generation, trigger_server_generation); } /* * Optionally start the debugger on ourself. */ if (debug_me) debug_process(); /* * Traditionally, BSD select() can't handle multiple processes selecting * on the same socket, and wakes up every process in select(). See TCP/IP * Illustrated volume 2 page 532. We avoid select() collisions with an * external lock file. */ if (stream == 0 && !alone) { lock_path = concatenate(DEF_PID_DIR, "/", transport, ".", service_name, (char *) 0); why = vstring_alloc(1); if ((trigger_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, (struct stat *) 0, -1, -1, why)) == 0) msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); close_on_exec(vstream_fileno(trigger_server_lock), CLOSE_ON_EXEC); myfree(lock_path); vstring_free(why); } /* * Set up call-back info. */ trigger_server_service = service; trigger_server_name = service_name; trigger_server_argv = argv + optind; /* * Run pre-jail initialization. */ if (chdir(var_queue_dir) < 0) msg_fatal("chdir(\"%s\"): %m", var_queue_dir); if (pre_init) pre_init(trigger_server_name, trigger_server_argv); /* * Optionally, restrict the damage that this process can do. */ resolve_local_init(); tzset(); chroot_uid(root_dir, user_name); /* * Run post-jail initialization. */ if (post_init) post_init(trigger_server_name, trigger_server_argv); /* * Are we running as a one-shot server with the client connection on * standard input? */ if (stream != 0) { if ((len = read(vstream_fileno(stream), buf, sizeof(buf))) <= 0) msg_fatal("read: %m"); service(buf, len, trigger_server_name, trigger_server_argv); vstream_fflush(stream); trigger_server_exit(); } /* * Running as a semi-resident server. Service connection requests. * Terminate when we have serviced a sufficient number of clients, when * no-one has been talking to us for a configurable amount of time, or * when the master process terminated abnormally. */ if (var_idle_limit > 0) event_request_timer(trigger_server_timeout, (char *) 0, var_idle_limit); for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { event_enable_read(fd, trigger_server_accept, CAST_INT_TO_CHAR_PTR(fd)); close_on_exec(fd, CLOSE_ON_EXEC); } event_enable_read(MASTER_STATUS_FD, trigger_server_abort, (char *) 0); close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); watchdog = watchdog_create(trigger_server_watchdog, (WATCHDOG_FN) 0, (char *) 0); /* * The event loop, at last. */ while (var_use_limit == 0 || use_count < var_use_limit) { if (trigger_server_lock != 0) { watchdog_stop(watchdog); if (myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("select lock: %m"); } watchdog_start(watchdog); delay = loop ? loop(trigger_server_name, trigger_server_argv) : -1; event_loop(delay); } trigger_server_exit(); }
static int postmap_queries(VSTREAM *in, char **maps, const int map_count, const int postmap_flags, const int dict_flags) { int found = 0; VSTRING *keybuf = vstring_alloc(100); DICT **dicts; const char *map_name; const char *value; int n; /* * Sanity check. */ if (map_count <= 0) msg_panic("postmap_queries: bad map count"); /* * Prepare to open maps lazily. */ dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); for (n = 0; n < map_count; n++) dicts[n] = 0; /* * Perform all queries. Open maps on the fly, to avoid opening unecessary * maps. */ if ((postmap_flags & POSTMAP_FLAG_HB_KEY) == 0) { while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) { for (n = 0; n < map_count; n++) { if (dicts[n] == 0) dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ? dict_open3(maps[n], map_name, O_RDONLY, dict_flags) : dict_open3(var_db_type, maps[n], O_RDONLY, dict_flags)); if ((value = dict_get(dicts[n], STR(keybuf))) != 0) { if (*value == 0) { msg_warn("table %s:%s: key %s: empty string result is not allowed", dicts[n]->type, dicts[n]->name, STR(keybuf)); msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", dicts[n]->type, dicts[n]->name); } vstream_printf("%s %s\n", STR(keybuf), value); found = 1; break; } if (dicts[n]->error) msg_fatal("table %s:%s: query error: %m", dicts[n]->type, dicts[n]->name); } } } else { POSTMAP_KEY_STATE key_state; MIME_STATE *mime_state; int mime_errs = 0; /* * Bundle up the request and instantiate a MIME parsing engine. */ key_state.dicts = dicts; key_state.maps = maps; key_state.map_count = map_count; key_state.dict_flags = dict_flags; key_state.header_done = 0; key_state.found = 0; mime_state = mime_state_alloc((postmap_flags & POSTMAP_FLAG_MIME_KEY) ? 0 : MIME_OPT_DISABLE_MIME, (postmap_flags & POSTMAP_FLAG_HEADER_KEY) ? postmap_header : (MIME_STATE_HEAD_OUT) 0, (postmap_flags & POSTMAP_FLAG_FULL_KEY) ? (MIME_STATE_ANY_END) 0 : postmap_head_end, (postmap_flags & POSTMAP_FLAG_BODY_KEY) ? postmap_body : (MIME_STATE_BODY_OUT) 0, (MIME_STATE_ANY_END) 0, (MIME_STATE_ERR_PRINT) 0, (void *) &key_state); /* * Process the input message. */ while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF && key_state.header_done == 0 && mime_errs == 0) mime_errs = mime_state_update(mime_state, REC_TYPE_NORM, STR(keybuf), LEN(keybuf)); /* * Flush the MIME engine output buffer and tidy up loose ends. */ if (mime_errs == 0) mime_errs = mime_state_update(mime_state, REC_TYPE_END, "", 0); if (mime_errs) msg_fatal("message format error: %s", mime_state_detail(mime_errs)->text); mime_state_free(mime_state); found = key_state.found; } if (found) vstream_fflush(VSTREAM_OUT); /* * Cleanup. */ for (n = 0; n < map_count; n++) if (dicts[n]) dict_close(dicts[n]); myfree((char *) dicts); vstring_free(keybuf); return (found); }
int deliver_file(LOCAL_STATE state, USER_ATTR usr_attr, char *path) { const char *myname = "deliver_file"; struct stat st; MBOX *mp; DSN_BUF *why = state.msg_attr.why; int mail_copy_status = MAIL_COPY_STAT_WRITE; int deliver_status; int copy_flags; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE ELIMINATION * * Skip this file if it was already delivered to as this user. */ if (been_here(state.dup_filter, "file %ld %s", (long) usr_attr.uid, path)) return (0); /* * DELIVERY POLICY * * Do we allow delivery to files? */ if ((local_file_deliver_mask & state.msg_attr.exp_type) == 0) { dsb_simple(why, "5.7.1", "mail to file is restricted"); /* Account for possible owner- sender address override. */ return (bounce_workaround(state)); } /* * Don't deliver trace-only requests. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to file: %s", path); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * DELIVERY RIGHTS * * Use a default uid/gid when none are given. */ 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"); /* * If the name ends in /, use maildir-style delivery instead. */ if (path[strlen(path) - 1] == '/') return (deliver_maildir(state, usr_attr, path)); /* * Deliver. From here on, no early returns or we have a memory leak. */ if (msg_verbose) msg_info("deliver_file (%ld,%ld): %s", (long) usr_attr.uid, (long) usr_attr.gid, path); if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("seek queue file %s: %m", state.msg_attr.queue_id); /* * As the specified user, open or create the file, lock it, and append * the message. */ copy_flags = MAIL_COPY_MBOX; if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0) copy_flags &= ~MAIL_COPY_DELIVERED; set_eugid(usr_attr.uid, usr_attr.gid); mp = mbox_open(path, O_APPEND | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR, &st, -1, -1, local_mbox_lock_mask | MBOX_DOT_LOCK_MAY_FAIL, "5.2.0", why); if (mp != 0) { if (S_ISREG(st.st_mode) && st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { vstream_fclose(mp->fp); dsb_simple(why, "5.7.1", "file is executable"); } else { mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, S_ISREG(st.st_mode) ? copy_flags : (copy_flags & ~MAIL_COPY_TOFILE), "\n", why); } mbox_release(mp); } set_eugid(var_owner_uid, var_owner_gid); /* * As the mail system, bounce, defer delivery, or report success. */ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { deliver_status = DEL_STAT_DEFER; } else if (mail_copy_status != 0) { vstring_sprintf_prepend(why->reason, "cannot append message to file %s: ", path); if (STR(why->status)[0] == '4') deliver_status = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); else /* Account for possible owner- sender address override. */ deliver_status = bounce_workaround(state); } else { dsb_simple(why, "2.0.0", "delivered to file: %s", path); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); } return (deliver_status); }
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); char *dir; dir = strcmp(sane_basename(buf, optarg), MAIN_CONF_FILE) == 0 ? sane_dirname(buf, optarg) : optarg; if (strcmp(dir, DEF_CONFIG_DIR) != 0 && geteuid() != 0) mail_conf_checkdir(dir); if (setenv(CONF_ENV_PATH, dir, 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 */ } }
static int eval_command_status(int command_status, char *service, DELIVER_REQUEST *request, PIPE_ATTR *attr, DSN_BUF *why) { RECIPIENT *rcpt; int status; int result = 0; int n; char *saved_text; /* * Depending on the result, bounce or defer the message, and mark the * recipient as done where appropriate. */ switch (command_status) { case PIPE_STAT_OK: /* Save the command output before dsb_update() clobbers it. */ vstring_truncate(why->reason, trimblanks(STR(why->reason), VSTRING_LEN(why->reason)) - STR(why->reason)); if (VSTRING_LEN(why->reason) > 0) { VSTRING_TERMINATE(why->reason); saved_text = vstring_export(vstring_sprintf( vstring_alloc(VSTRING_LEN(why->reason)), " (%.100s)", STR(why->reason))); } else saved_text = mystrdup(""); /* uses shared R/O storage */ dsb_update(why, "2.0.0", (attr->flags & PIPE_OPT_FINAL_DELIVERY) ? "delivered" : "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY, "delivered via %s service%s", service, saved_text); myfree(saved_text); (void) DSN_FROM_DSN_BUF(why); for (n = 0; n < request->rcpt_list.len; n++) { rcpt = request->rcpt_list.info + n; status = sent(DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, &request->msg_stats, rcpt, service, &why->dsn); if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS)) deliver_completed(request->fp, rcpt->offset); result |= status; } break; case PIPE_STAT_BOUNCE: case PIPE_STAT_DEFER: (void) DSN_FROM_DSN_BUF(why); for (n = 0; n < request->rcpt_list.len; n++) { rcpt = request->rcpt_list.info + n; /* XXX Maybe encapsulate this with ndr_append(). */ status = (STR(why->status)[0] != '4' ? bounce_append : defer_append) (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, &request->msg_stats, rcpt, service, &why->dsn); if (status == 0) deliver_completed(request->fp, rcpt->offset); result |= status; } break; case PIPE_STAT_CORRUPT: /* XXX DSN should we send something? */ result |= DEL_STAT_DEFER; break; default: msg_panic("eval_command_status: bad status %d", command_status); /* NOTREACHED */ } return (result); }
int attr_vprint_plain(VSTREAM *fp, int flags, va_list ap) { const char *myname = "attr_print_plain"; int attr_type; char *attr_name; unsigned int_val; unsigned long long_val; char *str_val; HTABLE_INFO **ht_info_list; HTABLE_INFO **ht; static VSTRING *base64_buf; ssize_t len_val; ATTR_PRINT_SLAVE_FN print_fn; void *print_arg; /* * Sanity check. */ if (flags & ~ATTR_FLAG_ALL) msg_panic("%s: bad flags: 0x%x", myname, flags); /* * Iterate over all (type, name, value) triples, and produce output on * the fly. */ while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) { switch (attr_type) { case ATTR_TYPE_INT: attr_name = va_arg(ap, char *); int_val = va_arg(ap, int); vstream_fprintf(fp, "%s=%u\n", attr_name, (unsigned) int_val); if (msg_verbose) msg_info("send attr %s = %u", attr_name, (unsigned) int_val); break; case ATTR_TYPE_LONG: attr_name = va_arg(ap, char *); long_val = va_arg(ap, long); vstream_fprintf(fp, "%s=%lu\n", attr_name, long_val); if (msg_verbose) msg_info("send attr %s = %lu", attr_name, long_val); break; case ATTR_TYPE_STR: attr_name = va_arg(ap, char *); str_val = va_arg(ap, char *); vstream_fprintf(fp, "%s=%s\n", attr_name, str_val); if (msg_verbose) msg_info("send attr %s = %s", attr_name, str_val); break; case ATTR_TYPE_DATA: attr_name = va_arg(ap, char *); len_val = va_arg(ap, ssize_t); str_val = va_arg(ap, char *); if (base64_buf == 0) base64_buf = vstring_alloc(10); base64_encode(base64_buf, str_val, len_val); vstream_fprintf(fp, "%s=%s\n", attr_name, STR(base64_buf)); if (msg_verbose) msg_info("send attr %s = [data %ld bytes]", attr_name, (long) len_val); break; case ATTR_TYPE_FUNC: print_fn = va_arg(ap, ATTR_PRINT_SLAVE_FN); print_arg = va_arg(ap, void *); print_fn(attr_print_plain, fp, flags | ATTR_FLAG_MORE, print_arg); break; case ATTR_TYPE_HASH: ht_info_list = htable_list(va_arg(ap, HTABLE *)); for (ht = ht_info_list; *ht; ht++) { vstream_fprintf(fp, "%s=%s\n", ht[0]->key, ht[0]->value); if (msg_verbose) msg_info("send attr name %s value %s", ht[0]->key, ht[0]->value); } myfree((char *) ht_info_list); break; default: msg_panic("%s: unknown type code: %d", myname, attr_type); } } if ((flags & ATTR_FLAG_MORE) == 0) VSTREAM_PUTC('\n', fp); return (vstream_ferror(fp)); }