1 /* GNU Mailutils -- a suite of utilities for electronic mail
2 Copyright (C) 1999-2021 Free Software Foundation, Inc.
3
4 This library is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as published by
6 the Free Software Foundation; either version 3, or (at your option)
7 any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public License
15 along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
16
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20 #include <stdlib.h>
21 #include <string.h>
22 #include <errno.h>
23
24 #include <mailutils/types.h>
25 #include <mailutils/alloc.h>
26 #include <mailutils/errno.h>
27
28 #include <mailutils/nls.h>
29 #include <mailutils/stream.h>
30 #include <mailutils/sys/stream.h>
31 #include <mailutils/sys/xscript-stream.h>
32 #include <mailutils/cctype.h>
33 #include <mailutils/cstr.h>
34
35 /* A "transcript stream" transparently writes data to and reads data from
36 an underlying transport stream, writing each lineful of data to a "log
37 stream". Writes to log stream are prefixed with a string indicating
38 direction of the data (read/write). Default prefixes are those used in
39 RFCs -- "S: ", for data written ("Server"), and "C: ", for data read
40 ("Client").
41
42 The stream can operate in three distinct modes, called "transcript
43 levels":
44
45 MU_XSCRIPT_NORMAL
46 The default level. Everything is logged indiscriminately. Programmers
47 should excercise care when using this mode, as it can reveal security
48 sensitive data (such as login credentials, etc.) as well as private
49 user's information (mail content).
50
51 MU_XSCRIPT_SECURE
52 The stream attempts to locate passwords and replaces them with three
53 asteriscs when sending to the log stream. This mode should be used before
54 the session is authenticated. Currently the following two constructs are
55 recognized:
56
57 PASS <STRING>
58 <WORD> LOGIN <STRING1> <STRING2>
59
60 Here, <WORD> stands for an unquoted word, and <STRING> stands for a
61 <WORD> or any sequence of characters enclosed in double-quotes. When
62 the first construct is encountered, <STRING> is replaced with "***" in
63 transcript. For the second construct, <STRING2> is replaced.
64
65 These two cover two most used authentication methods used in POP and
66 IMAP protocols.
67
68 MU_XSCRIPT_PAYLOAD
69 This mode is suitable when large amounts of user data are passed through
70 the stream, e.g. when sending literals in IMAP or sending replies to
71 RETR or TOP requests in POP protocol. In this mode, the stream logs
72 the single string "(data...)" upon receiving first block of data and
73 stops further logging.
74
75 This mode can be enabled for a certain amount of data that is about
76 to be sent, if such amount is known beforehand. In that case, the stream
77 will automatically revert to MU_XSCRIPT_NORMAL mode after that much
78 data has been processed. This is used when sending or receiving IMAP
79 literals.
80
81 Either direction (hereinafter called "channel") is implemented as an
82 independent state machine, so that the above modes can be configured
83 independently for each channel.
84
85 The stream is configured using the MU_IOCTL_XSCRIPTSTREAM ioctl family.
86 The following opcodes are implemented:
87
88 MU_IOCTL_XSCRIPTSTREAM_LEVEL
89 Set new transcript level(s) for both channels.
90
91 Argument: int *X
92
93 X is the desired transcript level. It will be set on both channels.
94 If different transcript levels are desired, they can be packed into
95 integer using the MU_XSCRIPT_LEVEL_PACK macro, e.g.
96
97 int n = MU_XSCRIPT_LEVEL_PACK(MU_XSCRIPT_SECURE, MU_XSCRIPT_NORMAL);
98 mu_stream_ioctl (str,
99 MU_IOCTL_XSCRIPTSTREAM,
100 MU_IOCTL_XSCRIPTSTREAM_LEVEL,
101 &n);
102
103 Upon successful return, previous transcript levels are stored in X.
104 This allows the programmer to restore prior settings using the
105 same ioctl.
106
107 The returned value is a single transcript level, if levels are the same
108 in both channels, or packed levels otherwise. This ensures compatibility
109 with the prior versions of libmailutils.
110
111 Whether it is packed or not, the level for each particular channel can be
112 retrieved from X using the MU_XSCRIPT_LEVEL_UNPACK macro.
113
114 It is advised to use MU_IOCTL_XSCRIPTSTREAM_LEVEL only when setting
115 same level for both channels. Otherwise, please use
116 MU_IOCTL_XSCRIPTSTREAM_CHANNEL.
117
118 MU_IOCTL_XSCRIPTSTREAM_CHANNEL
119 Reconfigure single channel.
120
121 Argument: struct mu_xscript_channel *C
122
123 Upon successful return, prior channel configuration is stored in the
124 memory location pointed to by C.
125
126 If C->level is MU_XSCRIPT_PAYLOAD, C->length can be set to a non-zero
127 value, indicated the amount of user data about to be passed through
128 the channel. The channel state will return to MU_XSCRIPT_NORMAL after
129 that much data are processed.
130
131 For any other value of C->level, C->length must be 0.
132 */
133
134 enum
135 {
136 CHAN_READ = MU_TRANSPORT_INPUT,
137 CHAN_WRITE = MU_TRANSPORT_OUTPUT
138 };
139
140 enum
141 {
142 XST_NORMAL = MU_XSCRIPT_NORMAL,
143 XST_SECURE = MU_XSCRIPT_SECURE,
144 XST_PAYLOAD = MU_XSCRIPT_PAYLOAD,
145 XST_SKIPLEN,
146 XST_DISABLED
147 };
148
149 static int
word_match(const char * buf,size_t len,int n,const char * word,size_t * pos)150 word_match (const char *buf, size_t len, int n, const char *word, size_t *pos)
151 {
152 size_t i = 0;
153 size_t wl = strlen (word);
154
155 for (;; n--)
156 {
157 /* Skip whitespace separator */
158 for (; i < len && mu_isspace (buf[i]); i++)
159 ;
160
161 if (n == 0)
162 break;
163
164 /* Skip the argument */
165 if (buf[i] == '"')
166 {
167 for (i++; i < len && buf[i] != '"'; i++)
168 if (buf[i] == '\'')
169 i++;
170 }
171 else
172 {
173 for (; i < len && !mu_isspace (buf[i]); i++)
174 ;
175 }
176 }
177
178 if (i + wl <= len &&
179 mu_c_strncasecmp (buf + i, word, wl) == 0 &&
180 mu_isblank (buf[i + wl]))
181 {
182 *pos = i + wl;
183 return 1;
184 }
185
186 return 0;
187 }
188
189 static void
print_transcript(struct _mu_xscript_stream * str,int dir,const char * buf,size_t size)190 print_transcript (struct _mu_xscript_stream *str, int dir,
191 const char *buf, size_t size)
192 {
193 while (size)
194 {
195 const char *p;
196 size_t len;
197
198 switch (str->channel[dir].state)
199 {
200 case XST_NORMAL:
201 case XST_SECURE:
202 mu_stream_write (str->logstr,
203 str->prefix[dir],
204 strlen (str->prefix[dir]),
205 NULL);
206 break;
207
208 case XST_PAYLOAD:
209 mu_stream_write (str->logstr,
210 str->prefix[dir],
211 strlen (str->prefix[dir]),
212 NULL);
213 mu_stream_printf (str->logstr, "(data...)\n");
214 if (str->channel[dir].length > 0)
215 str->channel[dir].state = XST_SKIPLEN;
216 else
217 str->channel[dir].state = XST_DISABLED;
218 continue;
219
220 case XST_SKIPLEN:
221 len = (size <= str->channel[dir].length)
222 ? size : str->channel[dir].length;
223 str->channel[dir].length -= len;
224 size -= len;
225 buf += len;
226 if (str->channel[dir].length == 0)
227 str->channel[dir].state = XST_NORMAL;
228 continue;
229
230 case XST_DISABLED:
231 return;
232 }
233
234 p = memchr (buf, '\n', size);
235 if (p)
236 {
237 len = p - buf;
238 if (p > buf && p[-1] == '\r')
239 len--;
240
241 if (str->channel[dir].state == MU_XSCRIPT_SECURE)
242 {
243 size_t i;
244
245 if (word_match (buf, len, 0, "PASS", &i))
246 mu_stream_printf (str->logstr, "PASS ***");
247 else if (word_match (buf, len, 1, "LOGIN", &i))
248 {
249 /* Skip the whitespace separator */
250 for (; i < len && mu_isspace (buf[i]); i++)
251 ;
252 /* Skip the first argument (presumably the user name) */
253 if (buf[i] == '"')
254 {
255 for (i++; i < len && buf[i] != '"'; i++)
256 if (buf[i] == '\\')
257 i++;
258 if (i < len && buf[i] == '"')
259 i++;
260 }
261 else
262 {
263 for (; i < len && !mu_isspace (buf[i]); i++)
264 ;
265 }
266 mu_stream_write (str->logstr, buf, i, NULL);
267 mu_stream_write (str->logstr, " \"***\"", 6, NULL);
268 }
269 else
270 mu_stream_write (str->logstr, buf, len, NULL);
271 }
272 else
273 mu_stream_write (str->logstr, buf, len, NULL);
274 mu_stream_write (str->logstr, "\n", 1, NULL);
275
276 len = p - buf + 1;
277 buf = p + 1;
278 size -= len;
279 }
280 else
281 {
282 mu_stream_write (str->logstr, buf, size, NULL);
283 break;
284 }
285 }
286 }
287
288 static void
_xscript_event_cb(mu_stream_t str,int ev,unsigned long size,void * ptr)289 _xscript_event_cb (mu_stream_t str, int ev, unsigned long size, void *ptr)
290 {
291 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
292
293 switch (ev)
294 {
295 case _MU_STR_EVENT_FILLBUF:
296 print_transcript (sp, CHAN_READ, ptr, size);
297 break;
298
299 case _MU_STR_EVENT_FLUSHBUF:
300 print_transcript (sp, CHAN_WRITE, ptr, size);
301 }
302 }
303
304 static int
_xscript_read(struct _mu_stream * str,char * buf,size_t bufsize,size_t * pnread)305 _xscript_read (struct _mu_stream *str, char *buf, size_t bufsize,
306 size_t *pnread)
307 {
308 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
309 return mu_stream_read (sp->transport, buf, bufsize, pnread);
310 }
311
312 static int
_xscript_write(struct _mu_stream * str,const char * buf,size_t bufsize,size_t * pnwrite)313 _xscript_write (struct _mu_stream *str, const char *buf, size_t bufsize,
314 size_t *pnwrite)
315 {
316 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
317 return mu_stream_write (sp->transport, buf, bufsize, pnwrite);
318 }
319
320 static int
_xscript_flush(struct _mu_stream * str)321 _xscript_flush (struct _mu_stream *str)
322 {
323 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
324 return mu_stream_flush (sp->transport);
325 }
326
327 static int
_xscript_open(struct _mu_stream * str)328 _xscript_open (struct _mu_stream *str)
329 {
330 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
331 return mu_stream_open (sp->transport);
332 }
333
334 static int
_xscript_close(struct _mu_stream * str)335 _xscript_close (struct _mu_stream *str)
336 {
337 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
338 return mu_stream_close (sp->transport);
339 }
340
341 static void
_xscript_done(struct _mu_stream * str)342 _xscript_done (struct _mu_stream *str)
343 {
344 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
345 free (sp->prefix[0]);
346 free (sp->prefix[1]);
347 mu_stream_unref (sp->transport);
348 mu_stream_unref (sp->logstr);
349 }
350
351 static int
_xscript_seek(struct _mu_stream * str,mu_off_t off,mu_off_t * ppos)352 _xscript_seek (struct _mu_stream *str, mu_off_t off, mu_off_t *ppos)
353 {
354 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
355 return mu_stream_seek (sp->transport, off, MU_SEEK_SET, ppos);
356 }
357
358 static int
_xscript_size(struct _mu_stream * str,mu_off_t * psize)359 _xscript_size (struct _mu_stream *str, mu_off_t *psize)
360 {
361 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
362 return mu_stream_size (sp->transport, psize);
363 }
364
365 static inline int
state_to_level(int s)366 state_to_level(int s)
367 {
368 return s >= XST_PAYLOAD ? XST_PAYLOAD : s;
369 }
370
371 static int
_xscript_ctl(struct _mu_stream * str,int code,int opcode,void * arg)372 _xscript_ctl (struct _mu_stream *str, int code, int opcode, void *arg)
373 {
374 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
375 int status = 0;
376
377 switch (code)
378 {
379 case MU_IOCTL_TRANSPORT:
380 if (!arg)
381 return EINVAL;
382 else
383 {
384 mu_transport_t *ptrans = arg;
385 switch (opcode)
386 {
387 case MU_IOCTL_OP_GET:
388 ptrans[0] = (mu_transport_t) sp->transport;
389 ptrans[1] = (mu_transport_t) sp->logstr;
390 break;
391
392 case MU_IOCTL_OP_SET:
393 ptrans = arg;
394 if (ptrans[0])
395 sp->transport = (mu_stream_t) ptrans[0];
396 if (ptrans[1])
397 sp->logstr = (mu_stream_t) ptrans[1];
398 break;
399
400 default:
401 return EINVAL;
402 }
403 }
404 break;
405
406 case MU_IOCTL_SUBSTREAM:
407 if (sp->transport
408 && ((status = mu_stream_ioctl (sp->transport, code, opcode, arg)) == 0
409 || status != ENOSYS))
410 return status;
411 /* fall through */
412
413 case MU_IOCTL_TOPSTREAM:
414 if (!arg)
415 return EINVAL;
416 switch (opcode)
417 {
418 case MU_IOCTL_OP_GET:
419 if (!sp->transport)
420 status = ENOSYS;
421 else
422 status = mu_stream_ioctl (sp->transport, code, opcode, arg);
423 if (status == EINVAL || status == ENOSYS)
424 {
425 mu_stream_t *pstr = arg;
426
427 pstr[0] = sp->transport;
428 mu_stream_ref (pstr[0]);
429 pstr[1] = sp->transport;
430 mu_stream_ref (pstr[1]);
431 status = 0;
432 }
433 break;
434
435 case MU_IOCTL_OP_SET:
436 if (!sp->transport)
437 status = ENOSYS;
438 else
439 status = mu_stream_ioctl (sp->transport, code, opcode, arg);
440 if (status == EINVAL || status == ENOSYS)
441 {
442 mu_stream_t *pstr = arg;
443 mu_stream_t tmp;
444
445 if (pstr[0] != pstr[1])
446 {
447 status = mu_iostream_create (&tmp, pstr[0], pstr[1]);
448 if (status)
449 return status;
450 }
451 else
452 {
453 tmp = pstr[0];
454 mu_stream_ref (tmp);
455 mu_stream_ref (tmp);
456 status = 0;
457 }
458
459 mu_stream_unref (sp->transport);
460 sp->transport = tmp;
461 }
462 }
463 break;
464
465 case MU_IOCTL_TRANSPORT_BUFFER:
466 if (!sp->transport)
467 return EINVAL;
468 return mu_stream_ioctl (sp->transport, code, opcode, arg);
469
470 case MU_IOCTL_XSCRIPTSTREAM:
471 if (!arg)
472 return EINVAL;
473 switch (opcode)
474 {
475 case MU_IOCTL_XSCRIPTSTREAM_LEVEL:
476 {
477 int imode = state_to_level (sp->channel[CHAN_READ].state);
478 int omode = state_to_level (sp->channel[CHAN_WRITE].state);
479 int pack = *(int*)arg;
480
481 sp->channel[CHAN_READ].state =
482 MU_XSCRIPT_LEVEL_UNPACK (CHAN_READ, pack);
483 sp->channel[CHAN_READ].length = 0;
484
485 sp->channel[CHAN_WRITE].state =
486 MU_XSCRIPT_LEVEL_UNPACK (CHAN_WRITE, pack);
487 sp->channel[CHAN_WRITE].length = 0;
488
489 *(int*)arg = MU_XSCRIPT_LEVEL_PACK (imode, omode);
490 }
491 break;
492
493 case MU_IOCTL_XSCRIPTSTREAM_CHANNEL:
494 {
495 struct mu_xscript_channel ret;
496 struct mu_xscript_channel *chp = arg;
497
498 if (chp->cd < 0 || chp->cd > 1
499 || chp->level > MU_XSCRIPT_PAYLOAD
500 || (chp->level != MU_XSCRIPT_PAYLOAD && chp->length > 0))
501 return EINVAL;
502
503 ret.cd = chp->cd;
504 ret.level = state_to_level (sp->channel[chp->cd].state);
505 ret.length = sp->channel[chp->cd].length;
506
507 sp->channel[chp->cd].state = chp->level;
508 if (chp->level == MU_XSCRIPT_PAYLOAD)
509 sp->channel[chp->cd].length = chp->length;
510 else
511 sp->channel[chp->cd].length = 0;
512
513 *chp = ret;
514 }
515 break;
516 default:
517 return EINVAL;
518 }
519 break;
520
521 default:
522 return mu_stream_ioctl (sp->transport, code, opcode, arg);
523 }
524 return status;
525 }
526
527 static int
_xscript_wait(struct _mu_stream * str,int * pflags,struct timeval * tvp)528 _xscript_wait (struct _mu_stream *str, int *pflags, struct timeval *tvp)
529 {
530 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
531 return mu_stream_wait (sp->transport, pflags, tvp);
532 }
533
534 static int
_xscript_truncate(struct _mu_stream * str,mu_off_t size)535 _xscript_truncate (struct _mu_stream *str, mu_off_t size)
536 {
537 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
538 return mu_stream_truncate (sp->transport, size);
539 }
540
541 static int
_xscript_shutdown(struct _mu_stream * str,int how)542 _xscript_shutdown (struct _mu_stream *str, int how)
543 {
544 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
545 return mu_stream_shutdown (sp->transport, how);
546 }
547
548 static const char *
_xscript_error_string(struct _mu_stream * str,int rc)549 _xscript_error_string (struct _mu_stream *str, int rc)
550 {
551 struct _mu_xscript_stream *sp = (struct _mu_xscript_stream *)str;
552 const char *p = mu_stream_strerror (sp->transport, rc);
553 if (!p)
554 p = mu_strerror (rc);
555 return p;
556 }
557
558 const char *default_prefix[2] = {
559 "C: ", "S: "
560 };
561
562 int
mu_xscript_stream_create(mu_stream_t * pref,mu_stream_t transport,mu_stream_t logstr,const char * prefix[])563 mu_xscript_stream_create (mu_stream_t *pref, mu_stream_t transport,
564 mu_stream_t logstr,
565 const char *prefix[])
566 {
567 int flags;
568 struct _mu_xscript_stream *sp;
569
570 mu_stream_get_flags (transport, &flags);
571 sp = (struct _mu_xscript_stream *) _mu_stream_create (sizeof (*sp),
572 flags | _MU_STR_OPEN);
573 if (!sp)
574 return ENOMEM;
575
576 sp->stream.read = _xscript_read;
577 sp->stream.write = _xscript_write;
578 sp->stream.flush = _xscript_flush;
579 sp->stream.open = _xscript_open;
580 sp->stream.close = _xscript_close;
581 sp->stream.done = _xscript_done;
582 sp->stream.seek = _xscript_seek;
583 sp->stream.size = _xscript_size;
584 sp->stream.ctl = _xscript_ctl;
585 sp->stream.wait = _xscript_wait;
586 sp->stream.truncate = _xscript_truncate;
587 sp->stream.shutdown = _xscript_shutdown;
588 sp->stream.error_string = _xscript_error_string;
589 sp->stream.event_cb = _xscript_event_cb;
590 sp->stream.event_mask = _MU_STR_EVMASK(_MU_STR_EVENT_FILLBUF) |
591 _MU_STR_EVMASK(_MU_STR_EVENT_FLUSHBUF);
592 mu_stream_ref (transport);
593 mu_stream_ref (logstr);
594
595 sp->transport = transport;
596 sp->logstr = logstr;
597
598 if (prefix)
599 {
600 sp->prefix[0] = strdup (prefix[0] ? prefix[0] : default_prefix[0]);
601 sp->prefix[1] = strdup (prefix[1] ? prefix[1] : default_prefix[1]);
602 }
603 else
604 {
605 sp->prefix[0] = strdup (default_prefix[0]);
606 sp->prefix[1] = strdup (default_prefix[1]);
607 }
608
609 if (sp->prefix[0] == NULL || sp->prefix[1] == 0)
610 {
611 free (sp->prefix[0]);
612 free (sp->prefix[1]);
613 free (sp);
614 return ENOMEM;
615 }
616 mu_stream_set_buffer ((mu_stream_t) sp, mu_buffer_line, 0);
617 *pref = (mu_stream_t) sp;
618 return 0;
619 }
620