Fix dmbjz on sqlite
authorJeremy Harris <jgh146exb@wizmail.org>
Sat, 27 Jul 2024 13:17:12 +0000 (14:17 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Sat, 27 Jul 2024 13:17:12 +0000 (14:17 +0100)
doc/doc-txt/ChangeLog
src/OS/Makefile-Base
src/src/dbfn.c
src/src/hintsdb/hints_sqlite.h
src/src/string.c
src/src/xclient.c
src/src/xtextencode.c
test/confs/0195
test/scripts/0000-Basic/0195

index 2a7a70c473aa226cf9406a91c6d345d91c0e984c..7a4f1757e3d2a16bad3272de8d1e227a6dc560b6 100644 (file)
@@ -22,6 +22,11 @@ JH/03 With dkim_verify_minimal, avoid calling the DKIM ACL after the first
 JH/04 Remove the docs and support scripts dealing with conversion of Exim
       version 3 installations.
 
+JH/05 Fix hintsdb support for dbmjz when compiled using sqlite3. Previously
+      the backend support assumed keys would be simple C strings, but dbmjz
+      uses keys with embedded NUL bytes.  The builtin hintsdb use is unaffected,
+      but installations using dbmjz will need to rebuild those DBs.
+
 Exim version 4.98
 -----------------
 
index 4df9451f80e298a01b6d3bed7cb4b1d901d9515b..f494249bfcea86328bfe4eb5084033a068ed075d 100644 (file)
@@ -544,7 +544,7 @@ exim:   buildlookups buildauths pdkim/pdkim.a \
 
 # The utility for dumping the contents of an exim database
 
-OBJ_DUMPDB = exim_dumpdb.o util-os.o util-store.o
+OBJ_DUMPDB = exim_dumpdb.o util-os.o util-store.o util-xtextencode.o
 
 exim_dumpdb: $(OBJ_DUMPDB)
        @echo "$(LNCC) -o exim_dumpdb"
@@ -559,7 +559,7 @@ exim_dumpdb: $(OBJ_DUMPDB)
 
 # The utility for interrogating/fixing the contents of an exim database
 
-OBJ_FIXDB = exim_fixdb.o util-os.o util-store.o util-md5.o
+OBJ_FIXDB = exim_fixdb.o util-os.o util-store.o util-md5.o util-xtextencode.o
 
 exim_fixdb:  $(OBJ_FIXDB)
        @echo "$(LNCC) -o exim_fixdb"
@@ -574,11 +574,12 @@ exim_fixdb:  $(OBJ_FIXDB)
 
 # The utility for tidying the contents of an exim database
 
-OBJ_TIDYDB = exim_tidydb.o util-os.o util-store.o
+OBJ_TIDYDB = exim_tidydb.o util-os.o util-store.o util-xtextencode.o
 
 exim_tidydb: $(OBJ_TIDYDB)
        @echo "$(LNCC) -o exim_tidydb"
-       $(FE)$(LNCC) $(CFLAGS) $(INCLUDE) -o exim_tidydb $(LFLAGS) $(OBJ_TIDYDB) \
+       $(FE)$(LNCC) $(CFLAGS) $(INCLUDE) -o exim_tidydb $(LFLAGS) \
+         $(OBJ_TIDYDB) \
          $(LIBS) $(EXTRALIBS) $(DBMLIB)
        @if [ x"$(STRIP_COMMAND)" != x"" ]; then \
          echo $(STRIP_COMMAND) exim_tidydb; \
@@ -589,9 +590,12 @@ exim_tidydb: $(OBJ_TIDYDB)
 
 # The utility for building dbm files
 
-exim_dbmbuild: exim_dbmbuild.o
+OBJ_DBMBUILD = exim_dbmbuild.o util-xtextencode.o
+
+exim_dbmbuild: $(OBJ_DBMBUILD)
        @echo "$(LNCC) -o exim_dbmbuild"
-       $(FE)$(LNCC) $(CFLAGS) $(INCLUDE) -o exim_dbmbuild $(LFLAGS) exim_dbmbuild.o \
+       $(FE)$(LNCC) $(CFLAGS) $(INCLUDE) -o exim_dbmbuild $(LFLAGS) \
+         $(OBJ_DBMBUILD) \
          $(LIBS) $(EXTRALIBS) $(DBMLIB)
        @if [ x"$(STRIP_COMMAND)" != x"" ]; then \
          echo $(STRIP_COMMAND) exim_dbmbuild; \
@@ -636,6 +640,7 @@ OBJ_MONBIN = util-host_address.o \
             util-string.o \
             util-tod.o \
             util-tree.o \
+            util-xtextencode.o \
             $(MONBIN)
 
 eximon.bin: $(EXIMON_EDITME) eximon $(OBJ_MONBIN) ../exim_monitor/em_version.c \
@@ -794,6 +799,10 @@ util-tree.o:   $(HDRS) tree.c
        @echo "$(CC) -DCOMPILE_UTILITY tree.c"
        $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY -o util-tree.o tree.c
 
+util-xtextencode.o:   $(HDRS) xtextencode.c
+       @echo "$(CC) -DCOMPILE_UTILITY xtextencode.c"
+       $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY -o util-xtextencode.o xtextencode.c
+
 util-os.o:       $(HDRS) os.c
        @echo "$(CC) -DCOMPILE_UTILITY os.c"
        $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) \
