Coverage Report

Created: 2026-02-26 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ntp-dev/libntp/authreadkeys.c
Line
Count
Source
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[AUTHPWD_MAXSECLEN];
146
0
  size_t  len;
147
0
  u_int   nerr;
148
0
  KeyDataT *list = NULL;
149
0
  KeyDataT *next = NULL;
150
151
  /*
152
   * Open file.  Complain and return if it can't be opened.
153
   */
154
0
  fp = fopen(file, "r");
155
0
  if (fp == NULL) {
156
0
    msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
157
0
        file);
158
0
    goto onerror;
159
0
  }
160
0
  INIT_SSL();
161
162
  /*
163
   * Now read lines from the file, looking for key entries. Put
164
   * the data into temporary store for later propagation to avoid
165
   * two-pass processing.
166
   */
167
0
  nerr = 0;
168
0
  while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
169
0
    if (nerr > nerr_maxlimit)
170
0
      break;
171
0
    token = nexttok(&line);
172
0
    if (token == NULL)
173
0
      continue;
174
175
    /*
176
     * First is key number.  See if it is okay.
177
     */
178
0
    keyno = atoi(token);
179
0
    if (keyno < 1) {
180
0
      log_maybe(&nerr,
181
0
          "authreadkeys: cannot change key %s",
182
0
          token);
183
0
      continue;
184
0
    }
185
186
0
    if (keyno > NTP_MAXKEY) {
187
0
      log_maybe(&nerr,
188
0
          "authreadkeys: key %s > %d reserved for Autokey",
189
0
          token, NTP_MAXKEY);
190
0
      continue;
191
0
    }
192
193
    /*
194
     * Next is keytype. See if that is all right.
195
     */
196
0
    token = nexttok(&line);
197
0
    if (token == NULL) {
198
0
      log_maybe(&nerr,
199
0
          "authreadkeys: no key type for key %d",
200
0
          keyno);
201
0
      continue;
202
0
    }
203
204
    /* We want to silently ignore keys where we do not
205
     * support the requested digest type. OTOH, we want to
206
     * make sure the file is well-formed.  That means we
207
     * have to process the line completely and have to
208
     * finally throw away the result... This is a bit more
209
     * work, but it also results in better error detection.
210
     */
211
0
#ifdef OPENSSL
212
    /*
213
     * The key type is the NID used by the message digest
214
     * algorithm. There are a number of inconsistencies in
215
     * the OpenSSL database. We attempt to discover them
216
     * here and prevent use of inconsistent data later.
217
     */
218
0
    keytype = keytype_from_text(token, NULL);
219
0
    if (keytype == 0) {
220
0
      log_maybe(NULL,
221
0
          "authreadkeys: unsupported type %s for key %d",
222
0
          token, keyno);
223
0
#  ifdef ENABLE_CMAC
224
0
    } else if (NID_cmac != keytype &&
225
0
        EVP_get_digestbynid(keytype) == NULL) {
226
0
      log_maybe(NULL,
227
0
          "authreadkeys: no algorithm for %s key %d",
228
0
          token, keyno);
229
0
      keytype = 0;
230
0
#  endif /* ENABLE_CMAC */
231
0
    }
232
#else /* !OPENSSL follows */
233
    /*
234
     * The key type is unused, but is required to be 'M' or
235
     * 'm' for compatibility.
236
     */
237
    if (! (toupper(*token) == 'M')) {
238
      log_maybe(NULL,
239
          "authreadkeys: invalid type for key %d",
240
          keyno);
241
      keytype = 0;
242
    } else {
243
      keytype = KEY_TYPE_MD5;
244
    }
245
#endif  /* !OPENSSL */
246
247
    /*
248
     * Finally, get key and insert it. If it is longer than 20
249
     * characters, it is a binary string encoded in hex;
250
     * otherwise, it is a text string of printable ASCII
251
     * characters.
252
     */
253
0
    token = nexttok(&line);
254
0
    if (token == NULL) {
255
0
      log_maybe(&nerr,
256
0
          "authreadkeys: no key for key %d", keyno);
257
0
      continue;
258
0
    }
259
0
    next = NULL;
260
0
    len = authdecodepw(keystr, sizeof(keystr), token, AUTHPWD_UNSPEC);
261
0
    if (len > sizeof(keystr)) {
262
0
      switch (errno) {
263
0
      case ENOMEM:
264
0
        log_maybe(&nerr,
265
0
            "authreadkeys: passwd too long for key %d",
266
0
            keyno);
267
0
        break;
268
0
      case EINVAL:
269
0
        log_maybe(&nerr,
270
0
            "authreadkeys: passwd has bad char for key %d",
271
0
            keyno);
272
0
        break;
273
0
#ifdef DEBUG
274
0
      default:
275
0
        log_maybe(&nerr,
276
0
            "authreadkeys: unexpected errno %d for key %d: %m",
277
0
            errno, keyno);
278
0
        break;
279
0
#endif
280
0
      }
281
0
      continue;
282
0
    }
