File: smtp.c

package info (click to toggle)
fetchmail 6.5.6-2
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 7,596 kB
  • sloc: ansic: 19,190; sh: 7,108; python: 2,395; perl: 564; yacc: 447; lex: 286; makefile: 260; awk: 124; lisp: 84; exp: 43; sed: 17
file content (516 lines) | stat: -rw-r--r-- 16,609 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
/**
 * \file smtp.c -- code for speaking SMTP, ESMTP or LMTP to a listener port
 *
 * Concept due to Harry Hochheiser.  Implementation by ESR.  Cleanup and
 * strict RFC821 compliance by Cameron MacPherson.
 *
 * Copyright 1997 Eric S. Raymond, 2009 - 2025 Matthias Andree
 * Contribution 2004 by Phil Endecott (by way of Rob Funk)
 * Contributions 2005, 2011 by Sunil Shetye
 * Contributions 2012, 2021 by Earl Chew
 * For license terms, see the file COPYING in this directory.
 */

#include "config.h"
#include "fetchmail.h"

#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <signal.h>

// for inet_pton, inet_ntop, inet_aton (if provided)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "socket.h"
#include "smtp.h"
#include "i18n.h"

/** Structure definition to hold pairs of an option \æ name and the ESMTP_* flags as integer \a value. */
struct opt
{
    const char *name; /**< option's \a name in ESMTP */
    int value;        /**< bitmask with single bit set to mark an option */
};

/** {NULL,0}-terminated list of known ESMTP extensions. Only for \ref SMTP_ehlo. */
static struct opt extensions[] =
{
    {"8BITMIME",	ESMTP_8BITMIME},
    {"SIZE",    	ESMTP_SIZE},
    {"ETRN",		ESMTP_ETRN},
    {"AUTH ",		ESMTP_AUTH},
#ifdef ODMR_ENABLE
    {"ATRN",		ESMTP_ATRN},
#endif /* ODMR_ENABLE */
    {(char *)NULL, 0},
};

/** Buffer to hold the SMTP server's responses.

This can be referenced after \ref SMTP_ok if more detailed
information from the reply is required, such as the list of
extensions obtained via EHLO.

Multiline responses are concatenated, with interior CRLF. No trailing CRLF.
*/
char smtp_response[MSGBUFSIZE];

/* XXX: this must not be used for LMTP! */
int SMTP_helo(int sock /** socket to converse with */,
        char smtp_mode /** only for logging */,
        const char *host /** host to identify as */)
/** send a "HELO" message to the SMTP listener. Does not support LMTP (which needs LHLO instead). */
{
  int ok;

  ASSERT(("This function is only for SMTP, not for LMTP or ESMTP" && smtp_mode != 'L'));

  SockPrintf(sock,"HELO %s\r\n", host);
  if (outlevel >= O_MONITOR)
      report(stdout, "%cMTP> HELO %s\n", smtp_mode, host);
  ok = SMTP_ok(sock, smtp_mode, TIMEOUT_HELO);
  return ok;
}

/** Send a * alone on a CRLF terminated line, to abort SASL authentication, to socket \a  sock.
In verbose mode, prints/logs \a msg without adding LF. After and excluding 6.5.5, \a msg may be NULL. */
static void SMTP_auth_error(int sock, const char *msg /** Message to print/log in verbose mode to stdout */)
{
    SockPrintf(sock, "*\r\n");
    SockRead(sock, smtp_response, sizeof(smtp_response) - 1);
    if (outlevel >= O_MONITOR && msg && *msg) report(stdout, "%s", msg);
}

/** Authenticate \a sock via ESMTP, currently supported mechanisms: CRAM-MD5, LOGIN, PLAIN.
 * Original ESMTP Authentication support for fetchmail by Wojciech Polak */
