authreadkeys.c revision 1.1.1.9 1 /* $NetBSD: authreadkeys.c,v 1.1.1.9 2016/01/08 21:21:24 christos Exp $ */
2
3 /*
4 * authreadkeys.c - routines to support the reading of the key file
5 */
6 #include <config.h>
7 #include <stdio.h>
8 #include <ctype.h>
9
10 #include "ntp_fp.h"
11 #include "ntp.h"
12 #include "ntp_syslog.h"
13 #include "ntp_stdlib.h"
14
15 #ifdef OPENSSL
16 #include "openssl/objects.h"
17 #include "openssl/evp.h"
18 #endif /* OPENSSL */
19
20 /* Forwards */
21 static char *nexttok (char **);
22
23 /*
24 * nexttok - basic internal tokenizing routine
25 */
26 static char *
27 nexttok(
28 char **str
29 )
30 {
31 register char *cp;
32 char *starttok;
33
34 cp = *str;
35
36 /*
37 * Space past white space
38 */
39 while (*cp == ' ' || *cp == '\t')
40 cp++;
41
42 /*
43 * Save this and space to end of token
44 */
45 starttok = cp;
46 while (*cp != '\0' && *cp != '\n' && *cp != ' '
47 && *cp != '\t' && *cp != '#')
48 cp++;
49
50 /*
51 * If token length is zero return an error, else set end of
52 * token to zero and return start.
53 */
54 if (starttok == cp)
55 return NULL;
56
57 if (*cp == ' ' || *cp == '\t')
58 *cp++ = '\0';
59 else
60 *cp = '\0';
61
62 *str = cp;
63 return starttok;
64 }
65
66
67 /* TALOS-CAN-0055: possibly DoS attack by setting the key file to the
68 * log file. This is hard to prevent (it would need to check two files
69 * to be the same on the inode level, which will not work so easily with
70 * Windows or VMS) but we can avoid the self-amplification loop: We only
71 * log the first 5 errors, silently ignore the next 10 errors, and give
72 * up when when we have found more than 15 errors.
73 *
74 * This avoids the endless file iteration we will end up with otherwise,
75 * and also avoids overflowing the log file.
76 *
77 * Nevertheless, once this happens, the keys are gone since this would
78 * require a save/swap strategy that is not easy to apply due to the
79 * data on global/static level.
80 */
81
82 static const u_int nerr_loglimit = 5u;
83 static const u_int nerr_maxlimit = 15;
84
85 static void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3);
86
87 typedef struct keydata KeyDataT;
88 struct keydata {
89 KeyDataT *next; /* queue/stack link */
90 keyid_t keyid; /* stored key ID */
91 u_short keytype; /* stored key type */
92 u_short seclen; /* length of secret */
93 u_char secbuf[1]; /* begin of secret (formal only)*/
94 };
95
96 static void
97 log_maybe(
98 u_int *pnerr,
99 const char *fmt ,
100 ...)
101 {
102 va_list ap;
103 if (++(*pnerr) <= nerr_loglimit) {
104 va_start(ap, fmt);
105 mvsyslog(LOG_ERR, fmt, ap);
106 va_end(ap);
107 }
108 }
109
110 /*
111 * authreadkeys - (re)read keys from a file.
112 */
113 int
114 authreadkeys(
115 const char *file
116 )
117 {
118 FILE *fp;
119 char *line;
120 char *token;
121 keyid_t keyno;
122 int keytype;
123 char buf[512]; /* lots of room for line */
124 u_char keystr[32]; /* Bug 2537 */
125 size_t len;
126 size_t j;
127 u_int nerr;
128 KeyDataT *list = NULL;
129 KeyDataT *next = NULL;
130 /*
131 * Open file. Complain and return if it can't be opened.
132 */
133 fp = fopen(file, "r");
134 if (fp == NULL) {
135 msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
136 file);
137 goto onerror;
138 }
139 INIT_SSL();
140
141 /*
142 * Now read lines from the file, looking for key entries. Put
143 * the data into temporary store for later propagation to avoid
144 * two-pass processing.
145 */
146 nerr = 0;
147 while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
148 if (nerr > nerr_maxlimit)
149 break;
150 token = nexttok(&line);
151 if (token == NULL)
152 continue;
153
154 /*
155 * First is key number. See if it is okay.
156 */
157 keyno = atoi(token);
158 if (keyno == 0) {
159 log_maybe(&nerr,
160 "authreadkeys: cannot change key %s",
161 token);
162 continue;
163 }
164
165 if (keyno > NTP_MAXKEY) {
166 log_maybe(&nerr,
167 "authreadkeys: key %s > %d reserved for Autokey",
168 token, NTP_MAXKEY);
169 continue;
170 }
171
172 /*
173 * Next is keytype. See if that is all right.
174 */
175 token = nexttok(&line);
176 if (token == NULL) {
177 log_maybe(&nerr,
178 "authreadkeys: no key type for key %d",
179 keyno);
180 continue;
181 }
182 #ifdef OPENSSL
183 /*
184 * The key type is the NID used by the message digest
185 * algorithm. There are a number of inconsistencies in
186 * the OpenSSL database. We attempt to discover them
187 * here and prevent use of inconsistent data later.
188 */
189 keytype = keytype_from_text(token, NULL);
190 if (keytype == 0) {
191 log_maybe(&nerr,
192 "authreadkeys: invalid type for key %d",
193 keyno);
194 continue;
195 }
196 if (EVP_get_digestbynid(keytype) == NULL) {
197 log_maybe(&nerr,
198 "authreadkeys: no algorithm for key %d",
199 keyno);
200 continue;
201 }
202 #else /* !OPENSSL follows */
203
204 /*
205 * The key type is unused, but is required to be 'M' or
206 * 'm' for compatibility.
207 */
208 if (!(*token == 'M' || *token == 'm')) {
209 log_maybe(&nerr,
210 "authreadkeys: invalid type for key %d",
211 keyno);
212 continue;
213 }
214 keytype = KEY_TYPE_MD5;
215 #endif /* !OPENSSL */
216
217 /*
218 * Finally, get key and insert it. If it is longer than 20
219 * characters, it is a binary string encoded in hex;
220 * otherwise, it is a text string of printable ASCII
221 * characters.
222 */
223 token = nexttok(&line);
224 if (token == NULL) {
225 log_maybe(&nerr,
226 "authreadkeys: no key for key %d", keyno);
227 continue;
228 }
229 next = NULL;
230 len = strlen(token);
231 if (len <= 20) { /* Bug 2537 */
232 next = emalloc(sizeof(KeyDataT) + len);
233 next->keyid = keyno;
234 next->keytype = keytype;
235 next->seclen = len;
236 memcpy(next->secbuf, token, len);
237 } else {
238 static const char hex[] = "0123456789abcdef";
239 u_char temp;
240 char *ptr;
241 size_t jlim;
242
243 jlim = min(len, 2 * sizeof(keystr));
244 for (j = 0; j < jlim; j++) {
245 ptr = strchr(hex, tolower((unsigned char)token[j]));
246 if (ptr == NULL)
247 break; /* abort decoding */
248 temp = (u_char)(ptr - hex);
249 if (j & 1)
250 keystr[j / 2] |= temp;
251 else
252 keystr[j / 2] = temp << 4;
253 }
254 if (j < jlim) {
255 log_maybe(&nerr,
256 "authreadkeys: invalid hex digit for key %d",
257 keyno);
258 continue;
259 }
260 len = jlim/2; /* hmmmm.... what about odd length?!? */
261 next = emalloc(sizeof(KeyDataT) + len);
262 next->keyid = keyno;
263 next->keytype = keytype;
264 next->seclen = len;
265 memcpy(next->secbuf, keystr, len);
266 }
267 INSIST(NULL != next);
268 next->next = list;
269 list = next;
270 }
271 fclose(fp);
272 if (nerr > nerr_maxlimit) {
273 msyslog(LOG_ERR,
274 "authreadkeys: rejecting file '%s' after %u errors (emergency break)",
275 file, nerr);
276 goto onerror;
277 }
278 if (nerr > 0) {
279 msyslog(LOG_ERR,
280 "authreadkeys: rejecting file '%s' after %u error(s)",
281 file, nerr);
282 goto onerror;
283 }
284
285 /* first remove old file-based keys */
286 auth_delkeys();
287 /* insert the new key material */
288 while (NULL != (next = list)) {
289 list = next->next;
290 MD5auth_setkey(next->keyid, next->keytype,
291 next->secbuf, next->seclen);
292 /* purge secrets from memory before free()ing it */
293 memset(next, 0, sizeof(*next) + next->seclen);
294 free(next);
295 }
296 return (1);
297
298 onerror:
299 /* Mop up temporary storage before bailing out. */
300 while (NULL != (next = list)) {
301 list = next->next;
302 /* purge secrets from memory before free()ing it */
303 memset(next, 0, sizeof(*next) + next->seclen);
304 free(next);
305 }
306 return (0);
307 }
308