OSDN Git Service

Add PuTTY 0.61 to contrib directory.
[ffftp/ffftp.git] / contrib / putty / LOGGING.C
diff --git a/contrib/putty/LOGGING.C b/contrib/putty/LOGGING.C
new file mode 100644 (file)
index 0000000..8fc5819
--- /dev/null
@@ -0,0 +1,430 @@
+/*\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