static void SMTP_auth(int sock, char smtp_mode, const char *username, const char *password,
        const char *ehlo_advertised_auth)
{
	int c;
	const char *p = 0;
	char b64buf[512];
	char tmp[512];

	if (!username || !password) return;

        if (strlen(username) > INT_MAX) return;
        const int unlen = (int)strlen(username);
        if (strlen(password) > INT_MAX) return;
        const int pwlen = (int)strlen(password);

	memset(b64buf, 0, sizeof(b64buf));
	memset(tmp, 0, sizeof(tmp));

	if (strstr(ehlo_advertised_auth, "CRAM-MD5")) {
		unsigned char digest[16];
		memset(digest, 0, sizeof(digest));

		if (outlevel >= O_MONITOR)
			report(stdout, GT_("ESMTP CRAM-MD5 Authentication...\n"));
		SockPrintf(sock, "AUTH CRAM-MD5\r\n");
		SockRead(sock, smtp_response, sizeof(smtp_response) - 1);
		strlcpy(tmp, smtp_response, sizeof(tmp));

		if (strncmp(tmp, "334", 3)) { /* Server rejects AUTH */
			report(stderr, "\"%s\" <- %s", visbuf(tmp), GT_("Server rejected the AUTH command.\n"));
			SMTP_auth_error(sock, "");
			return;
		}

		p = strchr(tmp, ' ');
		if (!p) {
			report(stderr, "%s: \"%s\"\n", GT_("Malformed server reply"), visbuf(tmp));
			SMTP_auth_error(sock, "");
			return;
		}
		p++;
		/* (hmh) from64tobits will not NULL-terminate strings! */
		if (from64tobits(b64buf, p, sizeof(b64buf) - 1) <= 0) {
			report(stderr, "\"%s\" <- %s", visbuf(tmp), GT_("Bad base64 reply from server.\n"));
			SMTP_auth_error(sock, "");
			return;
		}
		if (outlevel >= O_DEBUG)
			report(stdout, GT_("Challenge decoded: %s\n"), b64buf);
		hmac_md5((unsigned const char *)password, pwlen,
			 (unsigned char *)b64buf, strlen(b64buf), digest, sizeof(digest));
		snprintf(tmp, sizeof(tmp),
		"%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
		username,  digest[0], digest[1], digest[2], digest[3],
		digest[4], digest[5], digest[6], digest[7], digest[8],
		digest[9], digest[10], digest[11], digest[12], digest[13],
		digest[14], digest[15]);

		to64frombits(b64buf, tmp, strlen(tmp), sizeof b64buf); // NOSONAR: tmp is our own buffer, so strlen() shan't exceed its size
		SockPrintf(sock, "%s\r\n", b64buf);
		SMTP_ok(sock, smtp_mode, TIMEOUT_DEFAULT);
	}
	else if (strstr(ehlo_advertised_auth, "PLAIN")) {
		int len;
		if (outlevel >= O_MONITOR)
			report(stdout, GT_("ESMTP PLAIN Authentication...\n"));
		snprintf(tmp, sizeof(tmp), "^%s^%s", username, password);

		len = strlen(tmp); // NOSONAR: tmp is our own buffer, so strlen() shan't exceed its size

		/* Take care not to overflow the buffer */
		c = 0;
		tmp[c] = '\0';
		c += 1 + unlen;
		if (c < len)
			tmp[c] = '\0';

		to64frombits(b64buf, tmp, len, sizeof b64buf);
		fm_safe_clearmem(tmp, sizeof(tmp));
		SockPrintfClear(sock, "AUTH PLAIN %s\r\n", b64buf);
		fm_safe_clearmem(b64buf, sizeof(b64buf));
		SMTP_ok(sock, smtp_mode, TIMEOUT_DEFAULT);
	}
	else if (strstr(ehlo_advertised_auth, "LOGIN")) {
		if (outlevel >= O_MONITOR)
			report(stdout, GT_("ESMTP LOGIN Authentication...\n"));
		SockPrintf(sock, "AUTH LOGIN\r\n");
		SockRead(sock, smtp_response, sizeof(smtp_response) - 1);
		strlcpy(tmp, smtp_response, sizeof(tmp));

		if (strncmp(tmp, "334", 3)) { /* Server rejects AUTH */
			report(stderr, "\"%s\" <- %s", visbuf(tmp), GT_("Server rejected the AUTH command.\n"));
			SMTP_auth_error(sock, "");
			return;
		}

		p = strchr(tmp, ' ');
		if (!p) {
			report(stderr, "%s: \"%s\"\n", GT_("Malformed server reply"), visbuf(tmp));
			SMTP_auth_error(sock, "");
			return;
		}
		p++;
		if (from64tobits(b64buf, p, sizeof(b64buf) - 1) <= 0) {
			report(stderr, "\"%s\" <- %s", visbuf(tmp), GT_("Bad base64 reply from server.\n"));
			SMTP_auth_error(sock, "");
			return;
		}
		to64frombits(b64buf, username, unlen, sizeof b64buf);
		SockPrintf(sock, "%s\r\n", b64buf);
		SockRead(sock, smtp_response, sizeof(smtp_response) - 1);
		strlcpy(tmp, smtp_response, sizeof(tmp));
		p = strchr(tmp, ' ');
		if (!p) {
			report(stderr, "\"%s\" <- %s", visbuf(tmp), GT_("Bad base64 reply from server.\n"));
			SMTP_auth_error(sock, "");
			return;
		}
		p++;
		memset(b64buf, 0, sizeof(b64buf));
		if (from64tobits(b64buf, p, sizeof(b64buf) - 1) <= 0) {
			report(stderr, "\"%s\" <- %s", visbuf(tmp), GT_("Bad base64 reply from server.\n"));
			SMTP_auth_error(sock, "");
			return;
		}
		to64frombits(b64buf, password, pwlen, sizeof b64buf);
		SockPrintfClear(sock, "%s\r\n", b64buf);
		fm_safe_clearmem(b64buf, sizeof(b64buf));
		SMTP_ok(sock, smtp_mode, TIMEOUT_DEFAULT);
	}
	return;
}

