File: smtp.c

package info (click to toggle)
fetchmail 6.6.0-1
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 7,888 kB
  • sloc: ansic: 19,454; sh: 7,111; python: 2,395; perl: 564; yacc: 447; lex: 286; makefile: 261; awk: 124; lisp: 84; exp: 43; sed: 17
file content (589 lines) | stat: -rw-r--r-- 20,862 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
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
/**
 * \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.
 *
 * Relevant standards: RFC-5321, for SMTP AUTH: RFC-4954, RFC-4422.
 *
 * Copyright 1997 Eric S. Raymond
 * Copyright 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 <errno.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 */
#ifdef SSL_ENABLE
    {"STARTTLS",        ESMTP_STARTTLS},
#endif
    {(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);
}

/** This function checks if the \a smtp_reply starts with "334 " and reports either "Server rejected the AUTH command" (if the first part isn't 334)
or "Malformed server reply" if the blank is missing.
\returns
  - true if the reply starts with "334 "
  - false if the reply does not start with "334 ". */
static bool SMTP_check_AUTH_reply(const char *smtp_reply /** input: full reply line received from SMTP server */)
{
  if (strncmp(smtp_reply, "334", 3)) { /* Server rejects AUTH */
    report(stderr, "\"%s\" <- %s", visbuf(smtp_reply),
           GT_("Server rejected the AUTH command.\n"));
    return false;
  }
  if (smtp_reply[3] != ' ') {
    report(stderr, "\"%s\" <- %s",
		    visbuf(smtp_reply),
		    // GT_("Malformed server reply\n"),
		    GT_("protocol error\n"));
    return false;
  }
  return true;
}

/** Authenticate \a sock via ESMTP, currently supported mechanisms: CRAM-MD5, LOGIN, PLAIN.
 * Original ESMTP Authentication support for fetchmail by Wojciech Polak. */
static int 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) {
                report(stderr, "SMTP AUTH: %s\n", GT_("Invalid userid or passphrase"));
                return SM_UNRECOVERABLE;
        }

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

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

	if (strstr(ehlo_advertised_auth, "CRAM-MD5")) {
                // IETF RFC 4952, 2195
		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));

                // could use SMTP_check_AUTH_reply() here instead, but we need the pointer "after the blank" also

		if (strncmp(tmp, "334", 3)) { /* Server rejects AUTH */
			report(stderr, "\"%s\" <- %s", visbuf(tmp), GT_("Server rejected the AUTH command.\n"));
                        // No need to cancel SMTP AUTH here.
			return SM_ERROR;
		}

		p = strchr(tmp, ' ');
		if (!p) {
			report(stderr, "%s: \"%s\"\n", GT_("Malformed server reply"), visbuf(tmp));
			SMTP_auth_error(sock, "");
			return SM_ERROR;
		}
		p++;
		/* (hmh) from64tobits will not NULL-terminate strings! */
                // The challenge cannot be empty, so we use "<= 0"
		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 SM_ERROR;
		}
		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));
		int plen = 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]);
		if (plen < 0 || (size_t)plen >= sizeof(tmp))
                {
                        // username pretty long so we truncated the output - this is pointless to send, abort authentication instead
                        report(stderr, GT_("Digest text buffer too small!\n")); // XXX FIXME: this isn't accurate but already translated.
                        SMTP_auth_error(sock, "");
                        return SM_ERROR;
                }

		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);
		return SMTP_ok(sock, smtp_mode, TIMEOUT_DEFAULT);
	}
	else if (strstr(ehlo_advertised_auth, "PLAIN")) {
                // IETF RFC 4952, 4616.
		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));
		return SMTP_ok(sock, smtp_mode, TIMEOUT_DEFAULT);
	}
	else if (strstr(ehlo_advertised_auth, "LOGIN")) {
                // NONSTANDARD and also inefficient because it takes too many round trips
                // compared to RFC-4616 PLAIN authentication, see above
                // https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00
		// has expired and never became even a RFC, let alone a standard.
		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));

                /* we used to check the base64-encoded username and password prompts.
                   We don't do that any more, although RFC 4954 requires us to.

                   1. If I understand recent bug fixes to mutt 2.2.15 properly, then
                   some versions of Microsoft Exchange would send garbage here
                   such as "GSSAPI supported" that isn't base64.
                   <https://gitlab.com/muttmua/mutt/-/commit/b363b602e17906f63f3177f9899cd6c150e746ab>
                   Quote "Another workaround for broken SMTP servers.  Instead of an empty challenge,
                   some MS servers return a meaningless non-BASE64 encoded response in the initial reply,
                   e.g. "334 GSSAPI supported".
"
                   2. Servers that comply with RFC-4952 must just send "334 \r\n" which our base64 decoder
                   decodes to 0 bytes, and the former smtp.c client code required a non-zero length.
                   If the server's reply starts with "334 ", we're good and continue.
                 */
                if (!SMTP_check_AUTH_reply(smtp_response)) {
			// No need to cancel SMTP AUTH here.
                        return SM_ERROR;
                }

                // send base64(username)
                to64frombits(b64buf, username, unlen, sizeof b64buf);
		SockPrintf(sock, "%s\r\n", b64buf);
		SockRead(sock, smtp_response, sizeof(smtp_response) - 1);

                if (!SMTP_check_AUTH_reply(smtp_response)) {
                        return SM_ERROR;
                }

                /* again, RFC-4952 section 4 suggests we should decode the server
                 * challenge and abort authentication if we can't decode it; again,
                 * we just ignore anything after "334 ".
                 */

                // send base64(password)
		to64frombits(b64buf, password, pwlen, sizeof b64buf);
		SockPrintfClear(sock, "%s\r\n", b64buf);
		fm_safe_clearmem(b64buf, sizeof(b64buf));

		return SMTP_ok(sock, smtp_mode, TIMEOUT_DEFAULT);
	}
	return SM_UNRECOVERABLE;
}

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 (depending on \a smtp_mode) via \a sock identifying ourselves as \a host, potentially log in with \a name and \a password through SMTP AUTH, and 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 (name && password) {
            if (*opt & ESMTP_AUTH) {
		if (SM_OK != SMTP_auth(sock, smtp_mode, name, password, auth_response)) {
                        report_build(stderr, GT_("fetchmail authentication failed on %s@%s"), name, host);
                        report_complete(stderr, "\n");
                        return SM_UNRECOVERABLE;
                }
            } else {
                report(stderr, "%cMTP: %s\n", smtp_mode, GT_("authentication requested, but not offered by server."));
                return SM_UNRECOVERABLE;
            }
          }
	  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;
}

