Path: blob/main/crypto/krb5/src/lib/kadm5/logger.c
39537 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/* lib/kadm5/logger.c */2/*3* Copyright 1995, 2007 by the Massachusetts Institute of Technology.4* All Rights Reserved.5*6* Export of this software from the United States of America may7* require a specific license from the United States Government.8* It is the responsibility of any person or organization contemplating9* export to obtain such a license before exporting.10*11* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and12* distribute this software and its documentation for any purpose and13* without fee is hereby granted, provided that the above copyright14* notice appear in all copies and that both that copyright notice and15* this permission notice appear in supporting documentation, and that16* the name of M.I.T. not be used in advertising or publicity pertaining17* to distribution of the software without specific, written prior18* permission. Furthermore if you modify this software you must label19* your software as modified software and not distribute it in such a20* fashion that it might be confused with the original M.I.T. software.21* M.I.T. makes no representations about the suitability of22* this software for any purpose. It is provided "as is" without express23* or implied warranty.24*/2526/* KADM5 wants non-syslog log files to contain syslog-like entries */27#define VERBOSE_LOGS2829/*30* logger.c - Handle logging functions for those who want it.31*/32#include "k5-int.h"33#include "adm_proto.h"34#include "com_err.h"35#include <stdio.h>36#include <ctype.h>37#include <syslog.h>38#include <stdarg.h>3940#define KRB5_KLOG_MAX_ERRMSG_SIZE 204841#ifndef MAXHOSTNAMELEN42#define MAXHOSTNAMELEN 25643#endif /* MAXHOSTNAMELEN */4445/* This is to assure that we have at least one match in the syslog stuff */46#ifndef LOG_AUTH47#define LOG_AUTH 048#endif /* LOG_AUTH */49#ifndef LOG_ERR50#define LOG_ERR 051#endif /* LOG_ERR */5253#define lspec_parse_err_1 _("%s: cannot parse <%s>\n")54#define lspec_parse_err_2 _("%s: warning - logging entry syntax error\n")55#define log_file_err _("%s: error writing to %s\n")56#define log_device_err _("%s: error writing to %s device\n")57#define log_ufo_string "?\?\?" /* nb: avoid trigraphs */58#define log_emerg_string _("EMERGENCY")59#define log_alert_string _("ALERT")60#define log_crit_string _("CRITICAL")61#define log_err_string _("Error")62#define log_warning_string _("Warning")63#define log_notice_string _("Notice")64#define log_info_string _("info")65#define log_debug_string _("debug")6667/*68* Output logging.69*70* Output logging is now controlled by the configuration file. We can specify71* the following syntaxes under the [logging]->entity specification.72* FILE<opentype><pathname>73* SYSLOG[=<severity>[:<facility>]]74* STDERR75* CONSOLE76* DEVICE=<device-spec>77*78* Where:79* <opentype> is ":" for open/append, "=" for open/create.80* <pathname> is a valid path name.81* <severity> is one of: (default = ERR)82* EMERG83* ALERT84* CRIT85* ERR86* WARNING87* NOTICE88* INFO89* DEBUG90* <facility> is one of: (default = AUTH)91* KERN92* USER9394* DAEMON95* AUTH96* LPR97* NEWS98* UUCP99* CRON100* LOCAL0..LOCAL7101* <device-spec> is a valid device specification.102*/103struct log_entry {104enum log_type { K_LOG_FILE,105K_LOG_SYSLOG,106K_LOG_STDERR,107K_LOG_CONSOLE,108K_LOG_DEVICE,109K_LOG_NONE } log_type;110krb5_pointer log_2free;111union log_union {112struct log_file {113FILE *lf_filep;114char *lf_fname;115} log_file;116struct log_syslog {117int ls_facility;118} log_syslog;119struct log_device {120FILE *ld_filep;121char *ld_devname;122} log_device;123} log_union;124};125#define lfu_filep log_union.log_file.lf_filep126#define lfu_fname log_union.log_file.lf_fname127#define lsu_facility log_union.log_syslog.ls_facility128#define ldu_filep log_union.log_device.ld_filep129#define ldu_devname log_union.log_device.ld_devname130131struct log_control {132struct log_entry *log_entries;133int log_nentries;134char *log_whoami;135char *log_hostname;136krb5_boolean log_opened;137krb5_boolean log_debug;138};139140static struct log_control log_control = {141(struct log_entry *) NULL,1420,143(char *) NULL,144(char *) NULL,1450146};147static struct log_entry def_log_entry;148149/*150* These macros define any special processing that needs to happen for151* devices. For unix, of course, this is hardly anything.152*/153#define DEVICE_OPEN(d, m) fopen(d, m)154#define CONSOLE_OPEN(m) fopen("/dev/console", m)155#define DEVICE_PRINT(f, m) ((fprintf(f, "%s\r\n", m) >= 0) ? \156(fflush(f), 0) : \157-1)158#define DEVICE_CLOSE(d) fclose(d)159160/*161* klog_com_err_proc() - Handle com_err(3) messages as specified by the162* profile.163*/164static krb5_context err_context;165166static void167klog_com_err_proc(const char *whoami, long int code, const char *format, va_list ap)168#if !defined(__cplusplus) && (__GNUC__ > 2)169__attribute__((__format__(__printf__, 3, 0)))170#endif171;172173/*174* Write com_err() messages to the configured logging devices. Ignore whoami,175* as krb5_klog_init() already received a whoami value. If code is nonzero,176* log its error message (retrieved using err_context) and the formatted177* message at error severity. If code is zero, log the formatted message at178* informational severity.179*/180static void181klog_com_err_proc(const char *whoami, long int code, const char *format, va_list ap)182{183struct k5buf buf;184const char *emsg, *msg;185186if (format == NULL)187return;188189k5_buf_init_dynamic(&buf);190191if (code) {192/* Start with the error message and a separator. */193emsg = krb5_get_error_message(err_context, code);194k5_buf_add(&buf, emsg);195krb5_free_error_message(err_context, emsg);196k5_buf_add(&buf, " - ");197}198199/* Add the formatted message. */200k5_buf_add_vfmt(&buf, format, ap);201202msg = k5_buf_cstring(&buf);203if (msg != NULL)204krb5_klog_syslog(code ? LOG_ERR : LOG_INFO, "%s", msg);205206k5_buf_free(&buf);207}208209/*210* krb5_klog_init() - Initialize logging.211*212* This routine parses the syntax described above to specify destinations for213* com_err(3) or krb5_klog_syslog() messages generated by the caller.214*215* Parameters:216* kcontext - Kerberos context.217* ename - Entity name as it is to appear in the profile.218* whoami - Entity name as it is to appear in error output.219* do_com_err - Take over com_err(3) processing.220*221* Implicit inputs:222* stderr - This is where STDERR output goes.223*224* Implicit outputs:225* log_nentries - Number of log entries, both valid and invalid.226* log_control - List of entries (log_nentries long) which contains227* data for klog_com_err_proc() to use to determine228* where/how to send output.229*/230krb5_error_code231krb5_klog_init(krb5_context kcontext, char *ename, char *whoami, krb5_boolean do_com_err)232{233const char *logging_profent[3];234const char *logging_defent[3];235char **logging_specs;236int i, ngood, fd, append;237char *cp, *cp2;238char savec = '\0';239int error, debug;240int do_openlog, log_facility;241FILE *f = NULL;242243/* Initialize */244do_openlog = 0;245log_facility = 0;246247err_context = kcontext;248249/* Look up [logging]->debug in the profile to see if we should include250* debug messages for types other than syslog. Default to false. */251if (!profile_get_boolean(kcontext->profile, KRB5_CONF_LOGGING,252KRB5_CONF_DEBUG, NULL, 0, &debug))253log_control.log_debug = debug;254255/*256* Look up [logging]-><ename> in the profile. If that doesn't257* succeed, then look for [logging]->default.258*/259logging_profent[0] = KRB5_CONF_LOGGING;260logging_profent[1] = ename;261logging_profent[2] = (char *) NULL;262logging_defent[0] = KRB5_CONF_LOGGING;263logging_defent[1] = KRB5_CONF_DEFAULT;264logging_defent[2] = (char *) NULL;265logging_specs = (char **) NULL;266ngood = 0;267log_control.log_nentries = 0;268if (!profile_get_values(kcontext->profile,269logging_profent,270&logging_specs) ||271!profile_get_values(kcontext->profile,272logging_defent,273&logging_specs)) {274/*275* We have a match, so we first count the number of elements276*/277for (log_control.log_nentries = 0;278logging_specs[log_control.log_nentries];279log_control.log_nentries++);280281/*282* Now allocate our structure.283*/284log_control.log_entries = (struct log_entry *)285malloc(log_control.log_nentries * sizeof(struct log_entry));286if (log_control.log_entries) {287/*288* Scan through the list.289*/290for (i=0; i<log_control.log_nentries; i++) {291log_control.log_entries[i].log_type = K_LOG_NONE;292log_control.log_entries[i].log_2free = logging_specs[i];293/*294* The format is:295* <whitespace><data><whitespace>296* so, trim off the leading and trailing whitespace here.297*/298for (cp = logging_specs[i]; isspace((int) *cp); cp++);299for (cp2 = &logging_specs[i][strlen(logging_specs[i])-1];300isspace((int) *cp2); cp2--);301cp2++;302*cp2 = '\0';303/*304* Is this a file?305*/306if (!strncasecmp(cp, "FILE", 4)) {307/*308* Check for append/overwrite, then open the file.309*/310append = (cp[4] == ':') ? O_APPEND : 0;311if (append || cp[4] == '=') {312fd = open(&cp[5], O_CREAT | O_WRONLY | append,313S_IRUSR | S_IWUSR | S_IRGRP);314if (fd != -1)315f = fdopen(fd, append ? "a" : "w");316if (fd == -1 || f == NULL) {317fprintf(stderr,"Couldn't open log file %s: %s\n",318&cp[5], error_message(errno));319continue;320}321set_cloexec_file(f);322log_control.log_entries[i].lfu_filep = f;323log_control.log_entries[i].log_type = K_LOG_FILE;324log_control.log_entries[i].lfu_fname = &cp[5];325}326}327/*328* Is this a syslog?329*/330else if (!strncasecmp(cp, "SYSLOG", 6)) {331error = 0;332log_control.log_entries[i].lsu_facility = LOG_AUTH;333/*334* Is there a severify (which is now ignored) specified?335*/336if (cp[6] == ':') {337/*338* Find the end of the severity.339*/340cp2 = strchr(&cp[7], ':');341if (cp2) {342savec = *cp2;343*cp2 = '\0';344cp2++;345}346347/*348* If there is a facility present, then parse that.349*/350if (cp2) {351static const struct {352const char *name;353int value;354} facilities[] = {355{ "AUTH", LOG_AUTH },356#ifdef LOG_AUTHPRIV357{ "AUTHPRIV", LOG_AUTHPRIV },358#endif /* LOG_AUTHPRIV */359#ifdef LOG_KERN360{ "KERN", LOG_KERN },361#endif /* LOG_KERN */362#ifdef LOG_USER363{ "USER", LOG_USER },364#endif /* LOG_USER */365#ifdef LOG_MAIL366{ "MAIL", LOG_MAIL },367#endif /* LOG_MAIL */368#ifdef LOG_DAEMON369{ "DAEMON", LOG_DAEMON },370#endif /* LOG_DAEMON */371#ifdef LOG_FTP372{ "FTP", LOG_FTP },373#endif /* LOG_FTP */374#ifdef LOG_LPR375{ "LPR", LOG_LPR },376#endif /* LOG_LPR */377#ifdef LOG_NEWS378{ "NEWS", LOG_NEWS },379#endif /* LOG_NEWS */380#ifdef LOG_UUCP381{ "UUCP", LOG_UUCP },382#endif /* LOG_UUCP */383#ifdef LOG_CRON384{ "CRON", LOG_CRON },385#endif /* LOG_CRON */386#ifdef LOG_LOCAL0387{ "LOCAL0", LOG_LOCAL0 },388#endif /* LOG_LOCAL0 */389#ifdef LOG_LOCAL1390{ "LOCAL1", LOG_LOCAL1 },391#endif /* LOG_LOCAL1 */392#ifdef LOG_LOCAL2393{ "LOCAL2", LOG_LOCAL2 },394#endif /* LOG_LOCAL2 */395#ifdef LOG_LOCAL3396{ "LOCAL3", LOG_LOCAL3 },397#endif /* LOG_LOCAL3 */398#ifdef LOG_LOCAL4399{ "LOCAL4", LOG_LOCAL4 },400#endif /* LOG_LOCAL4 */401#ifdef LOG_LOCAL5402{ "LOCAL5", LOG_LOCAL5 },403#endif /* LOG_LOCAL5 */404#ifdef LOG_LOCAL6405{ "LOCAL6", LOG_LOCAL6 },406#endif /* LOG_LOCAL6 */407#ifdef LOG_LOCAL7408{ "LOCAL7", LOG_LOCAL7 },409#endif /* LOG_LOCAL7 */410};411unsigned int j;412413for (j = 0; j < sizeof(facilities)/sizeof(facilities[0]); j++)414if (!strcasecmp(cp2, facilities[j].name)) {415log_control.log_entries[i].lsu_facility = facilities[j].value;416break;417}418cp2--;419*cp2 = savec;420}421}422if (!error) {423log_control.log_entries[i].log_type = K_LOG_SYSLOG;424do_openlog = 1;425log_facility = log_control.log_entries[i].lsu_facility;426}427}428/*429* Is this a standard error specification?430*/431else if (!strcasecmp(cp, "STDERR")) {432log_control.log_entries[i].lfu_filep =433fdopen(fileno(stderr), "w");434if (log_control.log_entries[i].lfu_filep) {435log_control.log_entries[i].log_type = K_LOG_STDERR;436log_control.log_entries[i].lfu_fname =437"standard error";438}439}440/*441* Is this a specification of the console?442*/443else if (!strcasecmp(cp, "CONSOLE")) {444log_control.log_entries[i].ldu_filep =445CONSOLE_OPEN("a+");446if (log_control.log_entries[i].ldu_filep) {447set_cloexec_file(log_control.log_entries[i].ldu_filep);448log_control.log_entries[i].log_type = K_LOG_CONSOLE;449log_control.log_entries[i].ldu_devname = "console";450}451}452/*453* Is this a specification of a device?454*/455else if (!strncasecmp(cp, "DEVICE", 6)) {456/*457* We handle devices very similarly to files.458*/459if (cp[6] == '=') {460log_control.log_entries[i].ldu_filep =461DEVICE_OPEN(&cp[7], "w");462if (log_control.log_entries[i].ldu_filep) {463set_cloexec_file(log_control.log_entries[i].ldu_filep);464log_control.log_entries[i].log_type = K_LOG_DEVICE;465log_control.log_entries[i].ldu_devname = &cp[7];466}467}468}469/*470* See if we successfully parsed this specification.471*/472if (log_control.log_entries[i].log_type == K_LOG_NONE) {473fprintf(stderr, lspec_parse_err_1, whoami, cp);474fprintf(stderr, lspec_parse_err_2, whoami);475}476else477ngood++;478}479}480/*481* If we didn't find anything, then free our lists.482*/483if (ngood == 0) {484for (i=0; i<log_control.log_nentries; i++)485free(logging_specs[i]);486}487free(logging_specs);488}489/*490* If we didn't find anything, go for the default which is to log to491* the system log.492*/493if (ngood == 0) {494if (log_control.log_entries)495free(log_control.log_entries);496log_control.log_entries = &def_log_entry;497log_control.log_entries->log_type = K_LOG_SYSLOG;498log_control.log_entries->log_2free = (krb5_pointer) NULL;499log_facility = log_control.log_entries->lsu_facility = LOG_AUTH;500do_openlog = 1;501log_control.log_nentries = 1;502}503if (log_control.log_nentries) {504log_control.log_whoami = strdup(whoami);505log_control.log_hostname = (char *) malloc(MAXHOSTNAMELEN + 1);506if (log_control.log_hostname) {507if (gethostname(log_control.log_hostname, MAXHOSTNAMELEN) == -1) {508free(log_control.log_hostname);509log_control.log_hostname = NULL;510} else511log_control.log_hostname[MAXHOSTNAMELEN] = '\0';512}513if (do_openlog) {514openlog(whoami, LOG_NDELAY|LOG_PID, log_facility);515log_control.log_opened = 1;516}517if (do_com_err)518(void) set_com_err_hook(klog_com_err_proc);519}520return((log_control.log_nentries) ? 0 : ENOENT);521}522523/* Reset the context used by the com_err hook to retrieve error messages. */524void525krb5_klog_set_context(krb5_context kcontext)526{527err_context = kcontext;528}529530/*531* krb5_klog_close() - Close the logging context and free all data.532*/533void534krb5_klog_close(krb5_context kcontext)535{536int lindex;537(void) reset_com_err_hook();538for (lindex = 0; lindex < log_control.log_nentries; lindex++) {539switch (log_control.log_entries[lindex].log_type) {540case K_LOG_FILE:541case K_LOG_STDERR:542/*543* Files/standard error.544*/545fclose(log_control.log_entries[lindex].lfu_filep);546break;547case K_LOG_CONSOLE:548case K_LOG_DEVICE:549/*550* Devices (may need special handling)551*/552DEVICE_CLOSE(log_control.log_entries[lindex].ldu_filep);553break;554case K_LOG_SYSLOG:555/*556* System log.557*/558break;559default:560break;561}562if (log_control.log_entries[lindex].log_2free)563free(log_control.log_entries[lindex].log_2free);564}565if (log_control.log_entries != &def_log_entry)566free(log_control.log_entries);567log_control.log_entries = (struct log_entry *) NULL;568log_control.log_nentries = 0;569if (log_control.log_whoami)570free(log_control.log_whoami);571log_control.log_whoami = (char *) NULL;572if (log_control.log_hostname)573free(log_control.log_hostname);574log_control.log_hostname = (char *) NULL;575if (log_control.log_opened)576closelog();577}578579/*580* severity2string() - Convert a severity to a string.581*/582static const char *583severity2string(int severity)584{585int s;586const char *ss;587588s = severity & LOG_PRIMASK;589ss = log_ufo_string;590switch (s) {591case LOG_EMERG:592ss = log_emerg_string;593break;594case LOG_ALERT:595ss = log_alert_string;596break;597case LOG_CRIT:598ss = log_crit_string;599break;600case LOG_ERR:601ss = log_err_string;602break;603case LOG_WARNING:604ss = log_warning_string;605break;606case LOG_NOTICE:607ss = log_notice_string;608break;609case LOG_INFO:610ss = log_info_string;611break;612case LOG_DEBUG:613ss = log_debug_string;614break;615}616return(ss);617}618619/*620* krb5_klog_syslog() - Simulate the calling sequence of syslog(3), while621* also performing the logging redirection as specified622* by krb5_klog_init().623*/624static int625klog_vsyslog(int priority, const char *format, va_list arglist)626#if !defined(__cplusplus) && (__GNUC__ > 2)627__attribute__((__format__(__printf__, 2, 0)))628#endif629;630631static int632klog_vsyslog(int priority, const char *format, va_list arglist)633{634char outbuf[KRB5_KLOG_MAX_ERRMSG_SIZE];635int lindex;636char *syslogp;637char *cp;638time_t now;639size_t soff;640struct tm *tm;641642/*643* Format a syslog-esque message of the format:644*645* (verbose form)646* <date> <hostname> <id>[<pid>](<priority>): <message>647*648* (short form)649* <date> <message>650*/651cp = outbuf;652(void) time(&now);653654/*655* Format the date: mon dd hh:mm:ss656*/657tm = localtime(&now);658if (tm == NULL)659return(-1);660soff = strftime(outbuf, sizeof(outbuf), "%b %d %H:%M:%S", tm);661if (soff > 0)662cp += soff;663else664return(-1);665666#ifdef VERBOSE_LOGS667snprintf(cp, sizeof(outbuf) - (cp-outbuf), " %s %s[%ld](%s): ",668log_control.log_hostname ? log_control.log_hostname : "",669log_control.log_whoami ? log_control.log_whoami : "",670(long) getpid(),671severity2string(priority));672#else673snprintf(cp, sizeof(outbuf) - (cp-outbuf), " ");674#endif675syslogp = &outbuf[strlen(outbuf)];676677/* Now format the actual message */678vsnprintf(syslogp, sizeof(outbuf) - (syslogp - outbuf), format, arglist);679680/*681* If the user did not use krb5_klog_init() instead of dropping682* the request on the floor, syslog it - if it exists683*/684if (log_control.log_nentries == 0) {685/* Log the message with our header trimmed off */686syslog(priority, "%s", syslogp);687}688689/*690* Now that we have the message formatted, perform the output to each691* logging specification.692*/693for (lindex = 0; lindex < log_control.log_nentries; lindex++) {694/* Omit LOG_DEBUG messages for non-syslog outputs unless we are695* configured to include them. */696if (priority == LOG_DEBUG && !log_control.log_debug &&697log_control.log_entries[lindex].log_type != K_LOG_SYSLOG)698continue;699700switch (log_control.log_entries[lindex].log_type) {701case K_LOG_FILE:702case K_LOG_STDERR:703/*704* Files/standard error.705*/706if (fprintf(log_control.log_entries[lindex].lfu_filep, "%s\n",707outbuf) < 0) {708/* Attempt to report error */709fprintf(stderr, log_file_err, log_control.log_whoami,710log_control.log_entries[lindex].lfu_fname);711}712else {713fflush(log_control.log_entries[lindex].lfu_filep);714}715break;716case K_LOG_CONSOLE:717case K_LOG_DEVICE:718/*719* Devices (may need special handling)720*/721if (DEVICE_PRINT(log_control.log_entries[lindex].ldu_filep,722outbuf) < 0) {723/* Attempt to report error */724fprintf(stderr, log_device_err, log_control.log_whoami,725log_control.log_entries[lindex].ldu_devname);726}727break;728case K_LOG_SYSLOG:729/*730* System log.731*/732733/* Log the message with our header trimmed off */734syslog(priority, "%s", syslogp);735break;736default:737break;738}739}740return(0);741}742743int744krb5_klog_syslog(int priority, const char *format, ...)745{746int retval;747va_list pvar;748749va_start(pvar, format);750retval = klog_vsyslog(priority, format, pvar);751va_end(pvar);752return(retval);753}754755/*756* krb5_klog_reopen() - Close and reopen any open (non-syslog) log files.757* This function is called when a SIGHUP is received758* so that external log-archival utilities may759* alert the Kerberos daemons that they should get760* a new file descriptor for the give filename.761*/762void763krb5_klog_reopen(krb5_context kcontext)764{765int lindex;766FILE *f;767768/*769* Only logs which are actually files need to be closed770* and reopened in response to a SIGHUP771*/772for (lindex = 0; lindex < log_control.log_nentries; lindex++) {773if (log_control.log_entries[lindex].log_type == K_LOG_FILE) {774fclose(log_control.log_entries[lindex].lfu_filep);775/*776* In case the old logfile did not get moved out of the777* way, open for append to prevent squashing the old logs.778*/779f = fopen(log_control.log_entries[lindex].lfu_fname, "a+");780if (f) {781set_cloexec_file(f);782log_control.log_entries[lindex].lfu_filep = f;783} else {784fprintf(stderr, _("Couldn't open log file %s: %s\n"),785log_control.log_entries[lindex].lfu_fname,786error_message(errno));787}788}789}790}791792793