1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "submission-common.h"
4 #include "str.h"
5 #include "istream.h"
6 #include "istream-concat.h"
7 #include "istream-seekable.h"
8 #include "mail-storage.h"
9 #include "imap-url.h"
10 #include "imap-msgpart.h"
11 #include "imap-msgpart-url.h"
12 #include "imap-urlauth.h"
13 #include "imap-urlauth-fetch.h"
14 
15 #include "submission-recipient.h"
16 #include "submission-commands.h"
17 #include "submission-backend-relay.h"
18 
19 /*
20  * EHLO, HELO commands
21  */
22 
23 static void
submission_helo_reply_add_extra(struct client * client,struct smtp_server_reply * reply)24 submission_helo_reply_add_extra(struct client *client,
25 				struct smtp_server_reply *reply)
26 {
27 	const struct client_extra_capability *cap;
28 
29 	if (!array_is_created(&client->extra_capabilities))
30 		return;
31 
32 	array_foreach(&client->extra_capabilities, cap) {
33 		if (cap->params == NULL) {
34 			smtp_server_reply_ehlo_add(reply, cap->capability);
35 		} else {
36 			smtp_server_reply_ehlo_add_param(reply, cap->capability,
37 							 "%s", cap->params);
38 		}
39 	}
40 }
41 
submission_helo_reply_submit(struct smtp_server_cmd_ctx * cmd,struct smtp_server_cmd_helo * data)42 void submission_helo_reply_submit(struct smtp_server_cmd_ctx *cmd,
43 				  struct smtp_server_cmd_helo *data)
44 {
45 	struct client *client = smtp_server_connection_get_context(cmd->conn);
46 	enum smtp_capability backend_caps = client->backend_capabilities;
47 	struct smtp_server_reply *reply;
48 	uoff_t cap_size;
49 
50 	reply = smtp_server_reply_create_ehlo(cmd->cmd);
51 	if (!data->helo.old_smtp) {
52 		string_t *burl_params = t_str_new(256);
53 
54 		str_append(burl_params, "imap");
55 		if (*client->set->imap_urlauth_host == '\0' ||
56 			strcmp(client->set->imap_urlauth_host,
57 			       URL_HOST_ALLOW_ANY) == 0) {
58 			str_printfa(burl_params, " imap://%s",
59 				    client->set->hostname);
60 		} else {
61 			str_printfa(burl_params, " imap://%s",
62 				    client->set->imap_urlauth_host);
63 		}
64 		if (client->set->imap_urlauth_port != 143) {
65 			str_printfa(burl_params, ":%u",
66 				    client->set->imap_urlauth_port);
67 		}
68 
69 		if ((backend_caps & SMTP_CAPABILITY_8BITMIME) != 0)
70 			smtp_server_reply_ehlo_add(reply, "8BITMIME");
71 		smtp_server_reply_ehlo_add(reply, "AUTH");
72 		if ((backend_caps & SMTP_CAPABILITY_BINARYMIME) != 0 &&
73 		    (backend_caps & SMTP_CAPABILITY_CHUNKING) != 0)
74 			smtp_server_reply_ehlo_add(reply, "BINARYMIME");
75 		smtp_server_reply_ehlo_add_param(reply,
76 			"BURL", "%s", str_c(burl_params));
77 		smtp_server_reply_ehlo_add(reply, "CHUNKING");
78 		if ((backend_caps & SMTP_CAPABILITY_DSN) != 0)
79 			smtp_server_reply_ehlo_add(reply, "DSN");
80 		smtp_server_reply_ehlo_add(reply,
81 			"ENHANCEDSTATUSCODES");
82 		smtp_server_reply_ehlo_add(reply,
83 			"PIPELINING");
84 
85 		cap_size = client_get_max_mail_size(client);
86 		if (cap_size > 0) {
87 			smtp_server_reply_ehlo_add_param(reply,
88 				"SIZE", "%"PRIuUOFF_T, cap_size);
89 		} else {
90 			smtp_server_reply_ehlo_add(reply, "SIZE");
91 		}
92 		if ((backend_caps & SMTP_CAPABILITY_VRFY) != 0)
93 			smtp_server_reply_ehlo_add(reply, "VRFY");
94 
95 		submission_helo_reply_add_extra(client, reply);
96 	}
97 	smtp_server_reply_submit(reply);
98 }
99 
cmd_helo(void * conn_ctx,struct smtp_server_cmd_ctx * cmd,struct smtp_server_cmd_helo * data)100 int cmd_helo(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
101 	     struct smtp_server_cmd_helo *data)
102 {
103 	struct client *client = conn_ctx;
104 
105 	if (!data->first ||
106 	    smtp_server_connection_get_state(client->conn, NULL)
107 		>= SMTP_SERVER_STATE_READY)
108 		return client->v.cmd_helo(client, cmd, data);
109 
110 	/* respond right away */
111 	submission_helo_reply_submit(cmd, data);
112 	return 1;
113 }
114 
client_default_cmd_helo(struct client * client,struct smtp_server_cmd_ctx * cmd,struct smtp_server_cmd_helo * data)115 int client_default_cmd_helo(struct client *client,
116 			    struct smtp_server_cmd_ctx *cmd,
117 			    struct smtp_server_cmd_helo *data)
118 {
119 	return submission_backend_cmd_helo(client->backend_default, cmd, data);
120 }
121 
122 
123 /*
124  * MAIL command
125  */
126 
cmd_mail(void * conn_ctx,struct smtp_server_cmd_ctx * cmd,struct smtp_server_cmd_mail * data)127 int cmd_mail(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
128 	     struct smtp_server_cmd_mail *data)
129 {
130 	struct client *client = conn_ctx;
131 
132 	client->state.backend = client->backend_default;
133 
134 	return client->v.cmd_mail(client, cmd, data);
135 }
136 
client_default_cmd_mail(struct client * client,struct smtp_server_cmd_ctx * cmd,struct smtp_server_cmd_mail * data)137 int client_default_cmd_mail(struct client *client,
138 			    struct smtp_server_cmd_ctx *cmd,
139 			    struct smtp_server_cmd_mail *data)
140 {
141 	if (client->user->anonymous && !client->state.anonymous_allowed) {
142 		/* NOTE: may need to allow anonymous BURL access in the future,
143 		   but while that is not supported, deny all anonymous access
144 		   explicitly. */
145 		smtp_server_reply(cmd, 554, "5.7.1",
146 				  "Access denied (anonymous user)");
147 		return -1;
148 	}
149 
150 	return submission_backend_cmd_mail(client->state.backend, cmd, data);
151 }
152 
153 /*
154  * RCPT command
155  */
156 
cmd_rcpt(void * conn_ctx,struct smtp_server_cmd_ctx * cmd,struct smtp_server_recipient * rcpt)157 int cmd_rcpt(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
158 	     struct smtp_server_recipient *rcpt)
159 {
160 	struct client *client = conn_ctx;
161 	struct submission_recipient *srcpt;
162 
163 	srcpt = submission_recipient_create(client, rcpt);
164 
165 	return client->v.cmd_rcpt(client, cmd, srcpt);
166 }
167 
client_default_cmd_rcpt(struct client * client ATTR_UNUSED,struct smtp_server_cmd_ctx * cmd,struct submission_recipient * srcpt)168 int client_default_cmd_rcpt(struct client *client ATTR_UNUSED,
169 			    struct smtp_server_cmd_ctx *cmd,
170 			    struct submission_recipient *srcpt)
171 {
172 	if (client->user->anonymous && !srcpt->anonymous_allowed) {
173 		/* NOTE: may need to allow anonymous BURL access in the future,
174 		   but while that is not supported, deny all anonymous access
175 		   explicitly. */
176 		smtp_server_recipient_reply(
177 			srcpt->rcpt, 554, "5.7.1",
178 			"Access denied (anonymous user)");
179 		return -1;
180 	}
181 
182 	return submission_backend_cmd_rcpt(srcpt->backend, cmd, srcpt);
183 }
184 
185 /*
186  * RSET command
187  */
188 
cmd_rset(void * conn_ctx,struct smtp_server_cmd_ctx * cmd)189 int cmd_rset(void *conn_ctx, struct smtp_server_cmd_ctx *cmd)
190 {
191 	struct client *client = conn_ctx;
192 
193 	return client->v.cmd_rset(client, cmd);
194 }
195 
client_default_cmd_rset(struct client * client,struct smtp_server_cmd_ctx * cmd)196 int client_default_cmd_rset(struct client *client,
197 			    struct smtp_server_cmd_ctx *cmd)
198 {
199 	struct submission_backend *backend = client->state.backend;
200 
201 	if (backend == NULL)
202 		backend = client->backend_default;
203 
204 	/* all backends will also be notified through trans_free(), but that
205 	   doesn't allow changing the RSET command response. */
206 	return submission_backend_cmd_rset(backend, cmd);
207 }
208 
209 /*
210  * DATA/BDAT commands
211  */
212 
cmd_data_continue(void * conn_ctx,struct smtp_server_cmd_ctx * cmd,struct smtp_server_transaction * trans)213 int cmd_data_continue(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
214 		      struct smtp_server_transaction *trans)
215 {
216 	struct client *client = conn_ctx;
217 	struct istream *data_input = client->state.data_input;
218 	uoff_t data_size;
219 	struct istream *inputs[3];
220 	string_t *added_headers;
221 	const unsigned char *data;
222 	size_t size;
223 	int ret;
224 
225 	while ((ret = i_stream_read_more(data_input, &data, &size)) > 0) {
226 		i_stream_skip(data_input, size);
227 		if (!smtp_server_cmd_data_check_size(cmd))
228 			return -1;
229 	}
230 
231 	if (ret == 0)
232 		return 0;
233 	if (ret < 0 && data_input->stream_errno != 0)
234 		return -1;
235 
236 	/* Done reading DATA stream; remove it from state and continue with
237 	   local variable. */
238 	client->state.data_input = NULL;
239 
240 	/* Current data stream position is the data size */
241 	client->state.data_size = data_input->v_offset;
242 
243 	/* prepend our own headers */
244 	added_headers = t_str_new(200);
245 	smtp_server_transaction_write_trace_record(
246 		added_headers, trans, SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_FINAL);
247 
248 	i_stream_seek(data_input, 0);
249 	inputs[0] = i_stream_create_copy_from_data(
250 		str_data(added_headers), str_len(added_headers));
251 	inputs[1] = data_input;
252 	inputs[2] = NULL;
253 
254 	data_input = i_stream_create_concat(inputs);
255 	i_stream_set_name(data_input, "<submission DATA>");
256 	data_size = client->state.data_size + str_len(added_headers);
257 
258 	i_stream_unref(&inputs[0]);
259 	i_stream_unref(&inputs[1]);
260 
261 	ret = client->v.cmd_data(client, cmd, trans, data_input, data_size);
262 
263 	i_stream_unref(&data_input);
264 	return ret;
265 }
266 
cmd_data_begin(void * conn_ctx,struct smtp_server_cmd_ctx * cmd ATTR_UNUSED,struct smtp_server_transaction * trans ATTR_UNUSED,struct istream * data_input)267 int cmd_data_begin(void *conn_ctx,
268 		   struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
269 		   struct smtp_server_transaction *trans ATTR_UNUSED,
270 		   struct istream *data_input)
271 {
272 	struct client *client = conn_ctx;
273 	struct istream *inputs[2];
274 	string_t *path;
275 
276 	if (client->user->anonymous && !client->state.anonymous_allowed) {
277 		smtp_server_reply(cmd, 554, "5.7.1",
278 				  "Access denied (anonymous user)");
279 		return -1;
280 	}
281 
282 	inputs[0] = data_input;
283 	inputs[1] = NULL;
284 
285 	path = t_str_new(256);
286 	mail_user_set_get_temp_prefix(path, client->user->set);
287 	client->state.data_input = i_stream_create_seekable_path(inputs,
288 		SUBMISSION_MAIL_DATA_MAX_INMEMORY_SIZE, str_c(path));
289 	return 0;
290 }
291 
client_default_cmd_data(struct client * client,struct smtp_server_cmd_ctx * cmd,struct smtp_server_transaction * trans,struct istream * data_input,uoff_t data_size)292 int client_default_cmd_data(struct client *client,
293 			    struct smtp_server_cmd_ctx *cmd,
294 			    struct smtp_server_transaction *trans,
295 			    struct istream *data_input, uoff_t data_size)
296 {
297 	return submission_backends_cmd_data(client, cmd, trans,
298 					    data_input, data_size);
299 }
300 
301 /*
302  * BURL command
303  */
304 
305 /* FIXME: RFC 4468
306    If the  URL argument to BURL refers to binary data, then the submit server
307    MAY refuse the command or down convert as described in Binary SMTP.
308  */
309 
310 struct cmd_burl_context {
311 	struct client *client;
312 	struct smtp_server_cmd_ctx *cmd;
313 
314 	struct imap_urlauth_fetch *urlauth_fetch;
315 	struct imap_msgpart_url *url_fetch;
316 
317 	bool chunk_last:1;
318 };
319 
320 static void
cmd_burl_destroy(struct smtp_server_cmd_ctx * cmd ATTR_UNUSED,struct cmd_burl_context * burl_cmd)321 cmd_burl_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
322 		 struct cmd_burl_context *burl_cmd)
323 {
324 	if (burl_cmd->urlauth_fetch != NULL)
325 		imap_urlauth_fetch_deinit(&burl_cmd->urlauth_fetch);
326 	if (burl_cmd->url_fetch != NULL)
327 		imap_msgpart_url_free(&burl_cmd->url_fetch);
328 }
329 
330 static int
cmd_burl_fetch_cb(struct imap_urlauth_fetch_reply * reply,bool last,void * context)331 cmd_burl_fetch_cb(struct imap_urlauth_fetch_reply *reply,
332 		  bool last, void *context)
333 {
334 	struct cmd_burl_context *burl_cmd = context;
335 	struct smtp_server_cmd_ctx *cmd = burl_cmd->cmd;
336 	int ret;
337 
338 	i_assert(last);
339 
340 	if (reply == NULL) {
341 		/* fatal failure */
342 		// FIXME: make this an internal error
343 		smtp_server_reply(cmd, 554, "5.6.6",
344 			"IMAP URLAUTH resolution failed");
345 		return -1;
346 	}
347 	if (!reply->succeeded) {
348 		/* URL fetch failed */
349 		if (reply->error != NULL) {
350 			smtp_server_reply(cmd, 554, "5.6.6",
351 				"IMAP URLAUTH resolution failed: %s",
352 				reply->error);
353 		} else {
354 			smtp_server_reply(cmd, 554, "5.6.6",
355 				"IMAP URLAUTH resolution failed");
356 		}
357 		return 1;
358 	}
359 
360 	/* URL fetch succeeded */
361 	ret = smtp_server_connection_data_chunk_add(cmd,
362 		reply->input, reply->size, burl_cmd->chunk_last, FALSE);
363 	if (ret < 0)
364 		return -1;
365 
366 	/* Command is likely not yet complete at this point, so return 0 */
367 	return 0;
368 }
369 
370 static int
cmd_burl_fetch_trusted(struct cmd_burl_context * burl_cmd,struct imap_url * imap_url)371 cmd_burl_fetch_trusted(struct cmd_burl_context *burl_cmd,
372 		       struct imap_url *imap_url)
373 {
374 	struct smtp_server_cmd_ctx *cmd = burl_cmd->cmd;
375 	struct client *client = burl_cmd->client;
376 	const char *host_name = client->set->imap_urlauth_host;
377 	in_port_t host_port = client->set->imap_urlauth_port;
378 	struct imap_msgpart_open_result result;
379 	const char *error;
380 
381 	/* validate host */
382 	if (imap_url->host.name == NULL ||
383 		(strcmp(host_name, URL_HOST_ALLOW_ANY) != 0 &&
384 		  strcmp(imap_url->host.name, host_name) != 0)) {
385 		smtp_server_reply(cmd, 554, "5.6.6",
386 			"IMAP URL resolution failed: "
387 			"Inappropriate or missing host name");
388 		return -1;
389 	}
390 
391 	/* validate port */
392 	if ((imap_url->port == 0 && host_port != 143) ||
393 		(imap_url->port != 0 && host_port != imap_url->port)) {
394 		smtp_server_reply(cmd, 554, "5.6.6",
395 			"IMAP URL resolution failed: "
396 			"Inappropriate server port");
397 		return -1;
398 	}
399 
400 	/* retrieve URL */
401 	if (imap_msgpart_url_create
402 		(client->user, imap_url, &burl_cmd->url_fetch, &error) < 0) {
403 		smtp_server_reply(cmd, 554, "5.6.6",
404 			"IMAP URL resolution failed: %s", error);
405 		return -1;
406 	}
407 	if (imap_msgpart_url_read_part(burl_cmd->url_fetch,
408 		&result, &error) <= 0) {
409 		smtp_server_reply(cmd, 554, "5.6.6",
410 			"IMAP URL resolution failed: %s", error);
411 		return -1;
412 	}
413 
414 	return smtp_server_connection_data_chunk_add(cmd,
415 		result.input, result.size, burl_cmd->chunk_last, FALSE);
416 }
417 
418 static int
cmd_burl_fetch(struct cmd_burl_context * burl_cmd,const char * url,struct imap_url * imap_url)419 cmd_burl_fetch(struct cmd_burl_context *burl_cmd, const char *url,
420 	       struct imap_url *imap_url)
421 {
422 	struct smtp_server_cmd_ctx *cmd = burl_cmd->cmd;
423 	struct client *client = burl_cmd->client;
424 
425 	if (client->urlauth_ctx == NULL) {
426 		/* RFC5248, Section 2.4:
427 
428 		   554 5.7.14 Trust relationship required
429 
430 		   The submission server requires a configured trust
431 		   relationship with a third-party server in order to access
432 		   the message content. This value replaces the prior use of
433 		   X.7.8 for this error condition, thereby updating [RFC4468].
434 		 */
435 		smtp_server_reply(cmd, 554, "5.7.14",
436 			"No IMAP URLAUTH access available");
437 		return -1;
438 	}
439 
440 	/* urlauth */
441 	burl_cmd->urlauth_fetch =
442 		imap_urlauth_fetch_init(client->urlauth_ctx,
443 					cmd_burl_fetch_cb, burl_cmd);
444 	if (imap_urlauth_fetch_url_parsed(burl_cmd->urlauth_fetch,
445 		url, imap_url, IMAP_URLAUTH_FETCH_FLAG_BODY) == 0) {
446 		/* wait for URL fetch */
447 		return 0;
448 	}
449 	return 1;
450 }
451 
cmd_burl(struct smtp_server_cmd_ctx * cmd,const char * params)452 void cmd_burl(struct smtp_server_cmd_ctx *cmd, const char *params)
453 {
454 	struct smtp_server_connection *conn = cmd->conn;
455 	struct client *client = smtp_server_connection_get_context(conn);
456 	struct cmd_burl_context *burl_cmd;
457 	const char *const *argv;
458 	enum imap_url_parse_flags url_parse_flags =
459 		IMAP_URL_PARSE_ALLOW_URLAUTH;
460 	struct imap_url *imap_url;
461 	const char *url, *error;
462 	bool chunk_last = FALSE;
463 	int ret = 1;
464 
465 	smtp_server_connection_data_chunk_init(cmd);
466 
467 	/* burl-cmd   = "BURL" SP absolute-URI [SP end-marker] CRLF
468 	   end-marker = "LAST"
469 	 */
470 	argv = t_strsplit(params, " ");
471 	url = argv[0];
472 	if (url == NULL) {
473 		smtp_server_reply(cmd, 501, "5.5.4",
474 			"Missing chunk URL parameter");
475 		ret = -1;
476 	} else if (imap_url_parse(url, NULL, url_parse_flags,
477 				  &imap_url, &error) < 0) {
478 		smtp_server_reply(cmd, 501, "5.5.4",
479 			"Invalid chunk URL: %s", error);
480 		ret = -1;
481 	} else if (argv[1] != NULL) {
482 		if (strcasecmp(argv[1], "LAST") != 0) {
483 			smtp_server_reply(cmd, 501, "5.5.4",
484 				"Invalid end marker parameter");
485 			ret = -1;
486 		} else if (argv[2] != NULL) {
487 			smtp_server_reply(cmd, 501, "5.5.4",
488 				"Invalid parameters");
489 			ret = -1;
490 		} else {
491 			chunk_last = TRUE;
492 		}
493 	}
494 
495 	if (ret < 0 || !smtp_server_connection_data_check_state(cmd))
496 		return;
497 
498 	if (client->user->anonymous) {
499 		smtp_server_reply(cmd, 554, "5.7.1",
500 				  "Access denied (anonymous user)");
501 		return;
502 	}
503 
504 	burl_cmd = p_new(cmd->pool, struct cmd_burl_context, 1);
505 	burl_cmd->client = client;
506 	burl_cmd->cmd = cmd;
507 	burl_cmd->chunk_last = chunk_last;
508 
509 	smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY,
510 				     cmd_burl_destroy, burl_cmd);
511 
512 	if (imap_url->uauth_rumpurl == NULL) {
513 		/* direct local url */
514 		ret = cmd_burl_fetch_trusted(burl_cmd, imap_url);
515 	} else {
516 		ret = cmd_burl_fetch(burl_cmd, url, imap_url);
517 	}
518 
519 	if (ret == 0 && chunk_last)
520 		smtp_server_command_input_lock(cmd);
521 }
522 
523 /*
524  * VRFY command
525  */
526 
cmd_vrfy(void * conn_ctx,struct smtp_server_cmd_ctx * cmd,const char * param)527 int cmd_vrfy(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
528 	     const char *param)
529 {
530 	struct client *client = conn_ctx;
531 
532 	if (client->user->anonymous) {
533 		smtp_server_reply(cmd, 550, "5.7.1",
534 				  "Access denied (anonymous user)");
535 		return -1;
536 	}
537 
538 	return client->v.cmd_vrfy(client, cmd, param);
539 }
540 
client_default_cmd_vrfy(struct client * client,struct smtp_server_cmd_ctx * cmd,const char * param)541 int client_default_cmd_vrfy(struct client *client,
542 			    struct smtp_server_cmd_ctx *cmd, const char *param)
543 {
544 	return submission_backend_cmd_vrfy(client->backend_default, cmd, param);
545 }
546 
547 /*
548  * NOOP command
549  */
550 
cmd_noop(void * conn_ctx,struct smtp_server_cmd_ctx * cmd)551 int cmd_noop(void *conn_ctx, struct smtp_server_cmd_ctx *cmd)
552 {
553 	struct client *client = conn_ctx;
554 
555 	return client->v.cmd_noop(client, cmd);
556 }
557 
client_default_cmd_noop(struct client * client,struct smtp_server_cmd_ctx * cmd)558 int client_default_cmd_noop(struct client *client,
559 			    struct smtp_server_cmd_ctx *cmd)
560 {
561 	return submission_backend_cmd_noop(client->backend_default, cmd);
562 }
563 
564 /*
565  * QUIT command
566  */
567 
568 struct cmd_quit_context {
569 	struct client *client;
570 
571 	struct smtp_server_cmd_ctx *cmd;
572 };
573 
cmd_quit_finish(struct cmd_quit_context * quit_cmd)574 static void cmd_quit_finish(struct cmd_quit_context *quit_cmd)
575 {
576 	struct client *client = quit_cmd->client;
577 	struct smtp_server_cmd_ctx *cmd = quit_cmd->cmd;
578 
579 	timeout_remove(&client->to_quit);
580 	smtp_server_reply_quit(cmd);
581 }
582 
583 static void
cmd_quit_next(struct smtp_server_cmd_ctx * cmd ATTR_UNUSED,struct cmd_quit_context * quit_cmd)584 cmd_quit_next(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
585 	      struct cmd_quit_context *quit_cmd)
586 {
587 	struct client *client = quit_cmd->client;
588 
589 	/* give backend a brief interval to generate a quit reply */
590 	client->to_quit = timeout_add(SUBMISSION_MAX_WAIT_QUIT_REPLY_MSECS,
591 				      cmd_quit_finish, quit_cmd);
592 }
593 
cmd_quit(void * conn_ctx,struct smtp_server_cmd_ctx * cmd)594 int cmd_quit(void *conn_ctx, struct smtp_server_cmd_ctx *cmd)
595 {
596 	struct client *client = conn_ctx;
597 	struct cmd_quit_context *quit_cmd;
598 
599 	quit_cmd = p_new(cmd->pool, struct cmd_quit_context, 1);
600 	quit_cmd->client = client;
601 	quit_cmd->cmd = cmd;
602 
603 	smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_NEXT,
604 				     cmd_quit_next, quit_cmd);
605 
606 	return client->v.cmd_quit(client, cmd);
607 }
608 
client_default_cmd_quit(struct client * client,struct smtp_server_cmd_ctx * cmd)609 int client_default_cmd_quit(struct client *client,
610 			    struct smtp_server_cmd_ctx *cmd)
611 {
612 	return submission_backend_cmd_quit(client->backend_default, cmd);
613 }
614 
615 
616