index b9eef503fc7f1c149366ff1fde64371101710157..1d24769dbf20f6b202f5f7b9966c30dd559698cb 100644 (file)
@@ -600,7 +600,7 @@ yield = exim_dbscan(dbblock->dbptr, &key_datum, &value_datum, start, *cursor)
 if (!yield) exim_dbdelete_cursor(*cursor);
 return yield;
 }
-#endif
+#endif /*notdef*/
 
 
 
index d90c2b878349d5ac52f2f3b85c2e9fa9083030ef..6ef2145218f7cd6f7a7584988971462968b9f84b 100644 (file)
@@ -25,7 +25,13 @@ backend provider. */
 /* Some text for messages */
 # define EXIM_DBTYPE "sqlite3"
 
-# /* Access functions */
+/* Utility functionss */
+
+extern uschar *xtextencode(uschar *, int);
+extern int xtextdecode(uschar *, uschar**);
+
+
+/* Access functions */
 
 static inline BOOL
 exim_lockfile_needed(void)
@@ -86,15 +92,17 @@ sqlite3_stmt * statement;
 int ret;
 
 res->len = (size_t) -1;
-/* fprintf(stderr, "exim_dbget__(%s)\n", s); */
+/* DEBUG(D_hints_lookup) debug_printf_indent("exim_dbget__(%s)\n", s); */
 if ((ret = sqlite3_prepare_v2(dbp, CCS s, -1, &statement, NULL)) != SQLITE_OK)
   {
-/* fprintf(stderr, "prepare fail: %s\n", sqlite3_errmsg(dbp)); */
+  DEBUG(D_hints_lookup)
+    debug_printf_indent("prepare fail: %s\n", sqlite3_errmsg(dbp));
   return FALSE;
   }
 if (sqlite3_step(statement) != SQLITE_ROW)
   {
-/* fprintf(stderr, "step fail: %s\n", sqlite3_errmsg(dbp)); */
+  /* DEBUG(D_hints_lookup)
+    debug_printf_indent("step fail: %s\n", sqlite3_errmsg(dbp)); */
   sqlite3_finalize(statement);
   return FALSE;
   }
@@ -108,7 +116,8 @@ res->data = store_get(res->len +1, GET_TAINTED);
 # endif
 memcpy(res->data, sqlite3_column_blob(statement, 0), res->len);
 res->data[res->len] = '\0';
-/* fprintf(stderr, "res %d bytes: '%.*s'\n", (int)res->len, (int)res->len, res->data); */
+/* DEBUG(D_hints_lookup) debug_printf_indent("res %d bytes: '%.*s'\n",
+                                 (int)res->len, (int)res->len, res->data); */
 sqlite3_finalize(statement);
 return TRUE;
 }
@@ -116,22 +125,30 @@ return TRUE;
 static inline BOOL
 exim_dbget(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res)
 {
-# define FMT "SELECT dat FROM tbl WHERE ky = '%.*s';"
-uschar * qry;
+# define FMT "SELECT dat FROM tbl WHERE ky = '%s';"
+uschar * encoded_key, * qry;
 int i;
 BOOL ret;
 
 # ifdef COMPILE_UTILITY
-/* fprintf(stderr, "exim_dbget(k len %d '%.*s')\n", (int)key->len, (int)key->len, key->data); */
-i = snprintf(NULL, 0, FMT, (int) key->len, key->data)+1;
+if (!(encoded_key = xtextencode(key->data, key->len)))
+  return FALSE;
+# else
+encoded_key = xtextencode(key->data, key->len);
+# endif
+/* DEBUG(D_hints_lookup) debug_printf_indent("exim_dbget(k len %d '%s')\n",
+                                 (int)key->len, encoded_key); */
+
+# ifdef COMPILE_UTILITY
+i = snprintf(NULL, 0, FMT, encoded_key) + 1;
 if (!(qry = malloc(i)))
   return FALSE;
-snprintf(CS qry, i, FMT, (int) key->len, key->data);
+snprintf(CS qry, i, FMT, encoded_key);
 ret = exim_dbget__(dbp, qry, res);
 free(qry);
+free(encoded_key);
 # else
-/* fprintf(stderr, "exim_dbget(k len %d '%.*s')\n", (int)key->len, (int)key->len, key->data); */
-qry = string_sprintf(FMT, (int) key->len, key->data);
+qry = string_sprintf(FMT, encoded_key);
 ret = exim_dbget__(dbp, qry, res);
 # endif
 
@@ -140,7 +157,11 @@ return ret;
 }
 
 /* Note that we return claiming a duplicate record for any error.
-It seem not uncommon to get a "database is locked" error. */
+It seem not uncommon to get a "database is locked" error.
+
+Keys are stored xtext-encoded (which is mostly readable, for plaintext).
+Values are stored in a BLOB type in the DB, for which the SQL interface
+is hex-encoded. */
 # define EXIM_DBPUTB_OK  0
 # define EXIM_DBPUTB_DUP (-1)
 
