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