]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - logging.c
Post-release destabilisation! Completely remove the struct type
[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 void xlatlognam(Filename *d, 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     xlatlognam(&ctx->currlogfilename,
159                *conf_get_filename(ctx->conf, CONF_logfilename),
160                conf_get_str(ctx->conf, CONF_host), &tm);
161
162     ctx->lgfp = f_open(ctx->currlogfilename, "r", FALSE);  /* file already present? */
163     if (ctx->lgfp) {
164         int logxfovr = conf_get_int(ctx->conf, CONF_logxfovr);
165         fclose(ctx->lgfp);
166         if (logxfovr != LGXF_ASK) {
167             mode = ((logxfovr == LGXF_OVR) ? 2 : 1);
168         } else
169             mode = askappend(ctx->frontend, ctx->currlogfilename,
170                              logfopen_callback, ctx);
171     } else
172         mode = 2;                      /* create == overwrite */
173
174     if (mode < 0)
175         ctx->state = L_OPENING;
176     else
177         logfopen_callback(ctx, mode);  /* open the file */
178 }
179
180 void logfclose(void *handle)
181 {
182     struct LogContext *ctx = (struct LogContext *)handle;
183     if (ctx->lgfp) {
184         fclose(ctx->lgfp);
185         ctx->lgfp = NULL;
186     }
187     ctx->state = L_CLOSED;
188 }
189
190 /*
191  * Log session traffic.
192  */
193 void logtraffic(void *handle, unsigned char c, int logmode)
194 {
195     struct LogContext *ctx = (struct LogContext *)handle;
196     if (ctx->logtype > 0) {
197         if (ctx->logtype == logmode)
198             logwrite(ctx, &c, 1);
199     }
200 }
201
202 /*
203  * Log an Event Log entry. Used in SSH packet logging mode; this is
204  * also as convenient a place as any to put the output of Event Log
205  * entries to stderr when a command-line tool is in verbose mode.
206  * (In particular, this is a better place to put it than in the
207  * front ends, because it only has to be done once for all
208  * platforms. Platforms which don't have a meaningful stderr can
209  * just avoid defining FLAG_STDERR.
210  */
211 void log_eventlog(void *handle, const char *event)
212 {
213     struct LogContext *ctx = (struct LogContext *)handle;
214     if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) {
215         fprintf(stderr, "%s\n", event);
216         fflush(stderr);
217     }
218     /* If we don't have a context yet (eg winnet.c init) then skip entirely */
219     if (!ctx)
220         return;
221     if (ctx->logtype != LGTYP_PACKETS &&
222         ctx->logtype != LGTYP_SSHRAW)
223         return;
224     logprintf(ctx, "Event Log: %s\r\n", event);
225     logflush(ctx);
226 }
227
228 /*
229  * Log an SSH packet.
230  * If n_blanks != 0, blank or omit some parts.
231  * Set of blanking areas must be in increasing order.
232  */
233 void log_packet(void *handle, int direction, int type,
234                 char *texttype, const void *data, int len,
235                 int n_blanks, const struct logblank_t *blanks,
236                 const unsigned long *seq)
237 {
238     struct LogContext *ctx = (struct LogContext *)handle;
239     char dumpdata[80], smalldata[5];
240     int p = 0, b = 0, omitted = 0;
241     int output_pos = 0; /* NZ if pending output in dumpdata */
242
243     if (!(ctx->logtype == LGTYP_SSHRAW ||
244           (ctx->logtype == LGTYP_PACKETS && texttype)))
245         return;
246
247     /* Packet header. */
248     if (texttype) {
249         if (seq) {
250             logprintf(ctx, "%s packet #0x%lx, type %d / 0x%02x (%s)\r\n",
251                       direction == PKT_INCOMING ? "Incoming" : "Outgoing",
252                       *seq, type, type, texttype);
253         } else {
254             logprintf(ctx, "%s packet type %d / 0x%02x (%s)\r\n",
255                       direction == PKT_INCOMING ? "Incoming" : "Outgoing",
256                       type, type, texttype);
257         }
258     } else {
259         logprintf(ctx, "%s raw data\r\n",
260                   direction == PKT_INCOMING ? "Incoming" : "Outgoing");
261     }
262
263     /*
264      * Output a hex/ASCII dump of the packet body, blanking/omitting
265      * parts as specified.
266      */
267     while (p < len) {
268         int blktype;
269
270         /* Move to a current entry in the blanking array. */
271         while ((b < n_blanks) &&
272                (p >= blanks[b].offset + blanks[b].len))
273             b++;
274         /* Work out what type of blanking to apply to
275          * this byte. */
276         blktype = PKTLOG_EMIT; /* default */
277         if ((b < n_blanks) &&
278             (p >= blanks[b].offset) &&
279             (p < blanks[b].offset + blanks[b].len))
280             blktype = blanks[b].type;
281
282         /* If we're about to stop omitting, it's time to say how
283          * much we omitted. */
284         if ((blktype != PKTLOG_OMIT) && omitted) {
285             logprintf(ctx, "  (%d byte%s omitted)\r\n",
286                       omitted, (omitted==1?"":"s"));
287             omitted = 0;
288         }
289
290         /* (Re-)initialise dumpdata as necessary
291          * (start of row, or if we've just stopped omitting) */
292         if (!output_pos && !omitted)
293             sprintf(dumpdata, "  %08x%*s\r\n", p-(p%16), 1+3*16+2+16, "");
294
295         /* Deal with the current byte. */
296         if (blktype == PKTLOG_OMIT) {
297             omitted++;
298         } else {
299             int c;
300             if (blktype == PKTLOG_BLANK) {
301                 c = 'X';
302                 sprintf(smalldata, "XX");
303             } else {  /* PKTLOG_EMIT */
304                 c = ((unsigned char *)data)[p];
305                 sprintf(smalldata, "%02x", c);
306             }
307             dumpdata[10+2+3*(p%16)] = smalldata[0];
308             dumpdata[10+2+3*(p%16)+1] = smalldata[1];
309             dumpdata[10+1+3*16+2+(p%16)] = (isprint(c) ? c : '.');
310             output_pos = (p%16) + 1;
311         }
312
313         p++;
314
315         /* Flush row if necessary */
316         if (((p % 16) == 0) || (p == len) || omitted) {
317             if (output_pos) {
318                 strcpy(dumpdata + 10+1+3*16+2+output_pos, "\r\n");
319                 logwrite(ctx, dumpdata, strlen(dumpdata));
320                 output_pos = 0;
321             }
322         }
323
324     }
325
326     /* Tidy up */
327     if (omitted)
328         logprintf(ctx, "  (%d byte%s omitted)\r\n",
329                   omitted, (omitted==1?"":"s"));
330     logflush(ctx);
331 }
332
333 void *log_init(void *frontend, Conf *conf)
334 {
335     struct LogContext *ctx = snew(struct LogContext);
336     ctx->lgfp = NULL;
337     ctx->state = L_CLOSED;
338     ctx->frontend = frontend;
339     ctx->conf = conf_copy(conf);
340     ctx->logtype = conf_get_int(ctx->conf, CONF_logtype);
341     bufchain_init(&ctx->queue);
342     return ctx;
343 }
344
345 void log_free(void *handle)
346 {
347     struct LogContext *ctx = (struct LogContext *)handle;
348
349     logfclose(ctx);
350     bufchain_clear(&ctx->queue);
351     sfree(ctx);
352 }
353
354 void log_reconfig(void *handle, Conf *conf)
355 {
356     struct LogContext *ctx = (struct LogContext *)handle;
357     int reset_logging;
358
359     if (!filename_equal(*conf_get_filename(ctx->conf, CONF_logfilename),
360                         *conf_get_filename(conf, CONF_logfilename)) ||
361         conf_get_int(ctx->conf, CONF_logtype) !=
362         conf_get_int(conf, CONF_logtype))
363         reset_logging = TRUE;
364     else
365         reset_logging = FALSE;
366
367     if (reset_logging)
368         logfclose(ctx);
369
370     conf_free(ctx->conf);
371     ctx->conf = conf_copy(conf);
372
373     ctx->logtype = conf_get_int(ctx->conf, CONF_logtype);
374
375     if (reset_logging)
376         logfopen(ctx);
377 }
378
379 /*
380  * translate format codes into time/date strings
381  * and insert them into log file name
382  *
383  * "&Y":YYYY   "&m":MM   "&d":DD   "&T":hhmmss   "&h":<hostname>   "&&":&
384  */
385 static void xlatlognam(Filename *dest, Filename src,
386                        char *hostname, struct tm *tm) {
387     char buf[10], *bufp;
388     int size;
389     char buffer[FILENAME_MAX];
390     int len = sizeof(buffer)-1;
391     char *d;
392     const char *s;
393
394     d = buffer;
395     s = filename_to_str(&src);
396
397     while (*s) {
398         /* Let (bufp, len) be the string to append. */
399         bufp = buf;                    /* don't usually override this */
400         if (*s == '&') {
401             char c;
402             s++;
403             size = 0;
404             if (*s) switch (c = *s++, tolower((unsigned char)c)) {
405               case 'y':
406                 size = strftime(buf, sizeof(buf), "%Y", tm);
407                 break;
408               case 'm':
409                 size = strftime(buf, sizeof(buf), "%m", tm);
410                 break;
411               case 'd':
412                 size = strftime(buf, sizeof(buf), "%d", tm);
413                 break;
414               case 't':
415                 size = strftime(buf, sizeof(buf), "%H%M%S", tm);
416                 break;
417               case 'h':
418                 bufp = hostname;
419                 size = strlen(bufp);
420                 break;
421               default:
422                 buf[0] = '&';
423                 size = 1;
424                 if (c != '&')
425                     buf[size++] = c;
426             }
427         } else {
428             buf[0] = *s++;
429             size = 1;
430         }
431         if (size > len)
432             size = len;
433         memcpy(d, bufp, size);
434         d += size;
435         len -= size;
436     }
437     *d = '\0';
438
439     *dest = filename_from_str(buffer);
440 }