+++ /dev/null
-/*\r
- * Session logging.\r
- */\r
-\r
-#include <stdio.h>\r
-#include <stdlib.h>\r
-#include <ctype.h>\r
-\r
-#include <time.h>\r
-#include <assert.h>\r
-\r
-#include "putty.h"\r
-\r
-/* log session to file stuff ... */\r
-struct LogContext {\r
- FILE *lgfp;\r
- enum { L_CLOSED, L_OPENING, L_OPEN, L_ERROR } state;\r
- bufchain queue;\r
- Filename currlogfilename;\r
- void *frontend;\r
- Config cfg;\r
-};\r
-\r
-static void xlatlognam(Filename *d, Filename s, char *hostname, struct tm *tm);\r
-\r
-/*\r
- * Internal wrapper function which must be called for _all_ output\r
- * to the log file. It takes care of opening the log file if it\r
- * isn't open, buffering data if it's in the process of being\r
- * opened asynchronously, etc.\r
- */\r
-static void logwrite(struct LogContext *ctx, void *data, int len)\r
-{\r
- /*\r
- * In state L_CLOSED, we call logfopen, which will set the state\r
- * to one of L_OPENING, L_OPEN or L_ERROR. Hence we process all of\r
- * those three _after_ processing L_CLOSED.\r
- */\r
- if (ctx->state == L_CLOSED)\r
- logfopen(ctx);\r
-\r
- if (ctx->state == L_OPENING) {\r
- bufchain_add(&ctx->queue, data, len);\r
- } else if (ctx->state == L_OPEN) {\r
- assert(ctx->lgfp);\r
- if (fwrite(data, 1, len, ctx->lgfp) < (size_t)len) {\r
- logfclose(ctx);\r
- ctx->state = L_ERROR;\r
- /* Log state is L_ERROR so this won't cause a loop */\r
- logevent(ctx->frontend,\r
- "Disabled writing session log due to error while writing");\r
- }\r
- } /* else L_ERROR, so ignore the write */\r
-}\r
-\r
-/*\r
- * Convenience wrapper on logwrite() which printf-formats the\r
- * string.\r
- */\r
-static void logprintf(struct LogContext *ctx, const char *fmt, ...)\r
-{\r
- va_list ap;\r
- char *data;\r
-\r
- va_start(ap, fmt);\r
- data = dupvprintf(fmt, ap);\r
- va_end(ap);\r
-\r
- logwrite(ctx, data, strlen(data));\r
- sfree(data);\r
-}\r
-\r
-/*\r
- * Flush any open log file.\r
- */\r
-void logflush(void *handle) {\r
- struct LogContext *ctx = (struct LogContext *)handle;\r
- if (ctx->cfg.logtype > 0)\r
- if (ctx->state == L_OPEN)\r
- fflush(ctx->lgfp);\r
-}\r
-\r
-static void logfopen_callback(void *handle, int mode)\r
-{\r
- struct LogContext *ctx = (struct LogContext *)handle;\r
- char buf[256], *event;\r
- struct tm tm;\r
- const char *fmode;\r
-\r
- if (mode == 0) {\r
- ctx->state = L_ERROR; /* disable logging */\r
- } else {\r
- fmode = (mode == 1 ? "ab" : "wb");\r
- ctx->lgfp = f_open(ctx->currlogfilename, fmode, FALSE);\r
- if (ctx->lgfp)\r
- ctx->state = L_OPEN;\r
- else\r
- ctx->state = L_ERROR;\r
- }\r
-\r
- if (ctx->state == L_OPEN) {\r
- /* Write header line into log file. */\r
- tm = ltime();\r
- strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm);\r
- logprintf(ctx, "=~=~=~=~=~=~=~=~=~=~=~= PuTTY log %s"\r
- " =~=~=~=~=~=~=~=~=~=~=~=\r\n", buf);\r
- }\r
-\r
- event = dupprintf("%s session log (%s mode) to file: %s",\r
- ctx->state == L_ERROR ?\r
- (mode == 0 ? "Disabled writing" : "Error writing") :\r
- (mode == 1 ? "Appending" : "Writing new"),\r
- (ctx->cfg.logtype == LGTYP_ASCII ? "ASCII" :\r
- ctx->cfg.logtype == LGTYP_DEBUG ? "raw" :\r
- ctx->cfg.logtype == LGTYP_PACKETS ? "SSH packets" :\r
- ctx->cfg.logtype == LGTYP_SSHRAW ? "SSH raw data" :\r
- "unknown"),\r
- filename_to_str(&ctx->currlogfilename));\r
- logevent(ctx->frontend, event);\r
- sfree(event);\r
-\r
- /*\r
- * Having either succeeded or failed in opening the log file,\r
- * we should write any queued data out.\r
- */\r
- assert(ctx->state != L_OPENING); /* make _sure_ it won't be requeued */\r
- while (bufchain_size(&ctx->queue)) {\r
- void *data;\r
- int len;\r
- bufchain_prefix(&ctx->queue, &data, &len);\r
- logwrite(ctx, data, len);\r
- bufchain_consume(&ctx->queue, len);\r
- }\r
-}\r
-\r
-/*\r
- * Open the log file. Takes care of detecting an already-existing\r
- * file and asking the user whether they want to append, overwrite\r
- * or cancel logging.\r
- */\r
-void logfopen(void *handle)\r
-{\r
- struct LogContext *ctx = (struct LogContext *)handle;\r
- struct tm tm;\r
- int mode;\r
-\r
- /* Prevent repeat calls */\r
- if (ctx->state != L_CLOSED)\r
- return;\r
-\r
- if (!ctx->cfg.logtype)\r
- return;\r
-\r
- tm = ltime();\r
-\r
- /* substitute special codes in file name */\r
- xlatlognam(&ctx->currlogfilename, ctx->cfg.logfilename,ctx->cfg.host, &tm);\r
-\r
- ctx->lgfp = f_open(ctx->currlogfilename, "r", FALSE); /* file already present? */\r
- if (ctx->lgfp) {\r
- fclose(ctx->lgfp);\r
- if (ctx->cfg.logxfovr != LGXF_ASK) {\r
- mode = ((ctx->cfg.logxfovr == LGXF_OVR) ? 2 : 1);\r
- } else\r
- mode = askappend(ctx->frontend, ctx->currlogfilename,\r
- logfopen_callback, ctx);\r
- } else\r
- mode = 2; /* create == overwrite */\r
-\r
- if (mode < 0)\r
- ctx->state = L_OPENING;\r
- else\r
- logfopen_callback(ctx, mode); /* open the file */\r
-}\r
-\r
-void logfclose(void *handle)\r
-{\r
- struct LogContext *ctx = (struct LogContext *)handle;\r
- if (ctx->lgfp) {\r
- fclose(ctx->lgfp);\r
- ctx->lgfp = NULL;\r
- }\r
- ctx->state = L_CLOSED;\r
-}\r
-\r
-/*\r
- * Log session traffic.\r
- */\r
-void logtraffic(void *handle, unsigned char c, int logmode)\r
-{\r
- struct LogContext *ctx = (struct LogContext *)handle;\r
- if (ctx->cfg.logtype > 0) {\r
- if (ctx->cfg.logtype == logmode)\r
- logwrite(ctx, &c, 1);\r
- }\r
-}\r
-\r
-/*\r
- * Log an Event Log entry. Used in SSH packet logging mode; this is\r
- * also as convenient a place as any to put the output of Event Log\r
- * entries to stderr when a command-line tool is in verbose mode.\r
- * (In particular, this is a better place to put it than in the\r
- * front ends, because it only has to be done once for all\r
- * platforms. Platforms which don't have a meaningful stderr can\r
- * just avoid defining FLAG_STDERR.\r
- */\r
-void log_eventlog(void *handle, const char *event)\r
-{\r
- struct LogContext *ctx = (struct LogContext *)handle;\r
- if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) {\r
- fprintf(stderr, "%s\n", event);\r
- fflush(stderr);\r
- }\r
- /* If we don't have a context yet (eg winnet.c init) then skip entirely */\r
- if (!ctx)\r
- return;\r
- if (ctx->cfg.logtype != LGTYP_PACKETS &&\r
- ctx->cfg.logtype != LGTYP_SSHRAW)\r
- return;\r
- logprintf(ctx, "Event Log: %s\r\n", event);\r
- logflush(ctx);\r
-}\r
-\r
-/*\r
- * Log an SSH packet.\r
- * If n_blanks != 0, blank or omit some parts.\r
- * Set of blanking areas must be in increasing order.\r
- */\r
-void log_packet(void *handle, int direction, int type,\r
- char *texttype, const void *data, int len,\r
- int n_blanks, const struct logblank_t *blanks,\r
- const unsigned long *seq)\r
-{\r
- struct LogContext *ctx = (struct LogContext *)handle;\r
- char dumpdata[80], smalldata[5];\r
- int p = 0, b = 0, omitted = 0;\r
- int output_pos = 0; /* NZ if pending output in dumpdata */\r
-\r
- if (!(ctx->cfg.logtype == LGTYP_SSHRAW ||\r
- (ctx->cfg.logtype == LGTYP_PACKETS && texttype)))\r
- return;\r
-\r
- /* Packet header. */\r
- if (texttype) {\r
- if (seq) {\r
- logprintf(ctx, "%s packet #0x%lx, type %d / 0x%02x (%s)\r\n",\r
- direction == PKT_INCOMING ? "Incoming" : "Outgoing",\r
- *seq, type, type, texttype);\r
- } else {\r
- logprintf(ctx, "%s packet type %d / 0x%02x (%s)\r\n",\r
- direction == PKT_INCOMING ? "Incoming" : "Outgoing",\r
- type, type, texttype);\r
- }\r
- } else {\r
- logprintf(ctx, "%s raw data\r\n",\r
- direction == PKT_INCOMING ? "Incoming" : "Outgoing");\r
- }\r
-\r
- /*\r
- * Output a hex/ASCII dump of the packet body, blanking/omitting\r
- * parts as specified.\r
- */\r
- while (p < len) {\r
- int blktype;\r
-\r
- /* Move to a current entry in the blanking array. */\r
- while ((b < n_blanks) &&\r
- (p >= blanks[b].offset + blanks[b].len))\r
- b++;\r
- /* Work out what type of blanking to apply to\r
- * this byte. */\r
- blktype = PKTLOG_EMIT; /* default */\r
- if ((b < n_blanks) &&\r
- (p >= blanks[b].offset) &&\r
- (p < blanks[b].offset + blanks[b].len))\r
- blktype = blanks[b].type;\r
-\r
- /* If we're about to stop omitting, it's time to say how\r
- * much we omitted. */\r
- if ((blktype != PKTLOG_OMIT) && omitted) {\r
- logprintf(ctx, " (%d byte%s omitted)\r\n",\r
- omitted, (omitted==1?"":"s"));\r
- omitted = 0;\r
- }\r
-\r
- /* (Re-)initialise dumpdata as necessary\r
- * (start of row, or if we've just stopped omitting) */\r
- if (!output_pos && !omitted)\r
- sprintf(dumpdata, " %08x%*s\r\n", p-(p%16), 1+3*16+2+16, "");\r
-\r
- /* Deal with the current byte. */\r
- if (blktype == PKTLOG_OMIT) {\r
- omitted++;\r
- } else {\r
- int c;\r
- if (blktype == PKTLOG_BLANK) {\r
- c = 'X';\r
- sprintf(smalldata, "XX");\r
- } else { /* PKTLOG_EMIT */\r
- c = ((unsigned char *)data)[p];\r
- sprintf(smalldata, "%02x", c);\r
- }\r
- dumpdata[10+2+3*(p%16)] = smalldata[0];\r
- dumpdata[10+2+3*(p%16)+1] = smalldata[1];\r
- dumpdata[10+1+3*16+2+(p%16)] = (isprint(c) ? c : '.');\r
- output_pos = (p%16) + 1;\r
- }\r
-\r
- p++;\r
-\r
- /* Flush row if necessary */\r
- if (((p % 16) == 0) || (p == len) || omitted) {\r
- if (output_pos) {\r
- strcpy(dumpdata + 10+1+3*16+2+output_pos, "\r\n");\r
- logwrite(ctx, dumpdata, strlen(dumpdata));\r
- output_pos = 0;\r
- }\r
- }\r
-\r
- }\r
-\r
- /* Tidy up */\r
- if (omitted)\r
- logprintf(ctx, " (%d byte%s omitted)\r\n",\r
- omitted, (omitted==1?"":"s"));\r
- logflush(ctx);\r
-}\r
-\r
-void *log_init(void *frontend, Config *cfg)\r
-{\r
- struct LogContext *ctx = snew(struct LogContext);\r
- ctx->lgfp = NULL;\r
- ctx->state = L_CLOSED;\r
- ctx->frontend = frontend;\r
- ctx->cfg = *cfg; /* STRUCTURE COPY */\r
- bufchain_init(&ctx->queue);\r
- return ctx;\r
-}\r
-\r
-void log_free(void *handle)\r
-{\r
- struct LogContext *ctx = (struct LogContext *)handle;\r
-\r
- logfclose(ctx);\r
- bufchain_clear(&ctx->queue);\r
- sfree(ctx);\r
-}\r
-\r
-void log_reconfig(void *handle, Config *cfg)\r
-{\r
- struct LogContext *ctx = (struct LogContext *)handle;\r
- int reset_logging;\r
-\r
- if (!filename_equal(ctx->cfg.logfilename, cfg->logfilename) ||\r
- ctx->cfg.logtype != cfg->logtype)\r
- reset_logging = TRUE;\r
- else\r
- reset_logging = FALSE;\r
-\r
- if (reset_logging)\r
- logfclose(ctx);\r
-\r
- ctx->cfg = *cfg; /* STRUCTURE COPY */\r
-\r
- if (reset_logging)\r
- logfopen(ctx);\r
-}\r
-\r
-/*\r
- * translate format codes into time/date strings\r
- * and insert them into log file name\r
- *\r
- * "&Y":YYYY "&m":MM "&d":DD "&T":hhmmss "&h":<hostname> "&&":&\r
- */\r
-static void xlatlognam(Filename *dest, Filename src,\r
- char *hostname, struct tm *tm) {\r
- char buf[10], *bufp;\r
- int size;\r
- char buffer[FILENAME_MAX];\r
- int len = sizeof(buffer)-1;\r
- char *d;\r
- const char *s;\r
-\r
- d = buffer;\r
- s = filename_to_str(&src);\r
-\r
- while (*s) {\r
- /* Let (bufp, len) be the string to append. */\r
- bufp = buf; /* don't usually override this */\r
- if (*s == '&') {\r
- char c;\r
- s++;\r
- size = 0;\r
- if (*s) switch (c = *s++, tolower((unsigned char)c)) {\r
- case 'y':\r
- size = strftime(buf, sizeof(buf), "%Y", tm);\r
- break;\r
- case 'm':\r
- size = strftime(buf, sizeof(buf), "%m", tm);\r
- break;\r
- case 'd':\r
- size = strftime(buf, sizeof(buf), "%d", tm);\r
- break;\r
- case 't':\r
- size = strftime(buf, sizeof(buf), "%H%M%S", tm);\r
- break;\r
- case 'h':\r
- bufp = hostname;\r
- size = strlen(bufp);\r
- break;\r
- default:\r
- buf[0] = '&';\r
- size = 1;\r
- if (c != '&')\r
- buf[size++] = c;\r
- }\r
- } else {\r
- buf[0] = *s++;\r
- size = 1;\r
- }\r
- if (size > len)\r
- size = len;\r
- memcpy(d, bufp, size);\r
- d += size;\r
- len -= size;\r
- }\r
- *d = '\0';\r
-\r
- *dest = filename_from_str(buffer);\r
-}\r