Coverage Report

Created: 2025-07-18 06:53

/src/ntpsec/libntp/msyslog.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * msyslog - either send a message to the terminal or print it on
3
 *       the standard output.
4
 *
5
 * Converted to use varargs, much better ... jks
6
 */
7
8
#include "config.h"
9
10
#include <limits.h>
11
#include <sys/types.h>
12
#include <time.h>
13
#include <unistd.h>
14
#include <stdio.h>
15
#include <string.h>
16
17
#include "ntp.h"
18
#include "ntp_debug.h"
19
#include "ntp_stdlib.h"
20
#include "ntp_syslog.h"
21
#include "lib_strbuf.h"
22
23
/* start out with syslog and stderr, otherwise startup errors lost */
24
bool    syslogit = true;        /* log messages to syslog */
25
bool    termlogit = true;       /* duplicate to stdout/err */
26
bool  termlogit_pid = true;
27
bool  msyslog_include_timestamp = true;
28
29
static FILE * syslog_file;
30
static char * syslog_fname;
31
static char * syslog_abs_fname;
32
33
#ifdef DEBUG
34
int   debug;
35
#endif // DEBUG
36
37
/* Counters */
38
struct log_counters {   /* LOG_EMERG, LOG_ALERT, LOG_CRIT not used */
39
  uint64_t errors;      /* LOG_ERR */
40
  uint64_t warnings;    /* LOG_WARNING */
41
  uint64_t others;      /* LOG_NOTICE, LOG_INFO, and LOG_DEBUG */
42
};
43
struct log_counters log_cnt;
44
45
/* libntp default ntp_syslogmask is all bits lit */
46
0
#define INIT_NTP_SYSLOGMASK ~(uint32_t)0
47
uint32_t ntp_syslogmask = INIT_NTP_SYSLOGMASK;
48
49
extern  char *  progname;
50
51
/* Declare the local functions */
52
505
#define TIMESTAMP_LEN  128
53
static void humanlogtime(char buf[TIMESTAMP_LEN]);
54
static void addto_syslog  (int, const char *);
55
56
57
/* We don't want to clutter up the log with the year and day of the week,
58
   etc.; just the minimal date and time.  */
59
static void
60
humanlogtime(char buf[TIMESTAMP_LEN])
61
505
{
62
505
  time_t    cursec;
63
505
  struct tm tmbuf, *tm;
64
65
505
  cursec = time(NULL);
66
505
  tm = localtime_r(&cursec, &tmbuf);
67
505
  if (!tm) {
68
0
    strlcpy(buf, "-- --- --:--:--", TIMESTAMP_LEN);
69
0
    return;
70
0
  }
71
72
#ifdef ENABLE_CLASSIC_MODE
73
  const char * const months[12] = {
74
      "Jan", "Feb", "Mar", "Apr", "May", "Jun",
75
      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
76
  };
77
78
  snprintf(buf, TIMESTAMP_LEN, "%2d %s %02d:%02d:%02d",
79
     tm->tm_mday, months[tm->tm_mon],
80
     tm->tm_hour, tm->tm_min, tm->tm_sec);
81
#else
82
  /* ISO 8601 is a better format, sort order equals time order */
83
505
  snprintf(buf, TIMESTAMP_LEN, "%04d-%02d-%02dT%02d:%02d:%02d",
84
505
     tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
85
505
     tm->tm_hour, tm->tm_min, tm->tm_sec);
86
505
#endif /* ENABLE_CLASSIC_MODE */
87
505
}
88
89
90
/*
91
 * addto_syslog()
92
 * This routine adds the contents of a buffer to the syslog or an
93
 * application-specific logfile.
94
 */
95
static void
96
addto_syslog(
97
  int   level,
98
  const char *  msg
99
  )
