1/*	$NetBSD: log.c,v 1.3 2019/12/15 22:50:50 christos Exp $	*/
2
3/*
4 * Copyright (c) 1997-2006 Kungliga Tekniska H��gskolan
5 * (Royal Institute of Technology, Stockholm, Sweden).
6 * All rights reserved.
7 *
8 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 *
17 * 2. Redistributions in binary form must reproduce the above copyright
18 *    notice, this list of conditions and the following disclaimer in the
19 *    documentation and/or other materials provided with the distribution.
20 *
21 * 3. Neither the name of the Institute nor the names of its contributors
22 *    may be used to endorse or promote products derived from this software
23 *    without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
36 */
37
38#include "krb5_locl.h"
39#include <vis.h>
40
41struct facility {
42    int min;
43    int max;
44    krb5_log_log_func_t log_func;
45    krb5_log_close_func_t close_func;
46    void *data;
47};
48
49static struct facility*
50log_realloc(krb5_log_facility *f)
51{
52    struct facility *fp;
53    fp = realloc(f->val, (f->len + 1) * sizeof(*f->val));
54    if(fp == NULL)
55	return NULL;
56    f->len++;
57    f->val = fp;
58    fp += f->len - 1;
59    return fp;
60}
61
62struct s2i {
63    const char *s;
64    int val;
65};
66
67#define L(X) { #X, LOG_ ## X }
68
69static struct s2i syslogvals[] = {
70    L(EMERG),
71    L(ALERT),
72    L(CRIT),
73    L(ERR),
74    L(WARNING),
75    L(NOTICE),
76    L(INFO),
77    L(DEBUG),
78
79    L(AUTH),
80#ifdef LOG_AUTHPRIV
81    L(AUTHPRIV),
82#endif
83#ifdef LOG_CRON
84    L(CRON),
85#endif
86    L(DAEMON),
87#ifdef LOG_FTP
88    L(FTP),
89#endif
90    L(KERN),
91    L(LPR),
92    L(MAIL),
93#ifdef LOG_NEWS
94    L(NEWS),
95#endif
96    L(SYSLOG),
97    L(USER),
98#ifdef LOG_UUCP
99    L(UUCP),
100#endif
101    L(LOCAL0),
102    L(LOCAL1),
103    L(LOCAL2),
104    L(LOCAL3),
105    L(LOCAL4),
106    L(LOCAL5),
107    L(LOCAL6),
108    L(LOCAL7),
109    { NULL, -1 }
110};
111
112static int
113find_value(const char *s, struct s2i *table)
114{
115    while(table->s && strcasecmp(table->s, s))
116	table++;
117    return table->val;
118}
119
120KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
121krb5_initlog(krb5_context context,
122	     const char *program,
123	     krb5_log_facility **fac)
124{
125    krb5_log_facility *f = calloc(1, sizeof(*f));
126    if (f == NULL)
127	return krb5_enomem(context);
128    f->program = strdup(program);
129    if(f->program == NULL){
130	free(f);
131	return krb5_enomem(context);
132    }
133    *fac = f;
134    return 0;
135}
136
137KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
138krb5_addlog_func(krb5_context context,
139		 krb5_log_facility *fac,
140		 int min,
141		 int max,
142		 krb5_log_log_func_t log_func,
143		 krb5_log_close_func_t close_func,
144		 void *data)
145{
146    struct facility *fp = log_realloc(fac);
147    if (fp == NULL)
148	return krb5_enomem(context);
149    fp->min = min;
150    fp->max = max;
151    fp->log_func = log_func;
152    fp->close_func = close_func;
153    fp->data = data;
154    return 0;
155}
156
157
158struct _heimdal_syslog_data{
159    int priority;
160};
161
162static void KRB5_CALLCONV
163log_syslog(const char *timestr,
164	   const char *msg,
165	   void *data)
166
167{
168    struct _heimdal_syslog_data *s = data;
169    syslog(s->priority, "%s", msg);
170}
171
172static void KRB5_CALLCONV
173close_syslog(void *data)
174{
175    free(data);
176    closelog();
177}
178
179static krb5_error_code
180open_syslog(krb5_context context,
181	    krb5_log_facility *facility, int min, int max,
182	    const char *sev, const char *fac)
183{
184    struct _heimdal_syslog_data *sd = malloc(sizeof(*sd));
185    int i;
186
187    if (sd == NULL)
188	return krb5_enomem(context);
189    i = find_value(sev, syslogvals);
190    if(i == -1)
191	i = LOG_ERR;
192    sd->priority = i;
193    i = find_value(fac, syslogvals);
194    if(i == -1)
195	i = LOG_AUTH;
196    sd->priority |= i;
197    roken_openlog(facility->program, LOG_PID | LOG_NDELAY, i);
198    return krb5_addlog_func(context, facility, min, max,
199			    log_syslog, close_syslog, sd);
200}
201
202struct file_data{
203    const char *filename;
204    const char *mode;
205    FILE *fd;
206    int keep_open;
207    int freefilename;
208};
209
210static void KRB5_CALLCONV
211log_file(const char *timestr,
212	 const char *msg,
213	 void *data)
214{
215    struct file_data *f = data;
216    char *msgclean;
217    size_t len = strlen(msg);
218    if(f->keep_open == 0)
219	f->fd = fopen(f->filename, f->mode);
220    if(f->fd == NULL)
221	return;
222    /* make sure the log doesn't contain special chars */
223    msgclean = malloc((len + 1) * 4);
224    if (msgclean == NULL)
225	goto out;
226    strvisx(msgclean, rk_UNCONST(msg), len, VIS_OCTAL);
227    fprintf(f->fd, "%s %s\n", timestr, msgclean);
228    free(msgclean);
229 out:
230    if(f->keep_open == 0) {
231	fclose(f->fd);
232	f->fd = NULL;
233    }
234}
235
236static void KRB5_CALLCONV
237close_file(void *data)
238{
239    struct file_data *f = data;
240    if(f->keep_open && f->filename)
241	fclose(f->fd);
242    if (f->filename && f->freefilename)
243	free((char *)f->filename);
244    free(data);
245}
246
247static krb5_error_code
248open_file(krb5_context context, krb5_log_facility *fac, int min, int max,
249	  const char *filename, const char *mode, FILE *f, int keep_open,
250	  int freefilename)
251{
252    struct file_data *fd = malloc(sizeof(*fd));
253    if (fd == NULL) {
254	if (freefilename && filename)
255	    free((char *)filename);
256	return krb5_enomem(context);
257    }
258    fd->filename = filename;
259    fd->mode = mode;
260    fd->fd = f;
261    fd->keep_open = keep_open;
262    fd->freefilename = freefilename;
263
264    return krb5_addlog_func(context, fac, min, max, log_file, close_file, fd);
265}
266
267
268
269KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
270krb5_addlog_dest(krb5_context context, krb5_log_facility *f, const char *orig)
271{
272    krb5_error_code ret = 0;
273    int min = 0, max = -1, n;
274    char c;
275    const char *p = orig;
276#ifdef _WIN32
277    const char *q;
278#endif
279
280    n = sscanf(p, "%d%c%d/", &min, &c, &max);
281    if(n == 2){
282	if(ISPATHSEP(c)) {
283	    if(min < 0){
284		max = -min;
285		min = 0;
286	    }else{
287		max = min;
288	    }
289	}
290    }
291    if(n){
292#ifdef _WIN32
293	q = strrchr(p, '\\');
294	if (q != NULL)
295	    p = q;
296	else
297#endif
298	p = strchr(p, '/');
299	if(p == NULL) {
300	    krb5_set_error_message(context, HEIM_ERR_LOG_PARSE,
301				   N_("failed to parse \"%s\"", ""), orig);
302	    return HEIM_ERR_LOG_PARSE;
303	}
304	p++;
305    }
306    if(strcmp(p, "STDERR") == 0){
307	ret = open_file(context, f, min, max, NULL, NULL, stderr, 1, 0);
308    }else if(strcmp(p, "CONSOLE") == 0){
309	ret = open_file(context, f, min, max, "/dev/console", "w", NULL, 0, 0);
310    }else if(strncmp(p, "FILE", 4) == 0 && (p[4] == ':' || p[4] == '=')){
311	char *fn;
312	FILE *file = NULL;
313	int keep_open = 0;
314	fn = strdup(p + 5);
315	if (fn == NULL)
316	    return krb5_enomem(context);
317	if(p[4] == '='){
318	    int i = open(fn, O_WRONLY | O_CREAT |
319			 O_TRUNC | O_APPEND, 0666);
320	    if(i < 0) {
321		ret = errno;
322		krb5_set_error_message(context, ret,
323				       N_("open(%s) logfile: %s", ""), fn,
324				       strerror(ret));
325		free(fn);
326		return ret;
327	    }
328	    rk_cloexec(i);
329	    file = fdopen(i, "a");
330	    if(file == NULL){
331		ret = errno;
332		close(i);
333		krb5_set_error_message(context, ret,
334				       N_("fdopen(%s) logfile: %s", ""),
335				       fn, strerror(ret));
336		free(fn);
337		return ret;
338	    }
339	    keep_open = 1;
340	}
341	ret = open_file(context, f, min, max, fn, "a", file, keep_open, 1);
342    }else if(strncmp(p, "DEVICE", 6) == 0 && (p[6] == ':' || p[6] == '=')){
343	ret = open_file(context, f, min, max, strdup(p + 7), "w", NULL, 0, 1);
344    }else if(strncmp(p, "SYSLOG", 6) == 0 && (p[6] == '\0' || p[6] == ':')){
345	char severity[128] = "";
346	char facility[128] = "";
347	p += 6;
348	if(*p != '\0')
349	    p++;
350	if(strsep_copy(&p, ":", severity, sizeof(severity)) != -1)
351	    strsep_copy(&p, ":", facility, sizeof(facility));
352	if(*severity == '\0')
353	    strlcpy(severity, "ERR", sizeof(severity));
354 	if(*facility == '\0')
355	    strlcpy(facility, "AUTH", sizeof(facility));
356	ret = open_syslog(context, f, min, max, severity, facility);
357    }else{
358	ret = HEIM_ERR_LOG_PARSE; /* XXX */
359	krb5_set_error_message (context, ret,
360				N_("unknown log type: %s", ""), p);
361    }
362    return ret;
363}
364
365
366KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
367krb5_openlog(krb5_context context,
368	     const char *program,
369	     krb5_log_facility **fac)
370{
371    krb5_error_code ret;
372    char **p, **q;
373
374    ret = krb5_initlog(context, program, fac);
375    if(ret)
376	return ret;
377
378    p = krb5_config_get_strings(context, NULL, "logging", program, NULL);
379    if(p == NULL)
380	p = krb5_config_get_strings(context, NULL, "logging", "default", NULL);
381    if(p){
382	for(q = p; *q && ret == 0; q++)
383	    ret = krb5_addlog_dest(context, *fac, *q);
384	krb5_config_free_strings(p);
385    }else
386	ret = krb5_addlog_dest(context, *fac, "SYSLOG");
387    return ret;
388}
389
390KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
391krb5_closelog(krb5_context context,
392	      krb5_log_facility *fac)
393{
394    int i;
395    for(i = 0; i < fac->len; i++)
396	(*fac->val[i].close_func)(fac->val[i].data);
397    free(fac->val);
398    free(fac->program);
399    fac->val = NULL;
400    fac->len = 0;
401    fac->program = NULL;
402    free(fac);
403    return 0;
404}
405
406#undef __attribute__
407#define __attribute__(X)
408
409KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
410krb5_vlog_msg(krb5_context context,
411	      krb5_log_facility *fac,
412	      char **reply,
413	      int level,
414	      const char *fmt,
415	      va_list ap)
416     __attribute__ ((__format__ (__printf__, 5, 0)))
417{
418
419    char *msg = NULL;
420    const char *actual = NULL;
421    char buf[64];
422    time_t t = 0;
423    int i;
424
425    for(i = 0; fac && i < fac->len; i++)
426	if(fac->val[i].min <= level &&
427	   (fac->val[i].max < 0 || fac->val[i].max >= level)) {
428	    if(t == 0) {
429		t = time(NULL);
430		krb5_format_time(context, t, buf, sizeof(buf), TRUE);
431	    }
432	    if(actual == NULL) {
433		int ret = vasprintf(&msg, fmt, ap);
434		if(ret < 0 || msg == NULL)
435		    actual = fmt;
436		else
437		    actual = msg;
438	    }
439	    (*fac->val[i].log_func)(buf, actual, fac->val[i].data);
440	}
441    if(reply == NULL)
442	free(msg);
443    else
444	*reply = msg;
445    return 0;
446}
447
448KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
449krb5_vlog(krb5_context context,
450	  krb5_log_facility *fac,
451	  int level,
452	  const char *fmt,
453	  va_list ap)
454     __attribute__ ((__format__ (__printf__, 4, 0)))
455{
456    return krb5_vlog_msg(context, fac, NULL, level, fmt, ap);
457}
458
459KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
460krb5_log_msg(krb5_context context,
461	     krb5_log_facility *fac,
462	     int level,
463	     char **reply,
464	     const char *fmt,
465	     ...)
466     __attribute__ ((__format__ (__printf__, 5, 6)))
467{
468    va_list ap;
469    krb5_error_code ret;
470
471    va_start(ap, fmt);
472    ret = krb5_vlog_msg(context, fac, reply, level, fmt, ap);
473    va_end(ap);
474    return ret;
475}
476
477
478KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
479krb5_log(krb5_context context,
480	 krb5_log_facility *fac,
481	 int level,
482	 const char *fmt,
483	 ...)
484     __attribute__ ((__format__ (__printf__, 4, 5)))
485{
486    va_list ap;
487    krb5_error_code ret;
488
489    va_start(ap, fmt);
490    ret = krb5_vlog(context, fac, level, fmt, ap);
491    va_end(ap);
492    return ret;
493}
494
495void KRB5_LIB_FUNCTION
496_krb5_debug(krb5_context context,
497	    int level,
498	    const char *fmt,
499	    ...)
500    __attribute__ ((__format__ (__printf__, 3, 4)))
501{
502    va_list ap;
503
504    if (context == NULL || context->debug_dest == NULL)
505	return;
506
507    va_start(ap, fmt);
508    krb5_vlog(context, context->debug_dest, level, fmt, ap);
509    va_end(ap);
510}
511
512KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
513_krb5_have_debug(krb5_context context, int level)
514{
515    if (context == NULL || context->debug_dest == NULL)
516	return 0 ;
517    return 1;
518}
519
520KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
521krb5_set_debug_dest(krb5_context context, const char *program,
522                    const char *log_spec)
523{
524    krb5_error_code ret;
525
526    if (context->debug_dest == NULL) {
527        ret = krb5_initlog(context, program, &context->debug_dest);
528        if (ret)
529            return ret;
530    }
531
532    ret = krb5_addlog_dest(context, context->debug_dest, log_spec);
533    if (ret)
534        return ret;
535    return 0;
536}
537