chiark / gitweb /
Commit 2.4.5-5 as unpacked
[innduct.git] / lib / hash.c
1 /* This provides a generic hash function for use w/INN.  Currently
2    is implemented using MD5, but should probably have a mechanism for
3    choosing the hash algorithm and tagging the hash with the algorithm
4    used */
5 #include "config.h"
6 #include "clibrary.h"
7 #include <ctype.h>
8
9 #include "inn/md5.h"
10 #include "libinn.h"
11
12 static HASH empty= { { 0, 0, 0, 0, 0, 0, 0, 0,
13                        0, 0, 0, 0, 0, 0, 0, 0 }};
14
15 /* cipoint - where in this message-ID does it become case-insensitive?
16  *
17  * The RFC822 code is not quite complete.  Absolute, total, full RFC822
18  * compliance requires a horrible parsing job, because of the arcane
19  * quoting conventions -- abc"def"ghi is not equivalent to abc"DEF"ghi,
20  * for example.  There are three or four things that might occur in the
21  * domain part of a message-id that are case-sensitive.  They don't seem
22  * to ever occur in real news, thank Cthulhu.  (What?  You were expecting
23  * a merciful and forgiving deity to be invoked in connection with RFC822?
24  * Forget it; none of them would come near it.)
25  *
26  * Returns: pointer into s, or NULL for "nowhere"
27  */
28 static const char *
29 cipoint(const char *s, size_t size)
30 {
31     char *p;
32     static const char post[] = "postmaster";
33     static int plen = sizeof(post) - 1;
34
35     if ((p = memchr(s, '@', size))== NULL)      /* no local/domain split */
36         return NULL;                            /* assume all local */
37     if ((p - (s + 1) == plen) && !strncasecmp(post, s+1, plen)) {
38         /* crazy -- "postmaster" is case-insensitive */
39         return s;
40     }
41     return p;
42 }
43
44 HASH
45 Hash(const void *value, const size_t len)
46 {
47     struct md5_context context;
48     HASH hash;
49
50     md5_init(&context);
51     md5_update(&context, value, len);
52     md5_final(&context);
53     memcpy(&hash,
54            &context.digest,
55            (sizeof(hash) < sizeof(context.digest)) ? sizeof(hash) : sizeof(context.digest));
56     return hash;
57 }
58
59 HASH
60 HashMessageID(const char *MessageID)
61 {
62     char                *new = NULL;
63     const char          *cip, *p = NULL;
64     char                *q;
65     int                 len;
66     HASH                hash;
67
68     len = strlen(MessageID);
69     cip = cipoint(MessageID, len);
70     if (cip != NULL) {
71         for (p = cip + 1; *p != '\0'; p++) {
72             if (!CTYPE(islower, *p)) {
73                 new = xstrdup(MessageID);
74                 break;
75             }
76         }
77     }
78     if (new != NULL)
79         for (q = new + (p - MessageID); *q != '\0'; q++)
80             *q = tolower(*q);
81     hash = Hash(new ? new : MessageID, len);
82     if (new != NULL)
83         free(new);
84     return hash;
85 }
86
87 /*
88 **  Check if the hash is all zeros, and subseqently empty, see HashClear
89 **  for more info on this.
90 */
91 bool
92 HashEmpty(const HASH h)
93 {
94     return (memcmp(&empty, &h, sizeof(HASH)) == 0);
95 }
96
97 /*
98 **  Set the hash to all zeros.  Using all zeros as the value for empty 
99 **  introduces the possibility of colliding w/a value that actually hashes
100 **  to all zeros, but that's fairly unlikely.
101 */
102 void
103 HashClear(HASH *hash)
104 {
105     memset(hash, '\0', sizeof(HASH));
106 }
107
108 /*
109 **  Convert the binary form of the hash to a form that we can use in error
110 **  messages and logs.
111 */
112 char *
113 HashToText(const HASH hash)
114 {
115     static const char   hex[] = "0123456789ABCDEF";
116     const char          *p;
117     unsigned int        i;
118     static char         hashstr[(sizeof(HASH)*2) + 1];
119
120     for (p = hash.hash, i = 0; i < sizeof(HASH); i++, p++) {
121         hashstr[i * 2] = hex[(*p & 0xF0) >> 4];
122         hashstr[(i * 2) + 1] = hex[*p & 0x0F];
123     }
124     hashstr[(sizeof(HASH) * 2)] = '\0';
125     return hashstr;
126 }
127
128 /*
129 ** Converts a hex digit and converts it to a int
130 */
131 static
132 int hextodec(const int c)
133 {
134     return isdigit(c) ? (c - '0') : ((c - 'A') + 10);
135 }
136
137 /*
138 **  Convert the ASCII representation of the hash back to the canonical form
139 */
140 HASH
141 TextToHash(const char *text)
142 {
143     char                *q;
144     int                 i;
145     HASH                hash;
146
147     for (q = (char *)&hash, i = 0; i != sizeof(HASH); i++) {
148         q[i] = (hextodec(text[i * 2]) << 4) + hextodec(text[(i * 2) + 1]);
149     }
150     return hash;
151 }
152
153 /* This is rather gross, we compare like the last 4 bytes of the
154    hash are at the beginning because dbz considers them to be the
155    most significant bytes */
156 int HashCompare(const HASH *h1, const HASH *h2) {
157     int i;
158     int tocomp = sizeof(HASH) - sizeof(unsigned long);
159     
160     if ((i = memcmp(&h1->hash[tocomp], &h1->hash[tocomp], sizeof(unsigned long))))
161         return i;
162     return memcmp(h1, h2, sizeof(HASH));
163 }