100
505
{
101
505
  static char * prevcall_progname;
102
505
  static char * prog;
103
505
  const char  nl[] = "\n";
104
505
  const char  empty[] = "";
105
505
  FILE *    term_file;
106
505
  bool    log_to_term;
107
505
  bool    log_to_file;
108
505
  int   pid;
109
505
  const char *  nl_or_empty;
110
505
  const char *  human_time;
111
505
  char            tbuf[TIMESTAMP_LEN];
112
113
  /* setup program basename static var prog if needed */
114
505
  if (progname != prevcall_progname) {
115
1
    prevcall_progname = progname;
116
1
    prog = strrchr(progname, DIR_SEP);
117
1
    if (prog != NULL) {
118
0
      prog++;
119
1
    } else {
120
1
      prog = progname;
121
1
    }
122
1
  }
123
124
505
  log_to_term = termlogit;
125
505
  log_to_file = false;
126
505
  if (syslogit)
127
505
    syslog(level, "%s", msg);
128
0
  else
129
0
    if (syslog_file != NULL)
130
0
      log_to_file = true;
131
505
#if defined(DEBUG) && DEBUG
132
505
  if (debug > 0) /* SPECIAL DEBUG */
133
0
    log_to_term = true;
134
505
#endif
135
505
  if (!(log_to_file || log_to_term))
136
0
    return;
137
138
  /* syslog() adds the timestamp, name, and pid */
139
505
  if (msyslog_include_timestamp) {
140
505
    humanlogtime(tbuf);
141
505
    human_time = tbuf;
142
505
  } else /* suppress gcc pot. uninit. warning */
143
0
    human_time = NULL;
144
505
  if (termlogit_pid || log_to_file)
145
505
    pid = getpid();
146
0
  else  /* suppress gcc pot. uninit. warning */
147
0
    pid = -1;
148
149
  /* syslog() adds trailing \n if not present */
150
505
  if ('\n' != msg[strlen(msg) - 1]) {
151
505
    nl_or_empty = nl;
152
505
  } else {
153
0
    nl_or_empty = empty;
154
0
}
155
156
505
  if (log_to_term) {
157
505
    term_file = (level <= LOG_ERR)
158
505
        ? stderr
159
505
        : stdout;
160
505
    if (msyslog_include_timestamp)
161
505
      fprintf(term_file, "%s ", human_time);
162
505
    if (termlogit_pid)
163
505
      fprintf(term_file, "%s[%d]: ", prog, pid);
164
505
    fprintf(term_file, "%s%s", msg, nl_or_empty);
165
505
    fflush(term_file);
166
505
  }
167
168
505
  if (log_to_file) {
169
    /*
170
     * Thread-safe write, the de-facto way.  It's not
171
     * actually guaranteed by standards that a write of
172
     * PIPE_BUF chars or less is atomic anywhere but on a
173
     * pipe.  In ancient times this was 512 and happened to
174
     * be equal to the usual size of a hardware disk sector,
175
     * which was what really bounded atomicity. The actual
176
     * answer under POSIX is SSIZE_MAX, which is far larger
177
     * than we want or need to allocate here.
178
     */
179
0
    char buf[PIPE_BUF];
180
0
    buf[0] = '\0';
181
0
    if (msyslog_include_timestamp)
182
0
      snprintf(buf, sizeof(buf), "%s ", human_time);
183
0
    snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf) - 1,
184
0
       "%s[%d]: %s%s", prog, pid, msg, nl_or_empty);
185
0
    IGNORE(write(fileno(syslog_file), buf, strlen(buf)));
186
0
  }
187
505
}
188
189
190
void
191
msyslog(
192
  int   level,
193
  const char *  fmt,
194
  ...
195
  )
196
505
{
197
505
  char  buf[1024];
198
505
  va_list ap;
199
200
505
  switch (level) {
201
505
    case LOG_ERR:
202
505
      log_cnt.errors++;
203
505
      break;
204
0
    case LOG_WARNING:
205
0
      log_cnt.warnings++;
206
0
      break;
207
0
    case LOG_NOTICE:  /* FALLTHROUGH */
208
0
    case LOG_INFO:    /* FALLTHROUGH */
209
0
    case LOG_DEBUG:
210
0
      log_cnt.others++;
211
0
      break;
212
0
    default:
213
0
      break;
214
505
  }
215
216
505
  va_start(ap, fmt);
217
505
  vsnprintf(buf, sizeof(buf), fmt, ap);
218
505
  va_end(ap);
219
505
  addto_syslog(level, buf);
220
505
}
221
222
223
/*
224
  Do we log this sundry error?
225
*/
226
void
227
maybe_log(
228
  struct do_we_log *maybe,
229
  int   level,
230
  const char *  fmt,
231
  ...
232
  )