@@ -148,8 +169,8 @@ static inline int
 exim_s_dbp(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data, const uschar * alt)
 {
 int hlen = data->len * 2, off = 0, res;
-# define FMT "INSERT OR %s INTO tbl (ky,dat) VALUES ('%.*s', X'%.*s');"
-uschar * qry;
+# define FMT "INSERT OR %s INTO tbl (ky,dat) VALUES ('%s', X'%.*s');"
+uschar * encoded_key, * qry;
 # ifdef COMPILE_UTILITY
 uschar * hex = malloc(hlen+1);
 if (!hex) return EXIM_DBPUTB_DUP;      /* best we can do */
@@ -157,27 +178,37 @@ if (!hex) return EXIM_DBPUTB_DUP; /* best we can do */
 uschar * hex = store_get(hlen+1, data->data);
 # endif
 
+/* Encode the value for the SQL API */
+
 for (const uschar * s = data->data, * t = s + data->len; s < t; s++, off += 2)
   sprintf(CS hex + off, "%02X", *s);
 
 # ifdef COMPILE_UTILITY
-res = snprintf(CS hex, 0, FMT, alt, (int) key->len, key->data, hlen, hex) +1;
+if (!(encoded_key = xtextencode(key->data, key->len)))
+  return EXIM_DBPUTB_DUP;
+res = snprintf(CS hex, 0, FMT, alt, encoded_key, hlen, hex) +1;
 if (!(qry = malloc(res))) return EXIM_DBPUTB_DUP;
-snprintf(CS qry, res, FMT, alt, (int) key->len, key->data, hlen, hex);
-/* fprintf(stderr, "exim_s_dbp(%s)\n", qry); */
+snprintf(CS qry, res, FMT, alt, encoded_key, hlen, hex);
+DEBUG(D_hints_lookup) debug_printf_indent("exim_s_dbp(%s)\n", qry);
+
 res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL);
 free(qry);
+free(encoded_key);
 free(hex);
+
 # else
-qry = string_sprintf(FMT, alt, (int) key->len, key->data, hlen, hex);
-/* fprintf(stderr, "exim_s_dbp(%s)\n", qry); */
+encoded_key = xtextencode(key->data, key->len);
+qry = string_sprintf(FMT, alt, encoded_key, hlen, hex);
+/* DEBUG(D_hints_lookup) debug_printf_indent("exim_s_dbp(%s)\n", qry); */
+
 res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL);
-/* fprintf(stderr, "exim_s_dbp res %d\n", res); */
+/* DEBUG(D_hints_lookup) debug_printf_indent("exim_s_dbp res %d\n", res); */
 # endif
 
 # ifdef COMPILE_UTILITY
 if (res != SQLITE_OK)
-  fprintf(stderr, "sqlite3_exec: %s\n", sqlite3_errmsg(dbp));
+  DEBUG(D_hints_lookup)
+    debug_printf_indent("sqlite3_exec: %s\n", sqlite3_errmsg(dbp));
 # endif
 
 return res == SQLITE_OK ? EXIM_DBPUTB_OK : EXIM_DBPUTB_DUP;
@@ -189,7 +220,7 @@ return res == SQLITE_OK ? EXIM_DBPUTB_OK : EXIM_DBPUTB_DUP;
 static inline int
 exim_dbput(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data)
 {
-/* fprintf(stderr, "exim_dbput()\n"); */
+/* DEBUG(D_hints_lookup) debug_printf_indent("exim_dbput()\n"); */
 (void) exim_s_dbp(dbp, key, data, US"REPLACE");
 return 0;
 }
