Ensure unique message IDs. Always. fast-message-ids
authorHeiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Mon, 7 Mar 2016 22:13:22 +0000 (23:13 +0100)
committerHeiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Wed, 9 Mar 2016 08:24:20 +0000 (09:24 +0100)
On very fast systems we could get duplicate message IDs in case
the message was not accepted. But since already some traces of the
ID may appear in the logs we now take care to have a unique ID
always.

Introduce functions build_message_id() and get_message_id_data().

src/src/exim.c
src/src/globals.c
src/src/globals.h
src/src/receive.c
src/src/smtp_in.c

index e6acb0b80b3b221b9d01a91e925fff625983c4cb..a2b8fa34006691a71396cb25c7d2f00653693a73 100644 (file)
@@ -275,7 +275,7 @@ will wait for ever, so we panic in this instance. (There was a case of this
 when a bug in a function that calls milliwait() caused it to pass invalid data.
 That's when I added the check. :-)
 
-We assume it to be not worth sleeping for under 100us; this value will
+We assume it to be not worth 0us; this value will
 require revisiting as hardware advances.  This avoids the issue of
 a zero-valued timer setting meaning "never fire".
 
@@ -289,7 +289,7 @@ milliwait(struct itimerval *itval)
 sigset_t sigmask;
 sigset_t old_sigmask;
 
-if (itval->it_value.tv_usec < 100 && itval->it_value.tv_sec == 0)
+if (itval->it_value.tv_usec == 0 && itval->it_value.tv_sec == 0)
   return;
 (void)sigemptyset(&sigmask);                           /* Empty mask */
 (void)sigaddset(&sigmask, SIGALRM);                    /* Add SIGALRM */
index adf87380e3c796fdf27a227bfd82a85f5199515c..9fc25ec7e95af175659ea73deca75eab42bf28a6 100644 (file)
@@ -938,7 +938,6 @@ uschar *message_headers        = NULL;
 uschar *message_id;
 uschar *message_id_domain      = NULL;
 uschar *message_id_text        = NULL;
-struct timeval message_id_tv   = { 0, 0 };
 uschar  message_id_option[MESSAGE_ID_LENGTH + 3];
 uschar *message_id_external;
 int     message_linecount      = 0;
index 7f6537a9a3e921bddbd2012114f72b11fc3fbc6d..af6d0fb746a86c805ea8996d1888c275c3ab50c4 100644 (file)
@@ -573,7 +573,6 @@ extern uschar  message_id_option[];    /* -E<message-id> for use as option */
 extern uschar *message_id_external;    /* External form of following */
 extern uschar *message_id_domain;      /* Expanded to form domain-part of message_id */
 extern uschar *message_id_text;        /* Expanded to form message_id */
-extern struct timeval message_id_tv;   /* Time used to create last message_id */
 extern int     message_linecount;      /* As it says */
 extern BOOL    message_logs;           /* TRUE to write message logs */
 extern int     message_size;           /* Size of message */
index a479e12cda152c7ffe036f1b5637b8902796384b..48c26a08da4f499cfe53a153fa4c28fa3c43599b 100644 (file)
@@ -1439,6 +1439,66 @@ When reading a message for filter testing, the returned value indicates
 whether the headers (which is all that is read) were terminated by '.' or
 not. */
 
+typedef struct {
+  pid_t pid;
+  struct timeval tv;
+} message_id_data_t;
+
+message_id_data_t
+get_message_id_data(void)
+{
+/* The last part of the message id consists of 4 BaseX digits. It is
+constructed from the tv_us value of gettimeofday() and localhost_number.
+The usable time resolution depends on the X from BaseX (62 or only 36
+(on a case-ignorant OS)) and the usage of localhost_number */
+
+  static int cnt;
+  static int id_resolution;
+  static message_id_data_t id;
+
+
+  /* first time call, nothing to wait, but initialize or
+  internal data */
+  if (!id_resolution)
+    {
+    /* best resolution is 500 ticks, worst is 10000.
+    one tick is 1/1_000_000 second */
+    id_resolution = host_number_string
+      ? (BASE_62 == 62) ? 5000 : 10000
+      : (BASE_62 == 62) ? 500 : 1000;
+    id.pid = getpid();
+    gettimeofday(&id.tv, NULL);
+    }
+
+  /* next message id, ensure uniqness */
+  else
+    {
+    exim_wait_tick(&id.tv, id_resolution);
+    gettimeofday(&id.tv, NULL);
+    }
+
+  id.tv.tv_usec = (id.tv.tv_usec/id_resolution) * id_resolution;
+  if (host_number_string) id.tv.tv_usec += host_number * 1000000/id_resolution;
+
+  return id;
+}
+
+void
+build_message_id_string(uschar **string, const message_id_data_t *data)
+{
+  Ustrncpy(*string, string_base62((long int)data->tv.tv_sec), 6);
+  (*string)[6] = '-';
+  Ustrncpy(*string + 7, string_base62((long int)data->pid), 6);
+
+/* Deal with the case where the host number is set. The value of the number was
+checked when it was read, to ensure it isn't too big. The timing granularity is
+left in id_resolution so that an appropriate wait can be done after receiving
+the message, if necessary (we hope it won't be). */
+
+  sprintf(CS(*string + MESSAGE_ID_LENGTH - 3), "-%2s",
+    string_base62((long int)data->tv.tv_usec) + 4);
+}
+
 BOOL
 receive_msg(BOOL extract_recip)
 {
@@ -1450,7 +1510,6 @@ int  error_rc = (error_handling == ERRORS_SENDER)?
        errors_sender_rc : EXIT_FAILURE;
 int  header_size = 256;
 int  start, end, domain, size, sptr;
-int  id_resolution;
 int  had_zero = 0;
 int  prevlines_length = 0;
 
@@ -1477,6 +1536,8 @@ uschar *queued_by = NULL;
 uschar *errmsg, *s;
 struct stat statbuf;
 
+message_id_data_t message_id_data;
+
 /* Final message to give to SMTP caller, and messages from ACLs */
 
 uschar *smtp_reply = NULL;
@@ -1566,13 +1627,13 @@ dmarc_up = dmarc_init();
 ids, and fractions of a second are required. See the comments that precede the
 message id creation below. */
 
-(void)gettimeofday(&message_id_tv, NULL);
+message_id_data = get_message_id_data();
 
 /* For other uses of the received time we can operate with granularity of one
 second, and for that we use the global variable received_time. This is for
 things like ultimate message timeouts. */
 
-received_time = message_id_tv.tv_sec;
+received_time = message_id_data.tv.tv_sec;
 
 /* If SMTP input, set the special handler for timeouts. The alarm() calls
 happen in the smtp_getc() function when it refills its buffer. */
@@ -2431,33 +2492,7 @@ way. It appears in the initializing code in exim.c. The macro MESSAGE_ID_LENGTH
 must also be changed to reflect the correct string length. Then, of course,
 other programs that rely on the message id format will need updating too. */
 
-Ustrncpy(message_id, string_base62((long int)(message_id_tv.tv_sec)), 6);
-message_id[6] = '-';
-Ustrncpy(message_id + 7, string_base62((long int)getpid()), 6);
-
-/* Deal with the case where the host number is set. The value of the number was
-checked when it was read, to ensure it isn't too big. The timing granularity is
-left in id_resolution so that an appropriate wait can be done after receiving
-the message, if necessary (we hope it won't be). */
-
-if (host_number_string != NULL)
-  {
-  id_resolution = (BASE_62 == 62)? 5000 : 10000;
-  sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
-    string_base62((long int)(
-      host_number * (1000000/id_resolution) +
-        message_id_tv.tv_usec/id_resolution)) + 4);
-  }
-
-/* Host number not set: final field is just the fractional time at an
-appropriate resolution. */
-
-else
-  {
-  id_resolution = (BASE_62 == 62)? 500 : 1000;
-  sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
-    string_base62((long int)(message_id_tv.tv_usec/id_resolution)) + 4);
-  }
+build_message_id_string(&message_id, &message_id_data);
 
 /* Add the current message id onto the current process info string if
 it will fit. */
