xref: /illumos-gate/usr/src/lib/krb5/kadm5/clnt/logger.c (revision 74e20cfe817b82802b16fac8690dadcda76f54f5)
1 /*
2  * lib/kadm/logger.c
3  *
4  * Copyright 1995 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  M.I.T. makes no representations about the suitability of
20  * this software for any purpose.  It is provided "as is" without express
21  * or implied warranty.
22  *
23  */
24 
25 /*
26  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
27  * Use is subject to license terms.
28  */
29 
30 #pragma ident	"%Z%%M%	%I%	%E% SMI"
31 
32 
33 /* KADM5 wants non-syslog log files to contain syslog-like entries */
34 #define VERBOSE_LOGS
35 
36 /*
37  * logger.c	- Handle logging functions for those who want it.
38  */
39 #include "k5-int.h"
40 #include "adm_proto.h"
41 #include "com_err.h"
42 #include <stdio.h>
43 #include <ctype.h>
44 #include <syslog.h>
45 #include <stdarg.h>
46 #include <libintl.h>
47 
48 #define	KRB5_KLOG_MAX_ERRMSG_SIZE	1024
49 #ifndef	MAXHOSTNAMELEN
50 #define	MAXHOSTNAMELEN	256
51 #endif	/* MAXHOSTNAMELEN */
52 
53 #define LSPEC_PARSE_ERR_1 	1
54 #define LSPEC_PARSE_ERR_2	2
55 #define LOG_FILE_ERR		3
56 #define LOG_DEVICE_ERR		4
57 #define LOG_UFO_STRING		5
58 #define LOG_EMERG_STRING	6
59 #define LOG_ALERT_STRING	7
60 #define LOG_CRIT_STRING		8
61 #define LOG_ERR_STRING		9
62 #define LOG_WARNING_STRING	10
63 #define LOG_NOTICE_STRING	11
64 #define LOG_INFO_STRING	12
65 #define LOG_DEBUG_STRING	13
66 /* This is to assure that we have at least one match in the syslog stuff */
67 /*
68 static const char LSPEC_PARSE_ERR_1[] =	"%s: cannot parse <%s>\n";
69 static const char LSPEC_PARSE_ERR_2[] =	"%s: warning - logging entry syntax error\n";
70 static const char LOG_FILE_ERR[] =	"%s: error writing to %s\n";
71 static const char LOG_DEVICE_ERR[] =	"%s: error writing to %s device\n";
72 static const char LOG_UFO_STRING[] =	"???";
73 static const char LOG_EMERG_STRING[] =	"EMERGENCY";
74 static const char LOG_ALERT_STRING[] =	"ALERT";
75 static const char LOG_CRIT_STRING[] =	"CRITICAL";
76 static const char LOG_ERR_STRING[] =	"Error";
77 static const char LOG_WARNING_STRING[] =	"Warning";
78 static const char LOG_NOTICE_STRING[] =	"Notice";
79 static const char LOG_INFO_STRING[] =	"info";
80 static const char LOG_DEBUG_STRING[] =	"debug";
81 */
82 
83 
84 const char *
85 krb5_log_error_table(long errorno) {
86 switch (errorno) {
87 	case LSPEC_PARSE_ERR_1:
88 		return(gettext("%s: cannot parse <%s>\n"));
89 	case LSPEC_PARSE_ERR_2:
90 		return(gettext("%s: warning - logging entry syntax error\n"));
91 	case LOG_FILE_ERR:
92 		return(gettext("%s: error writing to %s\n"));
93 	case LOG_DEVICE_ERR:
94 		return(gettext("%s: error writing to %s device\n"));
95 	case LOG_UFO_STRING:
96 		return(gettext("???"));
97 	case LOG_EMERG_STRING:
98 		return(gettext("EMERGENCY"));
99 	case LOG_ALERT_STRING:
100 		return(gettext("ALERT"));
101 	case LOG_CRIT_STRING:
102 		return(gettext("CRITICAL"));
103 	case LOG_ERR_STRING:
104 		return(gettext("Error"));
105 	case LOG_WARNING_STRING:
106 		return(gettext("Warning"));
107 	case LOG_NOTICE_STRING:
108 		return(gettext("Notice"));
109 	case LOG_INFO_STRING:
110 	case LOG_DEBUG_STRING:
111 	default:
112 		return(gettext("info"));
113 	}
114 }
115 
116 /*
117  * Output logging.
118  *
119  * Output logging is now controlled by the configuration file.  We can specify
120  * the following syntaxes under the [logging]->entity specification.
121  *	FILE<opentype><pathname>
122  *	SYSLOG[=<severity>[:<facility>]]
123  *	STDERR
124  *	CONSOLE
125  *	DEVICE=<device-spec>
126  *
127  * Where:
128  *	<opentype> is ":" for open/append, "=" for open/create.
129  *	<pathname> is a valid path name.
130  *	<severity> is one of: (default = ERR)
131  *		EMERG
132  *		ALERT
133  *		CRIT
134  *		ERR
135  *		WARNING
136  *		NOTICE
137  *		INFO
138  *		DEBUG
139  *	<facility> is one of: (default = AUTH)
140  *		KERN
141  *		USER
142  *		MAIL
143  *		DAEMON
144  *		AUTH
145  *		LPR
146  *		NEWS
147  *		UUCP
148  *		AUDIT
149  *		CRON
150  *		LOCAL0..LOCAL7
151  *	<device-spec> is a valid device specification.
152  */
153 struct log_entry {
154     enum log_type { K_LOG_FILE,
155 			K_LOG_SYSLOG,
156 			K_LOG_STDERR,
157 			K_LOG_CONSOLE,
158 			K_LOG_DEVICE,
159 			K_LOG_NONE } log_type;
160     krb5_pointer log_2free;
161     union log_union {
162 	struct log_file {
163 	    FILE	*lf_filep;
164 	    char	*lf_fname;
165 	    char	*lf_fopen_mode; /* "a+" or "w" */
166 #define	K_LOG_DEF_FILE_ROTATE_PERIOD	-1	/* never */
167 #define	K_LOG_DEF_FILE_ROTATE_VERSIONS	0	/* no versions */
168 	    time_t	lf_rotate_period;
169 	    time_t	lf_last_rotated;
170 	    int		lf_rotate_versions;
171 	} log_file;
172 	struct log_syslog {
173 	    int		ls_facility;
174 	    int		ls_severity;
175 	} log_syslog;
176 	struct log_device {
177 	    FILE	*ld_filep;
178 	    char	*ld_devname;
179 	} log_device;
180     } log_union;
181 };
182 #define	lfu_filep	log_union.log_file.lf_filep
183 #define	lfu_fname	log_union.log_file.lf_fname
184 #define	lfu_fopen_mode	log_union.log_file.lf_fopen_mode
185 #define	lfu_rotate_period	log_union.log_file.lf_rotate_period
186 #define	lfu_last_rotated	log_union.log_file.lf_last_rotated
187 #define	lfu_rotate_versions	log_union.log_file.lf_rotate_versions
188 #define	lsu_facility	log_union.log_syslog.ls_facility
189 #define	lsu_severity	log_union.log_syslog.ls_severity
190 #define	ldu_filep	log_union.log_device.ld_filep
191 #define	ldu_devname	log_union.log_device.ld_devname
192 
193 struct log_control {
194     struct log_entry	*log_entries;
195     int			log_nentries;
196     char		*log_whoami;
197     char		*log_hostname;
198     krb5_boolean	log_opened;
199 
200 };
201 
202 static struct log_control log_control = {
203     (struct log_entry *) NULL,
204     0,
205     (char *) NULL,
206     (char *) NULL,
207     0
208 };
209 static struct log_entry	def_log_entry;
210 
211 /*
212  * These macros define any special processing that needs to happen for
213  * devices.  For unix, of course, this is hardly anything.
214  */
215 #define	DEVICE_OPEN(d, m)	fopen(d, m)
216 #define	CONSOLE_OPEN(m)		fopen("/dev/console", m)
217 #define	DEVICE_PRINT(f, m)	((fprintf(f, m) >= 0) ? 		\
218 				 (fprintf(f, "\r\n"), fflush(f), 0) :	\
219 				 -1)
220 #define	DEVICE_CLOSE(d)		fclose(d)
221 
222 
223 /*
224  * klog_rotate() - roate a log file if we have specified rotation
225  * parameters in krb5.conf.
226  */
227 static void
228 klog_rotate(struct log_entry *le)
229 {
230 	time_t t;
231 	int i;
232 	char *name_buf1;
233 	char *name_buf2;
234 	char *old_name;
235 	char *new_name;
236 	char *tmp;
237 	FILE *fp;
238 	int num_vers;
239 
240 	/*
241 	 * By default we don't rotate.
242 	 */
243 	if (le->lfu_rotate_period == K_LOG_DEF_FILE_ROTATE_PERIOD)
244 		return;
245 
246 	t = time(0);
247 
248 	if (t >= le->lfu_last_rotated + le->lfu_rotate_period) {
249 		/*
250 		 * The N log file versions will be renamed X.N-1 X.N-2, ... X.0.
251 		 * So the allocate file name buffers that can the version
252 		 * number extensions.
253 		 * 32 extra bytes is plenty.
254 		 */
255 		name_buf1 = malloc(strlen(le->lfu_fname) + 32);
256 
257 		if (name_buf1 == NULL)
258 			return;
259 
260 		name_buf2 = malloc(strlen(le->lfu_fname) + 32);
261 
262 		if (name_buf2 == NULL) {
263 			free(name_buf1);
264 			return;
265 		}
266 
267 		old_name = name_buf1;
268 		new_name = name_buf2;
269 
270 		/*
271 		 * If there N versions, then the first one has file extension
272 		 * of N-1.
273 		 */
274 		(void) sprintf(new_name, "%s.%d", le->lfu_fname,
275 			le->lfu_rotate_versions - 1);
276 
277 		/*
278 		 * Rename file.N-2 to file.N-1, file.N-3 to file.N-2, ...
279 		 * file.0 to file.1
280 		 */
281 		for (i = le->lfu_rotate_versions - 1; i > 0; i--) {
282 			(void) sprintf(old_name, "%s.%d", le->lfu_fname, i - 1);
283 			(void) rename(old_name, new_name);
284 
285 			/*
286 			 * swap old name and new name. This way,
287 			 * on the next iteration, new_name.X
288 			 * becomes new_name.X-1.
289 			 */
290 			tmp = old_name;
291 			old_name = new_name;
292 			new_name = tmp;
293 		}
294 		old_name = le->lfu_fname;
295 
296 		(void) rename(old_name, new_name);
297 
298 		/*
299 		 * Even though we don't know yet if the fopen()
300 		 * of the log file will succeed, we mark the log
301 		 * as rotated. This is so we don't repeatably
302 		 * rotate file.N-2 to file.N-1 ... etc without
303 		 * waiting for the rotate period to elapse.
304 		 */
305 		le->lfu_last_rotated = t;
306 
307 		fp = fopen(old_name, le->lfu_fopen_mode);
308 
309 		if (fp != NULL) {
310 
311 			/* Set the permissions to 644 */
312 			if (fchmod(fileno(fp),
313 			    S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1) {
314 			fprintf(stderr,
315 gettext("During rotate, couldn't set permissions for log file %s: %s\n"),
316 				old_name, error_message(errno));
317 			}
318 
319 			(void) fclose(le->lfu_filep);
320 			le->lfu_filep = fp;
321 
322 			/*
323 			 * If the version parameter in krb5.conf was
324 			 * 0, then we take this to mean that rotating the
325 			 * log file will cause us to dispose of the
326 			 * old one, and created a new one. We have just
327 			 * renamed the old one to file.-1, so remove it.
328 			 */
329 			if (le->lfu_rotate_versions <= 0)
330 				(void) unlink(new_name);
331 
332 		} else {
333 			fprintf(stderr,
334 		gettext("During rotate, couldn't open log file %s: %s\n"),
335 				old_name, error_message(errno));
336 			/*
337 			 * Put it back.
338 			 */
339 			(void) rename(new_name, old_name);
340 		}
341 		free(name_buf1);
342 		free(name_buf2);
343 	}
344 }
345 
346 /*
347  * klog_com_err_proc()	- Handle com_err(3) messages as specified by the
348  *			  profile.
349  */
350 static void
351 klog_com_err_proc(whoami, code, format, ap)
352     const char	*whoami;
353     long	code;
354     const char	*format;
355     va_list	ap;
356 {
357     char	outbuf[KRB5_KLOG_MAX_ERRMSG_SIZE];
358     int		lindex;
359     char	*actual_format;
360     int		log_pri = -1;
361     char	*cp;
362     char	*syslogp;
363 
364     /* Make the header */
365     sprintf(outbuf, "%s: ", whoami);
366     /*
367      * Squirrel away address after header for syslog since syslog makes
368      * a header
369      */
370     syslogp = &outbuf[strlen(outbuf)];
371 
372     /* If reporting an error message, separate it. */
373     if (code) {
374 	strcat(outbuf, error_message(code));
375 	strcat(outbuf, " - ");
376     }
377     cp = &outbuf[strlen(outbuf)];
378 
379     actual_format = (char *) format;
380     /*
381      * This is an unpleasant hack.  If the first character is less than
382      * 8, then we assume that it is a priority.
383      *
384      * Since it is not guaranteed that there is a direct mapping between
385      * syslog priorities (e.g. Ultrix and old BSD), we resort to this
386      * intermediate representation.
387      */
388     if ((((unsigned char) *format) > 0) && (((unsigned char) *format) <= 8)) {
389 	actual_format = (char *) (format + 1);
390 	switch ((unsigned char) *format) {
391 	case 1:
392 	    log_pri = LOG_EMERG;
393 	    break;
394 	case 2:
395 	    log_pri = LOG_ALERT;
396 	    break;
397 	case 3:
398 	    log_pri = LOG_CRIT;
399 	    break;
400 	default:
401 	case 4:
402 	    log_pri = LOG_ERR;
403 	    break;
404 	case 5:
405 	    log_pri = LOG_WARNING;
406 	    break;
407 	case 6:
408 	    log_pri = LOG_NOTICE;
409 	    break;
410 	case 7:
411 	    log_pri = LOG_INFO;
412 	    break;
413 	case 8:
414 	    log_pri = LOG_DEBUG;
415 	    break;
416 	}
417     }
418 
419     /* Now format the actual message */
420     vsprintf(cp, actual_format, ap);
421 
422     /*
423      * Now that we have the message formatted, perform the output to each
424      * logging specification.
425      */
426     for (lindex = 0; lindex < log_control.log_nentries; lindex++) {
427 	switch (log_control.log_entries[lindex].log_type) {
428 	case K_LOG_FILE:
429 
430 	    klog_rotate(&log_control.log_entries[lindex]);
431 	    /*FALLTHRU*/
432 	case K_LOG_STDERR:
433 	    /*
434 	     * Files/standard error.
435 	     */
436 	    if (fprintf(log_control.log_entries[lindex].lfu_filep,
437 			outbuf) < 0) {
438 		/* Attempt to report error */
439 		fprintf(stderr, krb5_log_error_table(LOG_FILE_ERR), whoami,
440 			log_control.log_entries[lindex].lfu_fname);
441 	    }
442 	    else {
443 		fprintf(log_control.log_entries[lindex].lfu_filep, "\n");
444 		fflush(log_control.log_entries[lindex].lfu_filep);
445 	    }
446 	    break;
447 	case K_LOG_CONSOLE:
448 	case K_LOG_DEVICE:
449 	    /*
450 	     * Devices (may need special handling)
451 	     */
452 	    if (DEVICE_PRINT(log_control.log_entries[lindex].ldu_filep,
453 			     outbuf) < 0) {
454 		/* Attempt to report error */
455 		fprintf(stderr, krb5_log_error_table(LOG_DEVICE_ERR), whoami,
456 			log_control.log_entries[lindex].ldu_devname);
457 	    }
458 	    break;
459 	case K_LOG_SYSLOG:
460 	    /*
461 	     * System log.
462 	     */
463 	    /*
464 	     * If we have specified a priority through our hackery, then
465 	     * use it, otherwise use the default.
466 	     */
467 	    if (log_pri >= 0)
468 		log_pri |= log_control.log_entries[lindex].lsu_facility;
469 	    else
470 		log_pri = log_control.log_entries[lindex].lsu_facility |
471 		    log_control.log_entries[lindex].lsu_severity;
472 
473 	    /* Log the message with our header trimmed off */
474 	    syslog(log_pri, syslogp);
475 	    break;
476 	default:
477 	    break;
478 	}
479     }
480 }
481 
482 /*
483  * krb5_klog_init()	- Initialize logging.
484  *
485  * This routine parses the syntax described above to specify destinations for
486  * com_err(3) or krb5_klog_syslog() messages generated by the caller.
487  *
488  * Parameters:
489  *	kcontext	- Kerberos context.
490  *	ename		- Entity name as it is to appear in the profile.
491  *	whoami		- Entity name as it is to appear in error output.
492  *	do_com_err	- Take over com_err(3) processing.
493  *
494  * Implicit inputs:
495  *	stderr		- This is where STDERR output goes.
496  *
497  * Implicit outputs:
498  *	log_nentries	- Number of log entries, both valid and invalid.
499  *	log_control	- List of entries (log_nentries long) which contains
500  *			  data for klog_com_err_proc() to use to determine
501  *			  where/how to send output.
502  */
503 krb5_error_code
504 krb5_klog_init(kcontext, ename, whoami, do_com_err)
505     krb5_context	kcontext;
506     char		*ename;
507     char		*whoami;
508     krb5_boolean	do_com_err;
509 {
510     const char	*logging_profent[3];
511     const char	*logging_defent[3];
512     char	**logging_specs;
513     int		i, ngood;
514     char	*cp, *cp2;
515     char	savec;
516     int		error;
517     int		do_openlog, log_facility;
518     FILE	*f;
519 
520     /* Initialize */
521     do_openlog = 0;
522     log_facility = 0;
523 
524     /*
525      * Look up [logging]-><ename> in the profile.  If that doesn't
526      * succeed, then look for [logging]->default.
527      */
528     logging_profent[0] = "logging";
529     logging_profent[1] = ename;
530     logging_profent[2] = (char *) NULL;
531     logging_defent[0] = "logging";
532     logging_defent[1] = "default";
533     logging_defent[2] = (char *) NULL;
534     logging_specs = (char **) NULL;
535     ngood = 0;
536     log_control.log_nentries = 0;
537     if (!profile_get_values(kcontext->profile,
538 			    logging_profent,
539 			    &logging_specs) ||
540 	!profile_get_values(kcontext->profile,
541 			    logging_defent,
542 			    &logging_specs)) {
543 	/*
544 	 * We have a match, so we first count the number of elements
545 	 */
546 	for (log_control.log_nentries = 0;
547 	     logging_specs[log_control.log_nentries];
548 	     log_control.log_nentries++);
549 
550 	/*
551 	 * Now allocate our structure.
552 	 */
553 	log_control.log_entries = (struct log_entry *)
554 	    malloc(log_control.log_nentries * sizeof(struct log_entry));
555 	if (log_control.log_entries) {
556 	    /*
557 	     * Scan through the list.
558 	     */
559 	    for (i=0; i<log_control.log_nentries; i++) {
560 		log_control.log_entries[i].log_type = K_LOG_NONE;
561 		log_control.log_entries[i].log_2free = logging_specs[i];
562 		/*
563 		 * The format is:
564 		 *	<whitespace><data><whitespace>
565 		 * so, trim off the leading and trailing whitespace here.
566 		 */
567 		for (cp = logging_specs[i]; isspace(*cp); cp++);
568 		for (cp2 = &logging_specs[i][strlen(logging_specs[i])-1];
569 		     isspace(*cp2); cp2--);
570 		cp2++;
571 		*cp2 = '\0';
572 		/*
573 		 * Is this a file?
574 		 */
575 		if (!strncasecmp(cp, "FILE", 4)) {
576 		    /*
577 		     * Check for append/overwrite, then open the file.
578 		     */
579 		    if (cp[4] == ':' || cp[4] == '=') {
580 			log_control.log_entries[i].lfu_fopen_mode =
581 				(cp[4] == ':') ? "a+F" : "wF";
582 			f = fopen(&cp[5],
583 				log_control.log_entries[i].lfu_fopen_mode);
584 			if (f) {
585 			    char rotate_kw[128];
586 				/* Set the permissions to 644 */
587 			    if (fchmod(fileno(f),
588 					S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1) {
589 			    		fprintf(stderr,gettext("Couldn't set permissions for log file %s: %s\n"),
590 				    		&cp[5], error_message(errno));
591 			    }
592 			    log_control.log_entries[i].lfu_filep = f;
593 			    log_control.log_entries[i].log_type = K_LOG_FILE;
594 			    log_control.log_entries[i].lfu_fname = &cp[5];
595 			    log_control.log_entries[i].lfu_rotate_period =
596 				K_LOG_DEF_FILE_ROTATE_PERIOD;
597 			    log_control.log_entries[i].lfu_rotate_versions =
598 				K_LOG_DEF_FILE_ROTATE_VERSIONS;
599 			    log_control.log_entries[i].lfu_last_rotated =
600 				time(0);
601 
602 			/*
603 			 * Now parse for ename_"rotate" = {
604 			 *	period = XXX
605 			 * 	versions = 10
606 			 * }
607 			 */
608 			    if (strlen(ename) + strlen("_rotate") <
609 				sizeof (rotate_kw)) {
610 
611 				    char *time;
612 				    krb5_deltat	dt;
613 				    int vers;
614 
615 				    strcpy(rotate_kw, ename);
616 				    strcat(rotate_kw, "_rotate");
617 
618 				    if (!profile_get_string(kcontext->profile,
619 				        "logging", rotate_kw, "period",
620 					NULL, &time)) {
621 
622 					if (time != NULL) {
623 					    if (!krb5_string_to_deltat(time,
624 						&dt)) {
625 			log_control.log_entries[i].lfu_rotate_period =
626 							(time_t) dt;
627 					    }
628 					    free(time);
629 					}
630 				    }
631 
632 				    if (!profile_get_integer(
633 					kcontext->profile, "logging",
634 					rotate_kw, "versions",
635 					K_LOG_DEF_FILE_ROTATE_VERSIONS,
636 					&vers)) {
637 			log_control.log_entries[i].lfu_rotate_versions = vers;
638 				    }
639 
640 			   }
641 			} else {
642 			    fprintf(stderr,gettext("Couldn't open log file %s: %s\n"),
643 				    &cp[5], error_message(errno));
644 			    continue;
645 			}
646 		    }
647 		}
648 		/*
649 		 * Is this a syslog?
650 		 */
651 		else if (!strncasecmp(cp, "SYSLOG", 6)) {
652 		    error = 0;
653 		    log_control.log_entries[i].lsu_facility = LOG_AUTH;
654 		    log_control.log_entries[i].lsu_severity = LOG_ERR;
655 		    /*
656 		     * Is there a severify specified?
657 		     */
658 		    if (cp[6] == ':') {
659 			/*
660 			 * Find the end of the severity.
661 			 */
662 			if (cp2 = strchr(&cp[7], ':')) {
663 			    savec = *cp2;
664 			    *cp2 = '\0';
665 			    cp2++;
666 			}
667 
668 			/*
669 			 * Match a severity.
670 			 */
671 			if (!strcasecmp(&cp[7], "ERR")) {
672 			    log_control.log_entries[i].lsu_severity = LOG_ERR;
673 			}
674 			else if (!strcasecmp(&cp[7], "EMERG")) {
675 			    log_control.log_entries[i].lsu_severity =
676 				LOG_EMERG;
677 			}
678 			else if (!strcasecmp(&cp[7], "ALERT")) {
679 			    log_control.log_entries[i].lsu_severity =
680 				LOG_ALERT;
681 			}
682 			else if (!strcasecmp(&cp[7], "CRIT")) {
683 			    log_control.log_entries[i].lsu_severity = LOG_CRIT;
684 			}
685 			else if (!strcasecmp(&cp[7], "WARNING")) {
686 			    log_control.log_entries[i].lsu_severity =
687 				LOG_WARNING;
688 			}
689 			else if (!strcasecmp(&cp[7], "NOTICE")) {
690 			    log_control.log_entries[i].lsu_severity =
691 				LOG_NOTICE;
692 			}
693 			else if (!strcasecmp(&cp[7], "INFO")) {
694 			    log_control.log_entries[i].lsu_severity = LOG_INFO;
695 			}
696 			else if (!strcasecmp(&cp[7], "DEBUG")) {
697 			    log_control.log_entries[i].lsu_severity =
698 				LOG_DEBUG;
699 			}
700 			else
701 			    error = 1;
702 
703 			/*
704 			 * If there is a facility present, then parse that.
705 			 */
706 			if (cp2) {
707 			    if (!strcasecmp(cp2, "AUTH")) {
708 				log_control.log_entries[i].lsu_facility = LOG_AUTH;
709 			    }
710 			    else if (!strcasecmp(cp2, "KERN")) {
711 				log_control.log_entries[i].lsu_facility = LOG_KERN;
712 			    }
713 			    else if (!strcasecmp(cp2, "USER")) {
714 				log_control.log_entries[i].lsu_facility = LOG_USER;
715 			    }
716 			    else if (!strcasecmp(cp2, "MAIL")) {
717 				log_control.log_entries[i].lsu_facility = LOG_MAIL;
718 			    }
719 			    else if (!strcasecmp(cp2, "DAEMON")) {
720 				log_control.log_entries[i].lsu_facility = LOG_DAEMON;
721 			    }
722 			    else if (!strcasecmp(cp2, "LPR")) {
723 				log_control.log_entries[i].lsu_facility = LOG_LPR;
724 			    }
725 			    else if (!strcasecmp(cp2, "NEWS")) {
726 				log_control.log_entries[i].lsu_facility = LOG_NEWS;
727 			    }
728 			    else if (!strcasecmp(cp2, "UUCP")) {
729 				log_control.log_entries[i].lsu_facility = LOG_UUCP;
730 			    }
731 			    else if (!strcasecmp(cp2, "CRON")) {
732 				log_control.log_entries[i].lsu_facility = LOG_CRON;
733 			    }
734 			    else if (!strcasecmp(cp2, "AUDIT")) {
735 				log_control.log_entries[i].lsu_facility = LOG_AUDIT;
736 			    }
737 			    else if (!strcasecmp(cp2, "LOCAL0")) {
738 				log_control.log_entries[i].lsu_facility = LOG_LOCAL0;
739 			    }
740 			    else if (!strcasecmp(cp2, "LOCAL1")) {
741 				log_control.log_entries[i].lsu_facility = LOG_LOCAL1;
742 			    }
743 			    else if (!strcasecmp(cp2, "LOCAL2")) {
744 				log_control.log_entries[i].lsu_facility = LOG_LOCAL2;
745 			    }
746 			    else if (!strcasecmp(cp2, "LOCAL3")) {
747 				log_control.log_entries[i].lsu_facility = LOG_LOCAL3;
748 			    }
749 			    else if (!strcasecmp(cp2, "LOCAL4")) {
750 				log_control.log_entries[i].lsu_facility = LOG_LOCAL4;
751 			    }
752 			    else if (!strcasecmp(cp2, "LOCAL5")) {
753 				log_control.log_entries[i].lsu_facility = LOG_LOCAL5;
754 			    }
755 			    else if (!strcasecmp(cp2, "LOCAL6")) {
756 				log_control.log_entries[i].lsu_facility = LOG_LOCAL6;
757 			    }
758 			    else if (!strcasecmp(cp2, "LOCAL7")) {
759 				log_control.log_entries[i].lsu_facility = LOG_LOCAL7;
760 			    }
761 			    cp2--;
762 			    *cp2 = savec;
763 			}
764 		    }
765 		    if (!error) {
766 			log_control.log_entries[i].log_type = K_LOG_SYSLOG;
767 			do_openlog = 1;
768 			log_facility = log_control.log_entries[i].lsu_facility;
769 		    }
770 		}
771 		/*
772 		 * Is this a standard error specification?
773 		 */
774 		else if (!strcasecmp(cp, "STDERR")) {
775 		    if (log_control.log_entries[i].lfu_filep =
776 			fdopen(fileno(stderr), "a+F")) {
777 			log_control.log_entries[i].log_type = K_LOG_STDERR;
778 			log_control.log_entries[i].lfu_fname =
779 			    "standard error";
780 		    }
781 		}
782 		/*
783 		 * Is this a specification of the console?
784 		 */
785 		else if (!strcasecmp(cp, "CONSOLE")) {
786 		    if (log_control.log_entries[i].ldu_filep =
787 			CONSOLE_OPEN("a+F")) {
788 			log_control.log_entries[i].log_type = K_LOG_CONSOLE;
789 			log_control.log_entries[i].ldu_devname = "console";
790 		    }
791 		}
792 		/*
793 		 * Is this a specification of a device?
794 		 */
795 		else if (!strncasecmp(cp, "DEVICE", 6)) {
796 		    /*
797 		     * We handle devices very similarly to files.
798 		     */
799 		    if (cp[6] == '=') {
800 			if (log_control.log_entries[i].ldu_filep =
801 			    DEVICE_OPEN(&cp[7], "wF")) {
802 			    log_control.log_entries[i].log_type = K_LOG_DEVICE;
803 			    log_control.log_entries[i].ldu_devname = &cp[7];
804 			}
805 		    }
806 		}
807 		/*
808 		 * See if we successfully parsed this specification.
809 		 */
810 		if (log_control.log_entries[i].log_type == K_LOG_NONE) {
811 		    fprintf(stderr, krb5_log_error_table(LSPEC_PARSE_ERR_1), whoami, cp);
812 		    fprintf(stderr, krb5_log_error_table(LSPEC_PARSE_ERR_2), whoami);
813 		}
814 		else
815 		    ngood++;
816 	    }
817 	}
818 	/*
819 	 * If we didn't find anything, then free our lists.
820 	 */
821 	if (ngood == 0) {
822 	    for (i=0; i<log_control.log_nentries; i++)
823 		free(logging_specs[i]);
824 	}
825 	free(logging_specs);
826     }
827     /*
828      * If we didn't find anything, go for the default which is to log to
829      * the system log.
830      */
831     if (ngood == 0) {
832 	if (log_control.log_entries)
833 	    free(log_control.log_entries);
834 	log_control.log_entries = &def_log_entry;
835 	log_control.log_entries->log_type = K_LOG_SYSLOG;
836 	log_control.log_entries->log_2free = (krb5_pointer) NULL;
837 	log_facility = log_control.log_entries->lsu_facility = LOG_AUTH;
838 	log_control.log_entries->lsu_severity = LOG_ERR;
839 	do_openlog = 1;
840 	log_control.log_nentries = 1;
841     }
842     if (log_control.log_nentries) {
843 	if (log_control.log_whoami = (char *) malloc(strlen(whoami)+1))
844 	    strcpy(log_control.log_whoami, whoami);
845 	if (log_control.log_hostname = (char *) malloc(MAXHOSTNAMELEN))
846 	    gethostname(log_control.log_hostname, MAXHOSTNAMELEN);
847 	if (do_openlog) {
848 	    openlog(whoami, LOG_NDELAY|LOG_PID, log_facility);
849 	    log_control.log_opened = 1;
850 	}
851 	if (do_com_err)
852 	    (void) set_com_err_hook(klog_com_err_proc);
853     }
854     return((log_control.log_nentries) ? 0 : ENOENT);
855 }
856 
857 /*
858  * krb5_klog_close()	- Close the logging context and free all data.
859  */
860 void
861 krb5_klog_close(kcontext)
862     krb5_context	kcontext;
863 {
864     int lindex;
865     (void) reset_com_err_hook();
866     for (lindex = 0; lindex < log_control.log_nentries; lindex++) {
867 	switch (log_control.log_entries[lindex].log_type) {
868 	case K_LOG_FILE:
869 	case K_LOG_STDERR:
870 	    /*
871 	     * Files/standard error.
872 	     */
873 	    fclose(log_control.log_entries[lindex].lfu_filep);
874 	    break;
875 	case K_LOG_CONSOLE:
876 	case K_LOG_DEVICE:
877 	    /*
878 	     * Devices (may need special handling)
879 	     */
880 	    DEVICE_CLOSE(log_control.log_entries[lindex].ldu_filep);
881 	    break;
882 	case K_LOG_SYSLOG:
883 	    /*
884 	     * System log.
885 	     */
886 	    break;
887 	default:
888 	    break;
889 	}
890 	if (log_control.log_entries[lindex].log_2free)
891 	    free(log_control.log_entries[lindex].log_2free);
892     }
893     if (log_control.log_entries != &def_log_entry)
894 	free(log_control.log_entries);
895     log_control.log_entries = (struct log_entry *) NULL;
896     log_control.log_nentries = 0;
897     if (log_control.log_whoami)
898 	free(log_control.log_whoami);
899     log_control.log_whoami = (char *) NULL;
900     if (log_control.log_hostname)
901 	free(log_control.log_hostname);
902     log_control.log_hostname = (char *) NULL;
903     if (log_control.log_opened)
904 	closelog();
905 }
906 
907 /*
908  * severity2string()	- Convert a severity to a string.
909  */
910 static char *
911 severity2string(severity)
912     int	severity;
913 {
914     int s;
915     const char *ss;
916 
917     s = severity & LOG_PRIMASK;
918     ss = krb5_log_error_table(LOG_UFO_STRING);
919     switch (s) {
920     case LOG_EMERG:
921 	ss = krb5_log_error_table(LOG_EMERG_STRING);
922 	break;
923     case LOG_ALERT:
924 	ss = krb5_log_error_table(LOG_ALERT_STRING);
925 	break;
926     case LOG_CRIT:
927 	ss = krb5_log_error_table(LOG_CRIT_STRING);
928 	break;
929     case LOG_ERR:
930 	ss = krb5_log_error_table(LOG_ERR_STRING);
931 	break;
932     case LOG_WARNING:
933 	ss = krb5_log_error_table(LOG_WARNING_STRING);
934 	break;
935     case LOG_NOTICE:
936 	ss = krb5_log_error_table(LOG_NOTICE_STRING);
937 	break;
938     case LOG_INFO:
939 	ss = krb5_log_error_table(LOG_INFO_STRING);
940 	break;
941     case LOG_DEBUG:
942 	ss = krb5_log_error_table(LOG_DEBUG_STRING);
943 	break;
944     }
945     return((char *) ss);
946 }
947 
948 /*
949  * krb5_klog_syslog()	- Simulate the calling sequence of syslog(3), while
950  *			  also performing the logging redirection as specified
951  *			  by krb5_klog_init().
952  */
953 static int
954 klog_vsyslog(priority, format, arglist)
955     int		priority;
956     const char	*format;
957     va_list	arglist;
958 {
959     char	outbuf[KRB5_KLOG_MAX_ERRMSG_SIZE];
960     int		lindex;
961     char	*syslogp;
962     char	*cp;
963     time_t	now;
964     size_t	soff;
965 
966     /*
967      * Format a syslog-esque message of the format:
968      *
969      * (verbose form)
970      * 		<date> <hostname> <id>[<pid>](<priority>): <message>
971      *
972      * (short form)
973      *		<date> <message>
974      */
975     cp = outbuf;
976     (void) time(&now);
977     /*
978      * Format the date: mon dd hh:mm:ss
979      */
980     soff = strftime(outbuf, sizeof(outbuf), "%b %d %H:%M:%S", localtime(&now));
981     if (soff > 0)
982 	cp += soff;
983     else
984 	return(-1);
985 #ifdef VERBOSE_LOGS
986     sprintf(cp, " %s %s[%d](%s): ",
987 	    log_control.log_hostname, log_control.log_whoami, getpid(),
988 	    severity2string(priority));
989 #else
990     sprintf(cp, " ");
991 #endif
992     syslogp = &outbuf[strlen(outbuf)];
993 
994     /* Now format the actual message */
995     vsprintf(syslogp, format, arglist);
996 
997     /*
998      * Now that we have the message formatted, perform the output to each
999      * logging specification.
1000      */
1001     for (lindex = 0; lindex < log_control.log_nentries; lindex++) {
1002 	switch (log_control.log_entries[lindex].log_type) {
1003 	case K_LOG_FILE:
1004 
1005 	    klog_rotate(&log_control.log_entries[lindex]);
1006 	    /*FALLTHRU*/
1007 	case K_LOG_STDERR:
1008 	    /*
1009 	     * Files/standard error.
1010 	     */
1011 	    if (fprintf(log_control.log_entries[lindex].lfu_filep,
1012 			outbuf) < 0) {
1013 		/* Attempt to report error */
1014 		fprintf(stderr, krb5_log_error_table(LOG_FILE_ERR),
1015 			log_control.log_entries[lindex].lfu_fname);
1016 	    }
1017 	    else {
1018 		fprintf(log_control.log_entries[lindex].lfu_filep, "\n");
1019 		fflush(log_control.log_entries[lindex].lfu_filep);
1020 	    }
1021 	    break;
1022 	case K_LOG_CONSOLE:
1023 	case K_LOG_DEVICE:
1024 	    /*
1025 	     * Devices (may need special handling)
1026 	     */
1027 	    if (DEVICE_PRINT(log_control.log_entries[lindex].ldu_filep,
1028 			     outbuf) < 0) {
1029 		/* Attempt to report error */
1030 		fprintf(stderr, krb5_log_error_table(LOG_DEVICE_ERR),
1031 			log_control.log_entries[lindex].ldu_devname);
1032 	    }
1033 	    break;
1034 	case K_LOG_SYSLOG:
1035 	    /*
1036 	     * System log.
1037 	     */
1038 
1039 	    /* Log the message with our header trimmed off */
1040 	    syslog(priority, syslogp);
1041 	    break;
1042 	default:
1043 	    break;
1044 	}
1045     }
1046     return(0);
1047 }
1048 
1049 int
1050 krb5_klog_syslog(int priority, const char *format, ...)
1051 {
1052     int		retval;
1053     va_list	pvar;
1054 
1055     va_start(pvar, format);
1056     retval = klog_vsyslog(priority, format, pvar);
1057     va_end(pvar);
1058     return(retval);
1059 }
1060