@@ -208,22 +239,27 @@ return exim_s_dbp(dbp, key, data, US"ABORT");
 static inline int
 exim_dbdel(EXIM_DB * dbp, EXIM_DATUM * key)
 {
-# define FMT "DELETE FROM tbl WHERE ky = '%.*s';"
-uschar * qry;
+# define FMT "DELETE FROM tbl WHERE ky = '%s';"
+uschar * encoded_key, * qry;
 int res;
 
 # ifdef COMPILE_UTILITY
-res = snprintf(NULL, 0, FMT, (int) key->len, key->data) +1; /* res includes nul */
+if (!(encoded_key = xtextencode(key->data, key->len)))
+  return EXIM_DBPUTB_DUP;
+res = snprintf(NULL, 0, FMT, encoded_key) +1;          /* res includes nul */
 if (!(qry = malloc(res))) return SQLITE_NOMEM;
-snprintf(CS qry, res, FMT, (int) key->len, key->data);
+snprintf(CS qry, res, FMT, encoded_key);
 res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL);
 free(qry);
+
 # else
-qry = string_sprintf(FMT, (int) key->len, key->data);
+encoded_key = xtextencode(key->data, key->len);
+qry = string_sprintf(FMT, encoded_key);
 res = sqlite3_exec(dbp, CS qry, NULL, NULL, NULL);
+
 # endif
 
-return res;
+return res == SQLITE_OK ? EXIM_DBPUTB_OK : EXIM_DBPUTB_DUP;
 # undef FMT
 }
 
@@ -245,31 +281,42 @@ return c;
 }
 
 /* EXIM_DBSCAN */
-/* Note that we return the (next) key, not the record value */
+/* Note that we return the (next) key, not the record value.
+We allocate memory for the return. */
+
 static inline BOOL
 exim_dbscan(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res, BOOL first,
   EXIM_CURSOR * cursor)
 {
 # define FMT "SELECT ky FROM tbl ORDER BY ky LIMIT 1 OFFSET %d;"
 uschar * qry;
-int i;
+EXIM_DATUM encoded_key;
 BOOL ret;
 
 # ifdef COMPILE_UTILITY
-i = snprintf(NULL, 0, FMT, *cursor)+1;
+int i = snprintf(NULL, 0, FMT, *cursor)+1;
+
 if (!(qry = malloc(i))) return FALSE;
 snprintf(CS qry, i, FMT, *cursor);
-/* fprintf(stderr, "exim_dbscan(%s)\n", qry); */
-ret = exim_dbget__(dbp, qry, key);
+DEBUG(D_hints_lookup) debug_printf_indent("exim_dbscan(%s)\n", qry);
+ret = exim_dbget__(dbp, qry, &encoded_key);
 free(qry);
-/* fprintf(stderr, "exim_dbscan ret %c\n", ret ? 'T':'F'); */
-# else
+
+# else /*!COMPILE_UTILITY*/
 qry = string_sprintf(FMT, *cursor);
-/* fprintf(stderr, "exim_dbscan(%s)\n", qry); */
-ret = exim_dbget__(dbp, qry, key);
-/* fprintf(stderr, "exim_dbscan ret %c\n", ret ? 'T':'F'); */
-# endif
-if (ret) *cursor = *cursor + 1;
+DEBUG(D_hints_lookup) debug_printf_indent("exim_dbscan(%s)\n", qry);
+ret = exim_dbget__(dbp, qry, &encoded_key);
+
+# endif        /*COMPILE_UTILITY*/
+
+DEBUG(D_hints_lookup)
+  debug_printf_indent("exim_dbscan ret %c\n", ret ? 'T':'F');
+
+if (ret)
+  {
+  key->len = xtextdecode(encoded_key.data, &key->data);
+  *cursor = *cursor + 1;
+  }
 return ret;
 # undef FMT
 }
index aa768d03c8f0c528cac7113c92575de3849b821e..5583d93de8714117d2965db806f361a6a3115294 100644 (file)
@@ -1626,7 +1626,7 @@ while (*fp)
 
     case 'W':                  /* Maybe mark up ctrls, spaces & newlines */
       s = va_arg(ap, char *);
-      if (Ustrpbrk(s, " \n") && !IS_DEBUG(D_noutf8))
+      if (s && !IS_DEBUG(D_noutf8))
        {
        gstring * zg = NULL;
        int p = precision;
@@ -1660,7 +1660,7 @@ while (*fp)
            }
          }
        if (zg) { s = CS zg->s; slen = gstring_length(zg); }
