logging.c 16 KB

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