--- /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