233
0
{
234
0
  struct timespec now;  /* time of current attempted print */
235
0
  char  buf[PIPE_BUF];
236
0
  va_list ap;
237
238
0
  if (NULL != maybe) {
239
0
    clock_gettime(CLOCK_MONOTONIC, &now);
240
0
    if (0 == maybe->last) {
241
      /* first time */
242
0
      maybe->last = now.tv_sec;
243
0
    } else {
244
0
      time_t interval_fp = now.tv_sec - maybe->last;
245
0
      double since_last = ((double)interval_fp)/3600.0;
246
0
      maybe->last = now.tv_sec;
247
0
      maybe->score *= exp(-since_last/maybe->c_decay);
248
0
      if (maybe->c_limit < maybe->score) {
249
0
        return;
250
0
      }
251
0
    }
252
0
    maybe->score += 1.0/maybe->c_decay;  /* only count the ones we print */
253
0
#if (1)  // Do we reject logging attempts when argument 0 is NULL?
254
0
  } else {
255
0
    return;
256
0
#endif
257
0
  }
258
259
0
  va_start(ap, fmt);
260
0
  vsnprintf(buf, sizeof(buf), fmt, ap);
261
0
  va_end(ap);
262
0
  addto_syslog(level, buf);
263
0
}
264
265
266
/*
267
 * Initialize the logging
268
 *
269
 * Called once per process, including forked children.
270
 */
271
void
272
init_logging(
273
  const char *  name,
274
  uint32_t    def_syslogmask,
275
  int   is_daemon
276
  )
277
0
{
278
0
  static bool was_daemon;
279
0
  const char *  cp;
280
0
  const char *  pname;
281
282
  /*
283
   * ntpd defaults to only logging sync-category events, when
284
   * NLOG() is used to conditionalize.  Other libntp clients
285
   * leave it alone so that all NLOG() conditionals will fire.
286
   * This presumes all bits lit in ntp_syslogmask can't be
287
   * configured via logconfig and all lit is thereby a sentinel
288
   * that ntp_syslogmask is still at its default from libntp,
289
   * keeping in mind this function is called in forked children
290
   * where it has already been called in the parent earlier.
291
   * Forked children pass 0 for def_syslogmask.
292
   */
293
0
  if (INIT_NTP_SYSLOGMASK == ntp_syslogmask &&
294
0
      0 != def_syslogmask)
295
0
    ntp_syslogmask = def_syslogmask; /* set more via logconfig */
296
297
  /*
298
   * Logging.  This may actually work on the gizmo board.  Find a name
299
   * to log with by using the basename
300
   */
301
0
  cp = strrchr(name, DIR_SEP);
302
0
  if (NULL == cp) {
303
0
    pname = name;
304
0
  } else {
305
0
    pname = 1 + cp; /* skip DIR_SEP */
306
0
  }
307
0
  progname = estrdup(pname);
308
309
0
  if (is_daemon)
310
0
    was_daemon = true;
311
# ifndef LOG_DAEMON
312
  openlog(progname, LOG_PID);
313
# else /* LOG_DAEMON */
314
315
0
#  ifndef LOG_NTP
316
0
# define  LOG_NTP LOG_DAEMON
317
0
#  endif
318
0
  openlog(progname, LOG_PID | LOG_NDELAY, (was_daemon)
319
0
                ? LOG_NTP
320
0
                : 0);
321
0
#  ifdef DEBUG
322
0
  if (debug) /* SPECIAL DEBUG */
323
0
    setlogmask(LOG_UPTO(LOG_DEBUG));
324
0
  else
325
0
#  endif /* DEBUG */
326
0
    setlogmask(LOG_UPTO(LOG_DEBUG)); /* @@@ was INFO */
327
0
# endif /* LOG_DAEMON */
328
0
}
329
330
331
/*
332
 * change_logfile()
333
 *
334
 * Used to change from syslog to a logfile, or from one logfile to
335
 * another, and to reopen logfiles after forking.  On systems where
336
 * ntpd forks, deals with converting relative logfile paths to
337
 * absolute (root-based) because we reopen logfiles after the current
338
 * directory has changed.
339
 */
