build: use pkg-config for i18n
[exim.git] / src / src / exim.c
index 1f76f8f1435347ae9571af16551c9c30e1fa6391..0eb221d43b3adc13159afdc44e07142503bfb7a6 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 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 */
 /* 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 */
@@ -33,6 +33,7 @@ Also a few functions that don't naturally fit elsewhere. */
 #endif
 
 extern void init_lookup_list(void);
 #endif
 
 extern void init_lookup_list(void);
+extern void init_misc_mod_list(void);
 
 
 
 
 
 
@@ -237,7 +238,7 @@ va_end(ap);
 static void
 term_handler(int sig)
 {
 static void
 term_handler(int sig)
 {
-exit(1);
+exim_exit(EXIT_FAILURE);
 }
 
 
 }
 
 
@@ -274,22 +275,28 @@ static void
 #ifdef SA_SIGINFO
 segv_handler(int sig, siginfo_t * info, void * uctx)
 {
 #ifdef SA_SIGINFO
 segv_handler(int sig, siginfo_t * info, void * uctx)
 {
-log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (fault address: %p)", info->si_addr);
-# if defined(SEGV_MAPERR) && defined(SEGV_ACCERR) && defined(SEGV_BNDERR) && defined(SEGV_PKUERR)
-switch (info->si_code)
+if (!panic_coredump)
   {
   {
-  case SEGV_MAPERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_MAPERR"); break;
-  case SEGV_ACCERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_ACCERR"); break;
-  case SEGV_BNDERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_BNDERR"); break;
-  case SEGV_PKUERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_PKUERR"); break;
+  log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (fault address: %p)", info->si_addr);
+  # if defined(SEGV_MAPERR) && defined(SEGV_ACCERR) && defined(SEGV_BNDERR) && defined(SEGV_PKUERR)
+  switch (info->si_code)
+    {
+    case SEGV_MAPERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_MAPERR"); break;
+    case SEGV_ACCERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_ACCERR"); break;
+    case SEGV_BNDERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_BNDERR"); break;
+    case SEGV_PKUERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_PKUERR"); break;
+    }
+  # endif
   }
   }
-# endif
-if (US info->si_addr < US 4096)
+if (panic_coredump)
+  log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (deliberate trap)");
+else if (US info->si_addr < US 4096)
   log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (null pointer indirection)");
 else
   log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (maybe attempt to write to immutable memory)");
 if (process_info_len > 0)
   log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (null pointer indirection)");
 else
   log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (maybe attempt to write to immutable memory)");
 if (process_info_len > 0)
-  log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (%.*s)", process_info_len, process_info);
+  log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (%s: %.*s)",
+    process_purpose, process_info_len, process_info);
 stackdump();
 signal(SIGSEGV, SIG_DFL);
 kill(getpid(), sig);
 stackdump();
 signal(SIGSEGV, SIG_DFL);
 kill(getpid(), sig);
@@ -997,38 +1004,204 @@ if (s)
 static gstring *
 show_db_version(gstring * g)
 {
 static gstring *
 show_db_version(gstring * g)
 {
+g = string_cat(g, US"Hints DB:\n");
 #ifdef DB_VERSION_STRING
 DEBUG(D_any)
   {
 #ifdef DB_VERSION_STRING
 DEBUG(D_any)
   {
-  g = string_fmt_append(g, "Library version: BDB: Compile: %s\n", DB_VERSION_STRING);
-  g = string_fmt_append(g, "                      Runtime: %s\n",
+  g = string_fmt_append(g, " Library version: BDB: Compile: %s\n", DB_VERSION_STRING);
+  g = string_fmt_append(g, "                       Runtime: %s\n",
     db_version(NULL, NULL, NULL));
   }
 else
     db_version(NULL, NULL, NULL));
   }
 else
-  g = string_fmt_append(g, "Berkeley DB: %s\n", DB_VERSION_STRING);
+  g = string_fmt_append(g, " Berkeley DB: %s\n", DB_VERSION_STRING);
 
 #elif defined(BTREEVERSION) && defined(HASHVERSION)
 # ifdef USE_DB
 
 #elif defined(BTREEVERSION) && defined(HASHVERSION)
 # ifdef USE_DB
-  g = string_cat(g, US"Probably Berkeley DB version 1.8x (native mode)\n");
+  g = string_cat(g, US" Probably Berkeley DB version 1.8x (native mode)\n");
 # else
 # else
-  g = string_cat(g, US"Probably Berkeley DB version 1.8x (compatibility mode)\n");
+  g = string_cat(g, US" Probably Berkeley DB version 1.8x (compatibility mode)\n");
 # endif
 
 #elif defined(_DBM_RDONLY) || defined(dbm_dirfno)
 # endif
 
 #elif defined(_DBM_RDONLY) || defined(dbm_dirfno)
-g = string_cat(g, US"Probably ndbm\n");
+g = string_cat(g, US" Probably ndbm\n");
+#elif defined(USE_SQLITE)
+g = string_cat(g, US" Using sqlite3\n");
 #elif defined(USE_TDB)
 #elif defined(USE_TDB)
-g = string_cat(g, US"Using tdb\n");
+g = string_cat(g, US" Using tdb\n");
 #else
 # ifdef USE_GDBM
 #else
 # ifdef USE_GDBM
-  g = string_cat(g, US"Probably GDBM (native mode)\n");
+g = string_cat(g, US" Probably GDBM (native mode)\n");
 # else
 # else
-  g = string_cat(g, US"Probably GDBM (compatibility mode)\n");
+g = string_cat(g, US" Probably GDBM (compatibility mode)\n");
 # endif
 #endif
 return g;
 }
 
 
 # endif
 #endif
 return g;
 }
 
 
+static gstring *
+lookup_show_supported(gstring * g)
+{
+gstring * b = NULL, * d = NULL;
+
+#ifdef LOOKUP_LSEARCH
+# if LOOKUP_LSEARCH!=2
+  b = string_cat(b, US" lsearch wildlsearch nwildlsearch iplsearch");
+# else
+  d = string_cat(d, US" lsearch wildlsearch nwildlsearch iplsearch");
+# endif
+#endif
+#ifdef LOOKUP_CDB
+# if LOOKUP_CDB!=2
+  b = string_cat(b, US" cdb");
+# else
+  d = string_cat(d, US" cdb");
+# endif
+#endif
+#ifdef LOOKUP_DBM
+# if LOOKUP_DBM!=2
+  b = string_cat(b, US" dbm dbmjz dbmnz");
+# else
+  d = string_cat(d, US" dbm dbmjz dbmnz");
+# endif
+#endif
+#ifdef LOOKUP_DNSDB
+# if LOOKUP_DNSDB!=2
+  b = string_cat(b, US" dnsdb");
+# else
+  d = string_cat(d, US" dnsdb");
+# endif
+#endif
+#ifdef LOOKUP_DSEARCH
+# if LOOKUP_DSEARCH!=2
+  b = string_cat(b, US" dsearch");
+# else
+  d = string_cat(d, US" dsearch");
+# endif
+#endif
+#ifdef LOOKUP_IBASE
+# if LOOKUP_IBASE!=2
+  b = string_cat(b, US" ibase");
+# else
+  d = string_cat(d, US" ibase");
+# endif
+#endif
+#ifdef LOOKUP_JSON
+# if LOOKUP_JSON!=2
+  b = string_cat(b, US" json");
+# else
+  d = string_cat(d, US" json");
+# endif
+#endif
+#ifdef LOOKUP_LDAP
+# if LOOKUP_LDAP!=2
+  b = string_cat(b, US" ldap ldapdn ldapm");
+# else
+  d = string_cat(d, US" ldap ldapdn ldapm");
+# endif
+#endif
+#ifdef LOOKUP_LMDB
+# if LOOKUP_LMDB!=2
+  b = string_cat(b, US" lmdb");
+# else
+  d = string_cat(d, US" lmdb");
+# endif
+#endif
+#ifdef LOOKUP_MYSQL
+# if LOOKUP_MYSQL!=2
+  b = string_cat(b, US" mysql");
+# else
+  d = string_cat(d, US" mysql");
+# endif
+#endif
+#ifdef LOOKUP_NIS
+# if LOOKUP_NIS!=2
+  b = string_cat(b, US" nis nis0");
+# else
+  d = string_cat(d, US" nis nis0");
+# endif
+#endif
+#ifdef LOOKUP_NISPLUS
+# if LOOKUP_NISPLUS!=2
+  b = string_cat(b, US" nisplus");
+# else
+  d = string_cat(d, US" nisplus");
+# endif
+#endif
+#ifdef LOOKUP_ORACLE
+# if LOOKUP_ORACLE!=2
+  b = string_cat(b, US" oracle");
+# else
+  d = string_cat(d, US" oracle");
+# endif
+#endif
+#ifdef LOOKUP_PASSWD
+# if LOOKUP_PASSWD!=2
+  b = string_cat(b, US" passwd");
+# else
+  d = string_cat(d, US" passwd");
+# endif
+#endif
+#ifdef LOOKUP_PGSQL
+# if LOOKUP_PGSQL!=2
+  b = string_cat(b, US" pgsql");
+# else
+  d = string_cat(d, US" pgsql");
+# endif
+#endif
+#ifdef LOOKUP_REDIS
+# if LOOKUP_REDIS!=2
+  b = string_cat(b, US" redis");
+# else
+  d = string_cat(d, US" redis");
+# endif
+#endif
+#ifdef SUPPORT_SPF
+# if SUPPORT_SPF!=2
+  b = string_cat(b, US" spf");
+# else
+  d = string_cat(d, US" spf");
+# endif
+#endif
+#ifdef LOOKUP_SQLITE
+# if LOOKUP_SQLITE!=2
+  b = string_cat(b, US" sqlite");
+# else
+  d = string_cat(d, US" sqlite");
+# endif
+#endif
+#ifdef LOOKUP_TESTDB
+# if LOOKUP_TESTDB!=2
+  b = string_cat(b, US" testdb");
+# else
+  d = string_cat(d, US" testdb");
+# endif
+#endif
+#ifdef LOOKUP_WHOSON
+# if LOOKUP_WHOSON!=2
+  b = string_cat(b, US" whoson");
+# else
+  d = string_cat(d, US" whoson");
+# endif
+#endif
+
+if (b) g = string_fmt_append(g, "Lookups (built-in):%Y\n", b);
+if (d) g = string_fmt_append(g, "Lookups (dynamic): %Y\n", d);
+return g;
+}
+
+
+static void
+lookup_version_report_cb(uschar * name, uschar * ptr, void * ctx)
+{
+const lookup_info * li = (lookup_info *)ptr;
+gstring ** gp = ctx;
+
+if (li->version_report)
+  *gp = li->version_report(*gp);
+}
+
+
 /* This function is called for -bV/--version and for -d to output the optional
 features of the current Exim binary.
 
 /* This function is called for -bV/--version and for -d to output the optional
 features of the current Exim binary.
 
@@ -1048,6 +1221,12 @@ g = string_cat(g, US"Support for:");
 #ifdef WITH_CONTENT_SCAN
   g = string_cat(g, US" Content_Scanning");
 #endif
 #ifdef WITH_CONTENT_SCAN
   g = string_cat(g, US" Content_Scanning");
 #endif
+#ifndef DISABLE_EXIM_FILTER
+  g = string_cat(g, US" Exim_filter");
+#endif
+#ifndef DISABLE_SIEVE_FILTER
+  g = string_cat(g, US" Sieve_filter");
+#endif
 #ifdef SUPPORT_CRYPTEQ
   g = string_cat(g, US" crypteq");
 #endif
 #ifdef SUPPORT_CRYPTEQ
   g = string_cat(g, US" crypteq");
 #endif
@@ -1108,6 +1287,9 @@ g = string_cat(g, US"Support for:");
 #ifndef DISABLE_ESMTP_LIMITS
   g = string_cat(g, US" ESMTP_Limits");
 #endif
 #ifndef DISABLE_ESMTP_LIMITS
   g = string_cat(g, US" ESMTP_Limits");
 #endif
+#ifndef DISABLE_WELLKNOWN
+  g = string_cat(g, US" ESMTP_Wellknown");
+#endif
 #ifndef DISABLE_EVENT
   g = string_cat(g, US" Event");
 #endif
 #ifndef DISABLE_EVENT
   g = string_cat(g, US" Event");
 #endif
@@ -1162,66 +1344,7 @@ g = string_cat(g, US"Support for:");
 #endif
 g = string_cat(g, US"\n");
 
 #endif
 g = string_cat(g, US"\n");
 
-g = string_cat(g, US"Lookups (built-in):");
-#if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
-  g = string_cat(g, US" lsearch wildlsearch nwildlsearch iplsearch");
-#endif
-#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
-  g = string_cat(g, US" cdb");
-#endif
-#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
-  g = string_cat(g, US" dbm dbmjz dbmnz");
-#endif
-#if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
-  g = string_cat(g, US" dnsdb");
-#endif
-#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
-  g = string_cat(g, US" dsearch");
-#endif
-#if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
-  g = string_cat(g, US" ibase");
-#endif
-#if defined(LOOKUP_JSON) && LOOKUP_JSON!=2
-  g = string_cat(g, US" json");
-#endif
-#if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
-  g = string_cat(g, US" ldap ldapdn ldapm");
-#endif
-#ifdef LOOKUP_LMDB
-  g = string_cat(g, US" lmdb");
-#endif
-#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
-  g = string_cat(g, US" mysql");
-#endif
-#if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
-  g = string_cat(g, US" nis nis0");
-#endif
-#if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
-  g = string_cat(g, US" nisplus");
-#endif
-#if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
-  g = string_cat(g, US" oracle");
-#endif
-#if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
-  g = string_cat(g, US" passwd");
-#endif
-#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
-  g = string_cat(g, US" pgsql");
-#endif
-#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
-  g = string_cat(g, US" redis");
-#endif
-#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
-  g = string_cat(g, US" sqlite");
-#endif
-#if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
-  g = string_cat(g, US" testdb");
-#endif
-#if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
-  g = string_cat(g, US" whoson");
-#endif
-g = string_cat(g, US"\n");
-
+g = lookup_show_supported(g);
 g = auth_show_supported(g);
 g = route_show_supported(g);
 g = transport_show_supported(g);
 g = auth_show_supported(g);
 g = route_show_supported(g);
 g = transport_show_supported(g);
@@ -1270,7 +1393,7 @@ DEBUG(D_any)
                gnu_get_libc_version());
 #endif
 
                gnu_get_libc_version());
 #endif
 
-g = show_db_version(g);
+  g = show_db_version(g);
 
 #ifndef DISABLE_TLS
   g = tls_version_report(g);
 
 #ifndef DISABLE_TLS
   g = tls_version_report(g);
@@ -1278,19 +1401,16 @@ g = show_db_version(g);
 #ifdef SUPPORT_I18N
   g = utf8_version_report(g);
 #endif
 #ifdef SUPPORT_I18N
   g = utf8_version_report(g);
 #endif
-#ifdef SUPPORT_DMARC
-  g = dmarc_version_report(g);
-#endif
-#ifdef SUPPORT_SPF
-  g = spf_lib_version_report(g);
-#endif
 
 
-show_string(is_stdout, g);
-g = NULL;
+/*XXX do we need a "show misc-mods version-report" ?
+Currently they are output in misc_mod_add() */
+
+  show_string(is_stdout, g);
+  g = NULL;
 
 
-for (auth_info * authi = auths_available; *authi->driver_name != '\0'; ++authi)
-  if (authi->version_report)
-    g = (*authi->version_report)(g);
+  for (auth_info * ai = auths_available; ai; ai = (auth_info *)ai->drinfo.next)
+    if (ai->version_report)
+      g = (*ai->version_report)(g);
 
   /* PCRE_PRERELEASE is either defined and empty or a bare sequence of
   characters; unless it's an ancient version of PCRE in which case it
 
   /* PCRE_PRERELEASE is either defined and empty or a bare sequence of
   characters; unless it's an ancient version of PCRE in which case it
@@ -1300,27 +1420,26 @@ for (auth_info * authi = auths_available; *authi->driver_name != '\0'; ++authi)
 #endif
 #define QUOTE(X) #X
 #define EXPAND_AND_QUOTE(X) QUOTE(X)
 #endif
 #define QUOTE(X) #X
 #define EXPAND_AND_QUOTE(X) QUOTE(X)
-  {
-  uschar buf[24];
-  pcre2_config(PCRE2_CONFIG_VERSION, buf);
-  g = string_fmt_append(g, "Library version: PCRE2: Compile: %d.%d%s\n"
-              "                        Runtime: %s\n",
-          PCRE2_MAJOR, PCRE2_MINOR,
-          EXPAND_AND_QUOTE(PCRE2_PRERELEASE) "",
-          buf);
-  }
+    {
+    uschar buf[24];
+    pcre2_config(PCRE2_CONFIG_VERSION, buf);
+    g = string_fmt_append(g, "Library version: PCRE2: Compile: %d.%d%s\n"
+               "                        Runtime: %s\n",
+           PCRE2_MAJOR, PCRE2_MINOR,
+           EXPAND_AND_QUOTE(PCRE2_PRERELEASE) "",
+           buf);
+    }
 #undef QUOTE
 #undef EXPAND_AND_QUOTE
 
 #undef QUOTE
 #undef EXPAND_AND_QUOTE
 
-show_string(is_stdout, g);
-g = NULL;
+  show_string(is_stdout, g);
+  g = NULL;
 
 
-init_lookup_list();
-for (int i = 0; i < lookup_list_count; i++)
-  if (lookup_list[i]->version_report)
-    g = lookup_list[i]->version_report(g);
-show_string(is_stdout, g);
-g = NULL;
+  init_lookup_list();
+  tree_walk(lookups_tree, lookup_version_report_cb, &g);
+  show_string(is_stdout, g);
+  g = NULL;
+  init_misc_mod_list();
 
 #ifdef WHITELIST_D_MACROS
   g = string_fmt_append(g, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
 
 #ifdef WHITELIST_D_MACROS
   g = string_fmt_append(g, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
@@ -1362,8 +1481,14 @@ switch(request)
 );
     return;
   case CMDINFO_SIEVE:
 );
     return;
   case CMDINFO_SIEVE:
-    for (const uschar ** pp = exim_sieve_extension_list; *pp; ++pp)
-      fprintf(stream, "%s\n", *pp);
+    {
+    const misc_module_info * mi;
+    typedef void (*fn_t)(FILE *);
+    if ((mi = misc_mod_find(US"sieve_filter", NULL)))
+      (((fn_t *) mi->functions)[SIEVE_EXTENSIONS]) (stream);
+    else
+      fprintf(stream, "Sieve filtering not available\n");
+    }
     return;
   case CMDINFO_DSCP:
     dscp_list_to_stream(stream);
     return;
   case CMDINFO_DSCP:
     dscp_list_to_stream(stream);
@@ -1699,13 +1824,19 @@ len = Ustrlen(big_buffer);
 
 (void) macros_expand(0, &len, &dummy_macexp);
 
 
 (void) macros_expand(0, &len, &dummy_macexp);
 
+#ifdef LOOKUP_MODULE_DIR
+//mod_load_check(big_buffer);
+#endif
+
 if (isupper(big_buffer[0]))
   {
   if (macro_read_assignment(big_buffer))
     printf("Defined macro '%s'\n", mlast->name);
   }
 if (isupper(big_buffer[0]))
   {
   if (macro_read_assignment(big_buffer))
     printf("Defined macro '%s'\n", mlast->name);
   }
+else if (Ustrncmp(big_buffer, "set,t ", 6) == 0)
+  printf("%s\n", acl_standalone_setvar(big_buffer+6, TRUE));
 else if (Ustrncmp(big_buffer, "set ", 4) == 0)
 else if (Ustrncmp(big_buffer, "set ", 4) == 0)
-  printf("%s\n", acl_standalone_setvar(big_buffer+4));
+  printf("%s\n", acl_standalone_setvar(big_buffer+4, FALSE));
 else
   if ((s = expand_string(big_buffer))) printf("%s\n", CS s);
   else printf("Failed: %s\n", expand_string_message);
 else
   if ((s = expand_string(big_buffer))) printf("%s\n", CS s);
   else printf("Failed: %s\n", expand_string_message);
@@ -2337,9 +2468,9 @@ on the second character (the one after '-'), to save some effort. */
 
        /* -bh: Host checking - an IP address must follow. */
        case 'h':
 
        /* -bh: Host checking - an IP address must follow. */
        case 'h':
-         if (!*argrest || Ustrcmp(argrest, "c") == 0)
+         if (  (!*argrest || Ustrcmp(argrest, "c") == 0)
+            && ++i < argc)
            {
            {
-           if (++i >= argc) { badarg = TRUE; break; }
            sender_host_address = string_copy_taint(
                  exim_str_fail_toolong(argv[i], EXIM_IPADDR_MAX, "-bh"),
                  GET_TAINTED);
            sender_host_address = string_copy_taint(
                  exim_str_fail_toolong(argv[i], EXIM_IPADDR_MAX, "-bh"),
                  GET_TAINTED);
@@ -2347,7 +2478,8 @@ on the second character (the one after '-'), to save some effort. */
            f.host_checking_callout = *argrest == 'c';
            message_logs = FALSE;
            }
            f.host_checking_callout = *argrest == 'c';
            message_logs = FALSE;
            }
-         else badarg = TRUE;
+         else
+           badarg = TRUE;
          break;
 
        /* -bi: This option is used by sendmail to initialize *the* alias file,
          break;
 
        /* -bi: This option is used by sendmail to initialize *the* alias file,
@@ -2630,14 +2762,11 @@ on the second character (the one after '-'), to save some effort. */
               reset_point = store_mark();
               while (Ufgets(big_buffer, big_buffer_size, trust_list))
                 {
               reset_point = store_mark();
               while (Ufgets(big_buffer, big_buffer_size, trust_list))
                 {
-                uschar *start = big_buffer, *nl;
-                while (*start && isspace(*start))
-                start++;
-                if (*start != '/')
+                uschar * start = big_buffer, * nl;
+                if (Uskip_whitespace(&start) != '/')
                   continue;
                   continue;
-                nl = Ustrchr(start, '\n');
-                if (nl)
-                  *nl = 0;
+                if ((nl = Ustrchr(start, '\n')))
+                  *nl = '\0';
                 trusted_configs[nr_configs++] = string_copy(start);
                 if (nr_configs == nelem(trusted_configs))
                   break;
                 trusted_configs[nr_configs++] = string_copy(start);
                 if (nr_configs == nelem(trusted_configs))
                   break;
@@ -2696,7 +2825,7 @@ on the second character (the one after '-'), to save some effort. */
       const uschar * s = argrest;
 
       opt_D_used = TRUE;
       const uschar * s = argrest;
 
       opt_D_used = TRUE;
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
 
       if (*s < 'A' || *s > 'Z')
         exim_fail("exim: macro name set by -D must start with "
 
       if (*s < 'A' || *s > 'Z')
         exim_fail("exim: macro name set by -D must start with "
@@ -2709,11 +2838,10 @@ on the second character (the one after '-'), to save some effort. */
         }
       name[ptr] = 0;
       if (ptr == 0) { badarg = TRUE; break; }
         }
       name[ptr] = 0;
       if (ptr == 0) { badarg = TRUE; break; }
-      while (isspace(*s)) s++;
-      if (*s != 0)
+      if (Uskip_whitespace(&s))
         {
         if (*s++ != '=') { badarg = TRUE; break; }
         {
         if (*s++ != '=') { badarg = TRUE; break; }
-        while (isspace(*s)) s++;
+        Uskip_whitespace(&s);
         }
 
       for (m = macros_user; m; m = m->next)
         }
 
       for (m = macros_user; m; m = m->next)
@@ -4094,6 +4222,7 @@ is equivalent to the ability to modify a setuid binary!
 
 This needs to happen before we read the main configuration. */
 init_lookup_list();
 
 This needs to happen before we read the main configuration. */
 init_lookup_list();
+init_misc_mod_list();
 
 /*XXX this excrescence could move to the testsuite standard config setup file */
 #ifdef SUPPORT_I18N
 
 /*XXX this excrescence could move to the testsuite standard config setup file */
 #ifdef SUPPORT_I18N
@@ -4382,9 +4511,16 @@ if (perl_start_option != 0)
   opt_perl_at_start = (perl_start_option > 0);
 if (opt_perl_at_start && opt_perl_startup != NULL)
   {
   opt_perl_at_start = (perl_start_option > 0);
 if (opt_perl_at_start && opt_perl_startup != NULL)
   {
-  uschar *errstr;
+  uschar * errstr;
+  const misc_module_info * mi = misc_mod_find(US"perl", &errstr);
+  typedef uschar * (*fn_t)(uschar *);
+
+  if (!mi)
+    exim_fail("exim: error finding perl module: %s\n", errstr);
+
   DEBUG(D_any) debug_printf("Starting Perl interpreter\n");
   DEBUG(D_any) debug_printf("Starting Perl interpreter\n");
-  if ((errstr = init_perl(opt_perl_startup)))
+
+  if ((errstr = (((fn_t *) mi->functions)[PERL_STARTUP]) (opt_perl_startup)))
     exim_fail("exim: error in perl_startup code: %s\n", errstr);
   opt_perl_started = TRUE;
   }
     exim_fail("exim: error in perl_startup code: %s\n", errstr);
   opt_perl_started = TRUE;
   }
@@ -4687,12 +4823,12 @@ if (malware_test_file)
   if ((result = malware_in_file(malware_test_file)) == FAIL)
     {
     printf("No malware found.\n");
   if ((result = malware_in_file(malware_test_file)) == FAIL)
     {
     printf("No malware found.\n");
-    exit(EXIT_SUCCESS);
+    exim_exit(EXIT_SUCCESS);
     }
   if (result != OK)
     {
     printf("Malware lookup returned non-okay/fail: %d\n", result);
     }
   if (result != OK)
     {
     printf("Malware lookup returned non-okay/fail: %d\n", result);
-    exit(EXIT_FAILURE);
+    exim_exit(EXIT_FAILURE);
     }
   if (malware_name)
     printf("Malware found: %s\n", malware_name);
     }
   if (malware_name)
     printf("Malware found: %s\n", malware_name);
@@ -4701,7 +4837,7 @@ if (malware_test_file)
 #else
   printf("Malware scanning not enabled at compile time.\n");
 #endif
 #else
   printf("Malware scanning not enabled at compile time.\n");
 #endif
-  exit(EXIT_FAILURE);
+  exim_exit(EXIT_FAILURE);
   }
 
 /* Handle a request to list the delivery queue */
   }
 
 /* Handle a request to list the delivery queue */
@@ -4710,7 +4846,7 @@ if (list_queue)
   {
   set_process_info("listing the queue");
   queue_list(list_queue_option, argv + recipients_arg, argc - recipients_arg);
   {
   set_process_info("listing the queue");
   queue_list(list_queue_option, argv + recipients_arg, argc - recipients_arg);
-  exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS);
   }
 
 /* Handle a request to count the delivery queue */
   }
 
 /* Handle a request to count the delivery queue */
