Cleaned up message_id().
[mmh] / sbr / message_id.c
1 /*
2  * message-id.c -- construct the body of a Message-ID or Content-ID
3  *                 header field
4  *
5  * This code is Copyright (c) 2012, by the authors of nmh.  See the
6  * COPYRIGHT file in the root directory of the nmh distribution for
7  * complete copyright information.
8  */
9
10 #include <h/mh.h>
11 #include <unistd.h>    /* for getpid() */
12 #include <sys/time.h>  /* for gettimeofday() */
13 #include <stdio.h>
14
15
16 static enum {
17   NMH_MESSAGE_ID_LOCALNAME,
18   NMH_MESSAGE_ID_RANDOM
19 } message_id_style = NMH_MESSAGE_ID_LOCALNAME;
20 static char message_id_[BUFSIZ];
21
22
23 /* Convert name of message id style to integer value and store it. */
24 int
25 save_message_id_style (const char *value) {
26   if (! mh_strcasecmp (value, "localname")) {
27     message_id_style = NMH_MESSAGE_ID_LOCALNAME;
28     return 0;
29   } else if (! mh_strcasecmp (value, "random")) {
30     message_id_style = NMH_MESSAGE_ID_RANDOM;
31     return 0;
32   } else {
33     return 1;
34   }
35 }
36
37
38 char *
39 message_id (time_t tclock, int content_id) {
40   switch (message_id_style) {
41     case NMH_MESSAGE_ID_LOCALNAME: {
42       char *format = content_id  ?  "<%d.%ld.%%d@%s>"  :  "<%d.%ld@%s>";
43
44       snprintf (message_id_, sizeof message_id_, format,
45                 (int) getpid (), (long) tclock, LocalName (1));
46
47       break;
48     }
49
50     case NMH_MESSAGE_ID_RANDOM: {
51       char *format = content_id
52         ?  "<%d-%ld.%06ld%%d@%.*s.%.*s.%.*s>"
53         :  "<%d-%ld.%06ld@%.*s.%.*s.%.*s>";
54       /* Use a sequence of digits divisible by 3 because that will
55          expand to base64 without any waste.  Must be shorter than 58,
56          see below. */
57       unsigned char rnd[9];
58       /* The part after the '@' is divided into thirds.  The base64
59          encoded string will be 4/3 the size of rnd. */
60       size_t one_third = sizeof rnd * 4/3/3;
61
62       if (m_rand (rnd, sizeof rnd) == 0) {
63         struct timeval now;
64         /* All we really need is 4 * [sizeof rnd/3] + 2, as long as
65            the base64 encoding stays shorter than 76 bytes so embedded
66            newlines aren't necessary.  But use double the sizeof rnd
67            just to be safe. */
68         unsigned char rnd_base64[2 * sizeof rnd];
69         unsigned char *cp;
70         int i;
71
72         writeBase64 (rnd, sizeof rnd, rnd_base64);
73
74         for (i = strlen ((const char *) rnd_base64) - 1;
75              i > 0  &&  iscntrl (rnd_base64[i]);
76              --i) {
77           /* Remove trailing newline.  rnd_base64 had better be
78              shorter than 76 characters, so don't bother to look for
79              embedded newlines. */
80           rnd_base64[i] = '\0';
81         }
82
83         /* Try to make the base64 string look a little more like a
84            hostname by replacing + with - and / with _. */
85         for (cp = rnd_base64; *cp; ++cp) {
86           if (*cp == '+') {
87             *cp = '-';
88           } else if (*cp == '/') {
89             *cp = '_';
90           }
91         }
92
93         /* gettimeofday() and getpid() shouldn't fail on POSIX platforms. */
94         gettimeofday (&now, 0);
95
96         /* The format string inserts a couple of dots, for the benefit
97            of spam filters that want to see a message id with a final
98            part that resembles a hostname. */
99         snprintf (message_id_, sizeof message_id_, format,
100                   getpid (), (long) now.tv_sec, (long) now.tv_usec,
101                   one_third, rnd_base64,
102                   one_third, &rnd_base64[one_third],
103                   one_third, &rnd_base64[2*one_third]);
104       }
105
106       break;
107     }
108   }
109
110   return message_id_;
111 }