340
int
341
change_logfile(
342
  const char *  fname,
343
  bool    leave_crumbs
344
  )
345
0
{
346
0
  FILE *    new_file;
347
0
  const char *  log_fname;
348
0
  char *    abs_fname;
349
0
  char    curdir[512];
350
0
  size_t    cd_octets;
351
0
  size_t    octets;
352
353
  //REQUIRE(fname != NULL);
354
0
  log_fname = fname;
355
356
  /*
357
   * In a forked child of a parent which is logging to a file
358
   * instead of syslog, syslog_file will be NULL and both
359
   * syslog_fname and syslog_abs_fname will be non-NULL.
360
   * If we are given the same filename previously opened
361
   * and it's still open, there's nothing to do here.
362
   */
363
0
  if (syslog_file != NULL && syslog_fname != NULL &&
364
0
      0 == strcmp(syslog_fname, log_fname)) {
365
0
    return 0;
366
0
  }
367
368
0
  if (0 == strcmp(log_fname, "stderr")) {
369
0
    new_file = stderr;
370
0
    abs_fname = estrdup(log_fname);
371
0
  } else if (0 == strcmp(log_fname, "stdout")) {
372
0
    new_file = stdout;
373
0
    abs_fname = estrdup(log_fname);
374
0
  } else {
375
0
    if (syslog_fname != NULL &&
376
0
        0 == strcmp(log_fname, syslog_fname)) {
377
0
      log_fname = syslog_abs_fname;
378
0
    }
379
0
    if (log_fname != syslog_abs_fname &&
380
0
        DIR_SEP != log_fname[0] &&
381
0
        0 != strcmp(log_fname, "stderr") &&
382
0
        0 != strcmp(log_fname, "stdout") &&
383
0
        NULL != getcwd(curdir, sizeof(curdir))) {
384
0
      cd_octets = strnlen(curdir, sizeof(curdir));
385
      /* trim any trailing '/' */
386
0
      if (cd_octets > 1 &&
387
0
          DIR_SEP == curdir[cd_octets - 1])
388
0
        cd_octets--;
389
0
      octets = cd_octets;
390
0
      octets += 1;  /* separator '/' */
391
0
      octets += strlen(log_fname);
392
0
      octets += 1;  /* NUL terminator */
393
0
      abs_fname = emalloc(octets);
394
0
      snprintf(abs_fname, octets, "%.*s%c%s",
395
0
         (int)cd_octets, curdir, DIR_SEP,
396
0
         log_fname);
397
0
    } else
398
0
      abs_fname = estrdup(log_fname);
399
0
    DPRINT(1, ("attempting to open log %s\n", abs_fname));
400
0
    new_file = fopen(abs_fname, "a");
401
0
  }
402
403
0
  if (NULL == new_file) {
404
0
    free(abs_fname);
405
0
    return -1;
406
0
  }
407
408
  /* leave a pointer in the old log */
409
0
  if (leave_crumbs && (syslogit || log_fname != syslog_abs_fname))
410
0
    msyslog(LOG_NOTICE, "LOG: switching logging to file %s",
411
0
      abs_fname);
412
413
0
  if (syslog_file != NULL &&
414
0
      syslog_file != stderr && syslog_file != stdout &&
415
0
      fileno(syslog_file) != fileno(new_file)) {
416
0
    fclose(syslog_file);
417
0
  }
418
0
  syslog_file = new_file;
419
0
  if (log_fname == syslog_abs_fname) {
420
0
    free(abs_fname);
421
0
  } else {
422
0
    if (syslog_abs_fname != NULL &&
423
0
        syslog_abs_fname != syslog_fname) {
424
0
      free(syslog_abs_fname);
425
0
    }
426
0
    if (syslog_fname != NULL) {
427
0
      free(syslog_fname);
428
0
    }
429
0
    syslog_fname = estrdup(log_fname);
430
0
    syslog_abs_fname = abs_fname;
431
0
  }
432
0
  syslogit = false;
433
434
  /* This leaves something in the log file if you have errors
435
   * parsing ntp.conf and you switch to a log file.
436
   * Maybe this should happen only during initialization.
437
   *   two places: command line and ntp.conf
438
   */ 
439
0
  if (0<log_cnt.errors || 0<log_cnt.warnings) {
440
0
    msyslog(log_cnt.errors? LOG_ERR: LOG_WARNING,
441
0
    "LOG: %lu errors and %lu warnings in previous log file(s)",
442
0
    (unsigned long)log_cnt.errors, (unsigned long)log_cnt.warnings);
443
0
  }
444
445
0
  return 0;
446
0
}
447
448
449
/*
450
 * setup_logfile()
451
 *
452
 * Redirect logging to a file if requested with -l/--logfile or via
453
 * ntp.conf logfile directive.
454
 *
455
 * This routine is invoked three different times in the sequence of a
456
 * typical daemon ntpd with DNS lookups to do.  First it is invoked in
457
 * the original ntpd process, then again in the daemon after closing
458
 * all descriptors.  In both of those cases, ntp.conf has not been
459
 * processed, so only -l/--logfile will trigger logfile redirection in
460
 * those invocations.  Finally, if DNS names are resolved, the worker
461
 * child invokes this routine after its fork and close of all
462
 * descriptors.  In this case, ntp.conf has been processed and any
463
 * "logfile" directive needs to be honored in the child as well.
464
 */