@@ -4719,7 +4855,7 @@ if (count_queue)
   {
   set_process_info("counting the queue");
   fprintf(stdout, "%u\n", queue_count());
   {
   set_process_info("counting the queue");
   fprintf(stdout, "%u\n", queue_count());
-  exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS);
   }
 
 /* Handle actions on specific messages, except for the force delivery and
   }
 
 /* Handle actions on specific messages, except for the force delivery and
@@ -4758,7 +4894,7 @@ if (msg_action_arg > 0 && msg_action != MSG_DELIVER && msg_action != MSG_LOAD)
 
   else if (!queue_action(argv[msg_action_arg], msg_action, argv, argc,
     recipients_arg)) yield = EXIT_FAILURE;
 
   else if (!queue_action(argv[msg_action_arg], msg_action, argv, argc,
     recipients_arg)) yield = EXIT_FAILURE;
-  exit(yield);
+  exim_exit(yield);
   }
 
 /* We used to set up here to skip reading the ACL section, on
   }
 
 /* We used to set up here to skip reading the ACL section, on
@@ -5063,6 +5199,8 @@ for (i = 0;;)
         /* If a pattern for matching the gecos field was supplied, apply
         it and then expand the name string. */
 
         /* If a pattern for matching the gecos field was supplied, apply
         it and then expand the name string. */
 
