Coverage Report

Created: 2023-05-19 06:16

/src/ntp-dev/libntp/authreadkeys.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * authreadkeys.c - routines to support the reading of the key file
3
 */
4
#include <config.h>
5
#include <stdio.h>
6
#include <ctype.h>
7
8
//#include "ntpd.h" /* Only for DPRINTF */
9
//#include "ntp_fp.h"
10
#include "ntp.h"
11
#include "ntp_syslog.h"
12
#include "ntp_stdlib.h"
13
#include "ntp_keyacc.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
0
{
31
0
  register char *cp;
32
0
  char *starttok;
33
34
0
  cp = *str;
35
36
  /*
37
   * Space past white space
38
   */
39
0
  while (*cp == ' ' || *cp == '\t')
40
0
    cp++;
41
  
42
  /*
43
   * Save this and space to end of token
44
   */
45
0
  starttok = cp;
46
0
  while (*cp != '\0' && *cp != '\n' && *cp != ' '
47
0
         && *cp != '\t' && *cp != '#')
48
0
    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
0
  if (starttok == cp)
55
0
    return NULL;
56
  
57
0
  if (*cp == ' ' || *cp == '\t')
58
0
    *cp++ = '\0';
59
0
  else
60
0
    *cp = '\0';
61
  
62
0
  *str = cp;
63
0
  return starttok;
64
0
}
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
  KeyAccT  *keyacclist; /* key access list    */
91
  keyid_t   keyid;  /* stored key ID    */
92
  u_short   keytype;  /* stored key type    */
93
  u_short   seclen; /* length of secret   */
94
  u_char    secbuf[1];  /* begin of secret (formal only)*/
95
};
96
97
static void
98
log_maybe(
99
  u_int      *pnerr,
100
  const char *fmt  ,
101
  ...)
102
0
{
103
0
  va_list ap;
104
0
  if ((NULL == pnerr) || (++(*pnerr) <= nerr_loglimit)) {
105
0
    va_start(ap, fmt);
106
0
    mvsyslog(LOG_ERR, fmt, ap);
107
0
    va_end(ap);
108
0
  }
109
0
}
110
111
static void
112
free_keydata(
113
  KeyDataT *node
114
  )
115
0
{
116
0
  KeyAccT *kap;
117
  
118
0
  if (node) {
119
0
    while (node->keyacclist) {
120
0
      kap = node->keyacclist;
121
0
      node->keyacclist = kap->next;
122
0
      free(kap);
123
0
    }
124
125
    /* purge secrets from memory before free()ing it */
126
0
    memset(node, 0, sizeof(*node) + node->seclen);
127
0
    free(node);
128
0
  }
129
0
}
130
131
/*
132
 * authreadkeys - (re)read keys from a file.
133
 */
134
int
135
authreadkeys(
136
  const char *file
137
  )
138
0
{
139
0
  FILE  *fp;
140
0
  char  *line;
141
0
  char  *token;
142
0
  keyid_t keyno;
143
0
  int keytype;
144
0
  char  buf[512];   /* lots of room for line */
145
0
  u_char  keystr[32];   /* Bug 2537 */
146
0
  size_t  len;
147
0
  size_t  j;
148
0
  u_int   nerr;
149
0
  KeyDataT *list = NULL;
150
0
  KeyDataT *next = NULL;
151
152
  /*
153
   * Open file.  Complain and return if it can't be opened.
154
   */
155
0
  fp = fopen(file, "r");
156
0
  if (fp == NULL) {
157
0
    msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
158
0
        file);
159
0
    goto onerror;
160
0
  }
161
0
  INIT_SSL();
162
163
  /*
164
   * Now read lines from the file, looking for key entries. Put
165
   * the data into temporary store for later propagation to avoid
166
   * two-pass processing.
167
   */
168
0
  nerr = 0;
169
0
  while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
170
0
    if (nerr > nerr_maxlimit)
171
0
      break;
172
0
    token = nexttok(&line);
173
0
    if (token == NULL)
174
0
      continue;
175
    
176
    /*
177
     * First is key number.  See if it is okay.
178
     */
179
0
    keyno = atoi(token);