int SMTP_ehlo(int sock, char smtp_mode /**< 'S' for ESMTP or 'L' for LMTP */,
        const char *host, const char *name, const char *password, int *opt)
/** send a "EHLO" or "LHLO" message to the SMTP or LMTP listener, return extension status bits in \a opt */
{
  char auth_response[511];
  SIGHANDLERTYPE alrmsave;
  const int tmout = (mytimeout >= TIMEOUT_HELO ? mytimeout : TIMEOUT_HELO);

  SockPrintf(sock,"%cHLO %s\r\n", (smtp_mode == 'S') ? 'E' : smtp_mode, host);
  if (outlevel >= O_MONITOR)
      report(stdout, "%cMTP> %cHLO %s\n",
	    smtp_mode, (smtp_mode == 'S') ? 'E' : smtp_mode, host);

  alrmsave = set_signal_handler(SIGALRM, null_signal_handler);
  set_timeout(tmout);

  *opt = 0;
  while ((SockRead(sock, smtp_response, sizeof(smtp_response)-1)) != -1)
  {
      set_timeout(0);
      (void)set_signal_handler(SIGALRM, alrmsave);

      size_t n = strlen(smtp_response);
      if (n > 0 && smtp_response[n-1] == '\n')
	  smtp_response[--n] = '\0';
      if (n > 0 && smtp_response[n-1] == '\r')
	  smtp_response[--n] = '\0';
      if (n < 4)
	  return SM_ERROR;
      smtp_response[n] = '\0';
      if (outlevel >= O_MONITOR)
	  report(stdout, "%cMTP< %s\n", smtp_mode, smtp_response);
      for (const struct opt *hp = extensions; hp->name; hp++)
	  if (!strncasecmp(hp->name, smtp_response+4, strlen(hp->name))) {
	      *opt |= hp->value;
	      if (strncmp(hp->name, "AUTH ", 5) == 0)
		strlcpy(auth_response, smtp_response, sizeof(auth_response));
	  }
      if ((smtp_response[0] == '1' || smtp_response[0] == '2' || smtp_response[0] == '3') && smtp_response[3] == ' ') {
	  if (*opt & ESMTP_AUTH)
		SMTP_auth(sock, smtp_mode, name, password, auth_response);
	  return SM_OK;
      }
      else if (smtp_response[3] != '-')
	  return SM_ERROR;

      alrmsave = set_signal_handler(SIGALRM, null_signal_handler);
      set_timeout(tmout);
  }
  return SM_UNRECOVERABLE;
}

int SMTP_from(int sock,
        char smtp_mode /** only for logging */,
        const char *from /** envelope sender */,
        const char *opts /** options for MAIL FROM:<>, will be concatenated to the command without additional blanks. NULL for no addition. */)