+       GET_OPTION("gecos_pattern");
+       GET_OPTION("gecos_name");
         if (gecos_pattern && gecos_name)
           {
           const pcre2_code *re;
         if (gecos_pattern && gecos_name)
           {
           const pcre2_code *re;
@@ -5108,6 +5246,7 @@ any setting of unknown_login overrides the actual name. */
 
 if (!originator_login || f.running_in_test_harness)
   {
 
 if (!originator_login || f.running_in_test_harness)
   {
+  GET_OPTION("unknown_login");
   if (unknown_login)
     {
     originator_login = expand_string(unknown_login);
   if (unknown_login)
     {
     originator_login = expand_string(unknown_login);
@@ -5158,7 +5297,7 @@ if (f.daemon_listen || f.inetd_wait_mode || is_multiple_qrun())
     (void)gettimeofday(&t0, NULL);
 # endif
     if (!tls_dropprivs_validate_require_cipher(FALSE))
     (void)gettimeofday(&t0, NULL);
 # endif
     if (!tls_dropprivs_validate_require_cipher(FALSE))
-      exit(1);
+      exim_exit(EXIT_FAILURE);
 # ifdef MEASURE_TIMING
     report_time_since(&t0, US"validate_ciphers (delta)");
 # endif
 # ifdef MEASURE_TIMING
     report_time_since(&t0, US"validate_ciphers (delta)");
 # endif
@@ -5339,6 +5478,7 @@ if (expansion_test)
 
   else if (expansion_test_message)
     {
 
   else if (expansion_test_message)
     {
+    uschar * rme = expand_string(recipients_max);
     int save_stdin = dup(0);
     int fd = Uopen(expansion_test_message, O_RDONLY, 0);
     if (fd < 0)
     int save_stdin = dup(0);
     int fd = Uopen(expansion_test_message, O_RDONLY, 0);
     if (fd < 0)
@@ -5347,6 +5487,7 @@ if (expansion_test)
     (void) dup2(fd, 0);
     filter_test = FTEST_USER;      /* Fudge to make it look like filter test */
     message_ended = END_NOTENDED;
     (void) dup2(fd, 0);
     filter_test = FTEST_USER;      /* Fudge to make it look like filter test */
     message_ended = END_NOTENDED;
+    recipients_max_expanded = atoi(CCS rme);
     read_message_body(receive_msg(extract_recipients));
     message_linecount += body_linecount;
     (void)dup2(save_stdin, 0);
     read_message_body(receive_msg(extract_recipients));
     message_linecount += body_linecount;
     (void)dup2(save_stdin, 0);
@@ -5365,8 +5506,12 @@ if (expansion_test)
   /* Expand command line items */
 
   if (recipients_arg < argc)
   /* Expand command line items */
 
   if (recipients_arg < argc)
-    while (recipients_arg < argc)
-      expansion_test_line(exim_str_fail_toolong(argv[recipients_arg++], EXIM_EMAILADDR_MAX, "recipient"));
+    {
+    config_filename = US"-be args";
+    for (config_lineno = 1; recipients_arg < argc; config_lineno++)
+      expansion_test_line(exim_str_fail_toolong(argv[recipients_arg++],
+                                             EXIM_EMAILADDR_MAX, "-be arg"));
+    }
 
   /* Read stdin */
 
 
   /* Read stdin */
 
@@ -5380,7 +5525,9 @@ if (expansion_test)
     void *dlhandle = set_readline(&fn_readline, &fn_addhist);
 #endif
 
     void *dlhandle = set_readline(&fn_readline, &fn_addhist);
 #endif
 
-    while (s = get_stdinput(fn_readline, fn_addhist))
+    config_filename = US"-be stdin";
+    for (config_lineno = 1; s = get_stdinput(fn_readline, fn_addhist);
+       config_lineno++)
       expansion_test_line(s);
 
 #ifdef USE_READLINE
       expansion_test_line(s);
 
 #ifdef USE_READLINE
@@ -5405,17 +5552,18 @@ for hosts that want to play several parts at once. We need to ensure that it is
 set for host checking, and for receiving messages. */
 
 smtp_active_hostname = primary_hostname;
 set for host checking, and for receiving messages. */
 
 smtp_active_hostname = primary_hostname;
-if (raw_active_hostname != NULL)
+GET_OPTION("smtp_active_hostname");
+if (raw_active_hostname)
   {
   {
-  uschar *nah = expand_string(raw_active_hostname);
-  if (nah == NULL)
+  uschar * nah = expand_string(raw_active_hostname);
+  if (!nah)
     {
     if (!f.expand_string_forcedfail)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand \"%s\" "
         "(smtp_active_hostname): %s", raw_active_hostname,
         expand_string_message);
     }
     {
     if (!f.expand_string_forcedfail)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand \"%s\" "
         "(smtp_active_hostname): %s", raw_active_hostname,
         expand_string_message);
     }
-  else if (nah[0] != 0) smtp_active_hostname = nah;
+  else if (nah[0]) smtp_active_hostname = nah;
   }
 
 /* Handle host checking: this facility mocks up an incoming SMTP call from a
   }
 
 /* Handle host checking: this facility mocks up an incoming SMTP call from a
@@ -5439,11 +5587,14 @@ if (host_checking)
     }
 
   /* In case the given address is a non-canonical IPv6 address, canonicalize
     }
 
   /* In case the given address is a non-canonical IPv6 address, canonicalize
-  it. The code works for both IPv4 and IPv6, as it happens. */
+  it. Use the compressed form for IPv6. */
 
   size = host_aton(sender_host_address, x);
   sender_host_address = store_get(48, GET_UNTAINTED);  /* large enough for full IPv6 */
 
   size = host_aton(sender_host_address, x);
   sender_host_address = store_get(48, GET_UNTAINTED);  /* large enough for full IPv6 */
-  (void)host_nmtoa(size, x, -1, sender_host_address, ':');
+  if (size == 1)
+    (void) host_nmtoa(size, x, -1, sender_host_address, ':');
+  else
+    (void) ipv6_nmtoa(x, sender_host_address);
 
   /* Now set up for testing */
 
 
   /* Now set up for testing */
 
@@ -5460,7 +5611,7 @@ if (host_checking)
     "**** This is not for real!\n\n",
       sender_host_address);
 
     "**** This is not for real!\n\n",
       sender_host_address);
 
-  connection_id = getpid();
+  set_connection_id();
   memset(sender_host_cache, 0, sizeof(sender_host_cache));
   if (verify_check_host(&hosts_connection_nolog) == OK)
     {
   memset(sender_host_cache, 0, sizeof(sender_host_cache));
   if (verify_check_host(&hosts_connection_nolog) == OK)
     {
@@ -5484,9 +5635,6 @@ if (host_checking)
 
       return_path = sender_address = NULL;
       dnslist_domain = dnslist_matched = NULL;
 
       return_path = sender_address = NULL;
       dnslist_domain = dnslist_matched = NULL;
-#ifndef DISABLE_DKIM
-      dkim_cur_signer = NULL;
-#endif
       acl_var_m = NULL;
       deliver_localpart_orig = NULL;
       deliver_domain_orig = NULL;
       acl_var_m = NULL;
       deliver_localpart_orig = NULL;
       deliver_domain_orig = NULL;
@@ -5649,7 +5797,7 @@ because a log line has already been written for all its failure exists
 (usually "connection refused: <reason>") and writing another one is
 unnecessary clutter. */
 
 (usually "connection refused: <reason>") and writing another one is
 unnecessary clutter. */
 
-connection_id = getpid();
+set_connection_id();
 if (smtp_input)
   {
   smtp_in = stdin;
 if (smtp_input)
   {
   smtp_in = stdin;
@@ -5673,6 +5821,7 @@ if (smtp_input)
 
 else
   {
 
 else
   {
+  GET_OPTION("message_size_limit");
   thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
   if (expand_string_message)
     if (thismessage_size_limit == -1)
   thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
   if (expand_string_message)
     if (thismessage_size_limit == -1)
@@ -5753,8 +5902,8 @@ for (BOOL more = TRUE; more; )
     int rc;
     if ((rc = smtp_setup_msg()) > 0)
       {
     int rc;
     if ((rc = smtp_setup_msg()) > 0)
       {
-      if (real_sender_address != NULL &&
-          !receive_check_set_sender(sender_address))
+      if (   real_sender_address
+         && !receive_check_set_sender(sender_address))
         {
         sender_address = raw_sender = real_sender_address;
         sender_address_unrewritten = NULL;
         {
         sender_address = raw_sender = real_sender_address;
         sender_address_unrewritten = NULL;
@@ -5765,14 +5914,18 @@ for (BOOL more = TRUE; more; )
       the very end. The result of the ACL is ignored (as for other non-SMTP
       messages). It is run for its potential side effects. */
 
       the very end. The result of the ACL is ignored (as for other non-SMTP
       messages). It is run for its potential side effects. */
 
-      if (smtp_batched_input && acl_not_smtp_start != NULL)
-        {
-        uschar *user_msg, *log_msg;
-        f.enable_dollar_recipients = TRUE;
-        (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
-          &user_msg, &log_msg);
-        f.enable_dollar_recipients = FALSE;
-        }
+      if (smtp_batched_input)
+       {
+       GET_OPTION("acl_not_smtp_start");
+       if (acl_not_smtp_start)
+         {
+         uschar * user_msg, * log_msg;
+         f.enable_dollar_recipients = TRUE;
+         (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
+           &user_msg, &log_msg);
+         f.enable_dollar_recipients = FALSE;
+         }
+       }
 
       /* Now get the data for the message */
 
 
       /* Now get the data for the message */
 
@@ -5801,10 +5954,12 @@ for (BOOL more = TRUE; more; )
 
   else
     {
 
   else
     {
-    int rcount = 0;
-    int count = argc - recipients_arg;
+    uschar * rme = expand_string(recipients_max);
+    int rcount = 0, count = argc - recipients_arg;
     const uschar ** list = argv + recipients_arg;
 
     const uschar ** list = argv + recipients_arg;
 
+    recipients_max_expanded = atoi(CCS rme);
+
     /* These options cannot be changed dynamically for non-SMTP messages */
 
     f.active_local_sender_retain = local_sender_retain;
     /* These options cannot be changed dynamically for non-SMTP messages */
 
     f.active_local_sender_retain = local_sender_retain;
@@ -5831,15 +5986,19 @@ for (BOOL more = TRUE; more; )
       while (*s)
         {
         BOOL finished = FALSE;
       while (*s)
         {
         BOOL finished = FALSE;
-        uschar *recipient;
-        uschar *ss = parse_find_address_end(s, FALSE);
+        uschar * recipient;
+        uschar * ss = parse_find_address_end(s, FALSE);
 
         if (*ss == ',') *ss = 0; else finished = TRUE;
 
         /* Check max recipients - if -t was used, these aren't recipients */
 
 
         if (*ss == ',') *ss = 0; else finished = TRUE;
 
         /* Check max recipients - if -t was used, these aren't recipients */
 
-        if (recipients_max > 0 && ++rcount > recipients_max &&
-            !extract_recipients)
+        if (  recipients_max_expanded > 0 && ++rcount > recipients_max_expanded
+          && !extract_recipients)
+         {
+         DEBUG(D_all) debug_printf("excess reipients (max %d)\n",
+           recipients_max_expanded);
+
           if (error_handling == ERRORS_STDERR)
             {
             fprintf(stderr, "exim: too many recipients\n");
           if (error_handling == ERRORS_STDERR)
             {
             fprintf(stderr, "exim: too many recipients\n");
@@ -5849,6 +6008,7 @@ for (BOOL more = TRUE; more; )
             return
               moan_to_sender(ERRMESS_TOOMANYRECIP, NULL, NULL, stdin, TRUE)?
                 errors_sender_rc : EXIT_FAILURE;
             return
               moan_to_sender(ERRMESS_TOOMANYRECIP, NULL, NULL, stdin, TRUE)?
                 errors_sender_rc : EXIT_FAILURE;
+         }
 
 #ifdef SUPPORT_I18N
        {
 
 #ifdef SUPPORT_I18N
        {
@@ -5873,6 +6033,10 @@ for (BOOL more = TRUE; more; )
           }
 
         if (!recipient)
           }
 
         if (!recipient)
+         {
+         DEBUG(D_all) debug_printf("bad recipient address \"%s\": %s\n",
+           string_printing(list[i]), errmess);
+
           if (error_handling == ERRORS_STDERR)
             {
             fprintf(stderr, "exim: bad recipient address \"%s\": %s\n",
           if (error_handling == ERRORS_STDERR)
             {
             fprintf(stderr, "exim: bad recipient address \"%s\": %s\n",
@@ -5889,11 +6053,12 @@ for (BOOL more = TRUE; more; )
               moan_to_sender(ERRMESS_BADARGADDRESS, &eblock, NULL, stdin, TRUE)?
                 errors_sender_rc : EXIT_FAILURE;
             }
               moan_to_sender(ERRMESS_BADARGADDRESS, &eblock, NULL, stdin, TRUE)?
                 errors_sender_rc : EXIT_FAILURE;
             }
+         }
 
         receive_add_recipient(string_copy_taint(recipient, GET_TAINTED), -1);
         s = ss;
         if (!finished)
 
         receive_add_recipient(string_copy_taint(recipient, GET_TAINTED), -1);
         s = ss;
         if (!finished)
-          while (*(++s) != 0 && (*s == ',' || isspace(*s)));
+          while (*++s && (*s == ',' || isspace(*s)));
         }
       }
 
         }
       }
 
@@ -5914,9 +6079,10 @@ for (BOOL more = TRUE; more; )
     ignored; rejecting here would just add complication, and it can just as
     well be done later. Allow $recipients to be visible in the ACL. */
 
     ignored; rejecting here would just add complication, and it can just as
     well be done later. Allow $recipients to be visible in the ACL. */
 
+    GET_OPTION("acl_not_smtp_start");
     if (acl_not_smtp_start)
       {
     if (acl_not_smtp_start)
       {
-      uschar *user_msg, *log_msg;
+      uschar * user_msg, * log_msg;
       f.enable_dollar_recipients = TRUE;
       (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
         &user_msg, &log_msg);
       f.enable_dollar_recipients = TRUE;
       (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
         &user_msg, &log_msg);
@@ -6167,3 +6333,5 @@ return 0;                  /* To stop compiler warning */
 
 
 /* End of exim.c */
 
 
 /* End of exim.c */
+/* vi: aw ai sw=2
+*/