465
void
466
setup_logfile(
467
  const char *  name
468
  )
469
0
{
470
0
  if (NULL == syslog_fname && NULL != name) {
471
0
    if (-1 == change_logfile(name, true))
472
0
      msyslog(LOG_ERR, "LOG: Cannot open log file %s, %s",
473
0
        name, strerror(errno));
474
0
    return ;
475
0
  }
476
0
  if (NULL == syslog_fname) {
477
0
    return;
478
0
  }
479
480
0
  if (-1 == change_logfile(syslog_fname, false))
481
0
    msyslog(LOG_ERR, "LOG: Cannot reopen log file %s, %s",
482
0
      syslog_fname, strerror(errno));
483
0
}
484
485
/*
486
 * check_logfile()
487
 *
488
 * reopen current logfile in case the old file has been renamed by logrotate
489
 * called on SIGHUP and hourly
490
 */
491
492
void
493
check_logfile(void)
494
0
{
495
0
  FILE *  new_file;
496
497
0
  if (NULL == syslog_file) {
498
0
    return;  /* no log file, no clutter */
499
0
  }
500
501
0
  new_file = fopen(syslog_fname, "a");
502
0
  if (NULL == new_file) {
503
0
    msyslog(LOG_ERR, "LOG: check_logfile: couldn't open %s %s",
504
0
                        syslog_fname, strerror(errno));
505
0
    return;
506
0
  }
507
508
  /* This is a hack to avoid cluttering the log if we would reuse
509
   * the same file all over again.
510
   * change_logfile compares filenos.  That doesn't work.
511
   * Can't check for a new file using a length of 0 since
512
   * newsyslog on FreeBSD puts a "logfile turned over" message there.
513
   * This seems to work.
514
   */
515
0
  if (ftell(syslog_file) == ftell(new_file)) {
516
0
    fclose(new_file);
517
0
    return;
518
0
  }
519
520
0
  msyslog(LOG_INFO, "LOG: check_logfile: closing old file");
521
0
  fclose(syslog_file);
522
0
  syslog_file = new_file;
523
0
  msyslog(LOG_INFO, "LOG: check_logfile: using %s", syslog_fname);
524
0
}
525
526
/* Hack because there are 2 APIs to strerror_r()  */
527
0
void ntp_strerror_r(int errnum, char *buf, size_t buflen) {
528
0
#ifdef STRERROR_CHAR
529
0
  char *answer = strerror_r(errnum, buf, buflen);
530
0
  if (answer != buf) {
531
0
    strlcpy(buf, answer, buflen);
532
0
  }
533
#else
534
  int answer = strerror_r(errnum, buf, buflen);
535
  UNUSED_LOCAL(answer);
536
#endif
537
0
}
538