e859c13444949078d186ca25efa973c75d263fc3
[krb5.git] / src / lib / krb5 / os / trace.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/os/trace.c - krb5int_trace implementation */
3 /*
4  * Copyright 2009 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  */
26
27 /*
28  * krb5int_trace is defined in k5-trace.h as a macro or static inline
29  * function, and is called like so:
30  *
31  *   void krb5int_trace(krb5_context context, const char *fmt, ...)
32  *
33  * Arguments may or may not be evaluated, so don't pass argument
34  * expressions with side effects.  Tracing support and calls can be
35  * explicitly compiled out with DISABLE_TRACING, but compile-time
36  * support is enabled by default.  Tracing calls use a custom
37  * formatter supporting the following format specifications:
38  */
39
40 #include "k5-int.h"
41 #include "cm.h"
42
43 #ifndef DISABLE_TRACING
44
45 static void subfmt(krb5_context context, struct k5buf *buf,
46                    const char *fmt, ...);
47
48 /* Return a four-byte hex string from the first two bytes of a SHA-1 hash of a
49  * byte array.  Return NULL on failure. */
50 static char *
51 hash_bytes(krb5_context context, const void *ptr, size_t len)
52 {
53     krb5_checksum cksum;
54     krb5_data d = make_data((void *) ptr, len);
55     char *s = NULL;
56
57     if (krb5_k_make_checksum(context, CKSUMTYPE_NIST_SHA, NULL, 0, &d,
58                              &cksum) != 0)
59         return NULL;
60     if (cksum.length >= 2)
61         (void) asprintf(&s, "%02X%02X", cksum.contents[0], cksum.contents[1]);
62     krb5_free_checksum_contents(context, &cksum);
63     return s;
64 }
65
66 static char *
67 trace_format(krb5_context context, const char *fmt, va_list ap)
68 {
69     struct k5buf buf;
70     krb5_error_code kerr;
71     size_t len, i;
72     int err;
73     struct conn_state *cs;
74     const krb5_data *d;
75     krb5_data data;
76     char addrbuf[NI_MAXHOST], portbuf[NI_MAXSERV], tmpbuf[200], *str;
77     const char *p;
78     krb5_const_principal princ;
79     const krb5_keyblock *keyblock;
80     krb5_key key;
81     const krb5_checksum *cksum;
82     krb5_pa_data **padata;
83     krb5_ccache ccache;
84     krb5_keytab keytab;
85     krb5_creds *creds;
86     krb5_enctype *etypes, etype;
87
88     krb5int_buf_init_dynamic(&buf);
89     while (TRUE) {
90         /* Advance to the next word in braces. */
91         len = strcspn(fmt, "{");
92         krb5int_buf_add_len(&buf, fmt, len);
93         if (fmt[len] == '\0')
94             break;
95         fmt += len + 1;
96         len = strcspn(fmt, "}");
97         if (fmt[len] == '\0' || len > sizeof(tmpbuf) - 1)
98             break;
99         memcpy(tmpbuf, fmt, len);
100         tmpbuf[len] = '\0';
101         fmt += len + 1;
102
103         /* Process the format word. */
104         if (strcmp(tmpbuf, "int") == 0) {
105             krb5int_buf_add_fmt(&buf, "%d", va_arg(ap, int));
106         } else if (strcmp(tmpbuf, "long") == 0) {
107             krb5int_buf_add_fmt(&buf, "%ld", va_arg(ap, long));
108         } else if (strcmp(tmpbuf, "str") == 0) {
109             p = va_arg(ap, const char *);
110             krb5int_buf_add(&buf, (p == NULL) ? "(null)" : p);
111         } else if (strcmp(tmpbuf, "lenstr") == 0) {
112             len = va_arg(ap, size_t);
113             p = va_arg(ap, const char *);
114             if (p == NULL && len != 0)
115                 krb5int_buf_add(&buf, "(null)");
116             else
117                 krb5int_buf_add_len(&buf, p, len);
118         } else if (strcmp(tmpbuf, "hexlenstr") == 0) {
119             len = va_arg(ap, size_t);
120             p = va_arg(ap, const char *);
121             if (p == NULL && len != 0)
122                 krb5int_buf_add(&buf, "(null)");
123             else {
124                 for (i = 0; i < len; i++)
125                     krb5int_buf_add_fmt(&buf, "%02X", (unsigned char) p[i]);
126             }
127         } else if (strcmp(tmpbuf, "hashlenstr") == 0) {
128             len = va_arg(ap, size_t);
129             p = va_arg(ap, const char *);
130             if (p == NULL && len != 0)
131                 krb5int_buf_add(&buf, "(null)");
132             else {
133                 str = hash_bytes(context, p, len);
134                 if (str != NULL)
135                     krb5int_buf_add(&buf, str);
136                 free(str);
137             }
138         } else if (strcmp(tmpbuf, "connstate") == 0) {
139             cs = va_arg(ap, struct conn_state *);
140             if (cs->socktype == SOCK_DGRAM)
141                 krb5int_buf_add(&buf, "dgram");
142             else if (cs->socktype == SOCK_STREAM)
143                 krb5int_buf_add(&buf, "stream");
144             else
145                 krb5int_buf_add_fmt(&buf, "socktype%d", cs->socktype);
146
147             if (getnameinfo((struct sockaddr *)&cs->addr, cs->addrlen,
148                             addrbuf, sizeof(addrbuf), portbuf, sizeof(portbuf),
149                             NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
150                 if (cs->family == AF_UNSPEC)
151                     krb5int_buf_add(&buf, " AF_UNSPEC");
152                 else
153                     krb5int_buf_add_fmt(&buf, " af%d", cs->family);
154             } else
155                 krb5int_buf_add_fmt(&buf, " %s:%s", addrbuf, portbuf);
156         } else if (strcmp(tmpbuf, "data") == 0) {
157             d = va_arg(ap, krb5_data *);
158             if (d == NULL || (d->length != 0 && d->data == NULL))
159                 krb5int_buf_add(&buf, "(null)");
160             else
161                 krb5int_buf_add_len(&buf, d->data, d->length);
162         } else if (strcmp(tmpbuf, "hexdata") == 0) {
163             d = va_arg(ap, krb5_data *);
164             if (d == NULL)
165                 krb5int_buf_add(&buf, "(null)");
166             else
167                 subfmt(context, &buf, "{hexlenstr}", d->length, d->data);
168         } else if (strcmp(tmpbuf, "errno") == 0) {
169             err = va_arg(ap, int);
170             p = NULL;
171 #ifdef HAVE_STRERROR_R
172             if (strerror_r(err, tmpbuf, sizeof(tmpbuf)) == 0)
173                 p = tmpbuf;
174 #endif
175             if (p == NULL)
176                 p = strerror(err);
177             krb5int_buf_add_fmt(&buf, "%d/%s", err, p);
178         } else if (strcmp(tmpbuf, "kerr") == 0) {
179             kerr = va_arg(ap, krb5_error_code);
180             p = krb5_get_error_message(context, kerr);
181             krb5int_buf_add_fmt(&buf, "%ld/%s", (long) kerr,
182                                 kerr ? p : "Success");
183             krb5_free_error_message(context, p);
184         } else if (strcmp(tmpbuf, "keyblock") == 0) {
185             keyblock = va_arg(ap, const krb5_keyblock *);
186             if (keyblock == NULL)
187                 krb5int_buf_add(&buf, "(null)");
188             else {
189                 subfmt(context, &buf, "{etype}/{hashlenstr}",
190                        keyblock->enctype, keyblock->length,
191                        keyblock->contents);
192             }
193         } else if (strcmp(tmpbuf, "key") == 0) {
194             key = va_arg(ap, krb5_key);
195             if (key == NULL)
196                 krb5int_buf_add(&buf, "(null");
197             else
198                 subfmt(context, &buf, "{keyblock}", &key->keyblock);
199         } else if (strcmp(tmpbuf, "cksum") == 0) {
200             cksum = va_arg(ap, const krb5_checksum *);
201             data = make_data(cksum->contents, cksum->length);
202             subfmt(context, &buf, "{int}/{hexdata}",
203                    (int) cksum->checksum_type, &data);
204         } else if (strcmp(tmpbuf, "princ") == 0) {
205             princ = va_arg(ap, krb5_principal);
206             if (krb5_unparse_name(context, princ, &str) == 0) {
207                 krb5int_buf_add(&buf, str);
208                 krb5_free_unparsed_name(context, str);
209             }
210         } else if (strcmp(tmpbuf, "patypes") == 0) {
211             padata = va_arg(ap, krb5_pa_data **);
212             if (padata == NULL || *padata == NULL)
213                 krb5int_buf_add(&buf, "(empty)");
214             for (; padata != NULL && *padata != NULL; padata++) {
215                 krb5int_buf_add_fmt(&buf, "%d", (int) (*padata)->pa_type);
216                 if (*(padata + 1) != NULL)
217                     krb5int_buf_add(&buf, ", ");
218             }
219         } else if (strcmp(tmpbuf, "etype") == 0) {
220             etype = va_arg(ap, krb5_enctype);
221             if (krb5_enctype_to_name(etype, TRUE, tmpbuf, sizeof(tmpbuf)) == 0)
222                 krb5int_buf_add(&buf, tmpbuf);
223             else
224                 krb5int_buf_add_fmt(&buf, "%d", (int) etype);
225         } else if (strcmp(tmpbuf, "etypes") == 0) {
226             etypes = va_arg(ap, krb5_enctype *);
227             if (etypes == NULL || *etypes == 0)
228                 krb5int_buf_add(&buf, "(empty");
229             for (; etypes != NULL && *etypes != 0; etypes++) {
230                 subfmt(context, &buf, "{etype}", *etypes);
231                 if (*(etypes + 1) != 0)
232                     krb5int_buf_add(&buf, ", ");
233             }
234         } else if (strcmp(tmpbuf, "ccache") == 0) {
235             ccache = va_arg(ap, krb5_ccache);
236             krb5int_buf_add(&buf, krb5_cc_get_type(context, ccache));
237             krb5int_buf_add(&buf, ":");
238             krb5int_buf_add(&buf, krb5_cc_get_name(context, ccache));
239         } else if (strcmp(tmpbuf, "keytab") == 0) {
240             keytab = va_arg(ap, krb5_keytab);
241             if (krb5_kt_get_name(context, keytab, tmpbuf, sizeof(tmpbuf)) == 0)
242                 krb5int_buf_add(&buf, tmpbuf);
243         } else if (strcmp(tmpbuf, "creds") == 0) {
244             creds = va_arg(ap, krb5_creds *);
245             subfmt(context, &buf, "{princ} -> {princ}",
246                    creds->client, creds->server);
247         }
248     }
249     return krb5int_buf_data(&buf);
250 }
251
252 /* Allows trace_format formatters to be represented in terms of other
253  * formatters. */
254 static void
255 subfmt(krb5_context context, struct k5buf *buf, const char *fmt, ...)
256 {
257     va_list ap;
258     char *str;
259
260     va_start(ap, fmt);
261     str = trace_format(context, fmt, ap);
262     if (str != NULL)
263         krb5int_buf_add(buf, str);
264     free(str);
265     va_end(ap);
266 }
267
268 void
269 krb5int_init_trace(krb5_context context)
270 {
271     const char *filename;
272
273     filename = getenv("KRB5_TRACE");
274     if (filename)
275         (void) krb5_set_trace_filename(context, filename);
276 }
277
278 void
279 krb5int_trace(krb5_context context, const char *fmt, ...)
280 {
281     va_list ap;
282     struct krb5_trace_info info;
283     char *str = NULL, *msg = NULL;
284     krb5_int32 sec, usec;
285
286     if (context == NULL || context->trace_callback == NULL)
287         return;
288     va_start(ap, fmt);
289     str = trace_format(context, fmt, ap);
290     if (str == NULL)
291         goto cleanup;
292     if (krb5_crypto_us_timeofday(&sec, &usec) != 0)
293         goto cleanup;
294     if (asprintf(&msg, "[%d] %d.%d: %s\n", (int) getpid(), (int) sec,
295                  (int) usec, str) < 0)
296         goto cleanup;
297     info.message = msg;
298     context->trace_callback(context, &info, context->trace_callback_data);
299 cleanup:
300     free(str);
301     free(msg);
302     va_end(ap);
303 }
304
305 krb5_error_code KRB5_CALLCONV
306 krb5_set_trace_callback(krb5_context context, krb5_trace_callback fn,
307                         void *cb_data)
308 {
309     /* Allow the old callback to destroy its data if necessary. */
310     if (context->trace_callback != NULL)
311         context->trace_callback(context, NULL, context->trace_callback_data);
312     context->trace_callback = fn;
313     context->trace_callback_data = cb_data;
314     return 0;
315 }
316
317 static void KRB5_CALLCONV
318 file_trace_cb(krb5_context context, const struct krb5_trace_info *info, void *data)
319 {
320     int *fd = data;
321
322     if (info == NULL) {
323         /* Null info means destroy the callback data. */
324         close(*fd);
325         free(fd);
326         return;
327     }
328
329     (void) write(*fd, info->message, strlen(info->message));
330 }
331
332 krb5_error_code KRB5_CALLCONV
333 krb5_set_trace_filename(krb5_context context, const char *filename)
334 {
335     int *fd;
336
337     /* Create callback data containing a file descriptor. */
338     fd = malloc(sizeof(*fd));
339     if (fd == NULL)
340         return ENOMEM;
341     *fd = open(filename, O_WRONLY|O_CREAT|O_APPEND, 0600);
342     if (*fd == -1) {
343         free(fd);
344         return errno;
345     }
346
347     return krb5_set_trace_callback(context, file_trace_cb, fd);
348 }
349
350 #else /* DISABLE_TRACING */
351
352 krb5_error_code KRB5_CALLCONV
353 krb5_set_trace_callback(krb5_context context, krb5_trace_callback fn,
354                         void *cb_data)
355 {
356     if (fn == NULL)
357         return 0;
358     return KRB5_TRACE_NOSUPP;
359 }
360
361 krb5_error_code KRB5_CALLCONV
362 krb5_set_trace_filename(krb5_context context, const char *filename)
363 {
364     return KRB5_TRACE_NOSUPP;
365 }
366
367 #endif /* DISABLE_TRACING */