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     LogPolicy *lp;
21     Conf *conf;
22     int logtype;                       /* cached out of conf */
23 };
24 
25 static Filename *xlatlognam(Filename *s, char *hostname, int port,
26                             struct tm *tm);
27 
28 /*
29  * Internal wrapper function which must be called for _all_ output
30  * to the log file. It takes care of opening the log file if it
31  * isn't open, buffering data if it's in the process of being
32  * opened asynchronously, etc.
33  */
logwrite(LogContext * ctx,ptrlen data)34 static void logwrite(LogContext *ctx, ptrlen data)
35 {
36     /*
37      * In state L_CLOSED, we call logfopen, which will set the state
38      * to one of L_OPENING, L_OPEN or L_ERROR. Hence we process all of
39      * those three _after_ processing L_CLOSED.
40      */
41     if (ctx->state == L_CLOSED)
42         logfopen(ctx);
43 
44     if (ctx->state == L_OPENING) {
45         bufchain_add(&ctx->queue, data.ptr, data.len);
46     } else if (ctx->state == L_OPEN) {
47         assert(ctx->lgfp);
48         if (fwrite(data.ptr, 1, data.len, ctx->lgfp) < data.len) {
49             logfclose(ctx);
50             ctx->state = L_ERROR;
51             lp_eventlog(ctx->lp, "Disabled writing session log "
52                         "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  */
logprintf(LogContext * ctx,const char * fmt,...)61 static PRINTF_LIKE(2, 3) void logprintf(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, ptrlen_from_asciz(data));
71     sfree(data);
72 }
73 
74 /*
75  * Flush any open log file.
76  */
logflush(LogContext * ctx)77 void logflush(LogContext *ctx)
78 {
79     if (ctx->logtype > 0)
80         if (ctx->state == L_OPEN)
81             fflush(ctx->lgfp);
82 }
83 
logfopen_callback(void * vctx,int mode)84 static void logfopen_callback(void *vctx, int mode)
85 {
86     LogContext *ctx = (LogContext *)vctx;
87     char buf[256], *event;
88     struct tm tm;
89     const char *fmode;
90     bool shout = false;
91 
92     if (mode == 0) {
93         ctx->state = L_ERROR;          /* disable logging */
94     } else {
95         fmode = (mode == 1 ? "ab" : "wb");
96         ctx->lgfp = f_open(ctx->currlogfilename, fmode, false);
97         if (ctx->lgfp) {
98             ctx->state = L_OPEN;
99         } else {
100             ctx->state = L_ERROR;
101             shout = true;
102         }
103     }
104 
105     if (ctx->state == L_OPEN && conf_get_bool(ctx->conf, CONF_logheader)) {
106         /* Write header line into log file. */
107         tm = ltime();
108         strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm);
109         logprintf(ctx, "=~=~=~=~=~=~=~=~=~=~=~= PuTTY log %s"
110                   " =~=~=~=~=~=~=~=~=~=~=~=\r\n", buf);
111     }
112 
113     event = dupprintf("%s session log (%s mode) to file: %s",
114                       ctx->state == L_ERROR ?
115                       (mode == 0 ? "Disabled writing" : "Error writing") :
116                       (mode == 1 ? "Appending" : "Writing new"),
117                       (ctx->logtype == LGTYP_ASCII ? "ASCII" :
118                        ctx->logtype == LGTYP_DEBUG ? "raw" :
119                        ctx->logtype == LGTYP_PACKETS ? "SSH packets" :
120                        ctx->logtype == LGTYP_SSHRAW ? "SSH raw data" :
121                        "unknown"),
122                       filename_to_str(ctx->currlogfilename));
123     lp_eventlog(ctx->lp, event);
124     if (shout) {
125         /*
126          * If we failed to open the log file due to filesystem error
127          * (as opposed to user action such as clicking Cancel in the
128          * askappend box), we should log it more prominently.
129          */
130         lp_logging_error(ctx->lp, event);
131     }
132     sfree(event);
133 
134     /*
135      * Having either succeeded or failed in opening the log file,
136      * we should write any queued data out.
137      */
138     assert(ctx->state != L_OPENING);   /* make _sure_ it won't be requeued */
139     while (bufchain_size(&ctx->queue)) {
140         ptrlen data = bufchain_prefix(&ctx->queue);
141         logwrite(ctx, data);
142         bufchain_consume(&ctx->queue, data.len);
143     }
144     logflush(ctx);
145 }
146 
147 /*
148  * Open the log file. Takes care of detecting an already-existing
149  * file and asking the user whether they want to append, overwrite
150  * or cancel logging.
151  */
logfopen(LogContext * ctx)152 void logfopen(LogContext *ctx)
153 {
154     struct tm tm;
155     int mode;
156 
157     /* Prevent repeat calls */
158     if (ctx->state != L_CLOSED)
159         return;
160 
161     if (!ctx->logtype)
162         return;
163 
164     tm = ltime();
165 
166     /* substitute special codes in file name */
167     if (ctx->currlogfilename)
168         filename_free(ctx->currlogfilename);
169     ctx->currlogfilename =
170         xlatlognam(conf_get_filename(ctx->conf, CONF_logfilename),
171                    conf_get_str(ctx->conf, CONF_host),
172                    conf_get_int(ctx->conf, CONF_port), &tm);
173 
174     if (open_for_write_would_lose_data(ctx->currlogfilename)) {
175         int logxfovr = conf_get_int(ctx->conf, CONF_logxfovr);
176         if (logxfovr != LGXF_ASK) {
177             mode = ((logxfovr == LGXF_OVR) ? 2 : 1);
178         } else
179             mode = lp_askappend(ctx->lp, ctx->currlogfilename,
180                                 logfopen_callback, ctx);
181     } else
182         mode = 2;                      /* create == overwrite */
183 
184     if (mode < 0)
185         ctx->state = L_OPENING;
186     else
187         logfopen_callback(ctx, mode);  /* open the file */
188 }
189 
logfclose(LogContext * ctx)190 void logfclose(LogContext *ctx)
191 {
192     if (ctx->lgfp) {
193         fclose(ctx->lgfp);
194         ctx->lgfp = NULL;
195     }
196     ctx->state = L_CLOSED;
197 }
198 
199 /*
200  * Log session traffic.
201  */
logtraffic(LogContext * ctx,unsigned char c,int logmode)202 void logtraffic(LogContext *ctx, unsigned char c, int logmode)
203 {
204     if (ctx->logtype > 0) {
205         if (ctx->logtype == logmode)
206             logwrite(ctx, make_ptrlen(&c, 1));
207     }
208 }
209 
logevent_internal(LogContext * ctx,const char * event)210 static void logevent_internal(LogContext *ctx, const char *event)
211 {
212     if (ctx->logtype == LGTYP_PACKETS || ctx->logtype == LGTYP_SSHRAW) {
213         logprintf(ctx, "Event Log: %s\r\n", event);
214         logflush(ctx);
215     }
216     lp_eventlog(ctx->lp, event);
217 }
218 
logevent(LogContext * ctx,const char * event)219 void logevent(LogContext *ctx, const char *event)
220 {
221     if (!ctx)
222         return;
223 
224     /*
225      * Replace newlines in Event Log messages with spaces. (Sometimes
226      * the same message string is reused for the Event Log and a GUI
227      * dialog box; newlines are sometimes appropriate in the latter,
228      * but never in the former.)
229      */
230     if (strchr(event, '\n') || strchr(event, '\r')) {
231         char *dup = dupstr(event);
232         char *p = dup, *q = dup;
233         while (*p) {
234             if (*p == '\r' || *p == '\n') {
235                 do {
236                     p++;
237                 } while (*p == '\r' || *p == '\n');
238                 *q++ = ' ';
239             } else {
240                 *q++ = *p++;
241             }
242         }
243         *q = '\0';
244         logevent_internal(ctx, dup);
245         sfree(dup);
246     } else {
247         logevent_internal(ctx, event);
248     }
249 }
250 
logevent_and_free(LogContext * ctx,char * event)251 void logevent_and_free(LogContext *ctx, char *event)
252 {
253     logevent(ctx, event);
254     sfree(event);
255 }
256 
logeventvf(LogContext * ctx,const char * fmt,va_list ap)257 void logeventvf(LogContext *ctx, const char *fmt, va_list ap)
258 {
259     logevent_and_free(ctx, dupvprintf(fmt, ap));
260 }
261 
logeventf(LogContext * ctx,const char * fmt,...)262 void logeventf(LogContext *ctx, const char *fmt, ...)
263 {
264     va_list ap;
265 
266     va_start(ap, fmt);
267     logeventvf(ctx, fmt, ap);
268     va_end(ap);
269 }
270 
271 /*
272  * Log an SSH packet.
273  * If n_blanks != 0, blank or omit some parts.
274  * Set of blanking areas must be in increasing order.
275  */
log_packet(LogContext * ctx,int direction,int type,const char * texttype,const void * data,size_t len,int n_blanks,const struct logblank_t * blanks,const unsigned long * seq,unsigned downstream_id,const char * additional_log_text)276 void log_packet(LogContext *ctx, int direction, int type,
277                 const char *texttype, const void *data, size_t len,
278                 int n_blanks, const struct logblank_t *blanks,
279                 const unsigned long *seq,
280                 unsigned downstream_id, const char *additional_log_text)
281 {
282     char dumpdata[128], smalldata[5];
283     size_t p = 0, b = 0, omitted = 0;
284     int output_pos = 0; /* NZ if pending output in dumpdata */
285 
286     if (!(ctx->logtype == LGTYP_SSHRAW ||
287           (ctx->logtype == LGTYP_PACKETS && texttype)))
288         return;
289 
290     /* Packet header. */
291     if (texttype) {
292         logprintf(ctx, "%s packet ",
293                   direction == PKT_INCOMING ? "Incoming" : "Outgoing");
294 
295         if (seq)
296             logprintf(ctx, "#0x%lx, ", *seq);
297 
298         logprintf(ctx, "type %d / 0x%02x (%s)", type, type, texttype);
299 
300         if (downstream_id) {
301             logprintf(ctx, " on behalf of downstream #%u", downstream_id);
302             if (additional_log_text)
303                 logprintf(ctx, " (%s)", additional_log_text);
304         }
305 
306         logprintf(ctx, "\r\n");
307     } else {
308         /*
309          * Raw data is logged with a timestamp, so that it's possible
310          * to determine whether a mysterious delay occurred at the
311          * client or server end. (Timestamping the raw data avoids
312          * cluttering the normal case of only logging decrypted SSH
313          * messages, and also adds conceptual rigour in the case where
314          * an SSH message arrives in several pieces.)
315          */
316         char buf[256];
317         struct tm tm;
318         tm = ltime();
319         strftime(buf, 24, "%Y-%m-%d %H:%M:%S", &tm);
320         logprintf(ctx, "%s raw data at %s\r\n",
321                   direction == PKT_INCOMING ? "Incoming" : "Outgoing",
322                   buf);
323     }
324 
325     /*
326      * Output a hex/ASCII dump of the packet body, blanking/omitting
327      * parts as specified.
328      */
329     while (p < len) {
330         int blktype;
331 
332         /* Move to a current entry in the blanking array. */
333         while ((b < n_blanks) &&
334                (p >= blanks[b].offset + blanks[b].len))
335             b++;
336         /* Work out what type of blanking to apply to
337          * this byte. */
338         blktype = PKTLOG_EMIT; /* default */
339         if ((b < n_blanks) &&
340             (p >= blanks[b].offset) &&
341             (p < blanks[b].offset + blanks[b].len))
342             blktype = blanks[b].type;
343 
344         /* If we're about to stop omitting, it's time to say how
345          * much we omitted. */
346         if ((blktype != PKTLOG_OMIT) && omitted) {
347             logprintf(ctx, "  (%"SIZEu" byte%s omitted)\r\n",
348                       omitted, (omitted==1?"":"s"));
349             omitted = 0;
350         }
351 
352         /* (Re-)initialise dumpdata as necessary
353          * (start of row, or if we've just stopped omitting) */
354         if (!output_pos && !omitted)
355             sprintf(dumpdata, "  %08"SIZEx"%*s\r\n",
356                     p-(p%16), 1+3*16+2+16, "");
357 
358         /* Deal with the current byte. */
359         if (blktype == PKTLOG_OMIT) {
360             omitted++;
361         } else {
362             int c;
363             if (blktype == PKTLOG_BLANK) {
364                 c = 'X';
365                 sprintf(smalldata, "XX");
366             } else {  /* PKTLOG_EMIT */
367                 c = ((const unsigned char *)data)[p];
368                 sprintf(smalldata, "%02x", c);
369             }
370             dumpdata[10+2+3*(p%16)] = smalldata[0];
371             dumpdata[10+2+3*(p%16)+1] = smalldata[1];
372             dumpdata[10+1+3*16+2+(p%16)] = (c >= 0x20 && c < 0x7F ? c : '.');
373             output_pos = (p%16) + 1;
374         }
375 
376         p++;
377 
378         /* Flush row if necessary */
379         if (((p % 16) == 0) || (p == len) || omitted) {
380             if (output_pos) {
381                 strcpy(dumpdata + 10+1+3*16+2+output_pos, "\r\n");
382                 logwrite(ctx, ptrlen_from_asciz(dumpdata));
383                 output_pos = 0;
384             }
385         }
386 
387     }
388 
389     /* Tidy up */
390     if (omitted)
391         logprintf(ctx, "  (%"SIZEu" byte%s omitted)\r\n",
392                   omitted, (omitted==1?"":"s"));
393     logflush(ctx);
394 }
395 
log_init(LogPolicy * lp,Conf * conf)396 LogContext *log_init(LogPolicy *lp, Conf *conf)
397 {
398     LogContext *ctx = snew(LogContext);
399     ctx->lgfp = NULL;
400     ctx->state = L_CLOSED;
401     ctx->lp = lp;
402     ctx->conf = conf_copy(conf);
403     ctx->logtype = conf_get_int(ctx->conf, CONF_logtype);
404     ctx->currlogfilename = NULL;
405     bufchain_init(&ctx->queue);
406     return ctx;
407 }
408 
log_free(LogContext * ctx)409 void log_free(LogContext *ctx)
410 {
411     logfclose(ctx);
412     bufchain_clear(&ctx->queue);
413     if (ctx->currlogfilename)
414         filename_free(ctx->currlogfilename);
415     conf_free(ctx->conf);
416     sfree(ctx);
417 }
418 
log_reconfig(LogContext * ctx,Conf * conf)419 void log_reconfig(LogContext *ctx, Conf *conf)
420 {
421     bool reset_logging;
422 
423     if (!filename_equal(conf_get_filename(ctx->conf, CONF_logfilename),
424                         conf_get_filename(conf, CONF_logfilename)) ||
425         conf_get_int(ctx->conf, CONF_logtype) !=
426         conf_get_int(conf, CONF_logtype))
427         reset_logging = true;
428     else
429         reset_logging = false;
430 
431     if (reset_logging)
432         logfclose(ctx);
433 
434     conf_free(ctx->conf);
435     ctx->conf = conf_copy(conf);
436 
437     ctx->logtype = conf_get_int(ctx->conf, CONF_logtype);
438 
439     if (reset_logging)
440         logfopen(ctx);
441 }
442 
443 /*
444  * translate format codes into time/date strings
445  * and insert them into log file name
446  *
447  * "&Y":YYYY   "&m":MM   "&d":DD   "&T":hhmmss   "&h":<hostname>   "&&":&
448  */
xlatlognam(Filename * src,char * hostname,int port,struct tm * tm)449 static Filename *xlatlognam(Filename *src, char *hostname, int port,
450                             struct tm *tm)
451 {
452     char buf[32], *bufp;
453     int size;
454     strbuf *buffer;
455     const char *s;
456     Filename *ret;
457 
458     buffer = strbuf_new();
459     s = filename_to_str(src);
460 
461     while (*s) {
462         bool sanitise = false;
463         /* Let (bufp, len) be the string to append. */
464         bufp = buf;                    /* don't usually override this */
465         if (*s == '&') {
466             char c;
467             s++;
468             size = 0;
469             if (*s) switch (c = *s++, tolower((unsigned char)c)) {
470               case 'y':
471                 size = strftime(buf, sizeof(buf), "%Y", tm);
472                 break;
473               case 'm':
474                 size = strftime(buf, sizeof(buf), "%m", tm);
475                 break;
476               case 'd':
477                 size = strftime(buf, sizeof(buf), "%d", tm);
478                 break;
479               case 't':
480                 size = strftime(buf, sizeof(buf), "%H%M%S", tm);
481                 break;
482               case 'h':
483                 bufp = hostname;
484                 size = strlen(bufp);
485                 break;
486               case 'p':
487                 size = sprintf(buf, "%d", port);
488                 break;
489               default:
490                 buf[0] = '&';
491                 size = 1;
492                 if (c != '&')
493                     buf[size++] = c;
494             }
495             /* Never allow path separators - or any other illegal
496              * filename character - to come out of any of these
497              * auto-format directives. E.g. 'hostname' can contain
498              * colons, if it's an IPv6 address, and colons aren't
499              * legal in filenames on Windows. */
500             sanitise = true;
501         } else {
502             buf[0] = *s++;
503             size = 1;
504         }
505         while (size-- > 0) {
506             char c = *bufp++;
507             if (sanitise)
508                 c = filename_char_sanitise(c);
509             put_byte(buffer, c);
510         }
511     }
512 
513     ret = filename_from_str(buffer->s);
514     strbuf_free(buffer);
515     return ret;
516 }
517