283
0
    next = emalloc(sizeof(KeyDataT) + len);
284
0
    next->keyacclist = NULL;
285
0
    next->keyid   = keyno;
286
0
    next->keytype = keytype;
287
0
    next->seclen  = len;
288
0
    memcpy(next->secbuf, keystr, len);
289
290
0
    token = nexttok(&line);
291
0
    if (token != NULL) { /* A comma-separated IP access list */
292
0
      char *tp = token;
293
294
0
      while (tp) {
295
0
        char *i;
296
0
        char *snp;  /* subnet text pointer */
297
0
        unsigned int snbits;
298
0
        sockaddr_u addr;
299
300
0
        i = strchr(tp, (int)',');
301
0
        if (i) {
302
0
          *i = '\0';
303
0
        }
304
0
        snp = strchr(tp, (int)'/');
305
0
        if (snp) {
306
0
          char *sp;
307
308
0
          *snp++ = '\0';
309
0
          snbits = 0;
310
0
          sp = snp;
311
312
0
          while (*sp != '\0') {
313
0
            if (!isdigit((unsigned char)*sp))
314
0
                break;
315
0
            if (snbits > 1000)
316
0
                break; /* overflow */
317
0
            snbits = 10 * snbits + (*sp++ - '0');       /* ascii dependent */
318
0
          }
319
0
          if (*sp != '\0') {
320
0
            log_maybe(&nerr,
321
0
                "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d",
322
0
                sp, snp, keyno);
323
0
            goto nextip;
324
0
          }
325
0
        } else {
326
0
          snbits = UINT_MAX;
327
0
        }
328
329
0
        if (is_ip_address(tp, AF_UNSPEC, &addr)) {
330
          /* Make sure that snbits is valid for addr */
331
0
            if ((snbits < UINT_MAX) &&
332
0
          ( (IS_IPV4(&addr) && snbits > 32) ||
333
0
            (IS_IPV6(&addr) && snbits > 128))) {
334
0
            log_maybe(NULL,
335
0
                "authreadkeys: excessive subnet mask <%s/%s> for key %d",
336
0
                tp, snp, keyno);
337
0
            }
338
0
            next->keyacclist = keyacc_new_push(
339
0
          next->keyacclist, &addr, snbits);
340
0
        } else {
341
0
          log_maybe(&nerr,
342
0
              "authreadkeys: invalid IP address <%s> for key %d",
343
0
              tp, keyno);
344
0
        }
345
346
0
      nextip:
347
0
        if (i) {
348
0
          tp = i + 1;
349
0
        } else {
350
0
          tp = 0;
351
0
        }
352
0
      }
353
0
    }
354
355
    /* check if this has to be weeded out... */
356
0
    if (0 == keytype) {
357
0
      free_keydata(next);
358
0
      next = NULL;
359
0
      continue;
360
0
    }
361
362
0
    DEBUG_INSIST(NULL != next);
363
0
#if defined(OPENSSL) && defined(ENABLE_CMAC)
364
0
    if (NID_cmac == keytype && len < 16) {
365
0
      msyslog(LOG_WARNING, CMAC " keys are 128 bits, "
366
0
        "zero-extending key %u by %u bits",
367
0
        (u_int)keyno, 8 * (16 - (u_int)len));
368
0
    }
369
0
#endif  /* OPENSSL && ENABLE_CMAC */
370
0
    next->next = list;
371
0
    list = next;
372
0
  }
373
0
  fclose(fp);
374
0
  if (nerr > 0) {
375
0
    const char * why = "";
376
377
0
    if (nerr > nerr_maxlimit)
378
0
      why = " (emergency break)";
379
0
    msyslog(LOG_ERR,
380
0
      "authreadkeys: rejecting file '%s' after %u error(s)%s",
381
0
      file, nerr, why);
382
0
    goto onerror;
383
0
  }
384
385
  /* first remove old file-based keys */
386
0
  auth_delkeys();
387
  /* insert the new key material */
388
0
  while (NULL != (next = list)) {
389
0
    list = next->next;
390
0
    MD5auth_setkey(next->keyid, next->keytype,
391
0
             next->secbuf, next->seclen, next->keyacclist);
392
0
    next->keyacclist = NULL; /* consumed by MD5auth_setkey */
393
0
    free_keydata(next);
394
0
  }
395
0
  return (1);
396
397
0
  onerror:
398
  /* Mop up temporary storage before bailing out. */
399
0
  while (NULL != (next = list)) {
400
0
    list = next->next;
401
0
    free_keydata(next);
402
0
  }
403
0
  return (0);
404
0
}