180
0
    if (keyno < 1) {
181
0
      log_maybe(&nerr,
182
0
          "authreadkeys: cannot change key %s",
183
0
          token);
184
0
      continue;
185
0
    }
186
187
0
    if (keyno > NTP_MAXKEY) {
188
0
      log_maybe(&nerr,
189
0
          "authreadkeys: key %s > %d reserved for Autokey",
190
0
          token, NTP_MAXKEY);
191
0
      continue;
192
0
    }
193
194
    /*
195
     * Next is keytype. See if that is all right.
196
     */
197
0
    token = nexttok(&line);
198
0
    if (token == NULL) {
199
0
      log_maybe(&nerr,
200
0
          "authreadkeys: no key type for key %d",
201
0
          keyno);
202
0
      continue;
203
0
    }
204
205
    /* We want to silently ignore keys where we do not
206
     * support the requested digest type. OTOH, we want to
207
     * make sure the file is well-formed.  That means we
208
     * have to process the line completely and have to
209
     * finally throw away the result... This is a bit more
210
     * work, but it also results in better error detection.
211
     */ 
212
#ifdef OPENSSL
213
    /*
214
     * The key type is the NID used by the message digest 
215
     * algorithm. There are a number of inconsistencies in
216
     * the OpenSSL database. We attempt to discover them
217
     * here and prevent use of inconsistent data later.
218
     */
219
    keytype = keytype_from_text(token, NULL);
220
    if (keytype == 0) {
221
      log_maybe(NULL,
222
          "authreadkeys: invalid type for key %d",
223
          keyno);
224
#  ifdef ENABLE_CMAC
225
    } else if (NID_cmac != keytype &&
226
        EVP_get_digestbynid(keytype) == NULL) {
227
      log_maybe(NULL,
228
          "authreadkeys: no algorithm for key %d",
229
          keyno);
230
      keytype = 0;
231
#  endif /* ENABLE_CMAC */
232
    }
233
#else /* !OPENSSL follows */
234
    /*
235
     * The key type is unused, but is required to be 'M' or
236
     * 'm' for compatibility.
237
     */
238
0
    if (!(*token == 'M' || *token == 'm')) {
239
0
      log_maybe(NULL,
240
0
          "authreadkeys: invalid type for key %d",
241
0
          keyno);
242
0
      keytype = 0;
243
0
    } else {
244
0
      keytype = KEY_TYPE_MD5;
245
0
    }
246
0
#endif  /* !OPENSSL */
247
248
    /*
249
     * Finally, get key and insert it. If it is longer than 20
250
     * characters, it is a binary string encoded in hex;
251
     * otherwise, it is a text string of printable ASCII
252
     * characters.
253
     */
254
0
    token = nexttok(&line);
255
0
    if (token == NULL) {
256
0
      log_maybe(&nerr,
257
0
          "authreadkeys: no key for key %d", keyno);
258
0
      continue;
259
0
    }
260
0
    next = NULL;
261
0
    len = strlen(token);
262
0
    if (len <= 20) { /* Bug 2537 */
263
0
      next = emalloc(sizeof(KeyDataT) + len);
264
0
      next->keyacclist = NULL;
265
0
      next->keyid   = keyno;
266
0
      next->keytype = keytype;
267
0
      next->seclen  = len;
268
0
      memcpy(next->secbuf, token, len);
269
0
    } else {
270
0
      static const char hex[] = "0123456789abcdef";
271
0
      u_char  temp;
272
0
      char  *ptr;
273
0
      size_t  jlim;
274
275
0
      jlim = min(len, 2 * sizeof(keystr));
276
0
      for (j = 0; j < jlim; j++) {
277
0
        ptr = strchr(hex, tolower((unsigned char)token[j]));
278
0
        if (ptr == NULL)
279
0
          break; /* abort decoding */
280
0
        temp = (u_char)(ptr - hex);
281
0
        if (j & 1)
282
0
          keystr[j / 2] |= temp;
283
0
        else
284
0
          keystr[j / 2] = temp << 4;
285
0
      }
286
0
      if (j < jlim) {
287
0
        log_maybe(&nerr,
288
0
            "authreadkeys: invalid hex digit for key %d",
289
0
            keyno);
290
0
        continue;
291
0
      }
292
0
      len = jlim/2; /* hmmmm.... what about odd length?!? */
293
0
      next = emalloc(sizeof(KeyDataT) + len);
294
0
      next->keyacclist = NULL;
295
0
      next->keyid   = keyno;
296
0
      next->keytype = keytype;
297
0
      next->seclen  = len;
298
0
      memcpy(next->secbuf, keystr, len);
299
0
    }
