]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - logging.c
Turn 'Filename' into a dynamically allocated type with no arbitrary
[PuTTY.git] / logging.c
1 /*
2  * Session logging.
3  */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <ctype.h>
8
9 #include <time.h>
10 #include <assert.h>
11
12 #include "putty.h"
13
14 /* log session to file stuff ... */
15 struct LogContext {
16     FILE *lgfp;
17     enum { L_CLOSED, L_OPENING, L_OPEN, L_ERROR } state;
18     bufchain queue;
19     Filename *currlogfilename;
20     void *frontend;
21     Conf *conf;
22     int logtype;                       /* cached out of conf */
23 };
24
25 static Filename *xlatlognam(Filename *s, char *hostname, struct tm *tm);
26
27 /*
28  * Internal wrapper function which must be called for _all_ output
29  * to the log file. It takes care of opening the log file if it
30  * isn't open, buffering data if it's in the process of being
31  * opened asynchronously, etc.
32  */
33 static void logwrite(struct LogContext *ctx, void *data, int len)
34 {
35     /*
36      * In state L_CLOSED, we call logfopen, which will set the state
37      * to one of L_OPENING, L_OPEN or L_ERROR. Hence we process all of
38      * those three _after_ processing L_CLOSED.
39      */
40     if (ctx->state == L_CLOSED)
41         logfopen(ctx);
42
43     if (ctx->state == L_OPENING) {
44         bufchain_add(&ctx->queue, data, len);
45     } else if (ctx->state == L_OPEN) {
46         assert(ctx->lgfp);
47         if (fwrite(data, 1, len, ctx->lgfp) < (size_t)len) {
48             logfclose(ctx);
49             ctx->state = L_ERROR;
50             /* Log state is L_ERROR so this won't cause a loop */
51             logevent(ctx->frontend,
52                      "Disabled writing session log due to error while writing");
53         }
54     }                                  /* else L_ERROR, so ignore the write */
55 }
56
57 /*
58  * Convenience wrapper on logwrite() which printf-formats the
59  * string.
60  */
61 static void logprintf(struct LogContext *ctx, const char *fmt, ...)
62 {
63     va_list ap;
64     char *data;
65
66     va_start(ap, fmt);
67     data = dupvprintf(fmt, ap);
68     va_end(ap);
69
70     logwrite(ctx, data, strlen(data));
71     sfree(data);
72 }
73
74 /*
75  * Flush any open log file.
76  */
77 void logflush(void *handle) {
78     struct LogContext *ctx = (struct LogContext *)handle;
79     if (ctx->logtype > 0)
80         if (ctx->state == L_OPEN)
81             fflush(ctx->lgfp);
82 }
83
84 static void logfopen_callback(void *handle, int mode)
85 {
86     struct LogContext *ctx = (struct LogContext *)handle;
87     char buf[256], *event;
88     struct tm tm;
89     const char *fmode;
90
91     if (mode == 0) {
92         ctx->state = L_ERROR;          /* disable logging */
93     } else {
94         fmode = (mode == 1 ? "ab" : "wb");
95         ctx->lgfp = f_open(ctx->currlogfilename, fmode, FALSE);
96         if (ctx->lgfp)
97             ctx->state = L_OPEN;
98         else
99             ctx->state = L_ERROR;
100     }
101
102     if (ctx->state == L_OPEN) {
103         /* Write header line into log file. */
104         tm = ltime();
105         strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm);
106         logprintf(ctx, "=~=~=~=~=~=~=~=~=~=~=~= PuTTY log %s"
107                   " =~=~=~=~=~=~=~=~=~=~=~=\r\n", buf);
108     }
109
110     event = dupprintf("%s session log (%s mode) to file: %s",
111                       ctx->state == L_ERROR ?
112                       (mode == 0 ? "Disabled writing" : "Error writing") :
113                       (mode == 1 ? "Appending" : "Writing new"),
114                       (ctx->logtype == LGTYP_ASCII ? "ASCII" :
115                        ctx->logtype == LGTYP_DEBUG ? "raw" :
116                        ctx->logtype == LGTYP_PACKETS ? "SSH packets" :
117                        ctx->logtype == LGTYP_SSHRAW ? "SSH raw data" :
118                        "unknown"),
119                       filename_to_str(ctx->currlogfilename));
120     logevent(ctx->frontend, event);
121     sfree(event);
122
123     /*
124      * Having either succeeded or failed in opening the log file,
125      * we should write any queued data out.
126      */
127     assert(ctx->state != L_OPENING);   /* make _sure_ it won't be requeued */
128     while (bufchain_size(&ctx->queue)) {
129         void *data;
130         int len;
131         bufchain_prefix(&ctx->queue, &data, &len);
132         logwrite(ctx, data, len);
133         bufchain_consume(&ctx->queue, len);
134     }
135 }
136
137 /*
138  * Open the log file. Takes care of detecting an already-existing
139  * file and asking the user whether they want to append, overwrite
140  * or cancel logging.
141  */
142 void logfopen(void *handle)
143 {
144     struct LogContext *ctx = (struct LogContext *)handle;
145     struct tm tm;
146     int mode;
147
148     /* Prevent repeat calls */
149     if (ctx->state != L_CLOSED)
150         return;
151
152     if (!ctx->logtype)
153         return;
154
155     tm = ltime();
156
157     /* substitute special codes in file name */
158     if (ctx->currlogfilename)
159         filename_free(ctx->currlogfilename);
160     ctx->currlogfilename = 
161         xlatlognam(conf_get_filename(ctx->conf, CONF_logfilename),
162                    conf_get_str(ctx->conf, CONF_host), &tm);
163
164     ctx->lgfp = f_open(ctx->currlogfilename, "r", FALSE);  /* file already present? */
165     if (ctx->lgfp) {
166         int logxfovr = conf_get_int(ctx->conf, CONF_logxfovr);
167         fclose(ctx->lgfp);
168         if (logxfovr != LGXF_ASK) {
169             mode = ((logxfovr == LGXF_OVR) ? 2 : 1);
170         } else
171             mode = askappend(ctx->frontend, ctx->currlogfilename,
172                              logfopen_callback, ctx);
173     } else
174         mode = 2;                      /* create == overwrite */
175
176     if (mode < 0)
177         ctx->state = L_OPENING;
178     else
179         logfopen_callback(ctx, mode);  /* open the file */
180 }
181
182 void logfclose(void *handle)
183 {
184     struct LogContext *ctx = (struct LogContext *)handle;
185     if (ctx->lgfp) {
186         fclose(ctx->lgfp);
187         ctx->lgfp = NULL;
188     }
189     ctx->state = L_CLOSED;
190 }
191
192 /*
193  * Log session traffic.
194  */
195 void logtraffic(void *handle, unsigned char c, int logmode)
196 {
197     struct LogContext *ctx = (struct LogContext *)handle;
198     if (ctx->logtype > 0) {
199         if (ctx->logtype == logmode)
200             logwrite(ctx, &c, 1);
201     }
202 }
203
204 /*
205  * Log an Event Log entry. Used in SSH packet logging mode; this is
206  * also as convenient a place as any to put the output of Event Log
207  * entries to stderr when a command-line tool is in verbose mode.
208  * (In particular, this is a better place to put it than in the
209  * front ends, because it only has to be done once for all
210  * platforms. Platforms which don't have a meaningful stderr can
211  * just avoid defining FLAG_STDERR.
212  */
213 void log_eventlog(void *handle, const char *event)
214 {
215     struct LogContext *ctx = (struct LogContext *)handle;
216     if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) {
217         fprintf(stderr, "%s\n", event);
218         fflush(stderr);
219     }
220     /* If we don't have a context yet (eg winnet.c init) then skip entirely */
221     if (!ctx)
222         return;
223     if (ctx->logtype != LGTYP_PACKETS &&
224         ctx->logtype != LGTYP_SSHRAW)
225         return;
226     logprintf(ctx, "Event Log: %s\r\n", event);
227     logflush(ctx);
228 }
229
230 /*
231  * Log an SSH packet.
232  * If n_blanks != 0, blank or omit some parts.
233  * Set of blanking areas must be in increasing order.
234  */
235 void log_packet(void *handle, int direction, int type,
236                 char *texttype, const void *data, int len,
237                 int n_blanks, const struct logblank_t *blanks,
238                 const unsigned long *seq)
239 {
240     struct LogContext *ctx = (struct LogContext *)handle;
241     char dumpdata[80], smalldata[5];
242     int p = 0, b = 0, omitted = 0;
243     int output_pos = 0; /* NZ if pending output in dumpdata */
244
245     if (!(ctx->logtype == LGTYP_SSHRAW ||
246           (ctx->logtype == LGTYP_PACKETS && texttype)))
247         return;
248
249     /* Packet header. */
250     if (texttype) {
251         if (seq) {
252             logprintf(ctx, "%s packet #0x%lx, type %d / 0x%02x (%s)\r\n",
253                       direction == PKT_INCOMING ? "Incoming" : "Outgoing",
254                       *seq, type, type, texttype);
255         } else {
256             logprintf(ctx, "%s packet type %d / 0x%02x (%s)\r\n",
257                       direction == PKT_INCOMING ? "Incoming" : "Outgoing",
258                       type, type, texttype);
259         }
260     } else {
261         logprintf(ctx, "%s raw data\r\n",
262                   direction == PKT_INCOMING ? "Incoming" : "Outgoing");
263     }
264
265     /*
266      * Output a hex/ASCII dump of the packet body, blanking/omitting
267      * parts as specified.
268      */
269     while (p < len) {
270         int blktype;
271
272         /* Move to a current entry in the blanking array. */
273         while ((b < n_blanks) &&
274                (p >= blanks[b].offset + blanks[b].len))
275             b++;
276         /* Work out what type of blanking to apply to
277          * this byte. */
278         blktype = PKTLOG_EMIT; /* default */
279         if ((b < n_blanks) &&
280             (p >= blanks[b].offset) &&
281             (p < blanks[b].offset + blanks[b].len))
282             blktype = blanks[b].type;
283
284         /* If we're about to stop omitting, it's time to say how
285          * much we omitted. */
286         if ((blktype != PKTLOG_OMIT) && omitted) {
287             logprintf(ctx, "  (%d byte%s omitted)\r\n",
288                       omitted, (omitted==1?"":"s"));
289             omitted = 0;
290         }
291
292         /* (Re-)initialise dumpdata as necessary
293          * (start of row, or if we've just stopped omitting) */
294         if (!output_pos && !omitted)
295             sprintf(dumpdata, "  %08x%*s\r\n", p-(p%16), 1+3*16+2+16, "");
296
297         /* Deal with the current byte. */
298         if (blktype == PKTLOG_OMIT) {
299             omitted++;
300         } else {
301             int c;
302             if (blktype == PKTLOG_BLANK) {
303                 c = 'X';
304                 sprintf(smalldata, "XX");
305             } else {  /* PKTLOG_EMIT */
306                 c = ((unsigned char *)data)[p];
307                 sprintf(smalldata, "%02x", c);
308             }
309             dumpdata[10+2+3*(p%16)] = smalldata[0];
310             dumpdata[10+2+3*(p%16)+1] = smalldata[1];
311             dumpdata[10+1+3*16+2+(p%16)] = (isprint(c) ? c : '.');
312             output_pos = (p%16) + 1;
313         }
314
315         p++;
316
317         /* Flush row if necessary */
318         if (((p % 16) == 0) || (p == len) || omitted) {
319             if (output_pos) {
320                 strcpy(dumpdata + 10+1+3*16+2+output_pos, "\r\n");
321                 logwrite(ctx, dumpdata, strlen(dumpdata));
322                 output_pos = 0;
323             }
324         }
325
326     }
327
328     /* Tidy up */
329     if (omitted)
330         logprintf(ctx, "  (%d byte%s omitted)\r\n",
331                   omitted, (omitted==1?"":"s"));
332     logflush(ctx);
333 }
334
335 void *log_init(void *frontend, Conf *conf)
336 {
337     struct LogContext *ctx = snew(struct LogContext);
338     ctx->lgfp = NULL;
339     ctx->state = L_CLOSED;
340     ctx->frontend = frontend;
341     ctx->conf = conf_copy(conf);
342     ctx->logtype = conf_get_int(ctx->conf, CONF_logtype);
343     ctx->currlogfilename = NULL;
344     bufchain_init(&ctx->queue);
345     return ctx;
346 }
347
348 void log_free(void *handle)
349 {
350     struct LogContext *ctx = (struct LogContext *)handle;
351
352     logfclose(ctx);
353     bufchain_clear(&ctx->queue);
354     if (ctx->currlogfilename)
355         filename_free(ctx->currlogfilename);
356     sfree(ctx);
357 }
358
359 void log_reconfig(void *handle, Conf *conf)
360 {
361     struct LogContext *ctx = (struct LogContext *)handle;
362     int reset_logging;
363
364     if (!filename_equal(conf_get_filename(ctx->conf, CONF_logfilename),
365                         conf_get_filename(conf, CONF_logfilename)) ||
366         conf_get_int(ctx->conf, CONF_logtype) !=
367         conf_get_int(conf, CONF_logtype))
368         reset_logging = TRUE;
369     else
370         reset_logging = FALSE;
371
372     if (reset_logging)
373         logfclose(ctx);
374
375     conf_free(ctx->conf);
376     ctx->conf = conf_copy(conf);
377
378     ctx->logtype = conf_get_int(ctx->conf, CONF_logtype);
379
380     if (reset_logging)
381         logfopen(ctx);
382 }
383
384 /*
385  * translate format codes into time/date strings
386  * and insert them into log file name
387  *
388  * "&Y":YYYY   "&m":MM   "&d":DD   "&T":hhmmss   "&h":<hostname>   "&&":&
389  */
390 static Filename *xlatlognam(Filename *src, char *hostname, struct tm *tm)
391 {
392     char buf[10], *bufp;
393     int size;
394     char *buffer;
395     int buflen, bufsize;
396     const char *s;
397     Filename *ret;
398
399     bufsize = FILENAME_MAX;
400     buffer = snewn(bufsize, char);
401     buflen = 0;
402     s = filename_to_str(src);
403
404     while (*s) {
405         /* Let (bufp, len) be the string to append. */
406         bufp = buf;                    /* don't usually override this */
407         if (*s == '&') {
408             char c;
409             s++;
410             size = 0;
411             if (*s) switch (c = *s++, tolower((unsigned char)c)) {
412               case 'y':
413                 size = strftime(buf, sizeof(buf), "%Y", tm);
414                 break;
415               case 'm':
416                 size = strftime(buf, sizeof(buf), "%m", tm);
417                 break;
418               case 'd':
419                 size = strftime(buf, sizeof(buf), "%d", tm);
420                 break;
421               case 't':
422                 size = strftime(buf, sizeof(buf), "%H%M%S", tm);
423                 break;
424               case 'h':
425                 bufp = hostname;
426                 size = strlen(bufp);
427                 break;
428               default:
429                 buf[0] = '&';
430                 size = 1;
431                 if (c != '&')
432                     buf[size++] = c;
433             }
434         } else {
435             buf[0] = *s++;
436             size = 1;
437         }
438         if (bufsize <= buflen + size) {
439             bufsize = (buflen + size) * 5 / 4 + 512;
440             buffer = sresize(buffer, bufsize, char);
441         }
442         memcpy(buffer + buflen, bufp, size);
443         buflen += size;
444     }
445     buffer[buflen] = '\0';
446
447     ret = filename_from_str(buffer);
448     sfree(buffer);
449     return ret;
450 }