static char *SMTP_fixup_address_literal(const char *addr /** input address (user@domain) */);

int SMTP_from(int sock,
        char smtp_mode /** only for logging */,
        const char *_from /** envelope sender */,
        const char *opts /** options for MAIL FROM:<>, needs to start with a blank, will be concatenated to the command. NULL for no addition. */)
/**< send a "MAIL FROM:<\a from>" message to the SMTP listener */
{
    int ok;
    char buf[MSGBUFSIZE];
    char *from = SMTP_fixup_address_literal(_from);
    if (!from) return SM_UNRECOVERABLE;

    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);
    xfree(from);
    return ok;
}

/** This function returns a copy of \a addr with the domain part
 * reformatted as an IPv4 or IPv6 address literal, or NULL on failure.
 * \returns NULL or a buffer that the caller must \ref free(). */
static char *SMTP_fixup_address_literal(const char *addr /** input address (user@domain) */)
{
    const char *pfx = NULL;
    const char *pos = NULL;
    char *allocd = NULL;

    char newlit[INET_ADDRSTRLEN + 1] = "";

    if (1 != regex_ere_search("@[0-9a-fA-Fx:.]+[[:space:]]*$", addr))
        return xstrdup(addr);

    pos = strrchr(addr, '@');
    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 = "[";
      inet_ntop(AF_INET, &in.in4, newlit, INET_ADDRSTRLEN);
    }
#endif

    if (!pfx) return xstrdup(addr);

    if (pos < addr || pos - addr > INT_MAX) {
        return NULL;
    }

    size_t needsize =
        pos - addr + 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 - addr), addr,
                            pfx, *newlit ? newlit : pos);
    if (havesize <= 0 || (unsigned)havesize >= needsize) {
        xfree(allocd);
        return NULL;
    }
    return allocd;
}

/** 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 *newto = SMTP_fixup_address_literal(to);
    if (!newto)
      return SM_UNRECOVERABLE;

    SockPrintf(sock, "RCPT TO:<%s>\r\n", newto);
    if (outlevel >= O_MONITOR)
      report(stdout, "%cMTP> RCPT TO:<%s>\n", smtp_mode, newto);
    xfree(newto);
    ok = SMTP_ok(sock, smtp_mode, TIMEOUT_RCPT);
    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 */