X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/1e835086d1592bdfbcd8577133965b78470840ac..HEAD:/src/src/daemon.c diff --git a/src/src/daemon.c b/src/src/daemon.c index b0533c28f..20e6bc05d 100644 --- a/src/src/daemon.c +++ b/src/src/daemon.c @@ -2,8 +2,8 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) The Exim Maintainers 2020 - 2022 */ -/* Copyright (c) University of Cambridge 1995 - 2023 */ +/* Copyright (c) The Exim Maintainers 2020 - 2024 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* SPDX-License-Identifier: GPL-2.0-or-later */ @@ -128,7 +128,7 @@ never_error(uschar *log_msg, uschar *smtp_msg, int was_errno) uschar *emsg = was_errno <= 0 ? US"" : string_sprintf(": %s", strerror(was_errno)); log_write(0, LOG_MAIN|LOG_PANIC, "%s%s", log_msg, emsg); -if (smtp_out) smtp_printf("421 %s\r\n", FALSE, smtp_msg); +if (smtp_out) smtp_printf("421 %s\r\n", SP_NO_MORE, smtp_msg); } @@ -181,7 +181,7 @@ Returns: nothing */ static void -handle_smtp_call(struct pollfd *fd_polls, int listen_socket_count, +handle_smtp_call(struct pollfd * fd_polls, int listen_socket_count, int accept_socket, struct sockaddr *accepted) { pid_t pid; @@ -233,7 +233,7 @@ if (getsockname(accept_socket, (struct sockaddr *)(&interface_sockaddr), { log_write(0, LOG_MAIN | ((errno == ECONNRESET)? 0 : LOG_PANIC), "getsockname() failed: %s", strerror(errno)); - smtp_printf("421 Local problem: getsockname() failed; please try again later\r\n", FALSE); + smtp_printf("421 Local problem: getsockname() failed; please try again later\r\n", SP_NO_MORE); goto ERROR_RETURN; } @@ -254,8 +254,6 @@ if (LOGGING(incoming_interface)) whofrom = string_fmt_append(whofrom, " I=[%s]:%d", interface_address, interface_port); -(void) string_from_gstring(whofrom); /* Terminate the newly-built string */ - /* Check maximum number of connections. We do not check for reserved connections or unacceptable hosts here. That is done in the subprocess because it might take some time. */ @@ -265,10 +263,10 @@ if (smtp_accept_max > 0 && smtp_accept_count >= smtp_accept_max) DEBUG(D_any) debug_printf("rejecting SMTP connection: count=%d max=%d\n", smtp_accept_count, smtp_accept_max); smtp_printf("421 Too many concurrent SMTP connections; " - "please try again later.\r\n", FALSE); + "please try again later.\r\n", SP_NO_MORE); log_write(L_connection_reject, - LOG_MAIN, "Connection from %s refused: too many connections", - whofrom->s); + LOG_MAIN, "Connection from %Y refused: too many connections", + whofrom); goto ERROR_RETURN; } @@ -284,10 +282,10 @@ if (smtp_load_reserve >= 0) { DEBUG(D_any) debug_printf("rejecting SMTP connection: load average = %.2f\n", (double)load_average/1000.0); - smtp_printf("421 Too much load; please try again later.\r\n", FALSE); + smtp_printf("421 Too much load; please try again later.\r\n", SP_NO_MORE); log_write(L_connection_reject, - LOG_MAIN, "Connection from %s refused: load average = %.2f", - whofrom->s, (double)load_average/1000.0); + LOG_MAIN, "Connection from %Y refused: load average = %.2f", + whofrom, (double)load_average/1000.0); goto ERROR_RETURN; } } @@ -300,32 +298,34 @@ to provide host-specific limits according to $sender_host address, but because this is in the daemon mainline, only fast expansions (such as inline address checks) should be used. The documentation is full of warnings. */ +GET_OPTION("smtp_accept_max_per_host"); if (smtp_accept_max_per_host) { - uschar *expanded = expand_string(smtp_accept_max_per_host); + uschar * expanded = expand_string(smtp_accept_max_per_host); if (!expanded) { if (!f.expand_string_forcedfail) log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host " - "failed for %s: %s", whofrom->s, expand_string_message); + "failed for %Y: %s", whofrom, expand_string_message); } /* For speed, interpret a decimal number inline here */ else { - uschar *s = expanded; + uschar * s = expanded; while (isdigit(*s)) max_for_this_host = max_for_this_host * 10 + *s++ - '0'; if (*s) log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host " - "for %s contains non-digit: %s", whofrom->s, expanded); + "for %Y contains non-digit: %s", whofrom, expanded); } } -/* If we have fewer connections than max_for_this_host, we can skip the tedious -per host_address checks. Note that at this stage smtp_accept_count contains the -count of *other* connections, not including this one. */ +/* If we have fewer total connections than max_for_this_host, we can skip the +tedious per host_address checks. Note that at this stage smtp_accept_count +contains the count of *other* connections, not including this one. */ -if (max_for_this_host > 0 && smtp_accept_count >= max_for_this_host) +if ( smtp_slots + && max_for_this_host > 0 && smtp_accept_count >= max_for_this_host) { int host_accept_count = 0; int other_host_count = 0; /* keep a count of non matches to optimise */ @@ -353,40 +353,17 @@ if (max_for_this_host > 0 && smtp_accept_count >= max_for_this_host) "IP address: count=%d max=%d\n", host_accept_count, max_for_this_host); smtp_printf("421 Too many concurrent SMTP connections " - "from this IP address; please try again later.\r\n", FALSE); + "from this IP address; please try again later.\r\n", SP_NO_MORE); log_write(L_connection_reject, - LOG_MAIN, "Connection from %s refused: too many connections " - "from that IP address", whofrom->s); + LOG_MAIN, "Connection from %Y refused: too many connections " + "from that IP address", whofrom); search_tidyup(); goto ERROR_RETURN; } } -/* OK, the connection count checks have been passed. Before we can fork the -accepting process, we must first log the connection if requested. This logging -used to happen in the subprocess, but doing that means that the value of -smtp_accept_count can be out of step by the time it is logged. So we have to do -the logging here and accept the performance cost. Note that smtp_accept_count -hasn't yet been incremented to take account of this connection. - -In order to minimize the cost (because this is going to happen for every -connection), do a preliminary selector test here. This saves ploughing through -the generalized logging code each time when the selector is false. If the -selector is set, check whether the host is on the list for logging. If not, -arrange to unset the selector in the subprocess. */ - -if (LOGGING(smtp_connection)) - { - uschar *list = hosts_connection_nolog; - memset(sender_host_cache, 0, sizeof(sender_host_cache)); - if (list && verify_check_host(&list) == OK) - save_log_selector &= ~L_smtp_connection; - else - log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %s " - "(TCP/IP connection count = %d)", whofrom->s, smtp_accept_count + 1); - } - -/* Now we can fork the accepting process; do a lookup tidy, just in case any +/* OK, the connection count checks have been passed. +Now we can fork the accepting process; do a lookup tidy, just in case any expansion above did a lookup. */ search_tidyup(); @@ -406,6 +383,34 @@ if (pid == 0) #endif smtp_accept_count++; /* So that it includes this process */ + set_connection_id(); + + /* Log the connection if requested. + In order to minimize the cost (because this is going to happen for every + connection), do a preliminary selector test here. This saves ploughing through + the generalized logging code each time when the selector is false. If the + selector is set, check whether the host is on the list for logging. If not, + arrange to unset the selector in the subprocess. + + jgh 2023/08/08 :- moved this logging in from the parent process, just + pre-fork. There was a claim back from 4.21 (when it was moved from + smtp_start_session()) that smtp_accept_count could have become out-of-date by + the time the child could log it, and I can't see how that could happen. */ + + if (LOGGING(smtp_connection)) + { + uschar * list = hosts_connection_nolog; + memset(sender_host_cache, 0, sizeof(sender_host_cache)); + if (list && verify_check_host(&list) == OK) + save_log_selector &= ~L_smtp_connection; + else if (LOGGING(connection_id)) + log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %Y " + "Ci=%s (TCP/IP connection count = %d)", + whofrom, connection_id, smtp_accept_count); + else + log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %Y " + "(TCP/IP connection count = %d)", whofrom, smtp_accept_count); + } /* If the listen backlog was over the monitoring level, log it. */ @@ -432,6 +437,7 @@ if (pid == 0) likely what it depends on.) */ smtp_active_hostname = primary_hostname; + GET_OPTION("smtp_active_hostname"); if (raw_active_hostname) { uschar * nah = expand_string(raw_active_hostname); @@ -443,7 +449,7 @@ if (pid == 0) "(smtp_active_hostname): %s", raw_active_hostname, expand_string_message); smtp_printf("421 Local configuration error; " - "please try again later.\r\n", FALSE); + "please try again later.\r\n", SP_NO_MORE); mac_smtp_fflush(); search_tidyup(); exim_underbar_exit(EXIT_FAILURE); @@ -564,7 +570,7 @@ if (pid == 0) smtp_log_no_mail(); /* Log no mail if configured */ exim_underbar_exit(EXIT_SUCCESS); } - if (message_id[0] == 0) continue; /* No message was accepted */ + if (!message_id[0]) continue; /* No message was accepted */ } else /* bad smtp_setup_msg() */ { @@ -748,7 +754,7 @@ remember the pid for ticking off when the child completes. */ if (pid < 0) never_error(US"daemon: accept process fork failed", US"Fork failed", errno); -else +else if (smtp_slots) { for (int i = 0; i < smtp_accept_max; ++i) if (smtp_slots[i].pid <= 0) @@ -892,7 +898,7 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { DEBUG(D_any) { - debug_printf("child %d ended: status=0x%x\n", (int)pid, status); + debug_printf("child %ld ended: status=0x%x\n", (long)pid, status); #ifdef WCOREDUMP if (WIFEXITED(status)) debug_printf(" normal exit, %d\n", WEXITSTATUS(status)); @@ -908,15 +914,16 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) if (smtp_slots) { int i; - for (i = 0; i < smtp_accept_max; i++) - if (smtp_slots[i].pid == pid) + smtp_slot * sp; + for (i = 0, sp = smtp_slots; i < smtp_accept_max; i++, sp++) + if (sp->pid == pid) { - if (smtp_slots[i].host_address) - store_free(smtp_slots[i].host_address); - smtp_slots[i] = empty_smtp_slot; + if (sp->host_address) + store_free(sp->host_address); + *sp = empty_smtp_slot; if (--smtp_accept_count < 0) smtp_accept_count = 0; DEBUG(D_any) debug_printf("%d SMTP accept process%s now running\n", - smtp_accept_count, (smtp_accept_count == 1)? "" : "es"); + smtp_accept_count, smtp_accept_count == 1 ? "" : "es"); break; } if (i < smtp_accept_max) continue; /* Found an accepting process */ @@ -979,7 +986,7 @@ static BOOL operate_on_pid_file(const enum pid_op operation, const pid_t pid) { char pid_line[sizeof(int) * 3 + 2]; -const int pid_len = snprintf(pid_line, sizeof(pid_line), "%d\n", (int)pid); +const int pid_len = snprintf(pid_line, sizeof(pid_line), "%ld\n", (long)pid); BOOL lines_match = FALSE; uschar * path, * base, * dir; @@ -1109,7 +1116,7 @@ since we may require privs for the containing directory */ static void daemon_die(void) { -int pid; +pid_t pid; DEBUG(D_any) debug_printf("SIGTERM/SIGINT seen\n"); #if !defined(DISABLE_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)) @@ -1163,6 +1170,7 @@ return offsetof(struct sockaddr_un, sun_path) ssize_t daemon_notifier_sockname(struct sockaddr_un * sup) { +GET_OPTION("notifier_socket"); #ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS sup->sun_path[0] = 0; /* Abstract local socket addr - Linux-specific? */ return offsetof(struct sockaddr_un, sun_path) + 1 @@ -1256,10 +1264,9 @@ static const uschar * queuerun_msg_qname; /* The notifier socket has something to read. Pull the message from it, decode and do the action. +*/ -Return TRUE if a sigalrm should be emulated */ - -static BOOL +static void daemon_notification(void) { uschar buf[256], cbuf[256]; @@ -1275,8 +1282,8 @@ struct msghdr msg = { .msg_name = &sa_un, ssize_t sz; buf[sizeof(buf)-1] = 0; -if ((sz = recvmsg(daemon_notifier_fd, &msg, 0)) <= 0) return FALSE; -if (sz >= sizeof(buf)) return FALSE; +if ((sz = recvmsg(daemon_notifier_fd, &msg, 0)) <= 0) return; +if (sz >= sizeof(buf)) return; #ifdef notdef debug_printf("addrlen %d\n", msg.msg_namelen); @@ -1317,8 +1324,8 @@ for (struct cmsghdr * cp = CMSG_FIRSTHDR(&msg); struct ucred * cr = (struct ucred *) CMSG_DATA(cp); if (cr->uid && cr->uid != exim_uid) { - DEBUG(D_queue_run) debug_printf("%s: sender creds pid %d uid %d gid %d\n", - __FUNCTION__, (int)cr->pid, (int)cr->uid, (int)cr->gid); + DEBUG(D_queue_run) debug_printf("%s: sender creds pid %ld uid %d gid %d\n", + __FUNCTION__, (long)cr->pid, (int)cr->uid, (int)cr->gid); } # elif defined(LOCAL_CREDS) /* BSD-ish */ struct sockcred * cr = (struct sockcred *) CMSG_DATA(cp); @@ -1344,9 +1351,12 @@ switch (buf[0]) memcpy(queuerun_msgid, buf+1, MESSAGE_ID_LENGTH+1); for (qrunner * q = qrunners; q; q = q->next) - if (Ustrcmp(q->name, buf+1+MESSAGE_ID_LENGTH+1) == 0) + if (q->name + ? Ustrcmp(q->name, buf+1+MESSAGE_ID_LENGTH+1) == 0 + : !buf[1+MESSAGE_ID_LENGTH+1] + ) { queuerun_msg_qname = q->name; break; } - return TRUE; + return; #endif case NOTIFY_QUEUE_SIZE_REQ: @@ -1368,7 +1378,7 @@ switch (buf[0]) regex_at_daemon(buf); break; } -return FALSE; +return; } @@ -1427,7 +1437,7 @@ for (qrunner * q = qrunners, * next; q; q = next) if (sorted) { qrunner ** p = &sorted; - for (qrunner * qq; qq = *p; p = &(qq->next)) + for (qrunner * qq; qq = *p; p = &qq->next) if ( q->next_tick < qq->next_tick || q->next_tick == qq->next_tick && q->interval < qq->interval ) @@ -1446,6 +1456,13 @@ qrunners = sorted; return qrunners ? qrunners->next_tick - time(NULL) : 0; } +/* See if we can do a queue run. If policy limit permit, kick one off. +If both notification and timer events are present, handle the former +and leave the timer outstanding. + +Return the number of seconds until the next due runner. +*/ + static int daemon_qrun(int local_queue_run_max, struct pollfd * fd_polls, int listen_socket_count) { @@ -1459,13 +1476,16 @@ DEBUG(D_any) debug_printf("%s received\n", enough queue runners on the go. If we are not running as root, a re-exec is required. In the calling process, restart the alamr timer for the next run. */ -if (is_multiple_qrun()) +if (is_multiple_qrun()) /* we are managing periodic runs */ if (local_queue_run_max <= 0 || queue_run_count < local_queue_run_max) { qrunner * q = NULL; #ifndef DISABLE_QUEUE_RAMP - if (*queuerun_msgid) /* See if we can start another runner for this queue */ + /* If this is a triggered run for a specific message, see if we can start + another runner for this queue. */ + + if (*queuerun_msgid) { for (qrunner * qq = qrunners; qq; qq = qq->next) if (qq->name == queuerun_msg_qname) @@ -1476,13 +1496,13 @@ if (is_multiple_qrun()) } else #endif - /* In order of run priority, find the first queue for which we can start - a runner */ + /* Normal periodic run: in order of run priority, find the first queue + for which we can start a runner */ for (q = qrunners; q; q = q->next) if (q->run_count < q->run_max) break; - if (q) + if (q) /* found a queue to run */ { pid_t pid; @@ -1614,19 +1634,23 @@ if (is_multiple_qrun()) } } -sigalrm_seen = FALSE; +/* The queue run has been initiated (unless we were already running enough) */ + #ifndef DISABLE_QUEUE_RAMP -if (*queuerun_msgid) /* it was a fast-ramp kick */ +if (*queuerun_msgid) /* it was a fast-ramp kick; dealt with */ *queuerun_msgid = 0; else /* periodic or one-time queue run */ #endif - { /* Impose a minimum 1s tick, even when a run was outstanding */ + /* Set up next timer callback. Impose a minimum 1s tick, + even when a run was outstanding */ + { int interval = next_qrunner_interval(); if (interval <= 0) interval = 1; + sigalrm_seen = FALSE; if (qrunners) /* there are still periodic qrunners */ { - ALARM(interval); + ALARM(interval); /* set up next qrun tick */ return interval; } } @@ -1636,7 +1660,7 @@ return 0; -const uschar * +static const uschar * describe_queue_runners(void) { gstring * g = NULL; @@ -1646,6 +1670,7 @@ if (!is_multiple_qrun()) return US"no queue runs"; for (qrunner * q = qrunners; q; q = q->next) { g = string_catn(g, US"-q", 2); + if (q->queue_2stage) g = string_catn(g, US"q", 1); if (q->name) g = string_append(g, 3, US"G", q->name, US"/"); g = string_cat(g, readconf_printtime(q->interval)); g = string_catn(g, US" ", 1); @@ -1688,15 +1713,15 @@ int listen_socket_count = 0, poll_fd_count; ip_address_item * addresses = NULL; time_t last_connection_time = (time_t)0; int local_queue_run_max = 0; -BOOL queue_run_max_has_dollar; if (is_multiple_qrun()) - + { /* Nuber of runner-tracking structs needed: If the option queue_run_max has no expandable elements then it is the overall maximum; else we assume it depends on the queue name, and add them up to get the maximum. Evaluate both that and the individual limits. */ + GET_OPTION("queue_run_max"); if (Ustrchr(queue_run_max, '$') != NULL) { for (qrunner * q = qrunners; q; q = q->next) @@ -1713,6 +1738,7 @@ if (is_multiple_qrun()) for (qrunner * q = qrunners; q; q = q->next) q->run_max = local_queue_run_max; } + } process_purpose = US"daemon"; @@ -1950,16 +1976,17 @@ if (f.daemon_listen && !f.inetd_wait_mode) tls_in.on_connect_ports = NULL; sep = 0; while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) - { - if (!isdigit(*s)) + if (isdigit(*s)) + g = string_append_listele(g, ':', s); + else { struct servent * smtp_service = getservbyname(CS s, "tcp"); if (!smtp_service) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "TCP port \"%s\" not found", s); - s = string_sprintf("%d", (int)ntohs(smtp_service->s_port)); + g = string_append_listele_fmt(g, ':', FALSE, "%d", + (int)ntohs(smtp_service->s_port)); } - g = string_append_listele(g, ':', s); - } + if (g) tls_in.on_connect_ports = g->s; break; @@ -2134,7 +2161,7 @@ if (f.background_daemon) pid_t pid = exim_fork(US"daemon"); if (pid < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "fork() failed when starting daemon: %s", strerror(errno)); - if (pid > 0) exit(EXIT_SUCCESS); /* in parent process, just exit */ + if (pid > 0) exim_exit(EXIT_SUCCESS); /* in parent process, just exit */ (void)setsid(); /* release controlling terminal */ f.daemon_listen = daemon_listen; } @@ -2409,7 +2436,7 @@ if (f.inetd_wait_mode) sprintf(CS p, "with no wait timeout"); log_write(0, LOG_MAIN, - "exim %s daemon started: pid=%d, launched with listening socket, %s", + "exim %s daemon started: pid=%ld, launched with listening socket, %s", version_string, getpid(), big_buffer); set_process_info("daemon(%s): pre-listening socket", version_string); @@ -2515,7 +2542,7 @@ else if (f.daemon_listen) } log_write(0, LOG_MAIN, - "exim %s daemon started: pid=%d, %s, listening for %s", + "exim %s daemon started: pid=%ld, %s, listening for %s", version_string, getpid(), qinfo, big_buffer); set_process_info("daemon(%s): %s, listening for %s", version_string, qinfo, big_buffer); @@ -2525,7 +2552,7 @@ else /* no listening sockets, only queue-runs */ { const uschar * s = describe_queue_runners(); log_write(0, LOG_MAIN, - "exim %s daemon started: pid=%d, %s, not listening for SMTP", + "exim %s daemon started: pid=%ld, %s, not listening for SMTP", version_string, getpid(), s); set_process_info("daemon(%s): %s, not listening", version_string, s); } @@ -2536,25 +2563,9 @@ else /* no listening sockets, only queue-runs */ dns_pattern_init(); smtp_deliver_init(); /* Used for callouts */ -#ifndef DISABLE_DKIM - { -# ifdef MEASURE_TIMING - struct timeval t0; - gettimeofday(&t0, NULL); -# endif - dkim_exim_init(); -# ifdef MEASURE_TIMING - report_time_since(&t0, US"dkim_exim_init (delta)"); -# endif - } -#endif - #ifdef WITH_CONTENT_SCAN malware_init(); #endif -#ifdef SUPPORT_SPF -spf_init(); -#endif #ifndef DISABLE_TLS tls_daemon_init(); #endif @@ -2608,7 +2619,7 @@ for (;;) The other option is that we have an inetd wait timeout specified to -bw. */ - if (sigalrm_seen) + if (sigalrm_seen || *queuerun_msgid) if (inetd_wait_timeout > 0) daemon_inetd_wtimeout(last_connection_time); /* Might not return */ else @@ -2637,7 +2648,9 @@ for (;;) select() was interrupted so that we reap the child. This might still leave a small window when a SIGCHLD could get lost. However, since we use SIGCHLD only to do the reaping more quickly, it shouldn't result in anything other - than a delay until something else causes a wake-up. */ + than a delay until something else causes a wake-up. + For the normal case, wait for either a pollable fd (eg. new connection) or + or a SIGALRM (for a periodic queue run) */ if (sigchld_seen) { @@ -2702,10 +2715,13 @@ for (;;) break; /* to top of daemon loop */ } #endif + /* Handle the daemon-notifier socket. If it was a fast-ramp + notification then queuerun_msgid will have a nonzerolength string. */ + if (dnotify_poll && dnotify_poll->revents & POLLIN) { dnotify_poll->revents = 0; - sigalrm_seen = daemon_notification(); + daemon_notification(); break; /* to top of daemon loop */ } for (struct pollfd * p = fd_polls; p < fd_polls + listen_socket_count; @@ -2841,7 +2857,7 @@ for (;;) if (sighup_seen) { - log_write(0, LOG_MAIN, "pid %d: SIGHUP received: re-exec daemon", + log_write(0, LOG_MAIN, "pid %ld: SIGHUP received: re-exec daemon", getpid()); close_daemon_sockets(daemon_notifier_fd, fd_polls, listen_socket_count); unlink_notifier_socket(); @@ -2850,7 +2866,7 @@ for (;;) sighup_argv[0] = exim_path; exim_nullstd(); execv(CS exim_path, (char *const *)sighup_argv); - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "pid %d: exec of %s failed: %s", + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "pid %ld: exec of %s failed: %s", getpid(), exim_path, strerror(errno)); log_close_all(); }