xref: /openbsd-src/usr.sbin/smtpd/smtpc.c (revision 8e996a8e97c7e612ed9d362e431f1b8d4939e244)
1*8e996a8eSjsg /*	$OpenBSD: smtpc.c,v 1.21 2024/05/13 06:48:26 jsg Exp $	*/
2ef573529Seric 
3ef573529Seric /*
4ef573529Seric  * Copyright (c) 2018 Eric Faurot <eric@openbsd.org>
5ef573529Seric  *
6ef573529Seric  * Permission to use, copy, modify, and distribute this software for any
7ef573529Seric  * purpose with or without fee is hereby granted, provided that the above
8ef573529Seric  * copyright notice and this permission notice appear in all copies.
9ef573529Seric  *
10ef573529Seric  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11ef573529Seric  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12ef573529Seric  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13ef573529Seric  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14ef573529Seric  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15ef573529Seric  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16ef573529Seric  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17ef573529Seric  */
18ef573529Seric 
19ef573529Seric #include <sys/socket.h>
20ef573529Seric 
21ef573529Seric #include <event.h>
22ef573529Seric #include <netdb.h>
23ef573529Seric #include <pwd.h>
24ef573529Seric #include <stdio.h>
25ef573529Seric #include <stdlib.h>
26ef573529Seric #include <string.h>
270cef6e54Seric #include <syslog.h>
28eed85469Seric #include <tls.h>
29ef573529Seric #include <unistd.h>
30ef573529Seric 
31d6ee62d2Seric #include "smtp.h"
32d6ee62d2Seric #include "log.h"
33ef573529Seric 
34ef573529Seric static void parse_server(char *);
35ef573529Seric static void parse_message(FILE *);
36ef573529Seric static void resume(void);
37ef573529Seric 
38ef573529Seric static int verbose = 1;
39ef573529Seric static int done = 0;
40ef573529Seric static int noaction = 0;
41ef573529Seric static struct addrinfo *res0, *ai;
42ef573529Seric static struct smtp_params params;
43ef573529Seric static struct smtp_mail mail;
44d6ee62d2Seric static const char *servname = NULL;
45eed85469Seric static struct tls_config *tls_config;
46ef573529Seric 
47d4726537Seric static int nosni = 0;
48d4726537Seric static const char *cafile = NULL;
494021cd6eSeric static const char *protocols = NULL;
504021cd6eSeric static const char *ciphers = NULL;
514021cd6eSeric 
52ef573529Seric static void
usage(void)53ef573529Seric usage(void)
54ef573529Seric {
55ef573529Seric 	extern char *__progname;
56ef573529Seric 
57024609d0Smartijn 	fprintf(stderr, "usage: %s [-Chnv] [-a authfile] [-F from] [-H helo] "
584021cd6eSeric 	    "[-s server] [-T params] [recipient ...]\n", __progname);
59ef573529Seric 	exit(1);
60ef573529Seric }
61ef573529Seric 
624021cd6eSeric static void
parse_tls_options(char * opt)634021cd6eSeric parse_tls_options(char *opt)
644021cd6eSeric {
654021cd6eSeric 	static char * const tokens[] = {
66d4726537Seric #define CAFILE 0
67d4726537Seric 		"cafile",
68d4726537Seric #define CIPHERS 1
694021cd6eSeric 		"ciphers",
70d4726537Seric #define NOSNI 2
71d4726537Seric 		"nosni",
72d4726537Seric #define NOVERIFY 3
73d4726537Seric 		"noverify",
74d4726537Seric #define PROTOCOLS 4
754021cd6eSeric 		"protocols",
76d4726537Seric #define SERVERNAME 5
77d4726537Seric 		"servername",
784021cd6eSeric 		NULL };
794021cd6eSeric 	char *value;
804021cd6eSeric 
814021cd6eSeric 	while (*opt) {
824021cd6eSeric 		switch (getsubopt(&opt, tokens, &value)) {
83d4726537Seric 		case CAFILE:
84d4726537Seric 			if (value == NULL)
85d4726537Seric 				fatalx("missing value for cafile");
86d4726537Seric 			cafile = value;
87d4726537Seric 			break;
884021cd6eSeric 		case CIPHERS:
894021cd6eSeric 			if (value == NULL)
904021cd6eSeric 				fatalx("missing value for ciphers");
914021cd6eSeric 			ciphers = value;
924021cd6eSeric 			break;
93d4726537Seric 		case NOSNI:
94d4726537Seric 			if (value != NULL)
95d4726537Seric 				fatalx("no value expected for nosni");
96d4726537Seric 			nosni = 1;
97d4726537Seric 			break;
98d4726537Seric 		case NOVERIFY:
99d4726537Seric 			if (value != NULL)
100d4726537Seric 				fatalx("no value expected for noverify");
101d4726537Seric 			params.tls_verify = 0;
102d4726537Seric 			break;
1034021cd6eSeric 		case PROTOCOLS:
1044021cd6eSeric 			if (value == NULL)
1054021cd6eSeric 				fatalx("missing value for protocols");
1064021cd6eSeric 			protocols = value;
1074021cd6eSeric 			break;
108d4726537Seric 		case SERVERNAME:
109d4726537Seric 			if (value == NULL)
110d4726537Seric 				fatalx("missing value for servername");
111d4726537Seric 			servname = value;
112d4726537Seric 			break;
1134021cd6eSeric 		case -1:
1144021cd6eSeric 			if (suboptarg)
1154021cd6eSeric 				fatalx("invalid TLS option \"%s\"", suboptarg);
1164021cd6eSeric 			fatalx("missing TLS option");
1174021cd6eSeric 		}
1184021cd6eSeric 	}
1194021cd6eSeric }
1204021cd6eSeric 
121ef573529Seric int
main(int argc,char ** argv)122ef573529Seric main(int argc, char **argv)
123ef573529Seric {
124ef573529Seric 	char hostname[256];
125024609d0Smartijn 	FILE *authfile;
126ef573529Seric 	int ch, i;
1274021cd6eSeric 	uint32_t protos;
128ef573529Seric 	char *server = "localhost";
129024609d0Smartijn 	char *authstr = NULL;
130024609d0Smartijn 	size_t alloc = 0;
131024609d0Smartijn 	ssize_t len;
132ef573529Seric 	struct passwd *pw;
133ef573529Seric 
134ef573529Seric 	log_init(1, 0);
135ef573529Seric 
136ef573529Seric 	if (gethostname(hostname, sizeof(hostname)) == -1)
137ef573529Seric 		fatal("gethostname");
138ef573529Seric 
139ef573529Seric 	if ((pw = getpwuid(getuid())) == NULL)
140ef573529Seric 		fatal("getpwuid");
141ef573529Seric 
142ef573529Seric 	memset(&params, 0, sizeof(params));
143ef573529Seric 
144ef573529Seric 	params.linemax = 16392;
145ef573529Seric 	params.ibufmax = 65536;
146ef573529Seric 	params.obufmax = 65536;
147ef573529Seric 	params.timeout = 100000;
148ef573529Seric 	params.helo = hostname;
149ef573529Seric 
150ef573529Seric 	params.tls_verify = 1;
151ef573529Seric 
152ef573529Seric 	memset(&mail, 0, sizeof(mail));
153ef573529Seric 	mail.from = pw->pw_name;
154ef573529Seric 
1554021cd6eSeric 	while ((ch = getopt(argc, argv, "CF:H:S:T:a:hns:v")) != -1) {
156ef573529Seric 		switch (ch) {
157ef573529Seric 		case 'C':
158ef573529Seric 			params.tls_verify = 0;
159ef573529Seric 			break;
160ef573529Seric 		case 'F':
161ef573529Seric 			mail.from = optarg;
162ef573529Seric 			break;
163ef573529Seric 		case 'H':
164ef573529Seric 			params.helo = optarg;
165ef573529Seric 			break;
166d6ee62d2Seric 		case 'S':
167d6ee62d2Seric 			servname = optarg;
168d6ee62d2Seric 			break;
1694021cd6eSeric 		case 'T':
1704021cd6eSeric 			parse_tls_options(optarg);
1714021cd6eSeric 			break;
172024609d0Smartijn 		case 'a':
173024609d0Smartijn 			if ((authfile = fopen(optarg, "r")) == NULL)
174024609d0Smartijn 				fatal("%s: open", optarg);
175024609d0Smartijn 			if ((len = getline(&authstr, &alloc, authfile)) == -1)
176024609d0Smartijn 				fatal("%s: Failed to read username", optarg);
177024609d0Smartijn 			if (authstr[len - 1] == '\n')
178024609d0Smartijn 				authstr[len - 1] = '\0';
179024609d0Smartijn 			params.auth_user = authstr;
180024609d0Smartijn 			authstr = NULL;
181024609d0Smartijn 			len = 0;
182024609d0Smartijn 			if ((len = getline(&authstr, &alloc, authfile)) == -1)
183024609d0Smartijn 				fatal("%s: Failed to read password", optarg);
184024609d0Smartijn 			if (authstr[len - 1] == '\n')
185024609d0Smartijn 				authstr[len - 1] = '\0';
186024609d0Smartijn 			params.auth_pass = authstr;
187024609d0Smartijn 			fclose(authfile);
188024609d0Smartijn 			break;
189ef573529Seric 		case 'h':
190ef573529Seric 			usage();
191ef573529Seric 			break;
192ef573529Seric 		case 'n':
193ef573529Seric 			noaction = 1;
194ef573529Seric 			break;
195ef573529Seric 		case 's':
196ef573529Seric 			server = optarg;
197ef573529Seric 			break;
198ef573529Seric 		case 'v':
199ef573529Seric 			verbose++;
200ef573529Seric 			break;
201ef573529Seric 		default:
202ef573529Seric 			usage();
203ef573529Seric 		}
204ef573529Seric 	}
205ef573529Seric 
206ef573529Seric 	log_setverbose(verbose);
207ef573529Seric 
208ef573529Seric 	argc -= optind;
209ef573529Seric 	argv += optind;
210ef573529Seric 
211ef573529Seric 	if (argc) {
212ef573529Seric 		mail.rcpt = calloc(argc, sizeof(*mail.rcpt));
213ef573529Seric 		if (mail.rcpt == NULL)
214ef573529Seric 			fatal("calloc");
215ef573529Seric 		for (i = 0; i < argc; i++)
216ef573529Seric 			mail.rcpt[i].to = argv[i];
217ef573529Seric 		mail.rcptcount = argc;
218ef573529Seric 	}
219ef573529Seric 
220ef573529Seric 	event_init();
221ef573529Seric 
222eed85469Seric 	tls_config = tls_config_new();
223eed85469Seric 	if (tls_config == NULL)
224eed85469Seric 		fatal("tls_config_new");
2254021cd6eSeric 
2264021cd6eSeric 	if (protocols) {
2274021cd6eSeric 		if (tls_config_parse_protocols(&protos, protocols) == -1)
2284021cd6eSeric 			fatalx("failed to parse protocol '%s'", protocols);
2294021cd6eSeric 		if (tls_config_set_protocols(tls_config, protos) == -1)
2304021cd6eSeric 			fatalx("tls_config_set_protocols: %s",
2314021cd6eSeric 			    tls_config_error(tls_config));
2324021cd6eSeric 	}
2334021cd6eSeric 	if (ciphers && tls_config_set_ciphers(tls_config, ciphers) == -1)
2344021cd6eSeric 		fatalx("tls_config_set_ciphers: %s",
2354021cd6eSeric 		    tls_config_error(tls_config));
236d4726537Seric 
237d4726537Seric 	if (cafile == NULL)
238d4726537Seric 		cafile = tls_default_ca_cert_file();
239d4726537Seric 	if (tls_config_set_ca_file(tls_config, cafile) == -1)
24062e04d05Sop 		fatalx("tls_set_ca_file: %s", tls_config_error(tls_config));
241eed85469Seric 	if (!params.tls_verify) {
242eed85469Seric 		tls_config_insecure_noverifycert(tls_config);
243eed85469Seric 		tls_config_insecure_noverifyname(tls_config);
244eed85469Seric 		tls_config_insecure_noverifytime(tls_config);
245eed85469Seric 	} else
246eed85469Seric 		tls_config_verify(tls_config);
247d6ee62d2Seric 
248ef573529Seric 	if (pledge("stdio inet dns tmppath", NULL) == -1)
249ef573529Seric 		fatal("pledge");
250ef573529Seric 
251ef573529Seric 	if (!noaction)
252ef573529Seric 		parse_message(stdin);
253ef573529Seric 
254ef573529Seric 	if (pledge("stdio inet dns", NULL) == -1)
255ef573529Seric 		fatal("pledge");
256ef573529Seric 
257ef573529Seric 	parse_server(server);
258ef573529Seric 
259ef573529Seric 	if (pledge("stdio inet", NULL) == -1)
260ef573529Seric 		fatal("pledge");
261ef573529Seric 
262ef573529Seric 	resume();
263ef573529Seric 
264ef573529Seric 	log_debug("done...");
265ef573529Seric 
266ef573529Seric 	return 0;
267ef573529Seric }
268ef573529Seric 
269ef573529Seric static void
parse_server(char * server)270ef573529Seric parse_server(char *server)
271ef573529Seric {
272ef573529Seric 	struct addrinfo hints;
273ef573529Seric 	char *scheme, *creds, *host, *port, *p, *c;
274fe463512Skrw 	int error;
275ef573529Seric 
276ef573529Seric 	creds = NULL;
277ef573529Seric 	host = NULL;
278ef573529Seric 	port = NULL;
279ef573529Seric 	scheme = server;
280ef573529Seric 
281ef573529Seric 	p = strstr(server, "://");
282ef573529Seric 	if (p) {
283ef573529Seric 		*p = '\0';
284ef573529Seric 		p += 3;
285ef573529Seric 		/* check for credentials */
286c22056c0Smillert 		c = strrchr(p, '@');
287ef573529Seric 		if (c) {
288ef573529Seric 			creds = p;
289ef573529Seric 			*c = '\0';
290ef573529Seric 			host = c + 1;
291ef573529Seric 		} else
292ef573529Seric 			host = p;
293ef573529Seric 	} else {
294ef573529Seric 		/* Assume a simple server name */
295ef573529Seric 		scheme = "smtp";
296ef573529Seric 		host = server;
297ef573529Seric 	}
298ef573529Seric 
299ef573529Seric 	if (host[0] == '[') {
300ef573529Seric 		/* IPV6 address? */
301ef573529Seric 		p = strchr(host, ']');
302ef573529Seric 		if (p) {
303ef573529Seric 			if (p[1] == ':' || p[1] == '\0') {
304ef573529Seric 				*p++ = '\0';	/* remove ']' */
305ef573529Seric 				host++;		/* skip '[' */
306ef573529Seric 				if (*p == ':')
307ef573529Seric 					port = p + 1;
308ef573529Seric 			}
309ef573529Seric 		}
310ef573529Seric 	}
311ef573529Seric 	else {
312ef573529Seric 		port = strchr(host, ':');
313ef573529Seric 		if (port)
314ef573529Seric 			*port++ = '\0';
315ef573529Seric 	}
316ef573529Seric 
317ef573529Seric 	if (port && port[0] == '\0')
318ef573529Seric 		port = NULL;
319ef573529Seric 
32043ddfbdbSeric 	if (creds) {
32143ddfbdbSeric 		p = strchr(creds, ':');
32243ddfbdbSeric 		if (p == NULL)
32343ddfbdbSeric 			fatalx("invalid credentials");
32443ddfbdbSeric 		*p = '\0';
32543ddfbdbSeric 
32643ddfbdbSeric 		params.auth_user = creds;
32743ddfbdbSeric 		params.auth_pass = p + 1;
32843ddfbdbSeric 	}
329ef573529Seric 	params.tls_req = TLS_YES;
330ef573529Seric 
331ef573529Seric 	if (!strcmp(scheme, "lmtp")) {
332ef573529Seric 		params.lmtp = 1;
333ef573529Seric 	}
334ef573529Seric 	else if (!strcmp(scheme, "lmtp+tls")) {
335ef573529Seric 		params.lmtp = 1;
336ef573529Seric 		params.tls_req = TLS_FORCE;
337ef573529Seric 	}
338ef573529Seric 	else if (!strcmp(scheme, "lmtp+notls")) {
339ef573529Seric 		params.lmtp = 1;
340ef573529Seric 		params.tls_req = TLS_NO;
341ef573529Seric 	}
342ef573529Seric 	else if (!strcmp(scheme, "smtps")) {
343ef573529Seric 		params.tls_req = TLS_SMTPS;
344ef573529Seric 		if (port == NULL)
345c71c61b4Sgilles 			port = "smtps";
346ef573529Seric 	}
347ef573529Seric 	else if (!strcmp(scheme, "smtp")) {
348ef573529Seric 	}
349ef573529Seric 	else if (!strcmp(scheme, "smtp+tls")) {
350ef573529Seric 		params.tls_req = TLS_FORCE;
351ef573529Seric 	}
352ef573529Seric 	else if (!strcmp(scheme, "smtp+notls")) {
353ef573529Seric 		params.tls_req = TLS_NO;
354ef573529Seric 	}
355ef573529Seric 	else
356ef573529Seric 		fatalx("invalid url scheme %s", scheme);
357ef573529Seric 
358ef573529Seric 	if (port == NULL)
359ef573529Seric 		port = "smtp";
360ef573529Seric 
361d6ee62d2Seric 	if (servname == NULL)
362d6ee62d2Seric 		servname = host;
363d4726537Seric 	if (nosni == 0)
364eed85469Seric 		params.tls_servname = servname;
365d6ee62d2Seric 
366ef573529Seric 	memset(&hints, 0, sizeof(hints));
367ef573529Seric 	hints.ai_family = AF_UNSPEC;
368ef573529Seric 	hints.ai_socktype = SOCK_STREAM;
369ef573529Seric 	error = getaddrinfo(host, port, &hints, &res0);
370ef573529Seric 	if (error)
371ef573529Seric 		fatalx("%s: %s", host, gai_strerror(error));
372ef573529Seric 	ai = res0;
373ef573529Seric }
374ef573529Seric 
375ef573529Seric void
parse_message(FILE * ifp)376ef573529Seric parse_message(FILE *ifp)
377ef573529Seric {
378ef573529Seric 	char *line = NULL;
379ef573529Seric 	size_t linesz = 0;
380ef573529Seric 	ssize_t len;
381ef573529Seric 
382729f2a4cSmartijn 	if ((mail.fp = tmpfile()) == NULL)
383729f2a4cSmartijn 		fatal("tmpfile");
384ef573529Seric 
385ef573529Seric 	for (;;) {
386ef573529Seric 		if ((len = getline(&line, &linesz, ifp)) == -1) {
387ef573529Seric 			if (feof(ifp))
388ef573529Seric 				break;
389ef573529Seric 			fatal("getline");
390ef573529Seric 		}
391410c8eb6Seric 
392410c8eb6Seric 		if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n')
393410c8eb6Seric 			line[--len - 1] = '\n';
394410c8eb6Seric 
395ef573529Seric 		if (fwrite(line, 1, len, mail.fp) != len)
396410c8eb6Seric 			fatal("fwrite");
397410c8eb6Seric 
398410c8eb6Seric 		if (line[len - 1] != '\n' && fputc('\n', mail.fp) == EOF)
399410c8eb6Seric 			fatal("fputc");
400ef573529Seric 	}
401ef573529Seric 
402*8e996a8eSjsg 	free(line);
403ef573529Seric 	fclose(ifp);
404ef573529Seric 	rewind(mail.fp);
405ef573529Seric }
406ef573529Seric 
407ef573529Seric void
resume(void)408ef573529Seric resume(void)
409ef573529Seric {
410ef573529Seric 	static int started = 0;
411ef573529Seric 	char host[256];
412ef573529Seric 	char serv[16];
413ef573529Seric 
414ef573529Seric 	if (done) {
415ef573529Seric 		event_loopexit(NULL);
416ef573529Seric 		return;
417ef573529Seric 	}
418ef573529Seric 
419ef573529Seric 	if (ai == NULL)
420ef573529Seric 		fatalx("no more host");
421ef573529Seric 
422ef573529Seric 	getnameinfo(ai->ai_addr, ai->ai_addr->sa_len,
423ef573529Seric 	    host, sizeof(host), serv, sizeof(serv),
424ef573529Seric 	    NI_NUMERICHOST | NI_NUMERICSERV);
425ef573529Seric 	log_debug("trying host %s port %s...", host, serv);
426ef573529Seric 
427ef573529Seric 	params.dst = ai->ai_addr;
428ef573529Seric 	if (smtp_connect(&params, NULL) == NULL)
429ef573529Seric 		fatal("smtp_connect");
430ef573529Seric 
431ef573529Seric 	if (started == 0) {
432ef573529Seric 		started = 1;
433ef573529Seric 		event_loop(0);
434ef573529Seric 	}
435ef573529Seric }
436ef573529Seric 
437ef573529Seric void
log_trace(int lvl,const char * emsg,...)438ef573529Seric log_trace(int lvl, const char *emsg, ...)
439ef573529Seric {
440ef573529Seric 	va_list ap;
441ef573529Seric 
442ef573529Seric 	if (verbose > lvl) {
443ef573529Seric 		va_start(ap, emsg);
444ef573529Seric 		vlog(LOG_DEBUG, emsg, ap);
445ef573529Seric 		va_end(ap);
446ef573529Seric 	}
447ef573529Seric }
448ef573529Seric 
449ef573529Seric void
smtp_require_tls(void * tag,struct smtp_client * proto)450cde53503Seric smtp_require_tls(void *tag, struct smtp_client *proto)
451cde53503Seric {
452eed85469Seric 	struct tls *tls;
453cde53503Seric 
454eed85469Seric 	tls = tls_client();
455eed85469Seric 	if (tls == NULL)
456eed85469Seric 		fatal("tls_client");
457eed85469Seric 
458eed85469Seric 	if (tls_configure(tls, tls_config) == -1)
45962e04d05Sop 		fatalx("tls_configure: %s", tls_error(tls));
460eed85469Seric 
461eed85469Seric 	smtp_set_tls(proto, tls);
462cde53503Seric }
463cde53503Seric 
464cde53503Seric void
smtp_ready(void * tag,struct smtp_client * proto)465ef573529Seric smtp_ready(void *tag, struct smtp_client *proto)
466ef573529Seric {
467ef573529Seric 	log_debug("connection ready...");
468ef573529Seric 
469ef573529Seric 	if (done || noaction)
470ef573529Seric 		smtp_quit(proto);
471ef573529Seric 	else
472ef573529Seric 		smtp_sendmail(proto, &mail);
473ef573529Seric }
474ef573529Seric 
475ef573529Seric void
smtp_failed(void * tag,struct smtp_client * proto,int failure,const char * detail)476ef573529Seric smtp_failed(void *tag, struct smtp_client *proto, int failure, const char *detail)
477ef573529Seric {
478ef573529Seric 	switch (failure) {
479ef573529Seric 	case FAIL_INTERNAL:
480ef573529Seric 		log_warnx("internal error: %s", detail);
481ef573529Seric 		break;
482ef573529Seric 	case FAIL_CONN:
483ef573529Seric 		log_warnx("connection error: %s", detail);
484ef573529Seric 		break;
485ef573529Seric 	case FAIL_PROTO:
486ef573529Seric 		log_warnx("protocol error: %s", detail);
487ef573529Seric 		break;
488ef573529Seric 	case FAIL_IMPL:
489ef573529Seric 		log_warnx("missing feature: %s", detail);
490ef573529Seric 		break;
491ef573529Seric 	case FAIL_RESP:
492ef573529Seric 		log_warnx("rejected by server: %s", detail);
493ef573529Seric 		break;
494ef573529Seric 	default:
495ef573529Seric 		fatalx("unknown failure %d: %s", failure, detail);
496ef573529Seric 	}
497ef573529Seric }
498ef573529Seric 
499ef573529Seric void
smtp_status(void * tag,struct smtp_client * proto,struct smtp_status * status)500ef573529Seric smtp_status(void *tag, struct smtp_client *proto, struct smtp_status *status)
501ef573529Seric {
502ef573529Seric 	log_info("%s: %s: %s", status->rcpt->to, status->cmd, status->status);
503ef573529Seric }
504ef573529Seric 
505ef573529Seric void
smtp_done(void * tag,struct smtp_client * proto,struct smtp_mail * mail)506ef573529Seric smtp_done(void *tag, struct smtp_client *proto, struct smtp_mail *mail)
507ef573529Seric {
508ef573529Seric 	int i;
509ef573529Seric 
510ef573529Seric 	log_debug("mail done...");
511ef573529Seric 
512ef573529Seric 	if (noaction)
513ef573529Seric 		return;
514ef573529Seric 
515ef573529Seric 	for (i = 0; i < mail->rcptcount; i++)
516ef573529Seric 		if (!mail->rcpt[i].done)
517ef573529Seric 			return;
518ef573529Seric 
519ef573529Seric 	done = 1;
520ef573529Seric }
521ef573529Seric 
522ef573529Seric void
smtp_closed(void * tag,struct smtp_client * proto)523ef573529Seric smtp_closed(void *tag, struct smtp_client *proto)
524ef573529Seric {
525ef573529Seric 	log_debug("connection closed...");
526ef573529Seric 
527ef573529Seric 	ai = ai->ai_next;
528ef573529Seric 	if (noaction && ai == NULL)
529ef573529Seric 		done = 1;
530ef573529Seric 
531ef573529Seric 	resume();
532ef573529Seric }
533