@@ -3716,16 +3751,6 @@ else
 
 receive_messagecount++;
 
-/* In SMTP sessions we may receive several in one connection. After each one,
-we wait for the clock to tick at the level of message-id granularity. This is
-so that the combination of time+pid is unique, even on systems where the pid
-can be re-used within our time interval. We can't shorten the interval without
-re-designing the message-id. See comments above where the message id is
-created. This is Something For The Future. */
-
-message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution;
-exim_wait_tick(&message_id_tv, id_resolution);
-
 /* Add data size to written header size. We do not count the initial file name
 that is in the file, but we do add one extra for the notional blank line that
 precedes the data. This total differs from message_size in that it include the
@@ -4163,6 +4188,13 @@ when they shouldn't. */
 
 header_list = header_last = NULL;
 
+/* In SMTP sessions we may receive several in one connection. After each one,
+we wait for the clock to tick at the level of message-id granularity. This is
+so that the combination of time+pid is unique, even on systems where the pid
+can be re-used within our time interval. We can't shorten the interval without
+re-designing the message-id. See comments above where the message id is
+created. This is Something For The Future. */
+
 return yield;  /* TRUE if more messages (SMTP only) */
 }
 
index 0498ecb18d8399c643f1b70e043ebb403f5dd1ff..7600bf1a6ac68058a40b903ad7d45ca90c16e658 100644 (file)
@@ -11,7 +11,6 @@
 #include "exim.h"
 #include <assert.h>
 
-
 /* Initialize for TCP wrappers if so configured. It appears that the macro
 HAVE_IPV6 is used in some versions of the tcpd.h header, so we unset it before
 including that header, and restore its value afterwards. */