authreadkeys.c revision 1.2.22.2 1 /* $NetBSD: authreadkeys.c,v 1.2.22.2 2015/11/07 22:46:15 snj 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 size_t nerr_loglimit = 5u;
83 static const size_t nerr_maxlimit = 15;
84
85 static void log_maybe(size_t*, const char*, ...) NTP_PRINTF(2, 3);
86
87 static void
88 log_maybe(
89 size_t *pnerr,
90 const char *fmt ,
91 ...)
92 {
93 va_list ap;
94 if (++(*pnerr) <= nerr_loglimit) {
95 va_start(ap, fmt);
96 mvsyslog(LOG_ERR, fmt, ap);
97 va_end(ap);
98 }
99 }
100
101 /*
102 * authreadkeys - (re)read keys from a file.
103 */
104 int
105 authreadkeys(
106 const char *file
107 )
108 {
109 FILE *fp;
110 char *line;
111 char *token;
112 keyid_t keyno;
113 int keytype;
114 char buf[512]; /* lots of room for line */
115 u_char keystr[32]; /* Bug 2537 */
116 size_t len;
117 size_t j;
118 size_t nerr;
119 /*
120 * Open file. Complain and return if it can't be opened.
121 */
122 fp = fopen(file, "r");
123 if (fp == NULL) {
124 msyslog(LOG_ERR, "authreadkeys: file %s: %m",
125 file);
126 return (0);
127 }
128 INIT_SSL();
129
130 /*
131 * Remove all existing keys
132 */
133 auth_delkeys();
134
135 /*
136 * Now read lines from the file, looking for key entries
137 */
138 nerr = 0;
139 while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
140 if (nerr > nerr_maxlimit)
141 break;
142 token = nexttok(&line);
143 if (token == NULL)
144 continue;
145
146 /*
147 * First is key number. See if it is okay.
148 */
149 keyno = atoi(token);
150 if (keyno == 0) {
151 log_maybe(&nerr,
152 "authreadkeys: cannot change key %s",
153 token);
154 continue;
155 }
156
157 if (keyno > NTP_MAXKEY) {
158 log_maybe(&nerr,
159 "authreadkeys: key %s > %d reserved for Autokey",
160 token, NTP_MAXKEY);
161 continue;
162 }
163
164 /*
165 * Next is keytype. See if that is all right.
166 */
167 token = nexttok(&line);
168 if (token == NULL) {
169 log_maybe(&nerr,
170 "authreadkeys: no key type for key %d",
171 keyno);
172 continue;
173 }
174 #ifdef OPENSSL
175 /*
176 * The key type is the NID used by the message digest
177 * algorithm. There are a number of inconsistencies in
178 * the OpenSSL database. We attempt to discover them
179 * here and prevent use of inconsistent data later.
180 */
181 keytype = keytype_from_text(token, NULL);
182 if (keytype == 0) {
183 log_maybe(&nerr,
184 "authreadkeys: invalid type for key %d",
185 keyno);
186 continue;
187 }
188 if (EVP_get_digestbynid(keytype) == NULL) {
189 log_maybe(&nerr,
190 "authreadkeys: no algorithm for key %d",
191 keyno);
192 continue;
193 }
194 #else /* !OPENSSL follows */
195
196 /*
197 * The key type is unused, but is required to be 'M' or
198 * 'm' for compatibility.
199 */
200 if (!(*token == 'M' || *token == 'm')) {
201 log_maybe(&nerr,
202 "authreadkeys: invalid type for key %d",
203 keyno);
204 continue;
205 }
206 keytype = KEY_TYPE_MD5;
207 #endif /* !OPENSSL */
208
209 /*
210 * Finally, get key and insert it. If it is longer than 20
211 * characters, it is a binary string encoded in hex;
212 * otherwise, it is a text string of printable ASCII
213 * characters.
214 */
215 token = nexttok(&line);
216 if (token == NULL) {
217 log_maybe(&nerr,
218 "authreadkeys: no key for key %d", keyno);
219 continue;
220 }
221 len = strlen(token);
222 if (len <= 20) { /* Bug 2537 */
223 MD5auth_setkey(keyno, keytype, (u_char *)token, len);
224 } else {
225 char hex[] = "0123456789abcdef";
226 u_char temp;
227 char *ptr;
228 size_t jlim;
229
230 jlim = min(len, 2 * sizeof(keystr));
231 for (j = 0; j < jlim; j++) {
232 ptr = strchr(hex, tolower((unsigned char)token[j]));
233 if (ptr == NULL)
234 break; /* abort decoding */
235 temp = (u_char)(ptr - hex);
236 if (j & 1)
237 keystr[j / 2] |= temp;
238 else
239 keystr[j / 2] = temp << 4;
240 }
241 if (j < jlim) {
242 log_maybe(&nerr,
243 "authreadkeys: invalid hex digit for key %d",
244 keyno);
245 continue;
246 }
247 MD5auth_setkey(keyno, keytype, keystr, jlim / 2);
248 }
249 }
250 fclose(fp);
251 if (nerr > nerr_maxlimit) {
252 msyslog(LOG_ERR,
253 "authreadkeys: emergency break after %zu errors",
254 nerr);
255 return (0);
256 } else if (nerr > nerr_loglimit) {
257 msyslog(LOG_ERR,
258 "authreadkeys: found %zu more error(s)",
259 nerr - nerr_loglimit);
260 }
261 return (1);
262 }
263