static void verify_update_service(VSTREAM *client_stream) { VSTRING *buf = vstring_alloc(10); VSTRING *addr = vstring_alloc(10); int addr_status; VSTRING *text = vstring_alloc(10); const char *status_name; const char *raw_data; long probed; long updated; if (attr_scan(client_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_ADDR, addr), RECV_ATTR_INT(MAIL_ATTR_ADDR_STATUS, &addr_status), RECV_ATTR_STR(MAIL_ATTR_WHY, text), ATTR_TYPE_END) == 3) { /* FIX 200501 IPv6 patch did not neuter ":" in address literals. */ translit(STR(addr), ":", "_"); if ((status_name = verify_stat2name(addr_status)) == 0) { msg_warn("bad recipient status %d for recipient %s", addr_status, STR(addr)); attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_BAD), ATTR_TYPE_END); } else { /* * Robustness: don't allow a failed probe to clobber an OK * address before it expires. The failed probe is ignored so that * the address will be re-probed upon the next query. As long as * some probes succeed the address will remain cached as OK. */ if (addr_status == DEL_RCPT_STAT_OK || (raw_data = dict_cache_lookup(verify_map, STR(addr))) == 0 || STATUS_FROM_RAW_ENTRY(raw_data) != DEL_RCPT_STAT_OK) { probed = 0; updated = (long) time((time_t *) 0); verify_make_entry(buf, addr_status, probed, updated, STR(text)); if (msg_verbose) msg_info("PUT %s status=%d probed=%ld updated=%ld text=%s", STR(addr), addr_status, probed, updated, STR(text)); dict_cache_update(verify_map, STR(addr), STR(buf)); } attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_OK), ATTR_TYPE_END); } } vstring_free(buf); vstring_free(addr); vstring_free(text); }
static void verify_service(VSTREAM *client_stream, char *unused_service, char **argv) { VSTRING *request = vstring_alloc(10); /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * This routine runs whenever a client connects to the socket dedicated * to the address verification service. All connection-management stuff * is handled by the common code in multi_server.c. */ if (attr_scan(client_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_REQ, request), ATTR_TYPE_END) == 1) { if (STREQ(STR(request), VRFY_REQ_UPDATE)) { verify_update_service(client_stream); } else if (STREQ(STR(request), VRFY_REQ_QUERY)) { verify_query_service(client_stream); } else { msg_warn("unrecognized request: \"%s\", ignored", STR(request)); attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_BAD), ATTR_TYPE_END); } } vstream_fflush(client_stream); vstring_free(request); }
static void proxymap_open_service(VSTREAM *client_stream) { int request_flags; DICT *dict; int reply_status; int reply_flags; /* * Process the request. */ if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags, ATTR_TYPE_END) != 2) { reply_status = PROXY_STAT_BAD; reply_flags = 0; } else if ((dict = proxy_map_find(STR(request_map), request_flags, &reply_status)) == 0) { reply_flags = 0; } else { reply_status = PROXY_STAT_OK; reply_flags = dict->flags; } /* * Respond to the client. */ attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, reply_flags, ATTR_TYPE_END); }
static void qmqpd_open_file(QMQPD_STATE *state) { int cleanup_flags; /* * Connect to the cleanup server. Log client name/address with queue ID. */ cleanup_flags = input_transp_cleanup(CLEANUP_FLAG_MASK_EXTERNAL, qmqpd_input_transp_mask); state->dest = mail_stream_service(MAIL_CLASS_PUBLIC, var_cleanup_service); if (state->dest == 0 || attr_print(state->dest->stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, cleanup_flags, ATTR_TYPE_END) != 0) msg_fatal("unable to connect to the %s %s service", MAIL_CLASS_PUBLIC, var_cleanup_service); state->cleanup = state->dest->stream; state->queue_id = mystrdup(state->dest->id); msg_info("%s: client=%s", state->queue_id, state->namaddr); /* * Record the time of arrival. Optionally, enable content filtering (not * bloody likely, but present for the sake of consistency with all other * Postfix points of entrance). */ rec_fprintf(state->cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, REC_TYPE_TIME_ARG(state->arrival_time)); if (*var_filter_xport) rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport); }
int resolve_proto(RES_CONTEXT *context, VSTREAM *stream) { int flags; if (attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_ADDR, query, ATTR_TYPE_END) != 1) return (-1); resolve_addr(context, STR(query), channel, nexthop, nextrcpt, &flags); if (msg_verbose) msg_info("%s -> (`%s' `%s' `%s' `%d')", STR(query), STR(channel), STR(nexthop), STR(nextrcpt), flags); attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_STR, MAIL_ATTR_TRANSPORT, STR(channel), ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, STR(nexthop), ATTR_TYPE_STR, MAIL_ATTR_RECIP, STR(nextrcpt), ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, ATTR_TYPE_END); if (vstream_fflush(stream) != 0) { msg_warn("write resolver reply: %m"); return (-1); } return (0); }
static int deliver_request_final(VSTREAM *stream, DELIVER_REQUEST *request, int status) { DSN *hop_status; int err; /* XXX This DSN structure initialization bypasses integrity checks. */ static DSN dummy_dsn = {"", "", "", "", "", "", ""}; /* * Send the status and the optional reason. */ if ((hop_status = request->hop_status) == 0) hop_status = &dummy_dsn; if (msg_verbose) msg_info("deliver_request_final: send: \"%s\" %d", hop_status->reason, status); attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_FUNC, dsn_print, (void *) hop_status, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_END); if ((err = vstream_fflush(stream)) != 0) if (msg_verbose) msg_warn("send final status: %m"); /* * With some UNIX systems, stream sockets lose data when you close them * immediately after writing to them. That is not how sockets are * supposed to behave! The workaround is to wait until the receiver * closes the connection. Calling VSTREAM_GETC() has the benefit of using * whatever timeout is specified in the ipc_timeout parameter. */ (void) VSTREAM_GETC(stream); return (err); }
static void node_attr_print(const acl::xml_node& node, int depth) { const acl::xml_attr* attr = node.first_attr(); while (attr) { attr_print(*attr, depth); attr = node.next_attr(); } }
int rewrite_proto(VSTREAM *stream) { RWR_CONTEXT *context; TOK822 *tree; if (attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_RULE, ruleset, ATTR_TYPE_STR, MAIL_ATTR_ADDR, address, ATTR_TYPE_END) != 2) return (-1); if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_LOCAL) == 0) context = &local_context; else if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_REMOTE) == 0) context = &remote_context; else { msg_warn("unknown context: %s", vstring_str(ruleset)); return (-1); } /* * Sanity check. An address is supposed to be in externalized form. */ if (*vstring_str(address) == 0) { msg_warn("rewrite_addr: null address"); vstring_strcpy(result, vstring_str(address)); } /* * Convert the address from externalized (quoted) form to token list, * rewrite it, and convert back. */ else { tree = tok822_scan_addr(vstring_str(address)); rewrite_tree(context, tree); tok822_externalize(result, tree, TOK822_STR_DEFL); tok822_free_tree(tree); } if (msg_verbose) msg_info("`%s' `%s' -> `%s'", vstring_str(ruleset), vstring_str(address), vstring_str(result)); attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, server_flags, ATTR_TYPE_STR, MAIL_ATTR_ADDR, vstring_str(result), ATTR_TYPE_END); if (vstream_fflush(stream) != 0) { msg_warn("write rewrite reply: %m"); return (-1); } return (0); }
static void proxymap_service(VSTREAM *client_stream, char *unused_service, char **argv) { /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * Deadline enforcement. */ if (vstream_fstat(client_stream, VSTREAM_FLAG_DEADLINE) == 0) vstream_control(client_stream, VSTREAM_CTL_TIMEOUT, 1, VSTREAM_CTL_END); /* * This routine runs whenever a client connects to the socket dedicated * to the proxymap service. All connection-management stuff is handled by * the common code in multi_server.c. */ vstream_control(client_stream, VSTREAM_CTL_START_DEADLINE, VSTREAM_CTL_END); if (attr_scan(client_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_REQ, request, ATTR_TYPE_END) == 1) { if (VSTREQ(request, PROXY_REQ_LOOKUP)) { proxymap_lookup_service(client_stream); } else if (VSTREQ(request, PROXY_REQ_UPDATE)) { proxymap_update_service(client_stream); } else if (VSTREQ(request, PROXY_REQ_DELETE)) { proxymap_delete_service(client_stream); } else if (VSTREQ(request, PROXY_REQ_SEQUENCE)) { proxymap_sequence_service(client_stream); } else if (VSTREQ(request, PROXY_REQ_OPEN)) { proxymap_open_service(client_stream); } else { msg_warn("unrecognized request: \"%s\", ignored", STR(request)); attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, PROXY_STAT_BAD, ATTR_TYPE_END); } } vstream_control(client_stream, VSTREAM_CTL_START_DEADLINE, VSTREAM_CTL_END); vstream_fflush(client_stream); }
static void proxymap_update_service(VSTREAM *client_stream) { int request_flags; DICT *dict; int dict_status; int reply_status; /* * Process the request. * * XXX We don't close maps, so we must turn on synchronous update to ensure * that the on-disk data is in a consistent state between updates. * * XXX We ignore duplicates, because the proxymap server would abort * otherwise. */ if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags, ATTR_TYPE_STR, MAIL_ATTR_KEY, request_key, ATTR_TYPE_STR, MAIL_ATTR_VALUE, request_value, ATTR_TYPE_END) != 4) { reply_status = PROXY_STAT_BAD; } else if (proxy_writer == 0) { msg_warn("refusing %s update request on non-%s service", STR(request_map), MAIL_SERVICE_PROXYWRITE); reply_status = PROXY_STAT_DENY; } else if ((dict = proxy_map_find(STR(request_map), request_flags, &reply_status)) == 0) { /* void */ ; } else { dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) | (request_flags & DICT_FLAG_RQST_MASK) | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_DUP_REPLACE); dict_status = dict_put(dict, STR(request_key), STR(request_value)); if (dict_status == 0) { reply_status = PROXY_STAT_OK; } else if (dict->error == 0) { reply_status = PROXY_STAT_NOKEY; } else { reply_status = PROXY_STAT_RETRY; } } /* * Respond to the client. */ attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status, ATTR_TYPE_END); }
static void post_mail_init(VSTREAM *stream, const char *sender, const char *recipient, int filter_class, int trace_flags, VSTRING *queue_id) { VSTRING *id = queue_id ? queue_id : vstring_alloc(100); struct timeval now; const char *date; int cleanup_flags = int_filt_flags(filter_class) | CLEANUP_FLAG_MASK_INTERNAL; GETTIMEOFDAY(&now); date = mail_date(now.tv_sec); /* * Negotiate with the cleanup service. Give up if we can't agree. */ if (attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id, ATTR_TYPE_END) != 1 || attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, cleanup_flags, ATTR_TYPE_END) != 0) msg_fatal("unable to contact the %s service", var_cleanup_service); /* * Generate a minimal envelope section. The cleanup service will add a * size record. */ rec_fprintf(stream, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, REC_TYPE_TIME_ARG(now)); rec_fprintf(stream, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_LOG_ORIGIN, MAIL_ATTR_ORG_LOCAL); rec_fprintf(stream, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_TRACE_FLAGS, trace_flags); rec_fputs(stream, REC_TYPE_FROM, sender); rec_fputs(stream, REC_TYPE_RCPT, recipient); rec_fputs(stream, REC_TYPE_MESG, ""); /* * Do the Received: and Date: header lines. This allows us to shave a few * cycles by using the expensive date conversion result for both. */ post_mail_fprintf(stream, "Received: by %s (%s)", var_myhostname, var_mail_name); post_mail_fprintf(stream, "\tid %s; %s", vstring_str(id), date); post_mail_fprintf(stream, "Date: %s", date); if (queue_id == 0) vstring_free(id); }
static void proxymap_sequence_service(VSTREAM *client_stream) { int request_flags; DICT *dict; int request_func; const char *reply_key; const char *reply_value; int dict_status; int reply_status; /* * Process the request. */ if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags, ATTR_TYPE_INT, MAIL_ATTR_FUNC, &request_func, ATTR_TYPE_END) != 3 || (request_func != DICT_SEQ_FUN_FIRST && request_func != DICT_SEQ_FUN_NEXT)) { reply_status = PROXY_STAT_BAD; reply_key = reply_value = ""; } else if ((dict = proxy_map_find(STR(request_map), request_flags, &reply_status)) == 0) { reply_key = reply_value = ""; } else { dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) | (request_flags & DICT_FLAG_RQST_MASK)); dict_status = dict_seq(dict, request_func, &reply_key, &reply_value); if (dict_status == 0) { reply_status = PROXY_STAT_OK; } else if (dict->error == 0) { reply_status = PROXY_STAT_NOKEY; reply_key = reply_value = ""; } else { reply_status = PROXY_STAT_RETRY; reply_key = reply_value = ""; } } /* * Respond to the client. */ attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status, ATTR_TYPE_STR, MAIL_ATTR_KEY, reply_key, ATTR_TYPE_STR, MAIL_ATTR_VALUE, reply_value, ATTR_TYPE_END); }
static int deliver_request_initial(VSTREAM *stream) { int err; /* * The master processes runs a finite number of delivery agent processes * to handle service requests. Thus, a delivery agent process must send * something to inform the queue manager that it is ready to receive a * delivery request; otherwise the queue manager could block in write(). */ if (msg_verbose) msg_info("deliver_request_initial: send initial status"); attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, 0, ATTR_TYPE_END); if ((err = vstream_fflush(stream)) != 0) if (msg_verbose) msg_warn("send initial status: %m"); return (err); }
static void proxymap_lookup_service(VSTREAM *client_stream) { int request_flags; DICT *dict; const char *reply_value; int reply_status; /* * Process the request. */ if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags, ATTR_TYPE_STR, MAIL_ATTR_KEY, request_key, ATTR_TYPE_END) != 3) { reply_status = PROXY_STAT_BAD; reply_value = ""; } else if ((dict = proxy_map_find(STR(request_map), request_flags, &reply_status)) == 0) { reply_value = ""; } else if (dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) | (request_flags & DICT_FLAG_RQST_MASK)), (reply_value = dict_get(dict, STR(request_key))) != 0) { reply_status = PROXY_STAT_OK; } else if (dict->error == 0) { reply_status = PROXY_STAT_NOKEY; reply_value = ""; } else { reply_status = PROXY_STAT_RETRY; reply_value = ""; } /* * Respond to the client. */ attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status, ATTR_TYPE_STR, MAIL_ATTR_VALUE, reply_value, ATTR_TYPE_END); }
int verify_clnt_query(const char *addr, int *addr_status, VSTRING *why) { VSTREAM *stream; int request_status; int count = 0; /* * Do client-server plumbing. */ if (vrfy_clnt == 0) verify_clnt_init(); /* * Request status for this address. */ for (;;) { stream = clnt_stream_access(vrfy_clnt); errno = 0; count += 1; if (attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_STR, MAIL_ATTR_REQ, VRFY_REQ_QUERY, ATTR_TYPE_STR, MAIL_ATTR_ADDR, addr, ATTR_TYPE_END) != 0 || vstream_fflush(stream) || attr_scan(stream, ATTR_FLAG_MISSING, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &request_status, ATTR_TYPE_INT, MAIL_ATTR_ADDR_STATUS, addr_status, ATTR_TYPE_STR, MAIL_ATTR_WHY, why, ATTR_TYPE_END) != 3) { if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT)) msg_warn("problem talking to service %s: %m", var_verify_service); } else { break; } sleep(1); clnt_stream_recover(vrfy_clnt); } return (request_status); }
int verify_clnt_update(const char *addr, int addr_status, const char *why) { VSTREAM *stream; int request_status; /* * Do client-server plumbing. */ if (vrfy_clnt == 0) verify_clnt_init(); /* * Send status for this address. Supply a default status if the address * verification service is unavailable. */ for (;;) { stream = clnt_stream_access(vrfy_clnt); errno = 0; if (attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_STR, MAIL_ATTR_REQ, VRFY_REQ_UPDATE, ATTR_TYPE_STR, MAIL_ATTR_ADDR, addr, ATTR_TYPE_INT, MAIL_ATTR_ADDR_STATUS, addr_status, ATTR_TYPE_STR, MAIL_ATTR_WHY, why, ATTR_TYPE_END) != 0 || attr_scan(stream, ATTR_FLAG_MISSING, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &request_status, ATTR_TYPE_END) != 1) { if (msg_verbose || (errno != EPIPE && errno != ENOENT)) msg_warn("problem talking to service %s: %m", var_verify_service); } else { break; } sleep(1); clnt_stream_recover(vrfy_clnt); } return (request_status); }
static void tlsp_get_request_event(int event, void *context) { const char *myname = "tlsp_get_request_event"; TLSP_STATE *state = (TLSP_STATE *) context; VSTREAM *plaintext_stream = state->plaintext_stream; int plaintext_fd = vstream_fileno(plaintext_stream); static VSTRING *remote_endpt; static VSTRING *server_id; int req_flags; int timeout; int ready; /* * One-time initialization. */ if (remote_endpt == 0) { remote_endpt = vstring_alloc(10); server_id = vstring_alloc(10); } /* * At this point we still manually manage plaintext read/write/timeout * events. Turn off timer events. Below we disable read events on error, * and redefine read events on success. */ if (event != EVENT_TIME) event_cancel_timer(tlsp_get_request_event, (void *) state); else errno = ETIMEDOUT; /* * We must send some data, after receiving the request attributes and * before receiving the remote file descriptor. We can't assume * UNIX-domain socket semantics here. */ if (event != EVENT_READ || attr_scan(plaintext_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_REMOTE_ENDPT, remote_endpt), RECV_ATTR_INT(MAIL_ATTR_FLAGS, &req_flags), RECV_ATTR_INT(MAIL_ATTR_TIMEOUT, &timeout), RECV_ATTR_STR(MAIL_ATTR_SERVER_ID, server_id), ATTR_TYPE_END) != 4) { msg_warn("%s: receive request attributes: %m", myname); event_disable_readwrite(plaintext_fd); tlsp_state_free(state); return; } /* * If the requested TLS engine is unavailable, hang up after making sure * that the plaintext peer has received our "sorry" indication. */ ready = ((req_flags & TLS_PROXY_FLAG_ROLE_SERVER) != 0 && tlsp_server_ctx != 0); if (attr_print(plaintext_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, ready), ATTR_TYPE_END) != 0 || vstream_fflush(plaintext_stream) != 0 || ready == 0) { read_wait(plaintext_fd, TLSP_INIT_TIMEOUT); /* XXX */ event_disable_readwrite(plaintext_fd); tlsp_state_free(state); return; } /* * XXX We use the same fixed timeout throughout the entire session for * both plaintext and ciphertext communication. This timeout is just a * safety feature; the real timeout will be enforced by our plaintext * peer. */ else { state->remote_endpt = mystrdup(STR(remote_endpt)); state->server_id = mystrdup(STR(server_id)); msg_info("CONNECT %s %s", (req_flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "from" : (req_flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "to" : "(bogus_direction)", state->remote_endpt); state->req_flags = req_flags; state->timeout = timeout + 10; /* XXX */ event_enable_read(plaintext_fd, tlsp_get_fd_event, (void *) state); event_request_timer(tlsp_get_fd_event, (void *) state, TLSP_INIT_TIMEOUT); return; } }
int main(int argc, char **argv) { struct stat st; int fd; int c; VSTRING *buf; int status; MAIL_STREAM *dst; int rec_type; static char *segment_info[] = { REC_TYPE_POST_ENVELOPE, REC_TYPE_POST_CONTENT, REC_TYPE_POST_EXTRACT, "" }; char **expected; uid_t uid = getuid(); ARGV *import_env; const char *error_text; char *attr_name; char *attr_value; const char *errstr; char *junk; struct timeval start; int saved_errno; int from_count = 0; int rcpt_count = 0; int validate_input = 1; /* * 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("open /dev/null: %m"); /* * Set up logging. Censor the process name: it is provided by the user. */ argv[0] = "postdrop"; msg_vstream_init(argv[0], VSTREAM_ERR); msg_syslog_init(mail_task("postdrop"), 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; /* * Parse JCL. This program is set-gid and must sanitize all command-line * arguments. The configuration directory argument is validated by the * mail configuration read routine. Don't do complex things until we have * completed initializations. */ while ((c = GETOPT(argc, argv, "c:rv")) > 0) { switch (c) { case 'c': if (setenv(CONF_ENV_PATH, optarg, 1) < 0) msg_fatal("out of memory"); break; case 'r': /* forward compatibility */ break; case 'v': if (geteuid() == 0) msg_verbose++; break; default: msg_fatal("usage: %s [-c config_dir] [-v]", argv[0]); } } /* * Read the global configuration file and extract configuration * information. Some claim that the user should supply the working * directory instead. That might be OK, given that this command needs * write permission in a subdirectory called "maildrop". However we still * need to reliably detect incomplete input, and so we must perform * record-level I/O. With that, we should also take the opportunity to * perform some sanity checks on the input. */ mail_conf_read(); /* Re-evaluate mail_task() after reading main.cf. */ msg_syslog_init(mail_task("postdrop"), LOG_PID, LOG_FACILITY); get_mail_conf_str_table(str_table); /* * Mail submission access control. Should this be in the user-land gate, * or in the daemon process? */ mail_dict_init(); if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl, uid)) != 0) msg_fatal("User %s(%ld) is not allowed to submit mail", errstr, (long) uid); /* * Stop run-away process accidents by limiting the queue file size. This * is not a defense against DOS attack. */ if (var_message_limit > 0 && get_file_limit() > var_message_limit) set_file_limit((off_t) var_message_limit); /* * This program is installed with setgid privileges. Strip the process * environment so that we don't have to trust the C library. */ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); clean_env(import_env->argv); argv_free(import_env); if (chdir(var_queue_dir)) msg_fatal("chdir %s: %m", var_queue_dir); if (msg_verbose) msg_info("chdir %s", var_queue_dir); /* * Set up signal handlers and a runtime error handler so that we can * clean up incomplete output. * * postdrop_sig() uses the in-kernel SIGINT handler address as an atomic * variable to prevent nested postdrop_sig() calls. For this reason, the * SIGINT handler must be configured before other signal handlers are * allowed to invoke postdrop_sig(). */ signal(SIGPIPE, SIG_IGN); signal(SIGXFSZ, SIG_IGN); signal(SIGINT, postdrop_sig); signal(SIGQUIT, postdrop_sig); if (signal(SIGTERM, SIG_IGN) == SIG_DFL) signal(SIGTERM, postdrop_sig); if (signal(SIGHUP, SIG_IGN) == SIG_DFL) signal(SIGHUP, postdrop_sig); msg_cleanup(postdrop_cleanup); /* End of initializations. */ /* * Don't trust the caller's time information. */ GETTIMEOFDAY(&start); /* * Create queue file. mail_stream_file() never fails. Send the queue ID * to the caller. Stash away a copy of the queue file name so we can * clean up in case of a fatal error or an interrupt. */ dst = mail_stream_file(MAIL_QUEUE_MAILDROP, MAIL_CLASS_PUBLIC, var_pickup_service, 0444); attr_print(VSTREAM_OUT, ATTR_FLAG_NONE, SEND_ATTR_STR(MAIL_ATTR_QUEUEID, dst->id), ATTR_TYPE_END); vstream_fflush(VSTREAM_OUT); postdrop_path = mystrdup(VSTREAM_PATH(dst->stream)); /* * Copy stdin to file. The format is checked so that we can recognize * incomplete input and cancel the operation. With the sanity checks * applied here, the pickup daemon could skip format checks and pass a * file descriptor to the cleanup daemon. These are by no means all * sanity checks - the cleanup service and queue manager services will * reject messages that lack required information. * * If something goes wrong, slurp up the input before responding to the * client, otherwise the client will give up after detecting SIGPIPE. * * Allow attribute records if the attribute specifies the MIME body type * (sendmail -B). */ vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END); buf = vstring_alloc(100); expected = segment_info; /* Override time information from the untrusted caller. */ rec_fprintf(dst->stream, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, REC_TYPE_TIME_ARG(start)); for (;;) { /* Don't allow PTR records. */ rec_type = rec_get_raw(VSTREAM_IN, buf, var_line_limit, REC_FLAG_NONE); if (rec_type == REC_TYPE_EOF) { /* request cancelled */ mail_stream_cleanup(dst); if (remove(postdrop_path)) msg_warn("uid=%ld: remove %s: %m", (long) uid, postdrop_path); else if (msg_verbose) msg_info("remove %s", postdrop_path); myfree(postdrop_path); postdrop_path = 0; exit(0); } if (rec_type == REC_TYPE_ERROR) msg_fatal("uid=%ld: malformed input", (long) uid); if (strchr(*expected, rec_type) == 0) msg_fatal("uid=%ld: unexpected record type: %d", (long) uid, rec_type); if (rec_type == **expected) expected++; /* Override time information from the untrusted caller. */ if (rec_type == REC_TYPE_TIME) continue; /* Check these at submission time instead of pickup time. */ if (rec_type == REC_TYPE_FROM) from_count++; if (rec_type == REC_TYPE_RCPT) rcpt_count++; /* Limit the attribute types that users may specify. */ if (rec_type == REC_TYPE_ATTR) { if ((error_text = split_nameval(vstring_str(buf), &attr_name, &attr_value)) != 0) { msg_warn("uid=%ld: ignoring malformed record: %s: %.200s", (long) uid, error_text, vstring_str(buf)); continue; } #define STREQ(x,y) (strcmp(x,y) == 0) if ((STREQ(attr_name, MAIL_ATTR_ENCODING) && (STREQ(attr_value, MAIL_ATTR_ENC_7BIT) || STREQ(attr_value, MAIL_ATTR_ENC_8BIT) || STREQ(attr_value, MAIL_ATTR_ENC_NONE))) || STREQ(attr_name, MAIL_ATTR_DSN_ENVID) || STREQ(attr_name, MAIL_ATTR_DSN_NOTIFY) || rec_attr_map(attr_name) || (STREQ(attr_name, MAIL_ATTR_RWR_CONTEXT) && (STREQ(attr_value, MAIL_ATTR_RWR_LOCAL) || STREQ(attr_value, MAIL_ATTR_RWR_REMOTE))) || STREQ(attr_name, MAIL_ATTR_TRACE_FLAGS)) { /* XXX */ rec_fprintf(dst->stream, REC_TYPE_ATTR, "%s=%s", attr_name, attr_value); } else { msg_warn("uid=%ld: ignoring attribute record: %.200s=%.200s", (long) uid, attr_name, attr_value); } continue; } if (REC_PUT_BUF(dst->stream, rec_type, buf) < 0) { /* rec_get() errors must not clobber errno. */ saved_errno = errno; while ((rec_type = rec_get_raw(VSTREAM_IN, buf, var_line_limit, REC_FLAG_NONE)) != REC_TYPE_END && rec_type != REC_TYPE_EOF) if (rec_type == REC_TYPE_ERROR) msg_fatal("uid=%ld: malformed input", (long) uid); validate_input = 0; errno = saved_errno; break; } if (rec_type == REC_TYPE_END) break; } vstring_free(buf); /* * As of Postfix 2.7 the pickup daemon discards mail without recipients. * Such mail may enter the maildrop queue when "postsuper -r" is invoked * before the queue manager deletes an already delivered message. Looking * at file ownership is not a good way to make decisions on what mail to * discard. Instead, the pickup server now requires that new submissions * always have at least one recipient record. * * The Postfix sendmail command already rejects mail without recipients. * However, in the future postdrop may receive mail via other programs, * so we add a redundant recipient check here for future proofing. * * The test for the sender address is just for consistency of error * reporting (report at submission time instead of pickup time). Besides * the segment terminator records, there aren't any other mandatory * records in a Postfix submission queue file. */ if (validate_input && (from_count == 0 || rcpt_count == 0)) { status = CLEANUP_STAT_BAD; mail_stream_cleanup(dst); } /* * Finish the file. */ else if ((status = mail_stream_finish(dst, (VSTRING *) 0)) != 0) { msg_warn("uid=%ld: %m", (long) uid); postdrop_cleanup(); } /* * Disable deletion on fatal error before reporting success, so the file * will not be deleted after we have taken responsibility for delivery. */ if (postdrop_path) { junk = postdrop_path; postdrop_path = 0; myfree(junk); } /* * Send the completion status to the caller and terminate. */ attr_print(VSTREAM_OUT, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), SEND_ATTR_STR(MAIL_ATTR_WHY, ""), ATTR_TYPE_END); vstream_fflush(VSTREAM_OUT); exit(status); }
static void tlsp_strategy(TLSP_STATE *state) { TLS_SESS_STATE *tls_context = state->tls_context; NBBIO *plaintext_buf; int ssl_stat; int ssl_read_err; int ssl_write_err; int handshake_err; /* * Be sure to complete the TLS handshake before enabling plain-text I/O. * In case of an unrecoverable error, this automagically cleans up all * pending read/write and timeout event requests. */ if (state->flags & TLSP_FLAG_DO_HANDSHAKE) { ssl_stat = SSL_accept(tls_context->con); if (ssl_stat != 1) { handshake_err = SSL_get_error(tls_context->con, ssl_stat); tlsp_eval_tls_error(state, handshake_err); /* At this point, state could be a dangling pointer. */ return; } if ((state->tls_context = tls_server_post_accept(tls_context)) == 0) { tlsp_state_free(state); return; } if ((state->req_flags & TLS_PROXY_FLAG_SEND_CONTEXT) != 0 && (attr_print(state->plaintext_stream, ATTR_FLAG_NONE, SEND_ATTR_FUNC(tls_proxy_context_print, (void *) state->tls_context), ATTR_TYPE_END) != 0 || vstream_fflush(state->plaintext_stream) != 0)) { msg_warn("cannot send TLS context: %m"); tlsp_state_free(state); return; } state->flags &= ~TLSP_FLAG_DO_HANDSHAKE; } /* * Shutdown and self-destruct after NBBIO error. This automagically * cleans up all pending read/write and timeout event requests. Before * shutting down TLS, we stop all plain-text I/O events but keep the * NBBIO error flags. */ plaintext_buf = state->plaintext_buf; if (NBBIO_ERROR_FLAGS(plaintext_buf)) { if (NBBIO_ACTIVE_FLAGS(plaintext_buf)) nbbio_disable_readwrite(state->plaintext_buf); ssl_stat = SSL_shutdown(tls_context->con); /* XXX Wait for return value 1 if sessions are to be reused? */ if (ssl_stat < 0) { handshake_err = SSL_get_error(tls_context->con, ssl_stat); tlsp_eval_tls_error(state, handshake_err); /* At this point, state could be a dangling pointer. */ return; } tlsp_state_free(state); return; } /* * Try to move data from the plaintext input buffer to the TLS engine. * * XXX We're supposed to repeat the exact same SSL_write() call arguments * after an SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE result. Rumor has * it that this is because each SSL_write() call reads from the buffer * incrementally, and returns > 0 only after the final byte is processed. * Rumor also has it that setting SSL_MODE_ENABLE_PARTIAL_WRITE and * SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER voids this requirement, and that * repeating the request with an increased request size is OK. * Unfortunately all this is not or poorly documented, and one has to * rely on statements from OpenSSL developers in public mailing archives. */ ssl_write_err = SSL_ERROR_NONE; while (NBBIO_READ_PEND(plaintext_buf) > 0) { ssl_stat = SSL_write(tls_context->con, NBBIO_READ_BUF(plaintext_buf), NBBIO_READ_PEND(plaintext_buf)); ssl_write_err = SSL_get_error(tls_context->con, ssl_stat); if (ssl_write_err != SSL_ERROR_NONE) break; /* Allow the plaintext pseudothread to read more data. */ NBBIO_READ_PEND(plaintext_buf) -= ssl_stat; if (NBBIO_READ_PEND(plaintext_buf) > 0) memmove(NBBIO_READ_BUF(plaintext_buf), NBBIO_READ_BUF(plaintext_buf) + ssl_stat, NBBIO_READ_PEND(plaintext_buf)); } /* * Try to move data from the TLS engine to the plaintext output buffer. * Note: data may arrive as a side effect of calling SSL_write(), * therefore we call SSL_read() after calling SSL_write(). * * XXX We're supposed to repeat the exact same SSL_read() call arguments * after an SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE result. This * supposedly means that our plaintext writer must not memmove() the * plaintext output buffer until after the SSL_read() call succeeds. For * now I'll ignore this, because 1) SSL_read() is documented to return * the bytes available, instead of returning > 0 only after the entire * buffer is processed like SSL_write() does; and 2) there is no "read" * equivalent of the SSL_R_BAD_WRITE_RETRY, SSL_MODE_ENABLE_PARTIAL_WRITE * or SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER features. */ ssl_read_err = SSL_ERROR_NONE; while (NBBIO_WRITE_PEND(state->plaintext_buf) < NBBIO_BUFSIZE(plaintext_buf)) { ssl_stat = SSL_read(tls_context->con, NBBIO_WRITE_BUF(plaintext_buf) + NBBIO_WRITE_PEND(state->plaintext_buf), NBBIO_BUFSIZE(plaintext_buf) - NBBIO_WRITE_PEND(state->plaintext_buf)); ssl_read_err = SSL_get_error(tls_context->con, ssl_stat); if (ssl_read_err != SSL_ERROR_NONE) break; NBBIO_WRITE_PEND(plaintext_buf) += ssl_stat; } /* * Try to enable/disable ciphertext read/write events. If SSL_write() was * satisfied, see if SSL_read() wants to do some work. In case of an * unrecoverable error, this automagically destroys the session state * after cleaning up all pending read/write and timeout event requests. */ if (tlsp_eval_tls_error(state, ssl_write_err != SSL_ERROR_NONE ? ssl_write_err : ssl_read_err) < 0) return; /* * Try to enable/disable plaintext read/write events. Basically, if we * have nothing to write to the postscreen(8) server, see if there is * something to read. If the write buffer is empty and the read buffer is * full, suspend plaintext I/O until conditions change (but keep the * timer active, as a safety mechanism in case ciphertext I/O gets * stuck). * * XXX In theory, if the client keeps writing fast enough then we would * never read from postscreen(8), and cause postscreen(8) to block. In * practice, postscreen(8) limits the number of client commands, and thus * postscreen(8)'s output will fit in a kernel buffer. This may not be * true in other scenarios where the tlsproxy(8) server could be used. */ if (NBBIO_WRITE_PEND(plaintext_buf) > 0) { if (NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_READ) nbbio_disable_readwrite(plaintext_buf); if ((NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_WRITE) == 0) nbbio_enable_write(plaintext_buf, state->timeout); } else if (NBBIO_READ_PEND(plaintext_buf) < NBBIO_BUFSIZE(plaintext_buf)) { if (NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_WRITE) nbbio_disable_readwrite(plaintext_buf); if ((NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_READ) == 0) nbbio_enable_read(plaintext_buf, state->timeout); } else { if (NBBIO_ACTIVE_FLAGS(plaintext_buf)) nbbio_slumber(plaintext_buf, state->timeout); } }
static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) { QMGR_RCPT_LIST list = entry->rcpt_list; QMGR_RCPT *recipient; QMGR_MESSAGE *message = entry->message; VSTRING *sender_buf = 0; char *sender; int flags; /* * If variable envelope return path is requested, change prefix+@origin * into prefix+user=domain@origin. Note that with VERP there is only one * recipient per delivery. */ if (message->verp_delims == 0) { sender = message->sender; } else { sender_buf = vstring_alloc(100); verp_sender(sender_buf, message->verp_delims, message->sender, list.info->address); sender = vstring_str(sender_buf); } flags = message->tflags | (message->inspect_xport ? DEL_REQ_FLAG_BOUNCE : DEL_REQ_FLAG_DEFLT); attr_print(stream, ATTR_FLAG_MORE, ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUE, message->queue_name, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, message->queue_id, ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, message->data_offset, ATTR_TYPE_LONG, MAIL_ATTR_SIZE, message->data_size, ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, entry->queue->nexthop, ATTR_TYPE_STR, MAIL_ATTR_ENCODING, message->encoding, ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, ATTR_TYPE_STR, MAIL_ATTR_ERRTO, message->errors_to, ATTR_TYPE_STR, MAIL_ATTR_RRCPT, message->return_receipt, ATTR_TYPE_LONG, MAIL_ATTR_TIME, message->arrival_time, ATTR_TYPE_STR, MAIL_ATTR_CLIENT_NAME, message->client_name, ATTR_TYPE_STR, MAIL_ATTR_CLIENT_ADDR, message->client_addr, ATTR_TYPE_STR, MAIL_ATTR_PROTO_NAME, message->client_proto, ATTR_TYPE_STR, MAIL_ATTR_HELO_NAME, message->client_helo, ATTR_TYPE_END); if (sender_buf != 0) vstring_free(sender_buf); for (recipient = list.info; recipient < list.info + list.len; recipient++) attr_print(stream, ATTR_FLAG_MORE, ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, recipient->offset, ATTR_TYPE_STR, MAIL_ATTR_ORCPT, recipient->orig_rcpt, ATTR_TYPE_STR, MAIL_ATTR_RECIP, recipient->address, ATTR_TYPE_END); attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_NUM, MAIL_ATTR_OFFSET, 0, ATTR_TYPE_END); if (vstream_fflush(stream) != 0) { msg_warn("write to process (%s): %m", entry->queue->transport->name); return (-1); } else { if (msg_verbose) msg_info("qmgr_deliver: site `%s'", entry->queue->name); return (0); } }
static void tlsmgr_service(VSTREAM *client_stream, char *unused_service, char **argv) { static VSTRING *request = 0; static VSTRING *cache_type = 0; static VSTRING *cache_id = 0; static VSTRING *buffer = 0; int len; static char wakeup[] = { /* master wakeup request */ TRIGGER_REQ_WAKEUP, 0, }; TLSMGR_SCACHE *ent; int status = TLS_MGR_STAT_FAIL; /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * Initialize. We're select threaded, so we can use static buffers. */ if (request == 0) { request = vstring_alloc(10); cache_type = vstring_alloc(10); cache_id = vstring_alloc(10); buffer = vstring_alloc(10); } /* * This routine runs whenever a client connects to the socket dedicated * to the tlsmgr service (including wake up events sent by the master). * All connection-management stuff is handled by the common code in * multi_server.c. */ if (tlsmgr_request_receive(client_stream, request) == 0) { /* * Load session from cache. */ if (STREQ(STR(request), TLS_MGR_REQ_LOOKUP)) { if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id, ATTR_TYPE_END) == 2) { for (ent = cache_table; ent->cache_label; ++ent) if (strcmp(ent->cache_label, STR(cache_type)) == 0) break; if (ent->cache_label == 0) { msg_warn("bogus cache type \"%s\" in \"%s\" request", STR(cache_type), TLS_MGR_REQ_LOOKUP); VSTRING_RESET(buffer); } else if (ent->cache_info == 0) { /* * Cache type valid, but not enabled */ VSTRING_RESET(buffer); } else { status = tls_scache_lookup(ent->cache_info, STR(cache_id), buffer) ? TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR; } } attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_DATA, TLS_MGR_ATTR_SESSION, LEN(buffer), STR(buffer), ATTR_TYPE_END); } /* * Save session to cache. */ else if (STREQ(STR(request), TLS_MGR_REQ_UPDATE)) { if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id, ATTR_TYPE_DATA, TLS_MGR_ATTR_SESSION, buffer, ATTR_TYPE_END) == 3) { for (ent = cache_table; ent->cache_label; ++ent) if (strcmp(ent->cache_label, STR(cache_type)) == 0) break; if (ent->cache_label == 0) { msg_warn("bogus cache type \"%s\" in \"%s\" request", STR(cache_type), TLS_MGR_REQ_UPDATE); } else if (ent->cache_info != 0) { status = tls_scache_update(ent->cache_info, STR(cache_id), STR(buffer), LEN(buffer)) ? TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR; } } attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_END); } /* * Delete session from cache. */ else if (STREQ(STR(request), TLS_MGR_REQ_DELETE)) { if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id, ATTR_TYPE_END) == 2) { for (ent = cache_table; ent->cache_label; ++ent) if (strcmp(ent->cache_label, STR(cache_type)) == 0) break; if (ent->cache_label == 0) { msg_warn("bogus cache type \"%s\" in \"%s\" request", STR(cache_type), TLS_MGR_REQ_DELETE); } else if (ent->cache_info != 0) { status = tls_scache_delete(ent->cache_info, STR(cache_id)) ? TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR; } } attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_END); } /* * RFC 5077 TLS session ticket keys */ else if (STREQ(STR(request), TLS_MGR_REQ_TKTKEY)) { if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_DATA, TLS_MGR_ATTR_KEYNAME, buffer, ATTR_TYPE_END) == 1) { if (LEN(buffer) != 0 && LEN(buffer) != TLS_TICKET_NAMELEN) { msg_warn("invalid session ticket key name length: %ld", (long) LEN(buffer)); VSTRING_RESET(buffer); } else if (*smtpd_cache.cache_timeout <= 0) { status = TLS_MGR_STAT_ERR; VSTRING_RESET(buffer); } else { status = tlsmgr_key(buffer, *smtpd_cache.cache_timeout); } } attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_DATA, TLS_MGR_ATTR_KEYBUF, LEN(buffer), STR(buffer), ATTR_TYPE_END); } /* * Entropy request. */ else if (STREQ(STR(request), TLS_MGR_REQ_SEED)) { if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_INT, TLS_MGR_ATTR_SIZE, &len, ATTR_TYPE_END) == 1) { VSTRING_RESET(buffer); if (len <= 0 || len > 255) { msg_warn("bogus seed length \"%d\" in \"%s\" request", len, TLS_MGR_REQ_SEED); } else { VSTRING_SPACE(buffer, len); RAND_bytes((unsigned char *) STR(buffer), len); VSTRING_AT_OFFSET(buffer, len); /* XXX not part of the * official interface */ status = TLS_MGR_STAT_OK; } } attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_DATA, TLS_MGR_ATTR_SEED, LEN(buffer), STR(buffer), ATTR_TYPE_END); } /* * Caching policy request. */ else if (STREQ(STR(request), TLS_MGR_REQ_POLICY)) { int cachable = 0; int timeout = 0; if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type, ATTR_TYPE_END) == 1) { for (ent = cache_table; ent->cache_label; ++ent) if (strcmp(ent->cache_label, STR(cache_type)) == 0) break; if (ent->cache_label == 0) { msg_warn("bogus cache type \"%s\" in \"%s\" request", STR(cache_type), TLS_MGR_REQ_POLICY); } else { cachable = (ent->cache_info != 0) ? 1 : 0; timeout = *ent->cache_timeout; status = TLS_MGR_STAT_OK; } } attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_INT, TLS_MGR_ATTR_CACHABLE, cachable, ATTR_TYPE_INT, TLS_MGR_ATTR_SESSTOUT, timeout, ATTR_TYPE_END); } /* * Master trigger. Normally, these triggers arrive only after some * other process requested the tlsmgr's service. The purpose is to * restart the tlsmgr after it aborted due to a fatal run-time error, * so that it can continue its housekeeping even while nothing is * using TLS. * * XXX Which begs the question, if TLS isn't used often, do we need a * tlsmgr background process? It could terminate when the session * caches are empty. */ else if (STREQ(STR(request), wakeup)) { if (msg_verbose) msg_info("received master trigger"); multi_server_disconnect(client_stream); return; /* NOT: vstream_fflush */ } } /* * Protocol error. */ else { attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, TLS_MGR_STAT_FAIL, ATTR_TYPE_END); } vstream_fflush(client_stream); }
int psc_dnsbl_request(const char *client_addr, void (*callback) (int, void *), void *context) { const char *myname = "psc_dnsbl_request"; int fd; VSTREAM *stream; HTABLE_INFO **ht; PSC_DNSBL_SCORE *score; HTABLE_INFO *hash_node; static int request_count; /* * Some spambots make several connections at nearly the same time, * causing their pregreet delays to overlap. Such connections can share * the efforts of DNSBL lookup. * * We store a reference-counted DNSBL score under its client IP address. We * increment the reference count with each score request, and decrement * the reference count with each score retrieval. * * Do not notify the requestor NOW when the DNS replies are already in. * Reason: we must not make a backwards call while we are still in the * middle of executing the corresponding forward call. Instead we create * a zero-delay timer request and call the notification function from * there. * * psc_dnsbl_request() could instead return a result value to indicate that * the DNSBL score is already available, but that would complicate the * caller with two different notification code paths: one asynchronous * code path via the callback invocation, and one synchronous code path * via the psc_dnsbl_request() result value. That would be a source of * future bugs. */ if ((hash_node = htable_locate(dnsbl_score_cache, client_addr)) != 0) { score = (PSC_DNSBL_SCORE *) hash_node->value; score->refcount += 1; PSC_CALL_BACK_EXTEND(hash_node, score); PSC_CALL_BACK_ENTER(score, callback, context); if (msg_verbose > 1) msg_info("%s: reuse blocklist score for %s refcount=%d pending=%d", myname, client_addr, score->refcount, score->pending_lookups); if (score->pending_lookups == 0) event_request_timer(callback, context, EVENT_NULL_DELAY); return (PSC_CALL_BACK_INDEX_OF_LAST(score)); } if (msg_verbose > 1) msg_info("%s: create blocklist score for %s", myname, client_addr); score = (PSC_DNSBL_SCORE *) mymalloc(sizeof(*score)); score->request_id = request_count++; score->dnsbl_name = 0; score->dnsbl_weight = 0; /* As with dnsblog(8), a value < 0 means no reply TTL. */ score->pass_ttl = -1; score->fail_ttl = -1; score->total = 0; score->refcount = 1; score->pending_lookups = 0; PSC_CALL_BACK_INIT(score); PSC_CALL_BACK_ENTER(score, callback, context); (void) htable_enter(dnsbl_score_cache, client_addr, (void *) score); /* * Send a query to all DNSBL servers. Later, DNSBL lookup will be done * with an UDP-based DNS client that is built directly into Postfix code. * We therefore do not optimize the maximum out of this temporary * implementation. */ for (ht = dnsbl_site_list; *ht; ht++) { if ((fd = LOCAL_CONNECT(psc_dnsbl_service, NON_BLOCKING, 1)) < 0) { msg_warn("%s: connect to %s service: %m", myname, psc_dnsbl_service); continue; } stream = vstream_fdopen(fd, O_RDWR); vstream_control(stream, CA_VSTREAM_CTL_CONTEXT(ht[0]->key), CA_VSTREAM_CTL_END); attr_print(stream, ATTR_FLAG_NONE, SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, ht[0]->key), SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, client_addr), SEND_ATTR_INT(MAIL_ATTR_LABEL, score->request_id), ATTR_TYPE_END); if (vstream_fflush(stream) != 0) { msg_warn("%s: error sending to %s service: %m", myname, psc_dnsbl_service); vstream_fclose(stream); continue; } PSC_READ_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive, (void *) stream, var_psc_dnsbl_tmout); score->pending_lookups += 1; } return (PSC_CALL_BACK_INDEX_OF_LAST(score)); }
static void flush_service(VSTREAM *client_stream, char *unused_service, char **argv) { VSTRING *request = vstring_alloc(10); VSTRING *site = 0; VSTRING *queue_id = 0; static char wakeup[] = { /* master wakeup request */ TRIGGER_REQ_WAKEUP, 0, }; int status = FLUSH_STAT_BAD; /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * This routine runs whenever a client connects to the UNIX-domain socket * dedicated to the fast flush service. What we see below is a little * protocol to (1) read a request from the client (the name of the site) * and (2) acknowledge that we have received the request. * * All connection-management stuff is handled by the common code in * single_server.c. */ if (flush_request_receive(client_stream, request) == 0) { if (STREQ(STR(request), FLUSH_REQ_ADD)) { site = vstring_alloc(10); queue_id = vstring_alloc(10); if (attr_scan(client_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_SITE, site), RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), ATTR_TYPE_END) == 2 && mail_queue_id_ok(STR(queue_id))) status = flush_add_service(STR(site), STR(queue_id)); attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), ATTR_TYPE_END); } else if (STREQ(STR(request), FLUSH_REQ_SEND_SITE)) { site = vstring_alloc(10); if (attr_scan(client_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_SITE, site), ATTR_TYPE_END) == 1) status = flush_send_service(STR(site), UNTHROTTLE_BEFORE); attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), ATTR_TYPE_END); } else if (STREQ(STR(request), FLUSH_REQ_SEND_FILE)) { queue_id = vstring_alloc(10); if (attr_scan(client_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), ATTR_TYPE_END) == 1) status = flush_send_file_service(STR(queue_id)); attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), ATTR_TYPE_END); } else if (STREQ(STR(request), FLUSH_REQ_REFRESH) || STREQ(STR(request), wakeup)) { attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK), ATTR_TYPE_END); vstream_fflush(client_stream); (void) flush_refresh_service(var_fflush_refresh); } else if (STREQ(STR(request), FLUSH_REQ_PURGE)) { attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK), ATTR_TYPE_END); vstream_fflush(client_stream); (void) flush_refresh_service(0); } } else attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), ATTR_TYPE_END); vstring_free(request); if (site) vstring_free(site); if (queue_id) vstring_free(queue_id); }
MILTERS *milter_receive(VSTREAM *stream, int count) { MILTERS *milters; MILTER *head = 0; MILTER *tail = 0; MILTER *milter = 0; if (msg_verbose) msg_info("receive %d milters", count); /* * XXX We must instantiate a MILTERS structure even when the sender has * no active filters, otherwise the cleanup server would try to use its * own non_smtpd_milters settings. */ #define NO_MILTERS ((char *) 0) #define NO_TIMEOUTS 0, 0, 0 #define NO_PROTOCOL ((char *) 0) #define NO_ACTION ((char *) 0) #define NO_MACROS ((MILTER_MACROS *) 0) milters = milter_new(NO_MILTERS, NO_TIMEOUTS, NO_PROTOCOL, NO_ACTION, NO_MACROS); /* * XXX Optimization: don't send or receive further information when there * aren't any active filters. */ if (count <= 0) return (milters); /* * Receive the global macro name lists. */ milters->macros = milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO); if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, ATTR_TYPE_FUNC, milter_macros_scan, (void *) milters->macros, ATTR_TYPE_END) != 1) { milter_free(milters); return (0); } /* * Receive the filters. */ for (; count > 0; count--) { if ((milter = milter8_receive(stream, milters)) == 0) { msg_warn("cannot receive milters via service %s socket", VSTREAM_PATH(stream)); milter_free(milters); return (0); } if (head == 0) { /* Coverity: milter_free() depends on milters->milter_list. */ milters->milter_list = head = milter; } else { tail->next = milter; } tail = milter; } /* * Over to you. */ (void) attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, 0, ATTR_TYPE_END); return (milters); }
int milter_send(MILTERS *milters, VSTREAM *stream) { MILTER *m; int status = 0; int count = 0; /* * XXX Optimization: send only the filters that are actually used in the * remote process. No point sending a filter that looks at HELO commands * to a cleanup server. For now we skip only the filters that are known * to be disabled (either in global error state or in global accept * state). * * XXX We must send *some* information, even when there are no active * filters, otherwise the cleanup server would try to apply its own * non_smtpd_milters settings. */ if (milters != 0) for (m = milters->milter_list; m != 0; m = m->next) if (m->active(m)) count++; (void) rec_fprintf(stream, REC_TYPE_MILT_COUNT, "%d", count); if (msg_verbose) msg_info("send %d milters", count); /* * XXX Optimization: don't send or receive further information when there * aren't any active filters. */ if (count <= 0) return (0); /* * Send the filter macro name lists. */ (void) attr_print(stream, ATTR_FLAG_MORE, ATTR_TYPE_FUNC, milter_macros_print, (void *) milters->macros, ATTR_TYPE_END); /* * Send the filter instances. */ for (m = milters->milter_list; m != 0; m = m->next) if (m->active(m) && (status = m->send(m, stream)) != 0) break; /* * Over to you. */ if (status != 0 || attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status, ATTR_TYPE_END) != 1 || status != 0) { msg_warn("cannot send milters to service %s", VSTREAM_PATH(stream)); return (-1); } return (0); }
static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) { RECIPIENT_LIST list = entry->rcpt_list; RECIPIENT *recipient; QMGR_MESSAGE *message = entry->message; VSTRING *sender_buf = 0; MSG_STATS stats; char *sender; int flags; /* * If variable envelope return path is requested, change prefix+@origin * into prefix+user=domain@origin. Note that with VERP there is only one * recipient per delivery. */ if (message->verp_delims == 0) { sender = message->sender; } else { sender_buf = vstring_alloc(100); verp_sender(sender_buf, message->verp_delims, message->sender, list.info); sender = vstring_str(sender_buf); } flags = message->tflags | entry->queue->dflags | (message->inspect_xport ? DEL_REQ_FLAG_BOUNCE : DEL_REQ_FLAG_DEFLT); (void) QMGR_MSG_STATS(&stats, message); attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUE, message->queue_name, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, message->queue_id, ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, message->data_offset, ATTR_TYPE_LONG, MAIL_ATTR_SIZE, message->cont_length, ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, entry->queue->nexthop, ATTR_TYPE_STR, MAIL_ATTR_ENCODING, message->encoding, ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, ATTR_TYPE_STR, MAIL_ATTR_DSN_ENVID, message->dsn_envid, ATTR_TYPE_INT, MAIL_ATTR_DSN_RET, message->dsn_ret, ATTR_TYPE_FUNC, msg_stats_print, (void *) &stats, /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */ ATTR_TYPE_STR, MAIL_ATTR_LOG_CLIENT_NAME, message->client_name, ATTR_TYPE_STR, MAIL_ATTR_LOG_CLIENT_ADDR, message->client_addr, ATTR_TYPE_STR, MAIL_ATTR_LOG_CLIENT_PORT, message->client_port, ATTR_TYPE_STR, MAIL_ATTR_LOG_PROTO_NAME, message->client_proto, ATTR_TYPE_STR, MAIL_ATTR_LOG_HELO_NAME, message->client_helo, /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */ ATTR_TYPE_STR, MAIL_ATTR_SASL_METHOD, message->sasl_method, ATTR_TYPE_STR, MAIL_ATTR_SASL_USERNAME, message->sasl_username, ATTR_TYPE_STR, MAIL_ATTR_SASL_SENDER, message->sasl_sender, /* XXX Ditto if we want to pass TLS certificate info. */ ATTR_TYPE_STR, MAIL_ATTR_LOG_IDENT, message->log_ident, ATTR_TYPE_STR, MAIL_ATTR_RWR_CONTEXT, message->rewrite_context, ATTR_TYPE_INT, MAIL_ATTR_RCPT_COUNT, list.len, ATTR_TYPE_END); if (sender_buf != 0) vstring_free(sender_buf); for (recipient = list.info; recipient < list.info + list.len; recipient++) attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_FUNC, rcpt_print, (void *) recipient, ATTR_TYPE_END); if (vstream_fflush(stream) != 0) { msg_warn("write to process (%s): %m", entry->queue->transport->name); return (-1); } else { if (msg_verbose) msg_info("qmgr_deliver: site `%s'", entry->queue->name); return (0); } }
static void verify_query_service(VSTREAM *client_stream) { VSTRING *addr = vstring_alloc(10); VSTRING *get_buf = 0; VSTRING *put_buf = 0; const char *raw_data; int addr_status; long probed; long updated; char *text; if (attr_scan(client_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_ADDR, addr), ATTR_TYPE_END) == 1) { long now = (long) time((time_t *) 0); /* * Produce a default record when no usable record exists. * * If negative caching is disabled, purge an expired record from the * database. * * XXX Assume that a probe is lost if no response is received in 1000 * seconds. If this number is too small the queue will slowly fill up * with delayed probes. * * XXX Maintain a moving average for the probe turnaround time, and * allow probe "retransmission" when a probe is outstanding for, say * some minimal amount of time (1000 sec) plus several times the * observed probe turnaround time. This causes probing to back off * when the mail system becomes congested. */ #define POSITIVE_ENTRY_EXPIRED(addr_status, updated) \ (addr_status == DEL_RCPT_STAT_OK && updated + var_verify_pos_exp < now) #define NEGATIVE_ENTRY_EXPIRED(addr_status, updated) \ (addr_status != DEL_RCPT_STAT_OK && updated + var_verify_neg_exp < now) #define PROBE_TTL 1000 /* FIX 200501 IPv6 patch did not neuter ":" in address literals. */ translit(STR(addr), ":", "_"); if ((raw_data = dict_cache_lookup(verify_map, STR(addr))) == 0 /* not found */ || ((get_buf = vstring_alloc(10)), vstring_strcpy(get_buf, raw_data), /* malformed */ verify_parse_entry(STR(get_buf), &addr_status, &probed, &updated, &text) < 0) || (now - probed > PROBE_TTL /* safe to probe */ && (POSITIVE_ENTRY_EXPIRED(addr_status, updated) || NEGATIVE_ENTRY_EXPIRED(addr_status, updated)))) { addr_status = DEL_RCPT_STAT_TODO; probed = 0; updated = 0; text = "Address verification in progress"; if (raw_data != 0 && var_verify_neg_cache == 0) dict_cache_delete(verify_map, STR(addr)); } if (msg_verbose) msg_info("GOT %s status=%d probed=%ld updated=%ld text=%s", STR(addr), addr_status, probed, updated, text); /* * Respond to the client. */ attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_OK), SEND_ATTR_INT(MAIL_ATTR_ADDR_STATUS, addr_status), SEND_ATTR_STR(MAIL_ATTR_WHY, text), ATTR_TYPE_END); /* * Send a new probe when the information needs to be refreshed. * * XXX For an initial proof of concept implementation, use synchronous * mail submission. This needs to be made async for high-volume * sites, which makes it even more interesting to eliminate duplicate * queries while a probe is being built. * * If negative caching is turned off, update the database only when * refreshing an existing entry. */ #define POSITIVE_REFRESH_NEEDED(addr_status, updated) \ (addr_status == DEL_RCPT_STAT_OK && updated + var_verify_pos_try < now) #define NEGATIVE_REFRESH_NEEDED(addr_status, updated) \ (addr_status != DEL_RCPT_STAT_OK && updated + var_verify_neg_try < now) if (now - probed > PROBE_TTL && (POSITIVE_REFRESH_NEEDED(addr_status, updated) || NEGATIVE_REFRESH_NEEDED(addr_status, updated))) { if (msg_verbose) msg_info("PROBE %s status=%d probed=%ld updated=%ld", STR(addr), addr_status, now, updated); post_mail_fopen_async(make_verify_sender_addr(), STR(addr), MAIL_SRC_MASK_VERIFY, DEL_REQ_FLAG_MTA_VRFY, SMTPUTF8_FLAG_NONE, (VSTRING *) 0, verify_post_mail_action, (void *) 0); if (updated != 0 || var_verify_neg_cache != 0) { put_buf = vstring_alloc(10); verify_make_entry(put_buf, addr_status, now, updated, text); if (msg_verbose) msg_info("PUT %s status=%d probed=%ld updated=%ld text=%s", STR(addr), addr_status, now, updated, text); dict_cache_update(verify_map, STR(addr), STR(put_buf)); } } } vstring_free(addr); if (get_buf) vstring_free(get_buf); if (put_buf) vstring_free(put_buf); }
static void bounce_service(VSTREAM *client, char *service_name, char **argv) { int command; int status; /* * Sanity check. This service takes no command-line arguments. The * service name should be usable as a subdirectory name. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); if (mail_queue_name_ok(service_name) == 0) msg_fatal("malformed service name: %s", service_name); /* * Read and validate the first parameter of the client request. Let the * request-specific protocol routines take care of the remainder. */ if (attr_scan(client, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, ATTR_TYPE_NUM, MAIL_ATTR_NREQ, &command, 0) != 1) { msg_warn("malformed request"); status = -1; } else if (command == BOUNCE_CMD_VERP) { status = bounce_verp_proto(service_name, client); } else if (command == BOUNCE_CMD_FLUSH) { status = bounce_notify_proto(service_name, client, bounce_notify_service); } else if (command == BOUNCE_CMD_WARN) { status = bounce_notify_proto(service_name, client, bounce_warn_service); } else if (command == BOUNCE_CMD_TRACE) { status = bounce_notify_proto(service_name, client, bounce_trace_service); } else if (command == BOUNCE_CMD_APPEND) { status = bounce_append_proto(service_name, client); } else if (command == BOUNCE_CMD_ONE) { status = bounce_one_proto(service_name, client); } else { msg_warn("unknown command: %d", command); status = -1; } /* * When the request has completed, send the completion status to the * client. */ attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_NUM, MAIL_ATTR_STATUS, status, ATTR_TYPE_END); vstream_fflush(client); /* * When a cleanup trap was set, delete the log file in case of error. * This includes errors while sending the completion status to the * client. */ if (bounce_cleanup_path) { if (status || vstream_ferror(client)) bounce_cleanup_log(); bounce_cleanup_unregister(); } }
static void cleanup_service(VSTREAM *src, char *unused_service, char **argv) { VSTRING *buf = vstring_alloc(100); CLEANUP_STATE *state; int flags; int type = 0; int status; /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * Open a queue file and initialize state. */ state = cleanup_open(src); /* * Send the queue id to the client. Read client processing options. If we * can't read the client processing options we can pretty much forget * about the whole operation. */ attr_print(src, ATTR_FLAG_NONE, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, state->queue_id, ATTR_TYPE_END); if (attr_scan(src, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &flags, ATTR_TYPE_END) != 1) { state->errs |= CLEANUP_STAT_BAD; flags = 0; } cleanup_control(state, flags); /* * XXX Rely on the front-end programs to enforce record size limits. * * First, copy the envelope records to the queue file. Then, copy the * message content (headers and body). Finally, attach any information * extracted from message headers. */ while (CLEANUP_OUT_OK(state)) { if ((type = rec_get_raw(src, buf, 0, REC_FLAG_NONE)) < 0) { state->errs |= CLEANUP_STAT_BAD; break; } if (REC_GET_HIDDEN_TYPE(type)) { msg_warn("%s: record type %d not allowed - discarding this message", state->queue_id, type); state->errs |= CLEANUP_STAT_BAD; break; } CLEANUP_RECORD(state, type, vstring_str(buf), VSTRING_LEN(buf)); if (type == REC_TYPE_END) break; } /* * Keep reading in case of problems, until the sender is ready to receive * our status report. */ if (CLEANUP_OUT_OK(state) == 0 && type > 0) { while (type != REC_TYPE_END && (type = rec_get(src, buf, 0)) > 0) /* void */ ; } /* * Log something to make timeout errors easier to debug. */ if (vstream_ftimeout(src)) msg_warn("%s: read timeout on %s", state->queue_id, VSTREAM_PATH(src)); /* * Finish this message, and report the result status to the client. */ status = cleanup_flush(state); /* in case state is modified */ attr_print(src, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_STR, MAIL_ATTR_WHY, (state->flags & CLEANUP_FLAG_SMTP_REPLY) && state->smtp_reply ? state->smtp_reply : state->reason ? state->reason : "", ATTR_TYPE_END); cleanup_free(state); /* * Cleanup. */ vstring_free(buf); }
VSTREAM *tls_proxy_open(const char *service, int flags, VSTREAM *peer_stream, const char *peer_addr, const char *peer_port, int timeout) { VSTREAM *tlsproxy_stream; int status; int fd; static VSTRING *tlsproxy_service = 0; static VSTRING *remote_endpt = 0; /* * Initialize. */ if (tlsproxy_service == 0) { tlsproxy_service = vstring_alloc(20); remote_endpt = vstring_alloc(20); } /* * Connect to the tlsproxy(8) daemon. */ vstring_sprintf(tlsproxy_service, "%s/%s", MAIL_CLASS_PRIVATE, service); if ((fd = LOCAL_CONNECT(STR(tlsproxy_service), BLOCKING, TLSPROXY_INIT_TIMEOUT)) < 0) { msg_warn("connect to %s service: %m", STR(tlsproxy_service)); return (0); } /* * Initial handshake. Send the data attributes now, and send the client * file descriptor in a later transaction. * * XXX The formatted endpoint should be a state member. Then, we can * simplify all the format strings throughout the program. */ tlsproxy_stream = vstream_fdopen(fd, O_RDWR); vstring_sprintf(remote_endpt, "[%s]:%s", peer_addr, peer_port); attr_print(tlsproxy_stream, ATTR_FLAG_NONE, ATTR_TYPE_STR, MAIL_ATTR_REMOTE_ENDPT, STR(remote_endpt), ATTR_TYPE_INT, MAIL_ATTR_FLAGS, flags, ATTR_TYPE_INT, MAIL_ATTR_TIMEOUT, timeout, ATTR_TYPE_END); if (vstream_fflush(tlsproxy_stream) != 0) { msg_warn("error sending request to %s service: %m", STR(tlsproxy_service)); vstream_fclose(tlsproxy_stream); return (0); } /* * Receive the "TLS is available" indication. * * This may seem out of order, but we must have a read transaction between * sending the request attributes and sending the SMTP client file * descriptor. We can't assume UNIX-domain socket semantics here. */ if (attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status, ATTR_TYPE_END) != 1 || status == 0) { /* * The TLS proxy reports that the TLS engine is not available (due to * configuration error, or other causes). */ msg_warn("%s service role \"%s\" is not available", STR(tlsproxy_service), (flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "server" : (flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "client" : "bogus role"); vstream_fclose(tlsproxy_stream); return (0); } /* * Send the remote SMTP client file descriptor. */ if (LOCAL_SEND_FD(vstream_fileno(tlsproxy_stream), vstream_fileno(peer_stream)) < 0) { /* * Some error: drop the TLS proxy stream. */ msg_warn("sending file handle to %s service: %m", STR(tlsproxy_service)); vstream_fclose(tlsproxy_stream); return (0); } return (tlsproxy_stream); }