static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop, char *def_service) { DELIVER_REQUEST *request = state->request; SMTP_ITERATOR *iter = state->iterator; ARGV *sites; char *dest; char **cpp; int non_fallback_sites; int retry_plain = 0; DSN_BUF *why = state->why; /* * For sanity, require that at least one of INET or INET6 is enabled. * Otherwise, we can't look up interface information, and we can't * convert names or addresses. */ if (inet_proto_info()->ai_family_list[0] == 0) { dsb_simple(why, "4.4.4", "all network protocols are disabled"); return; } /* * Future proofing: do a null destination sanity check in case we allow * the primary destination to be a list (it could be just separators). */ sites = argv_alloc(1); argv_add(sites, nexthop, (char *) 0); if (sites->argc == 0) msg_panic("null destination: \"%s\"", nexthop); non_fallback_sites = sites->argc; argv_split_append(sites, var_fallback_relay, CHARS_COMMA_SP); /* * Don't give up after a hard host lookup error until we have tried the * fallback relay servers. * * Don't bounce mail after a host lookup problem with a relayhost or with a * fallback relay. * * Don't give up after a qualifying soft error until we have tried all * qualifying backup mail servers. * * All this means that error handling and error reporting depends on whether * the error qualifies for trying to deliver to a backup mail server, or * whether we're looking up a relayhost or fallback relay. The challenge * then is to build this into the pre-existing SMTP client without * getting lost in the complexity. */ #define IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites) \ (*(cpp) && (cpp) >= (sites)->argv + (non_fallback_sites)) for (cpp = sites->argv, (state->misc_flags |= SMTP_MISC_FLAG_FIRST_NEXTHOP); SMTP_RCPT_LEFT(state) > 0 && (dest = *cpp) != 0; cpp++, (state->misc_flags &= ~SMTP_MISC_FLAG_FIRST_NEXTHOP)) { char *dest_buf; char *domain; unsigned port; DNS_RR *addr_list; DNS_RR *addr; DNS_RR *next; int addr_count; int sess_count; SMTP_SESSION *session; int lookup_mx; unsigned domain_best_pref; MAI_HOSTADDR_STR hostaddr; if (cpp[1] == 0) state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; /* * Parse the destination. If no TCP port is specified, use the port * that is reserved for the protocol (SMTP or LMTP). */ dest_buf = smtp_parse_destination(dest, def_service, &domain, &port); if (var_helpful_warnings && var_smtp_tls_wrappermode == 0 && ntohs(port) == 465) { msg_info("SMTPS wrappermode (TCP port 465) requires setting " "\"%s = yes\", and \"%s = encrypt\" (or stronger)", VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL)); } #define NO_HOST "" /* safety */ #define NO_ADDR "" /* safety */ SMTP_ITER_INIT(iter, dest, NO_HOST, NO_ADDR, port, state); /* * Resolve an SMTP or LMTP server. In the case of SMTP, skip mail * exchanger lookups when a quoted host is specified or when DNS * lookups are disabled. */ if (msg_verbose) msg_info("connecting to %s port %d", domain, ntohs(port)); if (smtp_mode) { if (ntohs(port) == IPPORT_SMTP) state->misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT; else state->misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT; lookup_mx = (smtp_dns_support != SMTP_DNS_DISABLED && *dest != '['); } else lookup_mx = 0; if (!lookup_mx) { addr_list = smtp_host_addr(domain, state->misc_flags, why); /* XXX We could be an MX host for this destination... */ } else { int i_am_mx = 0; addr_list = smtp_domain_addr(domain, &iter->mx, state->misc_flags, why, &i_am_mx); /* If we're MX host, don't connect to non-MX backups. */ if (i_am_mx) state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; } /* * Don't try fall-back hosts if mail loops to myself. That would just * make the problem worse. */ if (addr_list == 0 && SMTP_HAS_LOOP_DSN(why)) state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; /* * No early loop exit or we have a memory leak with dest_buf. */ if (addr_list) domain_best_pref = addr_list->pref; /* * When session caching is enabled, store the first good session for * this delivery request under the next-hop destination name. All * good sessions will be stored under their specific server IP * address. * * XXX smtp_session_cache_destinations specifies domain names without * :port, because : is already used for maptype:mapname. Because of * this limitation we use the bare domain without the optional [] or * non-default TCP port. * * Opportunistic (a.k.a. on-demand) session caching on request by the * queue manager. This is turned temporarily when a destination has a * high volume of mail in the active queue. When the surge reaches * its end, the queue manager requests that connections be retrieved * but not stored. */ if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) { smtp_cache_policy(state, domain); if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK) SET_NEXTHOP_STATE(state, dest); } /* * Delete visited cached hosts from the address list. * * Optionally search the connection cache by domain name or by primary * MX address before we try to create new connections. * * Enforce the MX session and MX address counts per next-hop or * fall-back destination. smtp_reuse_session() will truncate the * address list when either limit is reached. */ if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD)) { if (state->cache_used->used > 0) smtp_scrub_addr_list(state->cache_used, &addr_list); sess_count = addr_count = smtp_reuse_session(state, &addr_list, domain_best_pref); } else sess_count = addr_count = 0; /* * Connect to an SMTP server: create primary MX connections, and * reuse or create backup MX connections. * * At the start of an SMTP session, all recipients are unmarked. In the * course of an SMTP session, recipients are marked as KEEP (deliver * to alternate mail server) or DROP (remove from recipient list). At * the end of an SMTP session, weed out the recipient list. Unmark * any left-over recipients and try to deliver them to a backup mail * server. * * Cache the first good session under the next-hop destination name. * Cache all good sessions under their physical endpoint. * * Don't query the session cache for primary MX hosts. We already did * that in smtp_reuse_session(), and if any were found in the cache, * they were already deleted from the address list. * * Currently, we use smtp_reuse_addr() only for SASL-unauthenticated * connections. Furthermore, we rely on smtp_reuse_addr() to look up * an existing SASL-unauthenticated connection only when a new * connection would be guaranteed not to require SASL authentication. * * In addition, we rely on smtp_reuse_addr() to look up an existing * plaintext connection only when a new connection would be * guaranteed not to use TLS. */ for (addr = addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) { next = addr->next; if (++addr_count == var_smtp_mxaddr_limit) next = 0; if (dns_rr_to_pa(addr, &hostaddr) == 0) { msg_warn("cannot convert type %s record to printable address", dns_strtype(addr->type)); /* XXX Assume there is no code at the end of this loop. */ continue; } vstring_strcpy(iter->addr, hostaddr.buf); vstring_strcpy(iter->host, SMTP_HNAME(addr)); iter->rr = addr; #ifdef USE_TLS if (!smtp_tls_policy_cache_query(why, state->tls, iter)) { msg_warn("TLS policy lookup for %s/%s: %s", STR(iter->dest), STR(iter->host), STR(why->reason)); continue; /* XXX Assume there is no code at the end of this loop. */ } if (var_smtp_tls_wrappermode && state->tls->level < TLS_LEV_ENCRYPT) { msg_warn("%s requires \"%s = encrypt\" (or stronger)", VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL)); continue; /* XXX Assume there is no code at the end of this loop. */ } /* Disable TLS when retrying after a handshake failure */ if (retry_plain) { state->tls->level = TLS_LEV_NONE; retry_plain = 0; } #endif if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 || addr->pref == domain_best_pref || !(session = smtp_reuse_addr(state, SMTP_KEY_MASK_SCACHE_ENDP_LABEL))) session = smtp_connect_addr(iter, why, state->misc_flags); if ((state->session = session) != 0) { session->state = state; #ifdef USE_TLS session->tls_nexthop = domain; #endif if (addr->pref == domain_best_pref) session->features |= SMTP_FEATURE_BEST_MX; /* Don't count handshake errors towards the session limit. */ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) && next == 0) state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0 && smtp_helo(state) != 0) { #ifdef USE_TLS /* * When an opportunistic TLS handshake fails, try the * same address again, with TLS disabled. See also the * RETRY_AS_PLAINTEXT macro. */ if ((retry_plain = session->tls_retry_plain) != 0) { --addr_count; next = addr; } #endif /* * When a TLS handshake fails, the stream is marked * "dead" to avoid further I/O over a broken channel. */ if (!THIS_SESSION_IS_FORBIDDEN && vstream_ferror(session->stream) == 0 && vstream_feof(session->stream) == 0) smtp_quit(state); } else { /* Do count delivery errors towards the session limit. */ if (++sess_count == var_smtp_mxsess_limit) next = 0; if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) && next == 0) state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; smtp_xfer(state); #ifdef USE_TLS /* * When opportunistic TLS fails after the STARTTLS * handshake, try the same address again, with TLS * disabled. See also the RETRY_AS_PLAINTEXT macro. */ if ((retry_plain = session->tls_retry_plain) != 0) { --sess_count; --addr_count; next = addr; } #endif } smtp_cleanup_session(state); } else { /* The reason already includes the IP address and TCP port. */ msg_info("%s", STR(why->reason)); } /* XXX Code above assumes there is no code at this loop ending. */ } dns_rr_free(addr_list); if (iter->mx) { dns_rr_free(iter->mx); iter->mx = 0; /* Just in case */ } myfree(dest_buf); if (state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) break; } /* * We still need to deliver, bounce or defer some left-over recipients: * either mail loops or some backup mail server was unavailable. */ if (SMTP_RCPT_LEFT(state) > 0) { /* * In case of a "no error" indication we make up an excuse: we did * find the host address, but we did not attempt to connect to it. * This can happen when the fall-back relay was already tried via a * cached connection, so that the address list scrubber left behind * an empty list. */ if (!SMTP_HAS_DSN(why)) { dsb_simple(why, "4.3.0", "server unavailable or unable to receive mail"); } /* * Pay attention to what could be configuration problems, and pretend * that these are recoverable rather than bouncing the mail. */ else if (!SMTP_HAS_SOFT_DSN(why)) { /* * The fall-back destination did not resolve as expected, or it * is refusing to talk to us, or mail for it loops back to us. */ if (IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites)) { msg_warn("%s configuration problem", VAR_SMTP_FALLBACK); vstring_strcpy(why->status, "4.3.5"); /* XXX Keep the diagnostic code and MTA. */ } /* * The next-hop relayhost did not resolve as expected, or it is * refusing to talk to us, or mail for it loops back to us. * * XXX There is no equivalent safety net for mis-configured * sender-dependent relay hosts. The trivial-rewrite resolver * would have to flag the result, and the queue manager would * have to provide that information to delivery agents. */ else if (smtp_mode && strcmp(sites->argv[0], var_relayhost) == 0) { msg_warn("%s configuration problem", VAR_RELAYHOST); vstring_strcpy(why->status, "4.3.5"); /* XXX Keep the diagnostic code and MTA. */ } /* * Mail for the next-hop destination loops back to myself. Pass * the mail to the best_mx_transport or bounce it. */ else if (smtp_mode && SMTP_HAS_LOOP_DSN(why) && *var_bestmx_transp) { dsb_reset(why); /* XXX */ state->status = deliver_pass_all(MAIL_CLASS_PRIVATE, var_bestmx_transp, request); SMTP_RCPT_LEFT(state) = 0; /* XXX */ } } } /* * Cleanup. */ if (HAVE_NEXTHOP_STATE(state)) FREE_NEXTHOP_STATE(state); argv_free(sites); }
static void smtp_connect_remote(SMTP_STATE *state, const char *nexthop, char *def_service) { DELIVER_REQUEST *request = state->request; ARGV *sites; char *dest; char **cpp; int non_fallback_sites; int retry_plain = 0; DSN_BUF *why = state->why; /* * First try to deliver to the indicated destination, then try to deliver * to the optional fall-back relays. * * Future proofing: do a null destination sanity check in case we allow the * primary destination to be a list (it could be just separators). */ sites = argv_alloc(1); argv_add(sites, nexthop, (char *) 0); if (sites->argc == 0) msg_panic("null destination: \"%s\"", nexthop); non_fallback_sites = sites->argc; if ((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) argv_split_append(sites, var_fallback_relay, ", \t\r\n"); /* * Don't give up after a hard host lookup error until we have tried the * fallback relay servers. * * Don't bounce mail after a host lookup problem with a relayhost or with a * fallback relay. * * Don't give up after a qualifying soft error until we have tried all * qualifying backup mail servers. * * All this means that error handling and error reporting depends on whether * the error qualifies for trying to deliver to a backup mail server, or * whether we're looking up a relayhost or fallback relay. The challenge * then is to build this into the pre-existing SMTP client without * getting lost in the complexity. */ #define IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites) \ (*(cpp) && (cpp) >= (sites)->argv + (non_fallback_sites)) for (cpp = sites->argv, (state->misc_flags |= SMTP_MISC_FLAG_FIRST_NEXTHOP); SMTP_RCPT_LEFT(state) > 0 && (dest = *cpp) != 0; cpp++, (state->misc_flags &= ~SMTP_MISC_FLAG_FIRST_NEXTHOP)) { char *dest_buf; char *domain; unsigned port; DNS_RR *addr_list; DNS_RR *addr; DNS_RR *next; int addr_count; int sess_count; SMTP_SESSION *session; int lookup_mx; unsigned domain_best_pref; MAI_HOSTADDR_STR hostaddr; if (cpp[1] == 0) state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; /* * Parse the destination. Default is to use the SMTP port. Look up * the address instead of the mail exchanger when a quoted host is * specified, or when DNS lookups are disabled. */ dest_buf = smtp_parse_destination(dest, def_service, &domain, &port); if (var_helpful_warnings && ntohs(port) == 465) { msg_info("CLIENT wrappermode (port smtps/465) is unimplemented"); msg_info("instead, send to (port submission/587) with STARTTLS"); } /* * Resolve an SMTP server. Skip mail exchanger lookups when a quoted * host is specified, or when DNS lookups are disabled. */ if (msg_verbose) msg_info("connecting to %s port %d", domain, ntohs(port)); if ((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) { if (ntohs(port) == IPPORT_SMTP) state->misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT; else state->misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT; lookup_mx = (var_disable_dns == 0 && *dest != '['); } else lookup_mx = 0; if (!lookup_mx) { addr_list = smtp_host_addr(domain, state->misc_flags, why); /* XXX We could be an MX host for this destination... */ } else { int i_am_mx = 0; addr_list = smtp_domain_addr(domain, state->misc_flags, why, &i_am_mx); /* If we're MX host, don't connect to non-MX backups. */ if (i_am_mx) state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; } /* * Don't try fall-back hosts if mail loops to myself. That would just * make the problem worse. */ if (addr_list == 0 && SMTP_HAS_LOOP_DSN(why)) state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; /* * No early loop exit or we have a memory leak with dest_buf. */ if (addr_list) domain_best_pref = addr_list->pref; /* * When session caching is enabled, store the first good session for * this delivery request under the next-hop destination name. All * good sessions will be stored under their specific server IP * address. * * XXX Replace sites->argv by (lookup_mx, domain, port) triples so we * don't have to make clumsy ad-hoc copies and keep track of who * free()s the memory. * * XXX smtp_session_cache_destinations specifies domain names without * :port, because : is already used for maptype:mapname. Because of * this limitation we use the bare domain without the optional [] or * non-default TCP port. * * Opportunistic (a.k.a. on-demand) session caching on request by the * queue manager. This is turned temporarily when a destination has a * high volume of mail in the active queue. * * XXX Disable connection caching when sender-dependent authentication * is enabled. We must not send someone elses mail over an * authenticated connection, and we must not send mail that requires * authentication over a connection that wasn't authenticated. */ if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) { smtp_cache_policy(state, domain); if (state->misc_flags & SMTP_MISC_FLAG_CONN_STORE) SET_NEXTHOP_STATE(state, lookup_mx, domain, port); } /* * Delete visited cached hosts from the address list. * * Optionally search the connection cache by domain name or by primary * MX address before we try to create new connections. * * Enforce the MX session and MX address counts per next-hop or * fall-back destination. smtp_reuse_session() will truncate the * address list when either limit is reached. */ if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD)) { if (state->cache_used->used > 0) smtp_scrub_addr_list(state->cache_used, &addr_list); sess_count = addr_count = smtp_reuse_session(state, lookup_mx, domain, port, &addr_list, domain_best_pref); } else sess_count = addr_count = 0; /* * Connect to an SMTP server: create primary MX connections, and * reuse or create backup MX connections. * * At the start of an SMTP session, all recipients are unmarked. In the * course of an SMTP session, recipients are marked as KEEP (deliver * to alternate mail server) or DROP (remove from recipient list). At * the end of an SMTP session, weed out the recipient list. Unmark * any left-over recipients and try to deliver them to a backup mail * server. * * Cache the first good session under the next-hop destination name. * Cache all good sessions under their physical endpoint. * * Don't query the session cache for primary MX hosts. We already did * that in smtp_reuse_session(), and if any were found in the cache, * they were already deleted from the address list. */ for (addr = addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) { next = addr->next; if (++addr_count == var_smtp_mxaddr_limit) next = 0; if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 || addr->pref == domain_best_pref || dns_rr_to_pa(addr, &hostaddr) == 0 || !(session = smtp_reuse_addr(state, hostaddr.buf, port))) session = smtp_connect_addr(dest, addr, port, why, state->misc_flags); if ((state->session = session) != 0) { session->state = state; if (addr->pref == domain_best_pref) session->features |= SMTP_FEATURE_BEST_MX; /* Don't count handshake errors towards the session limit. */ if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) && next == 0) state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; #ifdef USE_TLS /* Disable TLS when retrying after a handshake failure */ if (retry_plain) { if (session->tls_level >= TLS_LEV_ENCRYPT) msg_panic("Plain-text retry wrong for mandatory TLS"); session->tls_level = TLS_LEV_NONE; retry_plain = 0; } session->tls_nexthop = domain; /* for TLS_LEV_SECURE */ #endif if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0 && smtp_helo(state) != 0) { #ifdef USE_TLS /* * When an opportunistic TLS handshake fails, try the * same address again, with TLS disabled. See also the * RETRY_AS_PLAINTEXT macro. */ if ((retry_plain = session->tls_retry_plain) != 0) { --addr_count; next = addr; } #endif /* * When a TLS handshake fails, the stream is marked * "dead" to avoid further I/O over a broken channel. */ if (!THIS_SESSION_IS_DEAD && vstream_ferror(session->stream) == 0 && vstream_feof(session->stream) == 0) smtp_quit(state); } else { /* Do count delivery errors towards the session limit. */ if (++sess_count == var_smtp_mxsess_limit) next = 0; if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) && next == 0) state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; smtp_xfer(state); } smtp_cleanup_session(state); } else { /* The reason already includes the IP address and TCP port. */ msg_info("%s", STR(why->reason)); } /* Insert: test if we must skip the remaining MX hosts. */ } dns_rr_free(addr_list); myfree(dest_buf); if (state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) break; } /* * We still need to deliver, bounce or defer some left-over recipients: * either mail loops or some backup mail server was unavailable. */ if (SMTP_RCPT_LEFT(state) > 0) { /* * In case of a "no error" indication we make up an excuse: we did * find the host address, but we did not attempt to connect to it. * This can happen when the fall-back relay was already tried via a * cached connection, so that the address list scrubber left behind * an empty list. */ if (!SMTP_HAS_DSN(why)) { dsb_simple(why, "4.3.0", "server unavailable or unable to receive mail"); } /* * Pay attention to what could be configuration problems, and pretend * that these are recoverable rather than bouncing the mail. */ else if (!SMTP_HAS_SOFT_DSN(why) && (state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) { /* * The fall-back destination did not resolve as expected, or it * is refusing to talk to us, or mail for it loops back to us. */ if (IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites)) { msg_warn("%s configuration problem", VAR_SMTP_FALLBACK); vstring_strcpy(why->status, "4.3.5"); /* XXX Keep the diagnostic code and MTA. */ } /* * The next-hop relayhost did not resolve as expected, or it is * refusing to talk to us, or mail for it loops back to us. */ else if (strcmp(sites->argv[0], var_relayhost) == 0) { msg_warn("%s configuration problem", VAR_RELAYHOST); vstring_strcpy(why->status, "4.3.5"); /* XXX Keep the diagnostic code and MTA. */ } /* * Mail for the next-hop destination loops back to myself. Pass * the mail to the best_mx_transport or bounce it. */ else if (SMTP_HAS_LOOP_DSN(why) && *var_bestmx_transp) { dsb_reset(why); /* XXX */ state->status = deliver_pass_all(MAIL_CLASS_PRIVATE, var_bestmx_transp, request); SMTP_RCPT_LEFT(state) = 0; /* XXX */ } } } /* * Cleanup. */ if (HAVE_NEXTHOP_STATE(state)) FREE_NEXTHOP_STATE(state); argv_free(sites); }
int main(int argc, char **argv) { static char *full_name = 0; /* sendmail -F */ struct stat st; char *slash; char *sender = 0; /* sendmail -f */ int c; int fd; int mode; ARGV *ext_argv; int debug_me = 0; int err; int n; int flags = SM_FLAG_DEFAULT; char *site_to_flush = 0; char *id_to_flush = 0; char *encoding = 0; char *qtime = 0; const char *errstr; uid_t uid; const char *rewrite_context = MAIL_ATTR_RWR_LOCAL; int dsn_notify = 0; int dsn_ret = 0; const char *dsn_envid = 0; int saved_optind; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * Be consistent with file permissions. */ umask(022); /* * To minimize confusion, make sure that the standard file descriptors * are open before opening anything else. XXX Work around for 44BSD where * fstat can return EBADF on an open file descriptor. */ for (fd = 0; fd < 3; fd++) if (fstat(fd, &st) == -1 && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) msg_fatal_status(EX_OSERR, "open /dev/null: %m"); /* * The CDE desktop calendar manager leaks a parent file descriptor into * the child process. For the sake of sendmail compatibility we have to * close the file descriptor otherwise mail notification will hang. */ for ( /* void */ ; fd < 100; fd++) (void) close(fd); /* * Process environment options as early as we can. We might be called * from a set-uid (set-gid) program, so be careful with importing * environment variables. */ if (safe_getenv(CONF_ENV_VERB)) msg_verbose = 1; if (safe_getenv(CONF_ENV_DEBUG)) debug_me = 1; /* * Initialize. Set up logging, read the global configuration file and * extract configuration information. Set up signal handlers so that we * can clean up incomplete output. */ if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) argv[0] = slash + 1; msg_vstream_init(argv[0], VSTREAM_ERR); msg_cleanup(tempfail); msg_syslog_init(mail_task("sendmail"), LOG_PID, LOG_FACILITY); set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * Some sites mistakenly install Postfix sendmail as set-uid root. Drop * set-uid privileges only when root, otherwise some systems will not * reset the saved set-userid, which would be a security vulnerability. */ if (geteuid() == 0 && getuid() != 0) { msg_warn("the Postfix sendmail command has set-uid root file permissions"); msg_warn("or the command is run from a set-uid root process"); msg_warn("the Postfix sendmail command must be installed without set-uid root file permissions"); set_ugid(getuid(), getgid()); } /* * Further initialization. Load main.cf first, so that command-line * options can override main.cf settings. Pre-scan the argument list so * that we load the right main.cf file. */ #define GETOPT_LIST "A:B:C:F:GIL:N:O:R:UV:X:b:ce:f:h:imno:p:r:q:tvx" saved_optind = optind; while (argv[OPTIND] != 0) { if (strcmp(argv[OPTIND], "-q") == 0) { /* not getopt compatible */ optind++; continue; } if ((c = GETOPT(argc, argv, GETOPT_LIST)) <= 0) break; if (c == 'C') { VSTRING *buf = vstring_alloc(1); if (setenv(CONF_ENV_PATH, strcmp(sane_basename(buf, optarg), MAIN_CONF_FILE) == 0 ? sane_dirname(buf, optarg) : optarg, 1) < 0) msg_fatal_status(EX_UNAVAILABLE, "out of memory"); vstring_free(buf); } } optind = saved_optind; mail_conf_read(); /* Re-evaluate mail_task() after reading main.cf. */ msg_syslog_init(mail_task("sendmail"), LOG_PID, LOG_FACILITY); get_mail_conf_str_table(str_table); if (chdir(var_queue_dir)) msg_fatal_status(EX_UNAVAILABLE, "chdir %s: %m", var_queue_dir); signal(SIGPIPE, SIG_IGN); /* * Optionally start the debugger on ourself. This must be done after * reading the global configuration file, because that file specifies * what debugger command to execute. */ if (debug_me) debug_process(); /* * The default mode of operation is determined by the process name. It * can, however, be changed via command-line options (for example, * "newaliases -bp" will show the mail queue). */ if (strcmp(argv[0], "mailq") == 0) { mode = SM_MODE_MAILQ; } else if (strcmp(argv[0], "newaliases") == 0) { mode = SM_MODE_NEWALIAS; } else if (strcmp(argv[0], "smtpd") == 0) { mode = SM_MODE_DAEMON; } else { mode = SM_MODE_ENQUEUE; } /* * Parse JCL. Sendmail has been around for a long time, and has acquired * a large number of options in the course of time. Some options such as * -q are not parsable with GETOPT() and get special treatment. */ #define OPTIND (optind > 0 ? optind : 1) while (argv[OPTIND] != 0) { if (strcmp(argv[OPTIND], "-q") == 0) { if (mode == SM_MODE_DAEMON) msg_warn("ignoring -q option in daemon mode"); else mode = SM_MODE_FLUSHQ; optind++; continue; } if (strcmp(argv[OPTIND], "-V") == 0 && argv[OPTIND + 1] != 0 && strlen(argv[OPTIND + 1]) == 2) { msg_warn("option -V is deprecated with Postfix 2.3; " "specify -XV instead"); argv[OPTIND] = "-XV"; } if (strncmp(argv[OPTIND], "-V", 2) == 0 && strlen(argv[OPTIND]) == 4) { msg_warn("option %s is deprecated with Postfix 2.3; " "specify -X%s instead", argv[OPTIND], argv[OPTIND] + 1); argv[OPTIND] = concatenate("-X", argv[OPTIND] + 1, (char *) 0); } if (strcmp(argv[OPTIND], "-XV") == 0) { verp_delims = var_verp_delims; optind++; continue; } if ((c = GETOPT(argc, argv, GETOPT_LIST)) <= 0) break; switch (c) { default: if (msg_verbose) msg_info("-%c option ignored", c); break; case 'n': msg_fatal_status(EX_USAGE, "-%c option not supported", c); case 'B': if (strcmp(optarg, "8BITMIME") == 0)/* RFC 1652 */ encoding = MAIL_ATTR_ENC_8BIT; else if (strcmp(optarg, "7BIT") == 0) /* RFC 1652 */ encoding = MAIL_ATTR_ENC_7BIT; else msg_fatal_status(EX_USAGE, "-B option needs 8BITMIME or 7BIT"); break; case 'F': /* full name */ full_name = optarg; break; case 'G': /* gateway submission */ rewrite_context = MAIL_ATTR_RWR_REMOTE; break; case 'I': /* newaliases */ mode = SM_MODE_NEWALIAS; break; case 'N': if ((dsn_notify = dsn_notify_mask(optarg)) == 0) msg_warn("bad -N option value -- ignored"); break; case 'R': if ((dsn_ret = dsn_ret_code(optarg)) == 0) msg_warn("bad -R option value -- ignored"); break; case 'V': /* DSN, was: VERP */ if (strlen(optarg) > 100) msg_warn("too long -V option value -- ignored"); else if (!allprint(optarg)) msg_warn("bad syntax in -V option value -- ignored"); else dsn_envid = optarg; break; case 'X': switch (*optarg) { default: msg_fatal_status(EX_USAGE, "unsupported: -%c%c", c, *optarg); case 'V': /* VERP */ if (verp_delims_verify(optarg + 1) != 0) msg_fatal_status(EX_USAGE, "-V requires two characters from %s", var_verp_filter); verp_delims = optarg + 1; break; } break; case 'b': switch (*optarg) { default: msg_fatal_status(EX_USAGE, "unsupported: -%c%c", c, *optarg); case 'd': /* daemon mode */ case 'l': /* daemon mode */ if (mode == SM_MODE_FLUSHQ) msg_warn("ignoring -q option in daemon mode"); mode = SM_MODE_DAEMON; break; case 'h': /* print host status */ case 'H': /* flush host status */ mode = SM_MODE_IGNORE; break; case 'i': /* newaliases */ mode = SM_MODE_NEWALIAS; break; case 'm': /* deliver mail */ mode = SM_MODE_ENQUEUE; break; case 'p': /* mailq */ mode = SM_MODE_MAILQ; break; case 's': /* stand-alone mode */ mode = SM_MODE_USER; break; case 'v': /* expand recipients */ flags |= DEL_REQ_FLAG_USR_VRFY; break; } break; case 'f': sender = optarg; break; case 'i': flags &= ~SM_FLAG_AEOF; break; case 'o': switch (*optarg) { default: if (msg_verbose) msg_info("-%c%c option ignored", c, *optarg); break; case 'A': if (optarg[1] == 0) msg_fatal_status(EX_USAGE, "-oA requires pathname"); myfree(var_alias_db_map); var_alias_db_map = mystrdup(optarg + 1); set_mail_conf_str(VAR_ALIAS_DB_MAP, var_alias_db_map); break; case '7': case '8': break; case 'i': flags &= ~SM_FLAG_AEOF; break; case 'm': break; } break; case 'r': /* obsoleted by -f */ sender = optarg; break; case 'q': if (ISDIGIT(optarg[0])) { qtime = optarg; } else if (optarg[0] == 'R') { site_to_flush = optarg + 1; if (*site_to_flush == 0) msg_fatal_status(EX_USAGE, "specify: -qRsitename"); } else if (optarg[0] == 'I') { id_to_flush = optarg + 1; if (*id_to_flush == 0) msg_fatal_status(EX_USAGE, "specify: -qIqueueid"); } else { msg_fatal_status(EX_USAGE, "-q%c is not implemented", optarg[0]); } break; case 't': flags |= SM_FLAG_XRCPT; break; case 'v': msg_verbose++; break; case '?': msg_fatal_status(EX_USAGE, "usage: %s [options]", argv[0]); } } /* * Look for conflicting options and arguments. */ if ((flags & SM_FLAG_XRCPT) && mode != SM_MODE_ENQUEUE) msg_fatal_status(EX_USAGE, "-t can be used only in delivery mode"); if (site_to_flush && mode != SM_MODE_ENQUEUE) msg_fatal_status(EX_USAGE, "-qR can be used only in delivery mode"); if (id_to_flush && mode != SM_MODE_ENQUEUE) msg_fatal_status(EX_USAGE, "-qI can be used only in delivery mode"); if (flags & DEL_REQ_FLAG_USR_VRFY) { if (flags & SM_FLAG_XRCPT) msg_fatal_status(EX_USAGE, "-t option cannot be used with -bv"); if (dsn_notify) msg_fatal_status(EX_USAGE, "-N option cannot be used with -bv"); if (dsn_ret) msg_fatal_status(EX_USAGE, "-R option cannot be used with -bv"); if (msg_verbose == 1) msg_fatal_status(EX_USAGE, "-v option cannot be used with -bv"); } /* * The -v option plays double duty. One requests verbose delivery, more * than one requests verbose logging. */ if (msg_verbose == 1 && mode == SM_MODE_ENQUEUE) { msg_verbose = 0; flags |= DEL_REQ_FLAG_RECORD; } /* * Start processing. Everything is delegated to external commands. */ if (qtime && mode != SM_MODE_DAEMON) exit(0); switch (mode) { default: msg_panic("unknown operation mode: %d", mode); /* NOTREACHED */ case SM_MODE_ENQUEUE: if (site_to_flush) { if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "flush site requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postqueue", "-s", site_to_flush, (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ } else if (id_to_flush) { if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "flush queue_id requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postqueue", "-i", id_to_flush, (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ } else { enqueue(flags, encoding, dsn_envid, dsn_ret, dsn_notify, rewrite_context, sender, full_name, argv + OPTIND); exit(0); /* NOTREACHED */ } break; case SM_MODE_MAILQ: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "display queue mode requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postqueue", "-p", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ case SM_MODE_FLUSHQ: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "flush queue mode requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postqueue", "-f", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ case SM_MODE_DAEMON: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "daemon mode requires no recipient"); ext_argv = argv_alloc(2); argv_add(ext_argv, "postfix", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_add(ext_argv, "start", (char *) 0); argv_terminate(ext_argv); err = (mail_run_background(var_command_dir, ext_argv->argv) < 0); argv_free(ext_argv); exit(err); break; case SM_MODE_NEWALIAS: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "alias initialization mode requires no recipient"); if (*var_alias_db_map == 0) return (0); ext_argv = argv_alloc(2); argv_add(ext_argv, "postalias", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_split_append(ext_argv, var_alias_db_map, CHARS_COMMA_SP); argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ case SM_MODE_USER: if (argv[OPTIND]) msg_fatal_status(EX_USAGE, "stand-alone mode requires no recipient"); /* The actual enforcement happens in the postdrop command. */ if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl, uid = getuid())) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to submit mail", errstr, (long) uid); ext_argv = argv_alloc(2); argv_add(ext_argv, "smtpd", "-S", (char *) 0); for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_terminate(ext_argv); mail_run_replace(var_daemon_dir, ext_argv->argv); /* NOTREACHED */ case SM_MODE_IGNORE: exit(0); /* NOTREACHED */ } }
static int tls_policy_lookup_one(SMTP_SESSION *session, int *site_level, const char *site_name, const char *site_class) { const char *lookup; char *policy; char *saved_policy; char *tok; const char *err; char *name; char *val; static VSTRING *cbuf; #undef FREE_RETURN #define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0) if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { if (tls_policy->error) { msg_fatal("%s: %s lookup error for %s", session->state->request->queue_id, tls_policy->title, site_name); /* XXX session->stream has no longjmp context yet. */ } return (0); } if (cbuf == 0) cbuf = vstring_alloc(10); #define WHERE \ vstring_str(vstring_sprintf(cbuf, "TLS policy table, %s \"%s\"", \ site_class, site_name)) saved_policy = policy = mystrdup(lookup); if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) { msg_warn("%s: invalid empty policy", WHERE); *site_level = TLS_LEV_INVALID; FREE_RETURN(1); /* No further lookups */ } *site_level = tls_level_lookup(tok); if (*site_level == TLS_LEV_INVALID) { /* tls_level_lookup() logs no warning. */ msg_warn("%s: invalid security level \"%s\"", WHERE, tok); FREE_RETURN(1); /* No further lookups */ } /* * Warn about ignored attributes when TLS is disabled. */ if (*site_level < TLS_LEV_MAY) { while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", WHERE, tok); FREE_RETURN(1); } /* * Errors in attributes may have security consequences, don't ignore * errors that can degrade security. */ while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) { if ((err = split_nameval(tok, &name, &val)) != 0) { *site_level = TLS_LEV_INVALID; msg_warn("%s: malformed attribute/value pair \"%s\": %s", WHERE, tok, err); break; } /* Only one instance per policy. */ if (!strcasecmp(name, "ciphers")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); *site_level = TLS_LEV_INVALID; break; } if (session->tls_grade) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_grade = mystrdup(val); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "protocols")) { if (session->tls_protocols) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_protocols = mystrdup(val); continue; } /* Multiple instance(s) per policy. */ if (!strcasecmp(name, "match")) { char *delim = *site_level == TLS_LEV_FPRINT ? "|" : ":"; if (*site_level <= TLS_LEV_ENCRYPT) { msg_warn("%s: attribute \"%s\" invalid at security level \"%s\"", WHERE, name, policy_name(*site_level)); *site_level = TLS_LEV_INVALID; break; } if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); *site_level = TLS_LEV_INVALID; break; } if (session->tls_matchargv == 0) session->tls_matchargv = argv_split(val, delim); else argv_split_append(session->tls_matchargv, val, delim); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "exclude")) { if (session->tls_exclusions) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_exclusions = vstring_strcpy(vstring_alloc(10), val); continue; } else { msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); *site_level = TLS_LEV_INVALID; break; } } FREE_RETURN(1); }
static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level, const char *site_name, const char *site_class) { const char *lookup; char *policy; char *saved_policy; char *tok; const char *err; char *name; char *val; static VSTRING *cbuf; #undef FREE_RETURN #define FREE_RETURN do { myfree(saved_policy); return; } while (0) #define INVALID_RETURN(why, levelp) do { \ MARK_INVALID((why), (levelp)); FREE_RETURN; } while (0) #define WHERE \ STR(vstring_sprintf(cbuf, "%s, %s \"%s\"", \ tls_policy->title, site_class, site_name)) if (cbuf == 0) cbuf = vstring_alloc(10); if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { if (tls_policy->error) { msg_warn("%s: policy table lookup error", WHERE); MARK_INVALID(tls->why, site_level); } return; } saved_policy = policy = mystrdup(lookup); if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) { msg_warn("%s: invalid empty policy", WHERE); INVALID_RETURN(tls->why, site_level); } *site_level = tls_level_lookup(tok); if (*site_level == TLS_LEV_INVALID) { /* tls_level_lookup() logs no warning. */ msg_warn("%s: invalid security level \"%s\"", WHERE, tok); INVALID_RETURN(tls->why, site_level); } /* * Warn about ignored attributes when TLS is disabled. */ if (*site_level < TLS_LEV_MAY) { while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", WHERE, tok); FREE_RETURN; } /* * Errors in attributes may have security consequences, don't ignore * errors that can degrade security. */ while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) { if ((err = split_nameval(tok, &name, &val)) != 0) { msg_warn("%s: malformed attribute/value pair \"%s\": %s", WHERE, tok, err); INVALID_RETURN(tls->why, site_level); } /* Only one instance per policy. */ if (!strcasecmp(name, "ciphers")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } if (tls->grade) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->grade = mystrdup(val); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "protocols")) { if (tls->protocols) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->protocols = mystrdup(val); continue; } /* Multiple instances per policy. */ if (!strcasecmp(name, "match")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } switch (*site_level) { default: msg_warn("%s: attribute \"%s\" invalid at security level " "\"%s\"", WHERE, name, policy_name(*site_level)); INVALID_RETURN(tls->why, site_level); break; case TLS_LEV_FPRINT: if (!tls->dane) tls->dane = tls_dane_alloc(); tls_dane_add_ee_digests(tls->dane, var_smtp_tls_fpt_dgst, val, "|"); break; case TLS_LEV_VERIFY: case TLS_LEV_SECURE: if (tls->matchargv == 0) tls->matchargv = argv_split(val, ":"); else argv_split_append(tls->matchargv, val, ":"); break; } continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "exclude")) { if (tls->exclusions) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->exclusions = vstring_strcpy(vstring_alloc(10), val); continue; } /* Multiple instances per policy. */ if (!strcasecmp(name, "tafile")) { /* Only makes sense if we're using CA-based trust */ if (!TLS_MUST_PKIX(*site_level)) { msg_warn("%s: attribute \"%s\" invalid at security level" " \"%s\"", WHERE, name, policy_name(*site_level)); INVALID_RETURN(tls->why, site_level); } if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } if (!tls->dane) tls->dane = tls_dane_alloc(); if (!tls_dane_load_trustfile(tls->dane, val)) { INVALID_RETURN(tls->why, site_level); } continue; } msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); INVALID_RETURN(tls->why, site_level); } FREE_RETURN; }
DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) { char *myname = "dict_ldap_open"; DICT_LDAP *dict_ldap; VSTRING *url_list; char *s; char *h; char *server_host; char *domainlist; char *scope; char *attr; int tmp; if (msg_verbose) msg_info("%s: Using LDAP source %s", myname, ldapsource); dict_ldap = (DICT_LDAP *) dict_alloc(DICT_TYPE_LDAP, ldapsource, sizeof(*dict_ldap)); dict_ldap->dict.lookup = dict_ldap_lookup; dict_ldap->dict.close = dict_ldap_close; dict_ldap->dict.flags = dict_flags | DICT_FLAG_FIXED; dict_ldap->ld = NULL; dict_ldap->parser = cfg_parser_alloc(ldapsource); dict_ldap->ldapsource = mystrdup(ldapsource); server_host = cfg_get_str(dict_ldap->parser, "server_host", "localhost", 1, 0); /* * get configured value of "server_port"; default to LDAP_PORT (389) */ dict_ldap->server_port = cfg_get_int(dict_ldap->parser, "server_port", LDAP_PORT, 0, 0); /* * Define LDAP Version. */ dict_ldap->version = cfg_get_int(dict_ldap->parser, "version", 2, 2, 0); switch (dict_ldap->version) { case 2: dict_ldap->version = LDAP_VERSION2; break; case 3: dict_ldap->version = LDAP_VERSION3; break; default: msg_warn("%s: %s Unknown version %d.", myname, ldapsource, dict_ldap->version); dict_ldap->version = LDAP_VERSION2; } #if defined(LDAP_API_FEATURE_X_OPENLDAP) dict_ldap->ldap_ssl = 0; #endif url_list = vstring_alloc(32); s = server_host; while ((h = mystrtok(&s, " \t\n\r,")) != NULL) { #if defined(LDAP_API_FEATURE_X_OPENLDAP) /* * Convert (host, port) pairs to LDAP URLs */ if (ldap_is_ldap_url(h)) { LDAPURLDesc *url_desc; int rc; if ((rc = ldap_url_parse(h, &url_desc)) != 0) { msg_error("%s: error parsing URL %s: %d: %s; skipping", myname, h, rc, ldap_err2string(rc)); continue; } if (strcasecmp(url_desc->lud_scheme, "ldap") != 0 && dict_ldap->version != LDAP_VERSION3) { msg_warn("%s: URL scheme %s requires protocol version 3", myname, url_desc->lud_scheme); dict_ldap->version = LDAP_VERSION3; } if (strcasecmp(url_desc->lud_scheme, "ldaps") == 0) dict_ldap->ldap_ssl = 1; ldap_free_urldesc(url_desc); vstring_sprintf_append(url_list, " %s", h); } else { if (strrchr(h, ':')) vstring_sprintf_append(url_list, " ldap://%s", h); else vstring_sprintf_append(url_list, " ldap://%s:%d", h, dict_ldap->server_port); } #else vstring_sprintf_append(url_list, " %s", h); #endif } dict_ldap->server_host = mystrdup(VSTRING_LEN(url_list) > 0 ? vstring_str(url_list) + 1 : ""); #if defined(LDAP_API_FEATURE_X_OPENLDAP) /* * With URL scheme, clear port to normalize connection cache key */ dict_ldap->server_port = LDAP_PORT; if (msg_verbose) msg_info("%s: %s server_host URL is %s", myname, ldapsource, dict_ldap->server_host); #endif myfree(server_host); vstring_free(url_list); /* * Scope handling thanks to Carsten Hoeger of SuSE. */ scope = cfg_get_str(dict_ldap->parser, "scope", "sub", 1, 0); if (strcasecmp(scope, "one") == 0) { dict_ldap->scope = LDAP_SCOPE_ONELEVEL; } else if (strcasecmp(scope, "base") == 0) { dict_ldap->scope = LDAP_SCOPE_BASE; } else if (strcasecmp(scope, "sub") == 0) { dict_ldap->scope = LDAP_SCOPE_SUBTREE; } else { msg_warn("%s: %s: Unrecognized value %s specified for scope; using sub", myname, ldapsource, scope); dict_ldap->scope = LDAP_SCOPE_SUBTREE; } myfree(scope); dict_ldap->search_base = cfg_get_str(dict_ldap->parser, "search_base", "", 0, 0); domainlist = cfg_get_str(dict_ldap->parser, "domain", "", 0, 0); if (*domainlist) { #ifdef MATCH_FLAG_NONE dict_ldap->domain = match_list_init(MATCH_FLAG_NONE, domainlist, 1, match_string); #else dict_ldap->domain = match_list_init(domainlist, 1, match_string); #endif if (dict_ldap->domain == NULL) msg_warn("%s: domain match list creation using \"%s\" failed, will continue without it", myname, domainlist); if (msg_verbose) msg_info("%s: domain list created using \"%s\"", myname, domainlist); } else { dict_ldap->domain = NULL; } myfree(domainlist); /* * get configured value of "timeout"; default to 10 seconds * * Thanks to Manuel Guesdon for spotting that this wasn't really getting * set. */ dict_ldap->timeout = cfg_get_int(dict_ldap->parser, "timeout", 10, 0, 0); dict_ldap->query_filter = cfg_get_str(dict_ldap->parser, "query_filter", "(mailacceptinggeneralid=%s)", 0, 0); dict_ldap->result_filter = cfg_get_str(dict_ldap->parser, "result_filter", "%s", 0, 0); if (strcmp(dict_ldap->result_filter, "%s") == 0) { myfree(dict_ldap->result_filter); dict_ldap->result_filter = NULL; } attr = cfg_get_str(dict_ldap->parser, "result_attribute", "maildrop", 0, 0); dict_ldap->result_attributes = argv_split(attr, " ,\t\r\n"); dict_ldap->num_attributes = dict_ldap->result_attributes->argc; myfree(attr); attr = cfg_get_str(dict_ldap->parser, "special_result_attribute", "", 0, 0); if (*attr) { argv_split_append(dict_ldap->result_attributes, attr, " ,\t\r\n"); } myfree(attr); /* * get configured value of "bind"; default to true */ dict_ldap->bind = cfg_get_bool(dict_ldap->parser, "bind", 1); /* * get configured value of "bind_dn"; default to "" */ dict_ldap->bind_dn = cfg_get_str(dict_ldap->parser, "bind_dn", "", 0, 0); /* * get configured value of "bind_pw"; default to "" */ dict_ldap->bind_pw = cfg_get_str(dict_ldap->parser, "bind_pw", "", 0, 0); /* * get configured value of "cache"; default to false */ tmp = cfg_get_bool(dict_ldap->parser, "cache", 0); if (tmp) msg_warn("%s: %s ignoring cache", myname, ldapsource); /* * get configured value of "cache_expiry"; default to 30 seconds */ tmp = cfg_get_int(dict_ldap->parser, "cache_expiry", -1, 0, 0); if (tmp >= 0) msg_warn("%s: %s ignoring cache_expiry", myname, ldapsource); /* * get configured value of "cache_size"; default to 32k */ tmp = cfg_get_int(dict_ldap->parser, "cache_size", -1, 0, 0); if (tmp >= 0) msg_warn("%s: %s ignoring cache_size", myname, ldapsource); /* * get configured value of "recursion_limit"; default to 1000 */ dict_ldap->recursion_limit = cfg_get_int(dict_ldap->parser, "recursion_limit", 1000, 1, 0); /* * get configured value of "expansion_limit"; default to 0 */ dict_ldap->expansion_limit = cfg_get_int(dict_ldap->parser, "expansion_limit", 0, 0, 0); /* * get configured value of "size_limit"; default to expansion_limit */ dict_ldap->size_limit = cfg_get_int(dict_ldap->parser, "size_limit", dict_ldap->expansion_limit, 0, 0); /* * Alias dereferencing suggested by Mike Mattice. */ dict_ldap->dereference = cfg_get_int(dict_ldap->parser, "dereference", 0, 0, 0); if (dict_ldap->dereference < 0 || dict_ldap->dereference > 3) { msg_warn("%s: %s Unrecognized value %d specified for dereference; using 0", myname, ldapsource, dict_ldap->dereference); dict_ldap->dereference = 0; } /* Referral chasing */ dict_ldap->chase_referrals = cfg_get_bool(dict_ldap->parser, "chase_referrals", 0); #ifdef LDAP_API_FEATURE_X_OPENLDAP /* * TLS options */ /* get configured value of "start_tls"; default to no */ dict_ldap->start_tls = cfg_get_bool(dict_ldap->parser, "start_tls", 0); if (dict_ldap->start_tls && dict_ldap->version < LDAP_VERSION3) { msg_warn("%s: %s start_tls requires protocol version 3", myname, ldapsource); dict_ldap->version = LDAP_VERSION3; } /* get configured value of "tls_require_cert"; default to no */ dict_ldap->tls_require_cert = cfg_get_bool(dict_ldap->parser, "tls_require_cert", 0); /* get configured value of "tls_ca_cert_file"; default "" */ dict_ldap->tls_ca_cert_file = cfg_get_str(dict_ldap->parser, "tls_ca_cert_file", "", 0, 0); /* get configured value of "tls_ca_cert_dir"; default "" */ dict_ldap->tls_ca_cert_dir = cfg_get_str(dict_ldap->parser, "tls_ca_cert_dir", "", 0, 0); /* get configured value of "tls_cert"; default "" */ dict_ldap->tls_cert = cfg_get_str(dict_ldap->parser, "tls_cert", "", 0, 0); /* get configured value of "tls_key"; default "" */ dict_ldap->tls_key = cfg_get_str(dict_ldap->parser, "tls_key", "", 0, 0); /* get configured value of "tls_random_file"; default "" */ dict_ldap->tls_random_file = cfg_get_str(dict_ldap->parser, "tls_random_file", "", 0, 0); /* get configured value of "tls_cipher_suite"; default "" */ dict_ldap->tls_cipher_suite = cfg_get_str(dict_ldap->parser, "tls_cipher_suite", "", 0, 0); #endif /* * Debug level. */ #if defined(LDAP_OPT_DEBUG_LEVEL) && defined(LBER_OPT_LOG_PRINT_FN) dict_ldap->debuglevel = cfg_get_int(dict_ldap->parser, "debuglevel", 0, 0, 0); #endif /* * Find or allocate shared LDAP connection container. */ dict_ldap_conn_find(dict_ldap); /* * Return the new dict_ldap structure. */ return (DICT_DEBUG (&dict_ldap->dict)); }