/**< send a "MAIL FROM:<\a from>" message to the SMTP listener */
{
    int ok;
    char buf[MSGBUFSIZE];

    if (from[0]=='<')
	snprintf(buf, sizeof(buf), "MAIL FROM:%s", from);
    else
	snprintf(buf, sizeof(buf), "MAIL FROM:<%s>", from);
    if (opts)
	snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%s", opts);
    SockPrintf(sock,"%s\r\n", buf);
    if (outlevel >= O_MONITOR)
	report(stdout, "%cMTP> %s\n", smtp_mode, buf);
    ok = SMTP_ok(sock, smtp_mode, TIMEOUT_MAIL);
    return ok;
}

/** Send a "RCPT TO:" message with address \a to to the SMTP listener on socket \a sock */
int SMTP_rcpt(int sock /** connected SMTP socket */,
        char smtp_mode /** only for logging */,
        const char *to /** destination address - bare address literals after the last @ on the line will be reformatted for RFC-5321 compliance*/)
{
  int ok;
  char *allocd = NULL;
  const char *pfx = NULL;
  const char *pos = NULL;
  char *newlit = NULL;

  if (1 == match_regex("@[0-9a-fA-Fx:.]+[[:space:]]*$", to))
  {
    pos = strrchr(to, '@');
    ASSERT(pos);
    pos++;
    union {
      struct in_addr  in4;
#ifdef AF_INET6
      struct in6_addr in6;
#endif
    } in;

#ifdef AF_INET6
    if (1 == inet_pton(AF_INET6, pos, &in)) {
      pfx = "[IPv6:";
    }
#endif

    if (!pfx && 1 == inet_pton(AF_INET, pos, &in)) {
      pfx = "[";
    }

    // inet_pton requires dotted-quad form and does
    // not understand a.b.c, a.b or a short forms
#ifdef HAVE_INET_ATON
    if (!pfx && -1 != inet_aton(pos, &in.in4)) {
	    // this will barf on 255.255.255.255 but that shan't be used as
	    // smtphost anyways
      pfx = "[";
      newlit = (char *)xmalloc(INET_ADDRSTRLEN + 1);
      inet_ntop(AF_INET, &in.in4, newlit, INET_ADDRSTRLEN);
    }
#endif
  }

  if (pfx) {
    if (pos < to || pos - to > INT_MAX) {
      ok = SM_UNRECOVERABLE;
      goto SMTP_rcpt_fail;
    }

    size_t needsize = pos - to + strlen(pfx) + 2 + strlen(newlit ? newlit : pos);
    // what's past the @ is a literal IPv4 or IPv6 and needs [] around
    allocd = (char *)xmalloc(needsize);
    int havesize = snprintf(allocd, needsize, "%-.*s%s%s]",
	(int)(pos - to), to, pfx, newlit ? newlit : pos);
    if (havesize <= 0 || (unsigned)havesize >= needsize) {
      ok = SM_UNRECOVERABLE;
      goto SMTP_rcpt_fail;
    }
    to = allocd;
  }

  SockPrintf(sock,"RCPT TO:<%s>\r\n", to);
  if (outlevel >= O_MONITOR)
    report(stdout, "%cMTP> RCPT TO:<%s>\n", smtp_mode, to);
  ok = SMTP_ok(sock, smtp_mode, TIMEOUT_RCPT);
SMTP_rcpt_fail:
  xfree(newlit);
  xfree(allocd);
  return ok;
}

int SMTP_data(int sock, char smtp_mode /** only for logging */)
/** send a "DATA" message to the SMTP listener */
{
  int ok;

  SockPrintf(sock,"DATA\r\n");
  if (outlevel >= O_MONITOR)
      report(stdout, "%cMTP> DATA\n", smtp_mode);
  ok = SMTP_ok(sock, smtp_mode, TIMEOUT_DATA);
  return ok;
}

int SMTP_rset(int sock, char smtp_mode /** only for logging */)
/** send a "RSET" message to the SMTP listener */
{
  int ok;

  SockPrintf(sock,"RSET\r\n");
  if (outlevel >= O_MONITOR)
      report(stdout, "%cMTP> RSET\n", smtp_mode);
  ok = SMTP_ok(sock, smtp_mode, TIMEOUT_DEFAULT);
  return ok;
}

int SMTP_quit(int sock, char smtp_mode /** only for logging */)
/** send a "QUIT" message to the SMTP listener */
{
  int ok;

  SockPrintf(sock,"QUIT\r\n");
  if (outlevel >= O_MONITOR)
      report(stdout, "%cMTP> QUIT\n", smtp_mode);
  ok = SMTP_ok(sock, smtp_mode, TIMEOUT_DEFAULT);
  return ok;
}