300
301
0
    token = nexttok(&line);
302
0
    if (token != NULL) { /* A comma-separated IP access list */
303
0
      char *tp = token;
304
305
0
      while (tp) {
306
0
        char *i;
307
0
        char *snp;  /* subnet text pointer */
308
0
        unsigned int snbits;
309
0
        sockaddr_u addr;
310
311
0
        i = strchr(tp, (int)',');
312
0
        if (i) {
313
0
          *i = '\0';
314
0
        }
315
0
        snp = strchr(tp, (int)'/');
316
0
        if (snp) {
317
0
          char *sp;
318
319
0
          *snp++ = '\0';
320
0
          snbits = 0;
321
0
          sp = snp;
322
323
0
          while (*sp != '\0') {
324
0
            if (!isdigit((unsigned char)*sp))
325
0
                break;
326
0
            if (snbits > 1000)
327
0
                break; /* overflow */
328
0
            snbits = 10 * snbits + (*sp++ - '0');       /* ascii dependent */
329
0
          }
330
0
          if (*sp != '\0') {
331
0
            log_maybe(&nerr,
332
0
                "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d",
333
0
                sp, snp, keyno);
334
0
            goto nextip;
335
0
          }
336
0
        } else {
337
0
          snbits = UINT_MAX;
338
0
        }
339
340
0
        if (is_ip_address(tp, AF_UNSPEC, &addr)) {
341
          /* Make sure that snbits is valid for addr */
342
0
            if ((snbits < UINT_MAX) &&
343
0
          ( (IS_IPV4(&addr) && snbits > 32) ||
344
0
            (IS_IPV6(&addr) && snbits > 128))) {
345
0
            log_maybe(NULL,
346
0
                "authreadkeys: excessive subnet mask <%s/%s> for key %d",
347
0
                tp, snp, keyno);
348
0
            }
349
0
            next->keyacclist = keyacc_new_push(
350
0
          next->keyacclist, &addr, snbits);
351
0
        } else {
352
0
          log_maybe(&nerr,
353
0
              "authreadkeys: invalid IP address <%s> for key %d",
354
0
              tp, keyno);
355
0
        }
356
357
0
      nextip:
358
0
        if (i) {
359
0
          tp = i + 1;
360
0
        } else {
361
0
          tp = 0;
362
0
        }
363
0
      }
364
0
    }
365
366
    /* check if this has to be weeded out... */
367
0
    if (0 == keytype) {
368
0
      free_keydata(next);
369
0
      next = NULL;
370
0
      continue;
371
0
    }
372
    
373
0
    INSIST(NULL != next);
374
0
    next->next = list;
375
0
    list = next;
376
0
  }
377
0
  fclose(fp);
378
0
  if (nerr > 0) {
379
0
    const char * why = "";
380
0
    if (nerr > nerr_maxlimit)
381
0
      why = " (emergency break)";
382
0
    msyslog(LOG_ERR,
383
0
      "authreadkeys: rejecting file '%s' after %u error(s)%s",
384
0
      file, nerr, why);
385
0
    goto onerror;
386
0
  }
387
388
  /* first remove old file-based keys */
389
0
  auth_delkeys();
390
  /* insert the new key material */
391
0
  while (NULL != (next = list)) {
392
0
    list = next->next;
393
0
    MD5auth_setkey(next->keyid, next->keytype,
394
0
             next->secbuf, next->seclen, next->keyacclist);
395
0
    next->keyacclist = NULL; /* consumed by MD5auth_setkey */
396
0
    free_keydata(next);
397
0
  }
398
0
  return (1);
399
400
0
  onerror:
401
  /* Mop up temporary storage before bailing out. */
402
0
  while (NULL != (next = list)) {
403
0
    list = next->next;
404
0
    free_keydata(next);
405
0
  }
406
0
  return (0);
407
0
}