00db133f |
1 | #include <stdio.h> |
2 | #include <stdlib.h> |
3 | #include <ctype.h> |
4 | |
5 | #include <time.h> |
6 | #include <assert.h> |
7 | |
8 | #include "putty.h" |
9 | |
10 | /* log session to file stuff ... */ |
a8327734 |
11 | struct LogContext { |
12 | FILE *lgfp; |
9a30e26b |
13 | Filename currlogfilename; |
a8327734 |
14 | void *frontend; |
c229ef97 |
15 | Config cfg; |
a8327734 |
16 | }; |
00db133f |
17 | |
9a30e26b |
18 | static void xlatlognam(Filename *d, Filename s, char *hostname, struct tm *tm); |
00db133f |
19 | |
20 | /* |
21 | * Log session traffic. |
22 | */ |
a8327734 |
23 | void logtraffic(void *handle, unsigned char c, int logmode) |
00db133f |
24 | { |
a8327734 |
25 | struct LogContext *ctx = (struct LogContext *)handle; |
c229ef97 |
26 | if (ctx->cfg.logtype > 0) { |
27 | if (ctx->cfg.logtype == logmode) { |
00db133f |
28 | /* deferred open file from pgm start? */ |
a8327734 |
29 | if (!ctx->lgfp) |
30 | logfopen(ctx); |
31 | if (ctx->lgfp) |
32 | fputc(c, ctx->lgfp); |
00db133f |
33 | } |
34 | } |
35 | } |
36 | |
37 | /* |
11cc5e30 |
38 | * Flush any open log file. |
39 | */ |
40 | void logflush(void *handle) { |
41 | struct LogContext *ctx = (struct LogContext *)handle; |
42 | if (ctx->cfg.logtype > 0) |
43 | if (ctx->lgfp) |
44 | fflush(ctx->lgfp); |
45 | } |
46 | |
47 | /* |
382908ad |
48 | * Log an Event Log entry. Used in SSH packet logging mode; this is |
49 | * also as convenient a place as any to put the output of Event Log |
50 | * entries to stderr when a command-line tool is in verbose mode. |
51 | * (In particular, this is a better place to put it than in the |
52 | * front ends, because it only has to be done once for all |
53 | * platforms. Platforms which don't have a meaningful stderr can |
54 | * just avoid defining FLAG_STDERR. |
fb89f7ff |
55 | */ |
cbe2d68f |
56 | void log_eventlog(void *handle, const char *event) |
fb89f7ff |
57 | { |
a8327734 |
58 | struct LogContext *ctx = (struct LogContext *)handle; |
382908ad |
59 | if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) { |
60 | fprintf(stderr, "%s\n", event); |
61 | fflush(stderr); |
62 | } |
c229ef97 |
63 | if (ctx->cfg.logtype != LGTYP_PACKETS) |
fb89f7ff |
64 | return; |
a8327734 |
65 | if (!ctx->lgfp) |
66 | logfopen(ctx); |
67 | if (ctx->lgfp) |
fa76f4a3 |
68 | fprintf(ctx->lgfp, "Event Log: %s\r\n", event); |
fb89f7ff |
69 | } |
70 | |
71 | /* |
00db133f |
72 | * Log an SSH packet. |
9a10ecf4 |
73 | * If n_blanks != 0, blank or omit some parts. |
74 | * Set of blanking areas must be in increasing order. |
00db133f |
75 | */ |
a8327734 |
76 | void log_packet(void *handle, int direction, int type, |
9a10ecf4 |
77 | char *texttype, void *data, int len, |
78 | int n_blanks, const struct logblank_t *blanks) |
00db133f |
79 | { |
a8327734 |
80 | struct LogContext *ctx = (struct LogContext *)handle; |
00db133f |
81 | char dumpdata[80], smalldata[5]; |
82 | |
c229ef97 |
83 | if (ctx->cfg.logtype != LGTYP_PACKETS) |
00db133f |
84 | return; |
a8327734 |
85 | if (!ctx->lgfp) |
86 | logfopen(ctx); |
87 | if (ctx->lgfp) { |
9a10ecf4 |
88 | int p = 0, b = 0, omitted = 0; |
89 | int output_pos = 0; /* NZ if pending output in dumpdata */ |
90 | |
91 | /* Packet header. */ |
fa76f4a3 |
92 | fprintf(ctx->lgfp, "%s packet type %d / 0x%02x (%s)\r\n", |
00db133f |
93 | direction == PKT_INCOMING ? "Incoming" : "Outgoing", |
94 | type, type, texttype); |
9a10ecf4 |
95 | |
96 | /* |
97 | * Output a hex/ASCII dump of the packet body, blanking/omitting |
98 | * parts as specified. |
99 | */ |
100 | while (p < len) { |
101 | int blktype; |
102 | |
103 | /* Move to a current entry in the blanking array. */ |
104 | while ((b < n_blanks) && |
105 | (p >= blanks[b].offset + blanks[b].len)) |
106 | b++; |
107 | /* Work out what type of blanking to apply to |
108 | * this byte. */ |
109 | blktype = PKTLOG_EMIT; /* default */ |
110 | if ((b < n_blanks) && |
111 | (p >= blanks[b].offset) && |
112 | (p < blanks[b].offset + blanks[b].len)) |
113 | blktype = blanks[b].type; |
114 | |
115 | /* If we're about to stop omitting, it's time to say how |
116 | * much we omitted. */ |
117 | if ((blktype != PKTLOG_OMIT) && omitted) { |
118 | fprintf(ctx->lgfp, " (%d byte%s omitted)\r\n", |
119 | omitted, (omitted==1?"":"s")); |
120 | omitted = 0; |
121 | } |
122 | |
123 | /* (Re-)initialise dumpdata as necessary |
124 | * (start of row, or if we've just stopped omitting) */ |
125 | if (!output_pos && !omitted) |
126 | sprintf(dumpdata, " %08x%*s\r\n", p-(p%16), 1+3*16+2+16, ""); |
127 | |
128 | /* Deal with the current byte. */ |
129 | if (blktype == PKTLOG_OMIT) { |
130 | omitted++; |
131 | } else { |
132 | int c; |
133 | if (blktype == PKTLOG_BLANK) { |
134 | c = 'X'; |
135 | sprintf(smalldata, "XX"); |
136 | } else { /* PKTLOG_EMIT */ |
137 | c = ((unsigned char *)data)[p]; |
138 | sprintf(smalldata, "%02x", c); |
139 | } |
140 | dumpdata[10+2+3*(p%16)] = smalldata[0]; |
141 | dumpdata[10+2+3*(p%16)+1] = smalldata[1]; |
142 | dumpdata[10+1+3*16+2+(p%16)] = (isprint(c) ? c : '.'); |
143 | output_pos = (p%16) + 1; |
144 | } |
145 | |
146 | p++; |
147 | |
148 | /* Flush row if necessary */ |
149 | if (((p % 16) == 0) || (p == len) || omitted) { |
150 | if (output_pos) { |
151 | strcpy(dumpdata + 10+1+3*16+2+output_pos, "\r\n"); |
152 | fputs(dumpdata, ctx->lgfp); |
153 | output_pos = 0; |
154 | } |
00db133f |
155 | } |
9a10ecf4 |
156 | |
00db133f |
157 | } |
9a10ecf4 |
158 | |
159 | /* Tidy up */ |
160 | if (omitted) |
161 | fprintf(ctx->lgfp, " (%d byte%s omitted)\r\n", |
162 | omitted, (omitted==1?"":"s")); |
a8327734 |
163 | fflush(ctx->lgfp); |
00db133f |
164 | } |
165 | } |
166 | |
167 | /* open log file append/overwrite mode */ |
a8327734 |
168 | void logfopen(void *handle) |
00db133f |
169 | { |
a8327734 |
170 | struct LogContext *ctx = (struct LogContext *)handle; |
00db133f |
171 | char buf[256]; |
172 | time_t t; |
173 | struct tm tm; |
174 | char writemod[4]; |
175 | |
fb89f7ff |
176 | /* Prevent repeat calls */ |
a8327734 |
177 | if (ctx->lgfp) |
fb89f7ff |
178 | return; |
179 | |
c229ef97 |
180 | if (!ctx->cfg.logtype) |
00db133f |
181 | return; |
fa76f4a3 |
182 | sprintf(writemod, "wb"); /* default to rewrite */ |
00db133f |
183 | |
184 | time(&t); |
185 | tm = *localtime(&t); |
186 | |
187 | /* substitute special codes in file name */ |
9a30e26b |
188 | xlatlognam(&ctx->currlogfilename, ctx->cfg.logfilename,ctx->cfg.host, &tm); |
00db133f |
189 | |
9a30e26b |
190 | ctx->lgfp = f_open(ctx->currlogfilename, "r"); /* file already present? */ |
a8327734 |
191 | if (ctx->lgfp) { |
00db133f |
192 | int i; |
a8327734 |
193 | fclose(ctx->lgfp); |
c229ef97 |
194 | if (ctx->cfg.logxfovr != LGXF_ASK) { |
195 | i = ((ctx->cfg.logxfovr == LGXF_OVR) ? 2 : 1); |
bf61b566 |
196 | } else |
197 | i = askappend(ctx->frontend, ctx->currlogfilename); |
00db133f |
198 | if (i == 1) |
199 | writemod[0] = 'a'; /* set append mode */ |
200 | else if (i == 0) { /* cancelled */ |
a8327734 |
201 | ctx->lgfp = NULL; |
c229ef97 |
202 | ctx->cfg.logtype = 0; /* disable logging */ |
00db133f |
203 | return; |
204 | } |
205 | } |
206 | |
9a30e26b |
207 | ctx->lgfp = f_open(ctx->currlogfilename, writemod); |
a8327734 |
208 | if (ctx->lgfp) { /* enter into event log */ |
fb89f7ff |
209 | /* --- write header line into log file */ |
a8327734 |
210 | fputs("=~=~=~=~=~=~=~=~=~=~=~= PuTTY log ", ctx->lgfp); |
fb89f7ff |
211 | strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm); |
a8327734 |
212 | fputs(buf, ctx->lgfp); |
fa76f4a3 |
213 | fputs(" =~=~=~=~=~=~=~=~=~=~=~=\r\n", ctx->lgfp); |
fb89f7ff |
214 | |
215 | sprintf(buf, "%s session log (%s mode) to file: ", |
00db133f |
216 | (writemod[0] == 'a') ? "Appending" : "Writing new", |
c229ef97 |
217 | (ctx->cfg.logtype == LGTYP_ASCII ? "ASCII" : |
218 | ctx->cfg.logtype == LGTYP_DEBUG ? "raw" : |
219 | ctx->cfg.logtype == LGTYP_PACKETS ? "SSH packets" : "<ukwn>")); |
00db133f |
220 | /* Make sure we do not exceed the output buffer size */ |
9fab77dc |
221 | strncat(buf, filename_to_str(&ctx->currlogfilename), 128); |
00db133f |
222 | buf[strlen(buf)] = '\0'; |
a8327734 |
223 | logevent(ctx->frontend, buf); |
00db133f |
224 | } |
225 | } |
226 | |
a8327734 |
227 | void logfclose(void *handle) |
00db133f |
228 | { |
a8327734 |
229 | struct LogContext *ctx = (struct LogContext *)handle; |
230 | if (ctx->lgfp) { |
231 | fclose(ctx->lgfp); |
232 | ctx->lgfp = NULL; |
00db133f |
233 | } |
234 | } |
235 | |
c229ef97 |
236 | void *log_init(void *frontend, Config *cfg) |
a8327734 |
237 | { |
3d88e64d |
238 | struct LogContext *ctx = snew(struct LogContext); |
a8327734 |
239 | ctx->lgfp = NULL; |
240 | ctx->frontend = frontend; |
c229ef97 |
241 | ctx->cfg = *cfg; /* STRUCTURE COPY */ |
a8327734 |
242 | return ctx; |
243 | } |
244 | |
fabd1805 |
245 | void log_free(void *handle) |
246 | { |
247 | struct LogContext *ctx = (struct LogContext *)handle; |
248 | |
249 | logfclose(ctx); |
250 | sfree(ctx); |
251 | } |
252 | |
c229ef97 |
253 | void log_reconfig(void *handle, Config *cfg) |
254 | { |
255 | struct LogContext *ctx = (struct LogContext *)handle; |
256 | int reset_logging; |
257 | |
9a30e26b |
258 | if (!filename_equal(ctx->cfg.logfilename, cfg->logfilename) || |
c229ef97 |
259 | ctx->cfg.logtype != cfg->logtype) |
260 | reset_logging = TRUE; |
261 | else |
262 | reset_logging = FALSE; |
263 | |
264 | if (reset_logging) |
4512d432 |
265 | logfclose(ctx); |
c229ef97 |
266 | |
267 | ctx->cfg = *cfg; /* STRUCTURE COPY */ |
268 | |
269 | if (reset_logging) |
4512d432 |
270 | logfopen(ctx); |
c229ef97 |
271 | } |
272 | |
00db133f |
273 | /* |
274 | * translate format codes into time/date strings |
275 | * and insert them into log file name |
276 | * |
277 | * "&Y":YYYY "&m":MM "&d":DD "&T":hhmm "&h":<hostname> "&&":& |
278 | */ |
9a30e26b |
279 | static void xlatlognam(Filename *dest, Filename src, |
280 | char *hostname, struct tm *tm) { |
00db133f |
281 | char buf[10], *bufp; |
282 | int size; |
9a30e26b |
283 | char buffer[FILENAME_MAX]; |
284 | int len = sizeof(buffer)-1; |
9fab77dc |
285 | char *d; |
286 | const char *s; |
9a30e26b |
287 | |
288 | d = buffer; |
9fab77dc |
289 | s = filename_to_str(&src); |
00db133f |
290 | |
291 | while (*s) { |
292 | /* Let (bufp, len) be the string to append. */ |
293 | bufp = buf; /* don't usually override this */ |
294 | if (*s == '&') { |
295 | char c; |
296 | s++; |
1709795f |
297 | size = 0; |
00db133f |
298 | if (*s) switch (c = *s++, tolower(c)) { |
299 | case 'y': |
300 | size = strftime(buf, sizeof(buf), "%Y", tm); |
301 | break; |
302 | case 'm': |
303 | size = strftime(buf, sizeof(buf), "%m", tm); |
304 | break; |
305 | case 'd': |
306 | size = strftime(buf, sizeof(buf), "%d", tm); |
307 | break; |
308 | case 't': |
309 | size = strftime(buf, sizeof(buf), "%H%M%S", tm); |
310 | break; |
311 | case 'h': |
312 | bufp = hostname; |
313 | size = strlen(bufp); |
314 | break; |
315 | default: |
316 | buf[0] = '&'; |
317 | size = 1; |
318 | if (c != '&') |
319 | buf[size++] = c; |
320 | } |
321 | } else { |
322 | buf[0] = *s++; |
323 | size = 1; |
324 | } |
325 | if (size > len) |
326 | size = len; |
327 | memcpy(d, bufp, size); |
328 | d += size; |
329 | len -= size; |
330 | } |
331 | *d = '\0'; |
9a30e26b |
332 | |
0ac2edb3 |
333 | *dest = filename_from_str(buffer); |
00db133f |
334 | } |