int SMTP_eom(int sock, char smtp_mode /** 'S' for (E)SMTP, 'L' for LMTP */)
/** Send a message data terminator to the SMTP listener.
 * In (E)SMTP mode, collect the response and handle it.
 * \note that in LMTP mode, this function does \b not collect responses
 * and it's the caller's responsibility to handle them to assess the delivery status! */
{
  ssize_t res = SockPrintf(sock,".\r\n");
  if (res < 0) { /* write error is fatal, we can't be sure what state the session is in */
        return SM_UNRECOVERABLE;
  }
  if (outlevel >= O_MONITOR)
      report(stdout, "%cMTP>. (EOM)\n", smtp_mode);

  /*
   * When doing LMTP, must process many of these at the outer level.
   */
  if (smtp_mode == 'S')
      return SMTP_ok(sock, smtp_mode, TIMEOUT_EOM);
  else
      return SM_OK;
}

time_t last_smtp_ok = 0; /**< exported time_t timestamp when we last received a well-formatted SMTP response. Set by \ref SMTP_ok, never reset by \ref smtp.c */

int SMTP_ok(int sock /** socket to read from */,
        char smtp_mode /** used for logging only */,
        int mintimeout /** minimum bound for wait timeout */)
/**< Obtains status (possibly multi-line) of (E)SMTP/LMTP connection and saves the message in
 * \a smtp_response, without trailing [CR]LF, but with normalized CRLF
 * between multiple lines of multi-line replies.
 *
 * \note for multi-line replies, if the codes are inconsistent in violation of standards,
 * the code on the final (last) line of the multi-line reply prevails.
 *
 * \returns
 * + SM_OK for response codes 100...399
 * + SM_ERROR for response code with other numbers (including 400...599)
 * + SM_UNRECOVERABLE on protocol errors (malformatted answer, read error)
 */
{
    SIGHANDLERTYPE alrmsave;
    char reply[MSGBUFSIZE];

    /* set an alarm for smtp ok */
    alrmsave = set_signal_handler(SIGALRM, null_signal_handler);
    set_timeout(mytimeout >= mintimeout ? mytimeout : mintimeout);

    smtp_response[0] = '\0';

    while ((SockRead(sock, reply, sizeof(reply)-1)) != -1)
    {
	size_t n;

	/* restore alarm */
	set_timeout(0);
	set_signal_handler(SIGALRM, alrmsave);

	n = strlen(reply);
	if (n > 0 && reply[n-1] == '\n')
	    reply[--n] = '\0';
	if (n > 0 && reply[n-1] == '\r')
	    reply[--n] = '\0';

	/* stomp over control characters */
	for (char *i = reply; *i; i++)
	    if (iscntrl((unsigned char)*i))
		*i = '?';

	if (outlevel >= O_MONITOR)
	    report(stdout, "%cMTP< %s\n", smtp_mode, reply);
	/* note that \0 is part of the strchr search string and the
	 * blank after the reply code is optional (RFC 5321 4.2.1) */
	if (n < 3 || !strchr(" -", reply[3]))
	{
	    if (outlevel >= O_MONITOR)
		report(stderr, GT_("smtp listener protocol error\n"));
	    return SM_UNRECOVERABLE;
	}

	last_smtp_ok = time((time_t *) NULL);

	strlcat(smtp_response, reply,  sizeof(smtp_response));

	if (strchr("123", reply[0])
		&& isdigit((unsigned char)reply[1])
		&& isdigit((unsigned char)reply[2])
		&& strchr(" ", reply[3])) /* matches space and \0 */ {
	    return SM_OK;
	} else if (reply[3] != '-')
	    return SM_ERROR;

	strlcat(smtp_response, "\r\n", sizeof(smtp_response));

	/* set an alarm for smtp ok */
	set_signal_handler(SIGALRM, null_signal_handler);
	set_timeout(mytimeout);
    }

    /* restore alarm */
    set_timeout(0);
    set_signal_handler(SIGALRM, alrmsave);

    if (outlevel >= O_MONITOR)
	report(stderr, GT_("smtp listener protocol error\n"));
    return SM_UNRECOVERABLE;
}

/* smtp.c ends here */