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