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