-       else    { s = null;     slen = Ustrlen(s); }
+       else    { s = "";       slen = 0; }
        }
       else
        {
index af2f287b9f9b6609ed01cfd7b1461cf10aed477f..af7a94b2d83de62855f281dd06020a1b4138ddf2 100644 (file)
@@ -10,7 +10,9 @@
 
 #ifdef EXPERIMENTAL_XCLIENT
 
-/* From https://www.postfix.org/XCLIENT_README.html I infer two generations of
+/* This is a proxy protocol.
+
+From https://www.postfix.org/XCLIENT_README.html I infer two generations of
 protocol.  The more recent one obviates the utility of the HELO attribute, since
 it mandates the proxy always sending a HELO/EHLO smtp command following (a
 successful) XCLIENT command, and that will carry a NELO name (which we assume,
index fa2b26bed4bfef3e7dd49083aa01ffaf122075ce..71489bd4f428d9293b322ac1d56c7508fa111d26 100644 (file)
@@ -26,8 +26,9 @@ Returns:      a pointer to the zero-terminated xtext string, which
               is in working store
 */
 
+#ifndef COMPILE_UTILITY
 uschar *
-xtextencode(uschar *clear, int len)
+xtextencode(uschar * clear, int len)
 {
 gstring * g = NULL;
 for(uschar ch; len > 0; len--, clear++)
@@ -38,6 +39,31 @@ gstring_release_unused(g);
 return string_from_gstring(g);
 }
 
+#else  /*COMPILE_UTILITY*/
+uschar *
+xtextencode(uschar * clear, int len)
+{
+int enc_len = 1, i = len;      /* enc_len includes space for terminating NUL */
+uschar * yield, * s;
+
+for (s = clear; i; i--, s++)
+  {
+  uschar ch = *s;
+  enc_len += ch < 33 || ch > 126 || ch == '+' || ch == '='
+             ? 3 : 1;
+  }
+if (!(yield = s = malloc(enc_len)))
+  return NULL;
+for(uschar ch; len > 0; len--, clear++)
+  if ((ch = *clear) < 33 || ch > 126 || ch == '+' || ch == '=')
+    s += sprintf(CS s, "+%.02X", ch);
+  else
+    *s++ = ch;
+*s = '\0';
+return yield;
+}
+
+#endif /*COMPILE_UTILITY*/
 
 /*************************************************
 *          Decode byte-string in xtext           *
@@ -64,24 +90,29 @@ int
 xtextdecode(uschar * code, uschar ** ptr)
 {
 int x;
+#ifdef COMPILE_UTILITY
+uschar * result = malloc(Ustrlen(code) + 1);
+#else
 uschar * result = store_get(Ustrlen(code) + 1, code);
-*ptr = result;
+#endif
 
-while ((x = (*code++)) != 0)
+*ptr = result;
+while ((x = (*code++)))
   {
   if (x < 33 || x > 127 || x == '=') return -1;
   if (x == '+')
     {
-    register int y;
+    int y;
     if (!isxdigit((x = (*code++)))) return -1;
     y = ((isdigit(x))? x - '0' : (tolower(x) - 'a' + 10)) << 4;
     if (!isxdigit((x = (*code++)))) return -1;
     *result++ = y | ((isdigit(x))? x - '0' : (tolower(x) - 'a' + 10));
     }
-  else *result++ = x;
+  else
+    *result++ = x;
   }
 
-*result = 0;
+*result = '\0';
 return result - *ptr;
 }
 
index 46f1029efa86f2a2dabec5023b12348b711ac44b..fb69964d10901ef1989304528072085ceb790f12 100644 (file)
@@ -7,7 +7,7 @@ primary_hostname = myhost.test.ex
 # ----- Main settings -----
 
 domainlist local_domains = test.ex : *.test.ex
-
+queue_run_in_order
 
 # ----- Routers -----
 
index 3c487ded1188b5c037b7da22c70ed248c05ebba3..5ce0b3e9406088a20fc26c753beee92923290c13 100644 (file)
@@ -1,21 +1,30 @@
 # retry times on local addresses
+#
+# All three should be deferred
 exim -odi userx usery userz
 This is a test message.
 ****
+# retry records should have been created
 dump retry
 sleep 1
+# retry record should mean no delivery tried
 exim -R usery
 ****
+# should still be all three records
 dump retry
 sleep 1
+# insert another message; should be deferred only on first delivery try
 exim -odi usery
 one-defer: set
 second message
 ****
+# all three records should still be there
 dump retry
 sleep 1
+# msg2 has had it's single-defer so should get delivered
 exim -Mc $msg2
 ****
+# the record for that localpart should have been removed as a result
 dump retry
 sleep 1
 exim -q