logging.c 14 KB

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