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