void master_status_init(MASTER_SERV *serv) { const char *myname = "master_status_init"; /* * Sanity checks. */ if (serv->status_fd[0] >= 0 || serv->status_fd[1] >= 0) msg_panic("%s: status events already enabled", myname); if (msg_verbose) msg_info("%s: %s", myname, serv->name); /* * Make the read end of this service's status pipe non-blocking so that * we can detect partial writes on the child side. We use a duplex pipe * so that the child side becomes readable when the master goes away. */ if (duplex_pipe(serv->status_fd) < 0) msg_fatal("pipe: %m"); non_blocking(serv->status_fd[0], BLOCKING); close_on_exec(serv->status_fd[0], CLOSE_ON_EXEC); close_on_exec(serv->status_fd[1], CLOSE_ON_EXEC); event_enable_read(serv->status_fd[0], master_status_event, (char *) serv); }
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; } }
static int tlsp_eval_tls_error(TLSP_STATE *state, int err) { int ciphertext_fd = state->ciphertext_fd; /* * The ciphertext file descriptor is in non-blocking mode, meaning that * each SSL_accept/connect/read/write/shutdown request may return an * "error" indication that it needs to read or write more ciphertext. The * purpose of this routine is to translate those "error" indications into * the appropriate read/write/timeout event requests. */ switch (err) { /* * No error from SSL_read and SSL_write means that the plaintext * output buffer is full and that the plaintext input buffer is * empty. Stop read/write events on the ciphertext stream. Keep the * timer alive as a safety mechanism for the case that the plaintext * pseudothreads get stuck. */ case SSL_ERROR_NONE: if (state->ssl_last_err != SSL_ERROR_NONE) { event_disable_readwrite(ciphertext_fd); event_request_timer(tlsp_ciphertext_event, (void *) state, state->timeout); state->ssl_last_err = SSL_ERROR_NONE; } return (0); /* * The TLS engine wants to write to the network. Turn on * write/timeout events on the ciphertext stream. */ case SSL_ERROR_WANT_WRITE: if (state->ssl_last_err == SSL_ERROR_WANT_READ) event_disable_readwrite(ciphertext_fd); if (state->ssl_last_err != SSL_ERROR_WANT_WRITE) { event_enable_write(ciphertext_fd, tlsp_ciphertext_event, (void *) state); state->ssl_last_err = SSL_ERROR_WANT_WRITE; } event_request_timer(tlsp_ciphertext_event, (void *) state, state->timeout); return (0); /* * The TLS engine wants to read from the network. Turn on * read/timeout events on the ciphertext stream. */ case SSL_ERROR_WANT_READ: if (state->ssl_last_err == SSL_ERROR_WANT_WRITE) event_disable_readwrite(ciphertext_fd); if (state->ssl_last_err != SSL_ERROR_WANT_READ) { event_enable_read(ciphertext_fd, tlsp_ciphertext_event, (void *) state); state->ssl_last_err = SSL_ERROR_WANT_READ; } event_request_timer(tlsp_ciphertext_event, (void *) state, state->timeout); return (0); /* * Some error. Self-destruct. This automagically cleans up all * pending read/write and timeout event requests, making state a * dangling pointer. */ case SSL_ERROR_SSL: tls_print_errors(); /* FALLTHROUGH */ default: tlsp_state_free(state); return (-1); } }
NORETURN multi_server_main(int argc, char **argv, MULTI_SERVER_FN service,...) { const char *myname = "multi_server_main"; VSTREAM *stream = 0; char *root_dir = 0; char *user_name = 0; int debug_me = 0; int daemon_mode = 1; char *service_name = basename(argv[0]); int delay; int c; int fd; va_list ap; MAIL_SERVER_INIT_FN pre_init = 0; MAIL_SERVER_INIT_FN post_init = 0; MAIL_SERVER_LOOP_FN loop = 0; int key; char *transport = 0; #if 0 char *lock_path; VSTRING *why; #endif int alone = 0; int zerolimit = 0; WATCHDOG *watchdog; char *oname_val; char *oname; char *oval; const char *err; char *generation; int msg_vstream_needed = 0; int redo_syslog_init = 0; /* * Process environment options as early as we can. */ if (getenv(CONF_ENV_VERB)) msg_verbose = 1; if (getenv(CONF_ENV_DEBUG)) debug_me = 1; /* * Don't die when a process goes away unexpectedly. */ signal(SIGPIPE, SIG_IGN); /* * Don't die for frivolous reasons. */ #ifdef SIGXFSZ signal(SIGXFSZ, SIG_IGN); #endif /* * May need this every now and then. */ var_procname = mystrdup(basename(argv[0])); set_mail_conf_str(VAR_PROCNAME, var_procname); /* * Initialize logging and exit handler. Do the syslog first, so that its * initialization completes before we enter the optional chroot jail. */ msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); if (msg_verbose) msg_info("daemon started"); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * Initialize from the configuration file. Allow command-line options to * override compiled-in defaults or configured parameter values. */ mail_conf_suck(); /* * Register dictionaries that use higher-level interfaces and protocols. */ mail_dict_init(); /* * After database open error, continue execution with reduced * functionality. */ dict_allow_surrogate = 1; /* * Pick up policy settings from master process. Shut up error messages to * stderr, because no-one is going to see them. */ opterr = 0; while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) { switch (c) { case 'c': root_dir = "setme"; break; case 'd': daemon_mode = 0; break; case 'D': debug_me = 1; break; case 'i': mail_conf_update(VAR_MAX_IDLE, optarg); break; case 'l': alone = 1; break; case 'm': mail_conf_update(VAR_MAX_USE, optarg); break; case 'n': service_name = optarg; break; case 'o': oname_val = mystrdup(optarg); if ((err = split_nameval(oname_val, &oname, &oval)) != 0) msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); mail_conf_update(oname, oval); if (strcmp(oname, VAR_SYSLOG_NAME) == 0) redo_syslog_init = 1; myfree(oname_val); break; case 's': if ((socket_count = atoi(optarg)) <= 0) msg_fatal("invalid socket_count: %s", optarg); break; case 'S': stream = VSTREAM_IN; break; case 'u': user_name = "setme"; break; case 't': transport = optarg; break; case 'v': msg_verbose++; break; case 'V': if (++msg_vstream_needed == 1) msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); break; case 'z': zerolimit = 1; break; default: msg_fatal("invalid option: %c", c); break; } } /* * Initialize generic parameters. */ mail_params_init(); if (redo_syslog_init) msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); /* * If not connected to stdin, stdin must not be a terminal. */ if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) { msg_vstream_init(var_procname, VSTREAM_ERR); msg_fatal("do not run this command by hand"); } /* * Application-specific initialization. */ va_start(ap, service); while ((key = va_arg(ap, int)) != 0) { switch (key) { case MAIL_SERVER_INT_TABLE: get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); break; case MAIL_SERVER_LONG_TABLE: get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); break; case MAIL_SERVER_STR_TABLE: get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); break; case MAIL_SERVER_BOOL_TABLE: get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); break; case MAIL_SERVER_TIME_TABLE: get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); break; case MAIL_SERVER_RAW_TABLE: get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); break; case MAIL_SERVER_NINT_TABLE: get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); break; case MAIL_SERVER_NBOOL_TABLE: get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); break; case MAIL_SERVER_PRE_INIT: pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_POST_INIT: post_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_LOOP: loop = va_arg(ap, MAIL_SERVER_LOOP_FN); break; case MAIL_SERVER_EXIT: multi_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); break; case MAIL_SERVER_PRE_ACCEPT: multi_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); break; case MAIL_SERVER_PRE_DISCONN: multi_server_pre_disconn = va_arg(ap, MAIL_SERVER_DISCONN_FN); break; case MAIL_SERVER_IN_FLOW_DELAY: multi_server_in_flow_delay = 1; break; case MAIL_SERVER_SOLITARY: if (stream == 0 && !alone) msg_fatal("service %s requires a process limit of 1", service_name); break; case MAIL_SERVER_UNLIMITED: if (stream == 0 && !zerolimit) msg_fatal("service %s requires a process limit of 0", service_name); break; case MAIL_SERVER_PRIVILEGED: if (user_name) msg_fatal("service %s requires privileged operation", service_name); break; default: msg_panic("%s: unknown argument type: %d", myname, key); } } va_end(ap); if (root_dir) root_dir = var_queue_dir; if (user_name) user_name = var_mail_owner; /* * Can options be required? */ if (stream == 0) { if (transport == 0) msg_fatal("no transport type specified"); if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0) multi_server_accept = multi_server_accept_inet; else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) multi_server_accept = multi_server_accept_local; #ifdef MASTER_XPORT_NAME_PASS else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0) multi_server_accept = multi_server_accept_pass; #endif else msg_fatal("unsupported transport type: %s", transport); } /* * Retrieve process generation from environment. */ if ((generation = getenv(MASTER_GEN_NAME)) != 0) { if (!alldig(generation)) msg_fatal("bad generation: %s", generation); OCTAL_TO_UNSIGNED(multi_server_generation, generation); if (msg_verbose) msg_info("process generation: %s (%o)", generation, multi_server_generation); } /* * Optionally start the debugger on ourself. */ if (debug_me) debug_process(); /* * Traditionally, BSD select() can't handle multiple processes selecting * on the same socket, and wakes up every process in select(). See TCP/IP * Illustrated volume 2 page 532. We avoid select() collisions with an * external lock file. */ /* * XXX Can't compete for exclusive access to the listen socket because we * also have to monitor existing client connections for service requests. */ #if 0 if (stream == 0 && !alone) { lock_path = concatenate(DEF_PID_DIR, "/", transport, ".", service_name, (char *) 0); why = vstring_alloc(1); if ((multi_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, (struct stat *) 0, -1, -1, why)) == 0) msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); close_on_exec(vstream_fileno(multi_server_lock), CLOSE_ON_EXEC); myfree(lock_path); vstring_free(why); } #endif /* * Set up call-back info. */ multi_server_service = service; multi_server_name = service_name; multi_server_argv = argv + optind; /* * Run pre-jail initialization. */ if (chdir(var_queue_dir) < 0) msg_fatal("chdir(\"%s\"): %m", var_queue_dir); if (pre_init) pre_init(multi_server_name, multi_server_argv); /* * Optionally, restrict the damage that this process can do. */ resolve_local_init(); tzset(); chroot_uid(root_dir, user_name); /* * Run post-jail initialization. */ if (post_init) post_init(multi_server_name, multi_server_argv); /* * Are we running as a one-shot server with the client connection on * standard input? If so, make sure the output is written to stdout so as * to satisfy common expectation. */ if (stream != 0) { vstream_control(stream, VSTREAM_CTL_DOUBLE, VSTREAM_CTL_WRITE_FD, STDOUT_FILENO, VSTREAM_CTL_END); service(stream, multi_server_name, multi_server_argv); vstream_fflush(stream); multi_server_exit(); } /* * Running as a semi-resident server. Service connection requests. * Terminate when we have serviced a sufficient number of clients, when * no-one has been talking to us for a configurable amount of time, or * when the master process terminated abnormally. */ if (var_idle_limit > 0) event_request_timer(multi_server_timeout, (char *) 0, var_idle_limit); for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { event_enable_read(fd, multi_server_accept, CAST_INT_TO_CHAR_PTR(fd)); close_on_exec(fd, CLOSE_ON_EXEC); } event_enable_read(MASTER_STATUS_FD, multi_server_abort, (char *) 0); close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (char *) 0); /* * The event loop, at last. */ while (var_use_limit == 0 || use_count < var_use_limit || client_count > 0) { if (multi_server_lock != 0) { watchdog_stop(watchdog); if (myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("select lock: %m"); } watchdog_start(watchdog); delay = loop ? loop(multi_server_name, multi_server_argv) : -1; event_loop(delay); } multi_server_exit(); }
static void multi_server_enable_read(int unused_event, char *context) { VSTREAM *stream = (VSTREAM *) context; event_enable_read(vstream_fileno(stream), multi_server_execute, (char *) stream); }
int main(int argc, char **argv) { int sock; int backlog; int ch; int ttl; const char *protocols = INET_PROTO_NAME_ALL; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * Fix 20051207. */ signal(SIGPIPE, SIG_IGN); /* * Initialize diagnostics. */ msg_vstream_init(argv[0], VSTREAM_ERR); /* * Parse JCL. */ while ((ch = GETOPT(argc, argv, "46cvx:")) > 0) { switch (ch) { case '4': protocols = INET_PROTO_NAME_IPV4; break; case '6': protocols = INET_PROTO_NAME_IPV6; break; case 'c': count_deliveries++; break; case 'v': msg_verbose++; break; case 'x': if ((ttl = atoi(optarg)) <= 0) usage(argv[0]); event_request_timer(terminate, (void *) 0, ttl); break; default: usage(argv[0]); } } if (argc - optind != 2) usage(argv[0]); if ((backlog = atoi(argv[optind + 1])) <= 0) usage(argv[0]); /* * Initialize. */ (void) inet_proto_init("protocols", protocols); buffer = vstring_alloc(1024); if (strncmp(argv[optind], "unix:", 5) == 0) { sock = unix_listen(argv[optind] + 5, backlog, BLOCKING); } else { if (strncmp(argv[optind], "inet:", 5) == 0) argv[optind] += 5; sock = inet_listen(argv[optind], backlog, BLOCKING); } /* * Start the event handler. */ event_enable_read(sock, connect_event, CAST_INT_TO_VOID_PTR(sock)); for (;;) event_loop(-1); }
void qmgr_deliver(QMGR_TRANSPORT *transport, VSTREAM *stream) { QMGR_QUEUE *queue; QMGR_ENTRY *entry; DSN dsn; /* * Find out if this delivery process is really available. Once elected, * the delivery process is supposed to express its happiness. If there is * a problem, wipe the pending deliveries for this transport. This * routine runs in response to an external event, so it does not run * while some other queue manipulation is happening. */ if (stream == 0 || qmgr_deliver_initial_reply(stream) != 0) { #if 0 whatsup = concatenate(transport->name, " mail transport unavailable", (char *) 0); qmgr_transport_throttle(transport, DSN_SIMPLE(&dsn, "4.3.0", whatsup)); myfree(whatsup); #else qmgr_transport_throttle(transport, DSN_SIMPLE(&dsn, "4.3.0", "mail transport unavailable")); #endif qmgr_defer_transport(transport, &dsn); if (stream) (void) vstream_fclose(stream); return; } /* * Find a suitable queue entry. Things may have changed since this * transport was allocated. If no suitable entry is found, * unceremoniously disconnect from the delivery process. The delivery * agent request reading routine is prepared for the queue manager to * change its mind for no apparent reason. */ if ((queue = qmgr_queue_select(transport)) == 0 || (entry = qmgr_entry_select(queue)) == 0) { (void) vstream_fclose(stream); return; } /* * Send the queue file info and recipient info to the delivery process. * If there is a problem, wipe the pending deliveries for this transport. * This routine runs in response to an external event, so it does not run * while some other queue manipulation is happening. */ if (qmgr_deliver_send_request(entry, stream) < 0) { qmgr_entry_unselect(queue, entry); #if 0 whatsup = concatenate(transport->name, " mail transport unavailable", (char *) 0); qmgr_transport_throttle(transport, DSN_SIMPLE(&dsn, "4.3.0", whatsup)); myfree(whatsup); #else qmgr_transport_throttle(transport, DSN_SIMPLE(&dsn, "4.3.0", "mail transport unavailable")); #endif qmgr_defer_transport(transport, &dsn); /* warning: entry and queue may be dangling pointers here */ (void) vstream_fclose(stream); return; } /* * If we get this far, go wait for the delivery status report. */ qmgr_deliver_concurrency++; entry->stream = stream; event_enable_read(vstream_fileno(stream), qmgr_deliver_update, (char *) entry); /* * Guard against broken systems. */ event_request_timer(qmgr_deliver_abort, (char *) entry, var_daemon_timeout); }
static void send_quit(SESSION *session) { command(session->stream, "QUIT"); event_enable_read(vstream_fileno(session->stream), quit_done, (char *) session); }
static void data_done(int unused, char *context) { SESSION *session = (SESSION *) context; RESPONSE *resp; int except; static const char *mydate; static int mypid; /* * Get response to DATA command. */ if ((except = vstream_setjmp(session->stream)) != 0) msg_fatal("%s while sending DATA command", exception_text(except)); if ((resp = response(session->stream, buffer))->code == 354) { /* see below */ ; } else if (allow_reject) { msg_warn("data rejected: %d %s", resp->code, resp->str); if (resp->code == 421 || resp->code == 521) { close_session(session); return; } send_rset(unused, context); return; } else { msg_fatal("data rejected: %d %s", resp->code, resp->str); } /* * Send basic header to keep mailers that bother to examine them happy. */ if (send_headers) { if (mydate == 0) { mydate = mail_date(time((time_t *) 0)); mypid = getpid(); } smtp_printf(session->stream, "From: <%s>", sender); smtp_printf(session->stream, "To: <%s>", recipient); smtp_printf(session->stream, "Date: %s", mydate); smtp_printf(session->stream, "Message-Id: <%04x.%04x.%04x@%s>", mypid, vstream_fileno(session->stream), message_count, var_myhostname); if (subject) smtp_printf(session->stream, "Subject: %s", subject); smtp_fputs("", 0, session->stream); } /* * Send some garbage. */ if ((except = vstream_setjmp(session->stream)) != 0) msg_fatal("%s while sending message", exception_text(except)); if (message_length == 0) { smtp_fputs("La de da de da 1.", 17, session->stream); smtp_fputs("La de da de da 2.", 17, session->stream); smtp_fputs("La de da de da 3.", 17, session->stream); smtp_fputs("La de da de da 4.", 17, session->stream); } else { /* * XXX This may cause the process to block with message content * larger than VSTREAM_BUFIZ bytes. */ smtp_fputs(message_data, message_length, session->stream); } /* * Send end of message and process the server response. */ command(session->stream, "."); /* * Update the running counter. */ if (count) { counter++; vstream_printf("%d\r", counter); vstream_fflush(VSTREAM_OUT); } /* * Prepare for the next event. */ event_enable_read(vstream_fileno(session->stream), dot_done, (char *) session); }
void qmgr_transport_alloc(QMGR_TRANSPORT *transport, QMGR_TRANSPORT_ALLOC_NOTIFY notify) { QMGR_TRANSPORT_ALLOC *alloc; /* * Sanity checks. */ if (transport->flags & QMGR_TRANSPORT_STAT_DEAD) msg_panic("qmgr_transport: dead transport: %s", transport->name); if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) msg_panic("qmgr_transport: rate-locked transport: %s", transport->name); if (transport->pending >= QMGR_TRANSPORT_MAX_PEND) msg_panic("qmgr_transport: excess allocation: %s", transport->name); /* * When this message delivery transport is rate-limited, do not select it * again before the end of a message delivery transaction. */ if (transport->xport_rate_delay > 0) transport->flags |= QMGR_TRANSPORT_STAT_RATE_LOCK; /* * Connect to the well-known port for this delivery service, and wake up * when a process announces its availability. Allow only a limited number * of delivery process allocation attempts for this transport. In case of * problems, back off. Do not hose the system when it is in trouble * already. * * Use non-blocking connect(), so that Linux won't block the queue manager * until the delivery agent calls accept(). * * When the connection to delivery agent cannot be completed, notify the * event handler so that it can throttle the transport and defer the todo * queues, just like it does when communication fails *after* connection * completion. * * Before Postfix 2.4, the event handler was not invoked after connect() * error, and mail was not deferred. Because of this, mail would be stuck * in the active queue after triggering a "connection refused" condition. */ alloc = (QMGR_TRANSPORT_ALLOC *) mymalloc(sizeof(*alloc)); alloc->transport = transport; alloc->notify = notify; transport->pending += 1; if ((alloc->stream = mail_connect(MAIL_CLASS_PRIVATE, transport->name, NON_BLOCKING)) == 0) { msg_warn("connect to transport %s/%s: %m", MAIL_CLASS_PRIVATE, transport->name); event_request_timer(qmgr_transport_event, (void *) alloc, 0); return; } #if (EVENTS_STYLE != EVENTS_STYLE_SELECT) && defined(CA_VSTREAM_CTL_DUPFD) #ifndef THRESHOLD_FD_WORKAROUND #define THRESHOLD_FD_WORKAROUND 128 #endif vstream_control(alloc->stream, CA_VSTREAM_CTL_DUPFD(THRESHOLD_FD_WORKAROUND), CA_VSTREAM_CTL_END); #endif event_enable_read(vstream_fileno(alloc->stream), qmgr_transport_event, (void *) alloc); /* * Guard against broken systems. */ event_request_timer(qmgr_transport_abort, (void *) alloc, var_daemon_timeout); }
NORETURN trigger_server_main(int argc, char **argv, TRIGGER_SERVER_FN service,...) { char *myname = "trigger_server_main"; char *root_dir = 0; char *user_name = 0; int debug_me = 0; char *service_name = basename(argv[0]); VSTREAM *stream = 0; int delay; int c; int socket_count = 1; int fd; va_list ap; MAIL_SERVER_INIT_FN pre_init = 0; MAIL_SERVER_INIT_FN post_init = 0; MAIL_SERVER_LOOP_FN loop = 0; int key; char buf[TRIGGER_BUF_SIZE]; int len; char *transport = 0; char *lock_path; VSTRING *why; int alone = 0; int zerolimit = 0; WATCHDOG *watchdog; char *oval; /* * Process environment options as early as we can. */ if (getenv(CONF_ENV_VERB)) msg_verbose = 1; if (getenv(CONF_ENV_DEBUG)) debug_me = 1; /* * Don't die when a process goes away unexpectedly. */ signal(SIGPIPE, SIG_IGN); /* * Don't die for frivolous reasons. */ #ifdef SIGXFSZ signal(SIGXFSZ, SIG_IGN); #endif /* * May need this every now and then. */ var_procname = mystrdup(basename(argv[0])); set_mail_conf_str(VAR_PROCNAME, var_procname); /* * Initialize logging and exit handler. Do the syslog first, so that its * initialization completes before we enter the optional chroot jail. */ msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); if (msg_verbose) msg_info("daemon started"); /* * Initialize from the configuration file. Allow command-line options to * override compiled-in defaults or configured parameter values. */ mail_conf_suck(); /* * Register dictionaries that use higher-level interfaces and protocols. */ mail_dict_init(); /* * Pick up policy settings from master process. Shut up error messages to * stderr, because no-one is going to see them. */ opterr = 0; while ((c = GETOPT(argc, argv, "cDi:lm:n:o:s:St:uvzZ")) > 0) { switch (c) { case 'c': root_dir = "setme"; break; case 'D': debug_me = 1; break; case 'i': mail_conf_update(VAR_MAX_IDLE, optarg); break; case 'l': alone = 1; break; case 'm': mail_conf_update(VAR_MAX_USE, optarg); break; case 'n': service_name = optarg; break; case 'o': if ((oval = split_at(optarg, '=')) == 0) oval = ""; mail_conf_update(optarg, oval); break; case 's': if ((socket_count = atoi(optarg)) <= 0) msg_fatal("invalid socket_count: %s", optarg); break; case 'S': stream = VSTREAM_IN; break; case 't': transport = optarg; break; case 'u': user_name = "setme"; break; case 'v': msg_verbose++; break; case 'z': zerolimit = 1; break; case 'Z': msg_debug++; break; default: msg_fatal("invalid option: %c", c); break; } } /* * Initialize generic parameters. */ mail_params_init(); /* * Application-specific initialization. */ va_start(ap, service); while ((key = va_arg(ap, int)) != 0) { switch (key) { case MAIL_SERVER_INT_TABLE: get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); break; case MAIL_SERVER_STR_TABLE: get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); break; case MAIL_SERVER_BOOL_TABLE: get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); break; case MAIL_SERVER_TIME_TABLE: get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); break; case MAIL_SERVER_RAW_TABLE: get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); break; case MAIL_SERVER_PRE_INIT: pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_POST_INIT: post_init = va_arg(ap, MAIL_SERVER_INIT_FN); break; case MAIL_SERVER_LOOP: loop = va_arg(ap, MAIL_SERVER_LOOP_FN); break; case MAIL_SERVER_EXIT: trigger_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); break; case MAIL_SERVER_PRE_ACCEPT: trigger_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); break; case MAIL_SERVER_IN_FLOW_DELAY: trigger_server_in_flow_delay = 1; break; case MAIL_SERVER_SOLITARY: if (!alone) msg_fatal("service %s requires a process limit of 1", service_name); break; case MAIL_SERVER_UNLIMITED: if (!zerolimit) msg_fatal("service %s requires a process limit of 0", service_name); break; default: msg_panic("%s: unknown argument type: %d", myname, key); } } va_end(ap); if (root_dir) root_dir = var_queue_dir; if (user_name) user_name = var_mail_owner; /* * If not connected to stdin, stdin must not be a terminal. */ if (stream == 0 && isatty(STDIN_FILENO)) { msg_vstream_init(var_procname, VSTREAM_ERR); msg_fatal("do not run this command by hand"); } /* * Can options be required? * * XXX Initially this code was implemented with UNIX-domain sockets, but * Solaris <= 2.5 UNIX-domain sockets misbehave hopelessly when the * client disconnects before the server has accepted the connection. * Symptom: the server accept() fails with EPIPE or EPROTO, but the * socket stays readable, so that the program goes into a wasteful loop. * * The initial fix was to use FIFOs, but those turn out to have their own * problems, witness the workarounds in the fifo_listen() routine. * Therefore we support both FIFOs and UNIX-domain sockets, so that the * user can choose whatever works best. * * Well, I give up. Solaris UNIX-domain sockets still don't work properly, * so it will have to limp along with a streams-specific alternative. */ if (stream == 0) { if (transport == 0) msg_fatal("no transport type specified"); if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) trigger_server_accept = trigger_server_accept_local; else if (strcasecmp(transport, MASTER_XPORT_NAME_FIFO) == 0) trigger_server_accept = trigger_server_accept_fifo; else msg_fatal("unsupported transport type: %s", transport); } /* * Optionally start the debugger on ourself. */ if (debug_me) debug_process(); /* * Traditionally, BSD select() can't handle multiple processes selecting * on the same socket, and wakes up every process in select(). See TCP/IP * Illustrated volume 2 page 532. We avoid select() collisions with an * external lock file. */ if (stream == 0 && !alone) { lock_path = concatenate(DEF_PID_DIR, "/", transport, ".", service_name, (char *) 0); why = vstring_alloc(1); if ((trigger_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, (struct stat *) 0, -1, -1, why)) == 0) msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); close_on_exec(vstream_fileno(trigger_server_lock), CLOSE_ON_EXEC); myfree(lock_path); vstring_free(why); } /* * Set up call-back info. */ trigger_server_service = service; trigger_server_name = service_name; trigger_server_argv = argv + optind; /* * Run pre-jail initialization. */ if (chdir(var_queue_dir) < 0) msg_fatal("chdir(\"%s\"): %m", var_queue_dir); if (pre_init) pre_init(trigger_server_name, trigger_server_argv); /* * Optionally, restrict the damage that this process can do. */ resolve_local_init(); chroot_uid(root_dir, user_name); /* * Run post-jail initialization. */ if (post_init) post_init(trigger_server_name, trigger_server_argv); /* * Are we running as a one-shot server with the client connection on * standard input? */ if (stream != 0) { if ((len = read(vstream_fileno(stream), buf, sizeof(buf))) <= 0) msg_fatal("read: %m"); service(buf, len, trigger_server_name, trigger_server_argv); vstream_fflush(stream); trigger_server_exit(); } /* * Running as a semi-resident server. Service connection requests. * Terminate when we have serviced a sufficient number of clients, when * no-one has been talking to us for a configurable amount of time, or * when the master process terminated abnormally. */ if (var_idle_limit > 0) event_request_timer(trigger_server_timeout, (char *) 0, var_idle_limit); for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { event_enable_read(fd, trigger_server_accept, CAST_INT_TO_CHAR_PTR(fd)); close_on_exec(fd, CLOSE_ON_EXEC); } event_enable_read(MASTER_STATUS_FD, trigger_server_abort, (char *) 0); close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); watchdog = watchdog_create(1000, (WATCHDOG_FN) 0, (char *) 0); /* * The event loop, at last. */ while (var_use_limit == 0 || use_count < var_use_limit) { if (trigger_server_lock != 0) { watchdog_stop(watchdog); if (myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("select lock: %m"); } watchdog_start(watchdog); delay = loop ? loop(trigger_server_name, trigger_server_argv) : -1; event_loop(delay); } trigger_server_exit(); }