1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "str.h"
5 #include "array.h"
6 #include "istream.h"
7 #include "ostream.h"
8 #include "smtp-address.h"
9 #include "smtp-reply.h"
10 
11 #include "smtp-server-private.h"
12 
13 /*
14  * Reply
15  */
16 
smtp_server_reply_destroy(struct smtp_server_reply * reply)17 static void smtp_server_reply_destroy(struct smtp_server_reply *reply)
18 {
19 	if (reply->command == NULL)
20 		return;
21 
22 	if (reply->event != NULL) {
23 		e_debug(reply->event, "Destroy");
24 		event_unref(&reply->event);
25 	}
26 
27 	if (reply->content == NULL)
28 		return;
29 	str_free(&reply->content->text);
30 }
31 
smtp_server_reply_clear(struct smtp_server_reply * reply)32 static void smtp_server_reply_clear(struct smtp_server_reply *reply)
33 {
34 	smtp_server_reply_destroy(reply);
35 	if (reply->submitted) {
36 		i_assert(reply->command->replies_submitted > 0);
37 		reply->command->replies_submitted--;
38 	}
39 	reply->submitted = FALSE;
40 	reply->forwarded = FALSE;
41 }
42 
smtp_server_reply_update_event(struct smtp_server_reply * reply)43 static void smtp_server_reply_update_event(struct smtp_server_reply *reply)
44 {
45 	struct smtp_server_command *command = reply->command;
46 
47 	event_add_int(reply->event, "index", reply->index);
48 	event_add_int(reply->event, "status", reply->content->status);
49 
50 	if (command->replies_expected > 1) {
51 		event_set_append_log_prefix(reply->event,
52 			t_strdup_printf("%u reply [%u/%u]: ",
53 					reply->content->status,
54 					reply->index+1,
55 					command->replies_expected));
56 	} else {
57 		event_set_append_log_prefix(reply->event,
58 			t_strdup_printf("%u reply: ",
59 					reply->content->status));
60 	}
61 }
62 
63 static struct smtp_server_reply *
smtp_server_reply_alloc(struct smtp_server_command * cmd,unsigned int index)64 smtp_server_reply_alloc(struct smtp_server_command *cmd, unsigned int index)
65 {
66 	struct smtp_server_reply *reply;
67 	pool_t pool = cmd->context.pool;
68 
69 	if (array_is_created(&cmd->replies)) {
70 		reply = array_idx_modifiable(&cmd->replies, index);
71 		/* get rid of any existing reply */
72 		i_assert(!reply->sent);
73 		smtp_server_reply_clear(reply);
74 	} else {
75 		p_array_init(&cmd->replies, pool, cmd->replies_expected);
76 		array_idx_clear(&cmd->replies, cmd->replies_expected - 1);
77 		reply = array_idx_modifiable(&cmd->replies, index);
78 	}
79 	reply->event = event_create(cmd->context.event);
80 
81 	return reply;
82 }
83 
84 static void
smtp_server_reply_update_prefix(struct smtp_server_reply * reply,unsigned int status,const char * enh_code)85 smtp_server_reply_update_prefix(struct smtp_server_reply *reply,
86 				unsigned int status, const char *enh_code)
87 {
88 	pool_t pool = reply->command->context.pool;
89 	string_t *textbuf, *new_text;
90 	const char *new_prefix, *text, *p;
91 	size_t text_len, prefix_len, line_len;
92 
93 	if (enh_code == NULL || *enh_code == '\0') {
94 		new_prefix = p_strdup_printf(pool, "%03u-", status);
95 	} else {
96 		new_prefix = p_strdup_printf(pool, "%03u-%s ",
97 					     status, enh_code);
98 	}
99 
100 	i_assert(reply->content != NULL);
101 	textbuf = reply->content->text;
102 
103 	if (textbuf == NULL || str_len(textbuf) == 0) {
104 		reply->content->status_prefix = new_prefix;
105 		return;
106 	}
107 	new_text = str_new(default_pool, 256);
108 
109 	prefix_len = strlen(reply->content->status_prefix);
110 	text = str_c(textbuf);
111 	text_len = str_len(textbuf);
112 
113 	i_assert(text_len > prefix_len);
114 	text_len -= prefix_len;
115 	text += prefix_len;
116 
117 	for (;;) {
118 		reply->content->last_line = str_len(new_text);
119 
120 		p = strchr(text, '\n');
121 		i_assert(p != NULL && p > text && *(p-1) == '\r');
122 		p++;
123 
124 		str_append(new_text, new_prefix);
125 		str_append_data(new_text, text, p - text);
126 
127 		line_len = (size_t)(p - text);
128 		i_assert(text_len >= line_len);
129 		text_len -= line_len;
130 		text = p;
131 
132 		if (text_len <= prefix_len)
133 			break;
134 
135 		text_len -= prefix_len;
136 		text += prefix_len;
137 	}
138 
139 	str_free(&textbuf);
140 	reply->content->text = new_text;
141 	reply->content->status_prefix = new_prefix;
142 }
143 
smtp_server_reply_set_status(struct smtp_server_reply * reply,unsigned int status,const char * enh_code)144 void smtp_server_reply_set_status(struct smtp_server_reply *reply,
145 				  unsigned int status, const char *enh_code)
146 {
147 	pool_t pool = reply->command->context.pool;
148 
149 	/* RFC 5321, Section 4.2:
150 
151 	   In the absence of extensions negotiated with the client, SMTP servers
152 	   MUST NOT send reply codes whose first digits are other than 2, 3, 4,
153 	   or 5.  Clients that receive such out-of-range codes SHOULD normally
154 	   treat them as fatal errors and terminate the mail transaction.
155 	 */
156 	i_assert(status >= 200 && status < 560);
157 
158 	/* RFC 2034, Section 4:
159 
160 	   All status codes returned by the server must agree with the primary
161 	   response code, that is, a 2xx response must incorporate a 2.X.X code,
162 	   a 4xx response must incorporate a 4.X.X code, and a 5xx response must
163 	   incorporate a 5.X.X code.
164 	 */
165 	i_assert(enh_code == NULL || *enh_code == '\0' ||
166 		((unsigned int)(enh_code[0] - '0') == (status / 100)
167 			&& enh_code[1] == '.'));
168 
169 	if (reply->content->status == status &&
170 	    null_strcmp(reply->content->enhanced_code, enh_code) == 0)
171 		return;
172 
173 	smtp_server_reply_update_prefix(reply, status, enh_code);
174 	reply->content->status = status;
175 	reply->content->enhanced_code = p_strdup(pool, enh_code);
176 }
177 
smtp_server_reply_get_status(struct smtp_server_reply * reply,const char ** enh_code_r)178 unsigned int smtp_server_reply_get_status(struct smtp_server_reply *reply,
179 					  const char **enh_code_r)
180 {
181 	if (enh_code_r != NULL)
182 		*enh_code_r = reply->content->enhanced_code;
183 	return reply->content->status;
184 }
185 
186 struct smtp_server_reply *
smtp_server_reply_create_index(struct smtp_server_command * cmd,unsigned int index,unsigned int status,const char * enh_code)187 smtp_server_reply_create_index(struct smtp_server_command *cmd,
188 			       unsigned int index, unsigned int status,
189 			       const char *enh_code)
190 {
191 	struct smtp_server_reply *reply;
192 	pool_t pool = cmd->context.pool;
193 
194 	i_assert(cmd->replies_expected > 0);
195 	i_assert(index < cmd->replies_expected);
196 
197 	reply = smtp_server_reply_alloc(cmd, index);
198 	reply->index = index;
199 	reply->command = cmd;
200 
201 	if (reply->content == NULL)
202 		reply->content = p_new(pool, struct smtp_server_reply_content, 1);
203 	smtp_server_reply_set_status(reply, status, enh_code);
204 	reply->content->text = str_new(default_pool, 256);
205 
206 	smtp_server_reply_update_event(reply);
207 
208 	return reply;
209 }
210 
211 struct smtp_server_reply *
smtp_server_reply_create(struct smtp_server_command * cmd,unsigned int status,const char * enh_code)212 smtp_server_reply_create(struct smtp_server_command *cmd,
213 			 unsigned int status, const char *enh_code)
214 {
215 	return smtp_server_reply_create_index(cmd, 0, status, enh_code);
216 }
217 
218 struct smtp_server_reply *
smtp_server_reply_create_forward(struct smtp_server_command * cmd,unsigned int index,const struct smtp_reply * from)219 smtp_server_reply_create_forward(struct smtp_server_command *cmd,
220 	unsigned int index, const struct smtp_reply *from)
221 {
222 	struct smtp_server_reply *reply;
223 	string_t *textbuf;
224 	char *text;
225 	size_t last_line, i;
226 
227 	reply = smtp_server_reply_create_index(cmd, index,
228 		from->status, smtp_reply_get_enh_code(from));
229 	smtp_reply_write(reply->content->text, from);
230 
231 	i_assert(reply->content != NULL);
232 	textbuf = reply->content->text;
233 	text = str_c_modifiable(textbuf);
234 
235 	/* Find the last line */
236 	reply->content->last_line = last_line = 0;
237 	for (i = 0; i < str_len(textbuf); i++) {
238 		if (text[i] == '\n') {
239 			reply->content->last_line = last_line;
240 			last_line = i + 1;
241 		}
242 	}
243 
244 	/* Make this reply suitable for further amendment with
245 	   smtp_server_reply_add_text() */
246 	if ((reply->content->last_line + 3) < str_len(textbuf)) {
247 		i_assert(text[reply->content->last_line + 3] == ' ');
248 		text[reply->content->last_line + 3] = '-';
249 	} else {
250 		str_append_c(textbuf, '-');
251 	}
252 
253 	reply->forwarded = TRUE;
254 
255 	return reply;
256 }
257 
smtp_server_reply_free(struct smtp_server_command * cmd)258 void smtp_server_reply_free(struct smtp_server_command *cmd)
259 {
260 	unsigned int i;
261 
262 	if (!array_is_created(&cmd->replies))
263 		return;
264 
265 	for (i = 0; i < cmd->replies_expected; i++) {
266 		struct smtp_server_reply *reply =
267 			array_idx_modifiable(&cmd->replies, i);
268 		smtp_server_reply_destroy(reply);
269 	}
270 }
271 
smtp_server_reply_add_text(struct smtp_server_reply * reply,const char * text)272 void smtp_server_reply_add_text(struct smtp_server_reply *reply,
273 				const char *text)
274 {
275 	string_t *textbuf = reply->content->text;
276 
277 	i_assert(!reply->submitted);
278 
279 	if (*text == '\0')
280 		return;
281 
282 	do {
283 		const char *p;
284 
285 		reply->content->last_line = str_len(textbuf);
286 
287 		p = strchr(text, '\n');
288 		str_append(textbuf, reply->content->status_prefix);
289 		if (p == NULL) {
290 			str_append(textbuf, text);
291 			text = NULL;
292 		} else {
293 			if (p > text && *(p-1) == '\r')
294 				str_append_data(textbuf, text, p - text - 1);
295 			else
296 				str_append_data(textbuf, text, p - text);
297 			text = p + 1;
298 		}
299 		str_append(textbuf, "\r\n");
300 	} while (text != NULL && *text != '\0');
301 }
302 
303 static size_t
smtp_server_reply_get_path_len(struct smtp_server_reply * reply)304 smtp_server_reply_get_path_len(struct smtp_server_reply *reply)
305 {
306 	size_t prefix_len = strlen(reply->content->status_prefix);
307 	size_t text_len = str_len(reply->content->text), line_len, path_len;
308 	const char *text = str_c(reply->content->text);
309 	const char *text_end = text + text_len, *line_end;
310 
311 	i_assert(prefix_len <= text_len);
312 
313 	line_end = strchr(text, '\r');
314 	if (line_end == NULL) {
315 		line_end = text_end;
316 		line_len = text_len;
317 	} else {
318 		i_assert(line_end + 1 < text_end);
319 		i_assert(*(line_end + 1) == '\n');
320 		line_len = line_end - text;
321 	}
322 
323 	if (prefix_len == line_len || text[prefix_len] != '<') {
324 		path_len = 0;
325 	} else {
326 		const char *path_begin = &text[prefix_len], *path_end;
327 
328 		path_end = strchr(path_begin, '>');
329 		if (path_end == NULL || path_end > line_end)
330 			path_len = 0;
331 		else {
332 			i_assert(path_end < line_end);
333 			path_end++;
334 			path_len = path_end - path_begin;
335 			if (path_end < line_end && *path_end != ' ')
336 				path_len = 0;
337 		}
338 	}
339 
340 	i_assert(prefix_len + path_len <= text_len);
341 	return path_len;
342 }
343 
smtp_server_reply_prepend_text(struct smtp_server_reply * reply,const char * text_prefix)344 void smtp_server_reply_prepend_text(struct smtp_server_reply *reply,
345 				    const char *text_prefix)
346 {
347 	const char *text = str_c(reply->content->text);
348 	size_t tlen = str_len(reply->content->text), offset;
349 
350 	i_assert(!reply->sent);
351 	i_assert(reply->content != NULL);
352 	i_assert(reply->content->text != NULL);
353 
354 	offset = strlen(reply->content->status_prefix) +
355 		smtp_server_reply_get_path_len(reply);
356 	i_assert(offset < tlen);
357 	if (text[offset] == ' ')
358 		offset++;
359 
360 	str_insert(reply->content->text, offset, text_prefix);
361 
362 	if (reply->content->last_line > 0)
363 		reply->content->last_line += strlen(text_prefix);
364 }
365 
smtp_server_reply_replace_path(struct smtp_server_reply * reply,struct smtp_address * path,bool add)366 void smtp_server_reply_replace_path(struct smtp_server_reply *reply,
367 				    struct smtp_address *path, bool add)
368 {
369 	size_t prefix_len, path_len;
370 	const char *path_text;
371 
372 	i_assert(!reply->sent);
373 	i_assert(reply->content != NULL);
374 	i_assert(reply->content->text != NULL);
375 
376 	prefix_len = strlen(reply->content->status_prefix);
377 	path_len = smtp_server_reply_get_path_len(reply);
378 
379 	if (path_len > 0) {
380 		path_text = smtp_address_encode_path(path);
381 		str_replace(reply->content->text, prefix_len, path_len,
382 			    path_text);
383 	} else if (add) {
384 		path_text = t_strdup_printf(
385 			"<%s> ", smtp_address_encode(path));
386 		str_insert(reply->content->text, prefix_len, path_text);
387 	}
388 }
389 
smtp_server_reply_submit(struct smtp_server_reply * reply)390 void smtp_server_reply_submit(struct smtp_server_reply *reply)
391 {
392 	i_assert(!reply->submitted);
393 	i_assert(reply->content != NULL);
394 	i_assert(str_len(reply->content->text) >= 5);
395 	e_debug(reply->event, "Submitted");
396 
397 	reply->command->replies_submitted++;
398 	reply->submitted = TRUE;
399 	smtp_server_command_submit_reply(reply->command);
400 }
401 
smtp_server_reply_submit_duplicate(struct smtp_server_cmd_ctx * _cmd,unsigned int index,unsigned int from_index)402 void smtp_server_reply_submit_duplicate(struct smtp_server_cmd_ctx *_cmd,
403 					unsigned int index,
404 					unsigned int from_index)
405 {
406 	struct smtp_server_command *cmd = _cmd->cmd;
407 	struct smtp_server_reply *reply, *from_reply;
408 
409 	i_assert(cmd->replies_expected > 0);
410 	i_assert(index < cmd->replies_expected);
411 	i_assert(from_index < cmd->replies_expected);
412 	i_assert(array_is_created(&cmd->replies));
413 
414 	from_reply = array_idx_modifiable(&cmd->replies, from_index);
415 	i_assert(from_reply->content != NULL);
416 	i_assert(from_reply->submitted);
417 
418 	reply = smtp_server_reply_alloc(cmd, index);
419 	reply->index = index;
420 	reply->command = cmd;
421 	reply->content = from_reply->content;
422 	smtp_server_reply_update_event(reply);
423 
424 	smtp_server_reply_submit(reply);
425 }
426 
smtp_server_reply_indexv(struct smtp_server_cmd_ctx * _cmd,unsigned int index,unsigned int status,const char * enh_code,const char * fmt,va_list args)427 void smtp_server_reply_indexv(struct smtp_server_cmd_ctx *_cmd,
428 	unsigned int index, unsigned int status, const char *enh_code,
429 	const char *fmt, va_list args)
430 {
431 	struct smtp_server_command *cmd = _cmd->cmd;
432 	struct smtp_server_reply *reply;
433 
434 	reply = smtp_server_reply_create_index(cmd, index, status, enh_code);
435 	smtp_server_reply_add_text(reply, t_strdup_vprintf(fmt, args));
436 	smtp_server_reply_submit(reply);
437 }
438 
smtp_server_reply(struct smtp_server_cmd_ctx * _cmd,unsigned int status,const char * enh_code,const char * fmt,...)439 void smtp_server_reply(struct smtp_server_cmd_ctx *_cmd,
440 	unsigned int status, const char *enh_code, const char *fmt, ...)
441 {
442 	struct smtp_server_command *cmd = _cmd->cmd;
443 	va_list args;
444 
445 	i_assert(cmd->replies_expected <= 1);
446 
447 	va_start(args, fmt);
448 	smtp_server_reply_indexv(_cmd, 0, status, enh_code, fmt, args);
449 	va_end(args);
450 }
451 
smtp_server_reply_index(struct smtp_server_cmd_ctx * _cmd,unsigned int index,unsigned int status,const char * enh_code,const char * fmt,...)452 void smtp_server_reply_index(struct smtp_server_cmd_ctx *_cmd,
453 	unsigned int index, unsigned int status, const char *enh_code,
454 	const char *fmt, ...)
455 {
456 	va_list args;
457 
458 	va_start(args, fmt);
459 	smtp_server_reply_indexv(_cmd, index, status, enh_code, fmt, args);
460 	va_end(args);
461 }
462 
smtp_server_reply_index_forward(struct smtp_server_cmd_ctx * cmd,unsigned int index,const struct smtp_reply * from)463 void smtp_server_reply_index_forward(struct smtp_server_cmd_ctx *cmd,
464 	unsigned int index, const struct smtp_reply *from)
465 {
466 	smtp_server_reply_submit(
467 		smtp_server_reply_create_forward(cmd->cmd, index, from));
468 }
469 
smtp_server_reply_forward(struct smtp_server_cmd_ctx * _cmd,const struct smtp_reply * from)470 void smtp_server_reply_forward(struct smtp_server_cmd_ctx *_cmd,
471 			       const struct smtp_reply *from)
472 {
473 	struct smtp_server_command *cmd = _cmd->cmd;
474 
475 	i_assert(cmd->replies_expected <= 1);
476 
477 	smtp_server_reply_submit(
478 		smtp_server_reply_create_forward(cmd, 0, from));
479 }
480 
481 static void ATTR_FORMAT(4, 0)
smtp_server_reply_allv(struct smtp_server_cmd_ctx * _cmd,unsigned int status,const char * enh_code,const char * fmt,va_list args)482 smtp_server_reply_allv(struct smtp_server_cmd_ctx *_cmd,
483 		       unsigned int status, const char *enh_code,
484 		       const char *fmt, va_list args)
485 {
486 	struct smtp_server_command *cmd = _cmd->cmd;
487 	struct smtp_server_reply *reply;
488 	const char *text;
489 	unsigned int first, i = 0;
490 
491 	/* find the first unsent reply */
492 	if (array_is_created(&cmd->replies)) {
493 		for (; i < cmd->replies_expected; i++) {
494 			struct smtp_server_reply *reply =
495 				array_idx_modifiable(&cmd->replies, i);
496 			if (!reply->sent)
497 				break;
498 		}
499 		i_assert (i < cmd->replies_expected);
500 	}
501 	first = i++;
502 
503 	/* compose the reply text */
504 	text = t_strdup_vprintf(fmt, args);
505 
506 	/* submit the first remaining reply */
507 	reply = smtp_server_reply_create_index(cmd, first, status, enh_code);
508 	smtp_server_reply_add_text(reply, text);
509 	smtp_server_reply_submit(reply);
510 
511 	/* duplicate the rest from it */
512 	for (; i < cmd->replies_expected; i++)
513 		smtp_server_reply_submit_duplicate(_cmd, i, first);
514 }
515 
smtp_server_reply_all(struct smtp_server_cmd_ctx * _cmd,unsigned int status,const char * enh_code,const char * fmt,...)516 void smtp_server_reply_all(struct smtp_server_cmd_ctx *_cmd,
517 			   unsigned int status, const char *enh_code,
518 			   const char *fmt, ...)
519 {
520 	va_list args;
521 
522 	va_start(args, fmt);
523 	smtp_server_reply_allv(_cmd, status, enh_code, fmt, args);
524 	va_end(args);
525 }
526 
smtp_server_reply_early(struct smtp_server_cmd_ctx * _cmd,unsigned int status,const char * enh_code,const char * fmt,...)527 void smtp_server_reply_early(struct smtp_server_cmd_ctx *_cmd,
528 			     unsigned int status, const char *enh_code,
529 			     const char *fmt, ...)
530 {
531 	va_list args;
532 
533 	_cmd->cmd->reply_early = TRUE;
534 
535 	va_start(args, fmt);
536 	smtp_server_reply_allv(_cmd, status, enh_code, fmt, args);
537 	va_end(args);
538 }
539 
smtp_server_reply_quit(struct smtp_server_cmd_ctx * _cmd)540 void smtp_server_reply_quit(struct smtp_server_cmd_ctx *_cmd)
541 {
542 	struct smtp_server_command *cmd = _cmd->cmd;
543 	struct smtp_server_reply *reply;
544 
545 	reply = smtp_server_reply_create(cmd, 221, "2.0.0");
546 	smtp_server_reply_add_text(reply, "Bye");
547 	smtp_server_reply_submit(reply);
548 }
549 
550 static void
smtp_server_reply_write_one_line(const struct smtp_server_reply * reply,string_t * str,bool skip_status)551 smtp_server_reply_write_one_line(const struct smtp_server_reply *reply,
552 				 string_t *str, bool skip_status)
553 {
554 	string_t *textbuf;
555 	const char *text, *p;
556 	size_t text_len, prefix_len, line_len;
557 
558 	i_assert(reply->content != NULL);
559 	textbuf = reply->content->text;
560 	i_assert(str_len(textbuf) > 0);
561 
562 	prefix_len = strlen(reply->content->status_prefix);
563 	text = str_c(textbuf);
564 	text_len = str_len(textbuf);
565 
566 	if (skip_status) {
567 		i_assert(text_len > prefix_len);
568 		text_len -= prefix_len;
569 		text += prefix_len;
570 	}
571 
572 	for (;;) {
573 		p = strchr(text, '\n');
574 		i_assert(p != NULL && p > text && *(p-1) == '\r');
575 		str_append_data(str, text, p - text - 1);
576 		line_len = (size_t)(p - text) + 1;
577 		i_assert(text_len >= line_len);
578 		text_len -= line_len;
579 		text = p + 1;
580 
581 		if (text_len <= prefix_len)
582 			break;
583 
584 		text_len -= prefix_len;
585 		text += prefix_len;
586 		str_append_c(str, ' ');
587 	}
588 }
589 
590 const char *
smtp_server_reply_get_one_line(const struct smtp_server_reply * reply)591 smtp_server_reply_get_one_line(const struct smtp_server_reply *reply)
592 {
593 	string_t *str = t_str_new(256);
594 
595 	smtp_server_reply_write_one_line(reply, str, FALSE);
596 	return str_c(str);
597 }
598 
599 const char *
smtp_server_reply_get_message(const struct smtp_server_reply * reply)600 smtp_server_reply_get_message(const struct smtp_server_reply *reply)
601 {
602 	string_t *str = t_str_new(256);
603 
604 	smtp_server_reply_write_one_line(reply, str, TRUE);
605 	return str_c(str);
606 }
607 
smtp_server_reply_send_real(struct smtp_server_reply * reply)608 static int smtp_server_reply_send_real(struct smtp_server_reply *reply)
609 {
610 	struct smtp_server_command *cmd = reply->command;
611 	struct smtp_server_connection *conn = cmd->context.conn;
612 	struct ostream *output = conn->conn.output;
613 	string_t *textbuf;
614 	char *text;
615 	int ret = 0;
616 
617 	i_assert(reply->content != NULL);
618 	textbuf = reply->content->text;
619 	i_assert(str_len(textbuf) > 0);
620 
621 	/* substitute '-' with ' ' in last line */
622 	text = str_c_modifiable(textbuf);
623 	text = text + reply->content->last_line + 3;
624 	if (text[0] != ' ') {
625 		i_assert(text[0] == '-');
626 		text[0] = ' ';
627 	}
628 
629 	if (o_stream_send(output, str_data(textbuf), str_len(textbuf)) < 0) {
630 		e_debug(reply->event, "Send failed: %s",
631 			o_stream_get_disconnect_reason(output));
632 		smtp_server_connection_handle_output_error(conn);
633 		return -1;
634 	}
635 
636 	e_debug(reply->event, "Sent: %s",
637 		smtp_server_reply_get_one_line(reply));
638 	return ret;
639 }
640 
smtp_server_reply_send(struct smtp_server_reply * reply)641 int smtp_server_reply_send(struct smtp_server_reply *reply)
642 {
643 	int ret;
644 
645 	if (reply->sent)
646 		return 0;
647 
648 	T_BEGIN {
649 		ret = smtp_server_reply_send_real(reply);
650 	} T_END;
651 
652 	reply->sent = TRUE;
653 	return ret;
654 }
655 
smtp_server_reply_is_success(const struct smtp_server_reply * reply)656 bool smtp_server_reply_is_success(const struct smtp_server_reply *reply)
657 {
658 	i_assert(reply->content != NULL);
659 	return (reply->content->status / 100 == 2);
660 }
661 
smtp_server_reply_add_to_event(const struct smtp_server_reply * reply,struct event_passthrough * e)662 void smtp_server_reply_add_to_event(const struct smtp_server_reply *reply,
663 				    struct event_passthrough *e)
664 {
665 	i_assert(reply->content != NULL);
666 	e->add_int("status_code", reply->content->status);
667 	if (reply->content->enhanced_code != NULL &&
668 	    reply->content->enhanced_code[0] != '\0')
669 		e->add_str("enhanced_code", reply->content->enhanced_code);
670 	if (!smtp_server_reply_is_success(reply))
671 		e->add_str("error", smtp_server_reply_get_message(reply));
672 }
673 
674 /*
675  * EHLO reply
676  */
677 
678 struct smtp_server_reply *
smtp_server_reply_create_ehlo(struct smtp_server_command * cmd)679 smtp_server_reply_create_ehlo(struct smtp_server_command *cmd)
680 {
681 	struct smtp_server_connection *conn = cmd->context.conn;
682 	struct smtp_server_reply *reply;
683 	string_t *textbuf;
684 
685 	reply = smtp_server_reply_create(cmd, 250, "");
686 	textbuf = reply->content->text;
687 	str_append(textbuf, reply->content->status_prefix);
688 	str_append(textbuf, conn->set.hostname);
689 	str_append(textbuf, "\r\n");
690 
691 	return reply;
692 }
693 
smtp_server_reply_ehlo_add(struct smtp_server_reply * reply,const char * keyword)694 void smtp_server_reply_ehlo_add(struct smtp_server_reply *reply,
695 				const char *keyword)
696 {
697 	string_t *textbuf;
698 
699 	i_assert(!reply->submitted);
700 	i_assert(reply->content != NULL);
701 	textbuf = reply->content->text;
702 
703 	reply->content->last_line = str_len(textbuf);
704 	str_append(textbuf, reply->content->status_prefix);
705 	str_append(textbuf, keyword);
706 	str_append(textbuf, "\r\n");
707 }
708 
smtp_server_reply_ehlo_add_param(struct smtp_server_reply * reply,const char * keyword,const char * param_fmt,...)709 void smtp_server_reply_ehlo_add_param(struct smtp_server_reply *reply,
710 	const char *keyword, const char *param_fmt, ...)
711 {
712 	va_list args;
713 	string_t *textbuf;
714 
715 	i_assert(!reply->submitted);
716 	i_assert(reply->content != NULL);
717 	textbuf = reply->content->text;
718 
719 	reply->content->last_line = str_len(textbuf);
720 	str_append(textbuf, reply->content->status_prefix);
721 	str_append(textbuf, keyword);
722 	if (*param_fmt != '\0') {
723 		va_start(args, param_fmt);
724 		str_append_c(textbuf, ' ');
725 		str_vprintfa(textbuf, param_fmt, args);
726 		va_end(args);
727 	}
728 	str_append(textbuf, "\r\n");
729 }
730 
smtp_server_reply_ehlo_add_params(struct smtp_server_reply * reply,const char * keyword,const char * const * params)731 void smtp_server_reply_ehlo_add_params(struct smtp_server_reply *reply,
732 				       const char *keyword,
733 				       const char *const *params)
734 {
735 	string_t *textbuf;
736 
737 	i_assert(!reply->submitted);
738 	i_assert(reply->content != NULL);
739 	textbuf = reply->content->text;
740 
741 	reply->content->last_line = str_len(textbuf);
742 	str_append(textbuf, reply->content->status_prefix);
743 	str_append(textbuf, keyword);
744 	if (params != NULL) {
745 		while (*params != NULL) {
746 			str_append_c(textbuf, ' ');
747 			str_append(textbuf, *params);
748 			params++;
749 		}
750 	}
751 	str_append(textbuf, "\r\n");
752 }
753 
smtp_server_reply_ehlo_add_8bitmime(struct smtp_server_reply * reply)754 void smtp_server_reply_ehlo_add_8bitmime(struct smtp_server_reply *reply)
755 {
756 	struct smtp_server_cmd_ctx *cmd = &reply->command->context;
757 	struct smtp_server_connection *conn = cmd->conn;
758 	enum smtp_capability caps = conn->set.capabilities;
759 
760 	if ((caps & SMTP_CAPABILITY_8BITMIME) == 0)
761 		return;
762 	smtp_server_reply_ehlo_add(reply, "8BITMIME");
763 }
764 
smtp_server_reply_ehlo_add_binarymime(struct smtp_server_reply * reply)765 void smtp_server_reply_ehlo_add_binarymime(struct smtp_server_reply *reply)
766 {
767 	struct smtp_server_cmd_ctx *cmd = &reply->command->context;
768 	struct smtp_server_connection *conn = cmd->conn;
769 	enum smtp_capability caps = conn->set.capabilities;
770 
771 	if ((caps & SMTP_CAPABILITY_BINARYMIME) == 0 ||
772 	    (caps & SMTP_CAPABILITY_CHUNKING) == 0)
773 		return;
774 	smtp_server_reply_ehlo_add(reply, "BINARYMIME");
775 }
776 
smtp_server_reply_ehlo_add_chunking(struct smtp_server_reply * reply)777 void smtp_server_reply_ehlo_add_chunking(struct smtp_server_reply *reply)
778 {
779 	struct smtp_server_cmd_ctx *cmd = &reply->command->context;
780 	struct smtp_server_connection *conn = cmd->conn;
781 	enum smtp_capability caps = conn->set.capabilities;
782 
783 	if ((caps & SMTP_CAPABILITY_CHUNKING) == 0)
784 		return;
785 	smtp_server_reply_ehlo_add(reply, "CHUNKING");
786 }
787 
smtp_server_reply_ehlo_add_dsn(struct smtp_server_reply * reply)788 void smtp_server_reply_ehlo_add_dsn(struct smtp_server_reply *reply)
789 {
790 	struct smtp_server_cmd_ctx *cmd = &reply->command->context;
791 	struct smtp_server_connection *conn = cmd->conn;
792 	enum smtp_capability caps = conn->set.capabilities;
793 
794 	if ((caps & SMTP_CAPABILITY_DSN) == 0)
795 		return;
796 	smtp_server_reply_ehlo_add(reply, "DSN");
797 }
798 
smtp_server_reply_ehlo_add_enhancedstatuscodes(struct smtp_server_reply * reply)799 void smtp_server_reply_ehlo_add_enhancedstatuscodes(
800 	struct smtp_server_reply *reply)
801 {
802 	struct smtp_server_cmd_ctx *cmd = &reply->command->context;
803 	struct smtp_server_connection *conn = cmd->conn;
804 	enum smtp_capability caps = conn->set.capabilities;
805 
806 	if ((caps & SMTP_CAPABILITY_ENHANCEDSTATUSCODES) == 0)
807 		return;
808 	smtp_server_reply_ehlo_add(reply, "ENHANCEDSTATUSCODES");
809 }
810 
smtp_server_reply_ehlo_add_pipelining(struct smtp_server_reply * reply)811 void smtp_server_reply_ehlo_add_pipelining(struct smtp_server_reply *reply)
812 {
813 	smtp_server_reply_ehlo_add(reply, "PIPELINING");
814 }
815 
smtp_server_reply_ehlo_add_size(struct smtp_server_reply * reply)816 void smtp_server_reply_ehlo_add_size(struct smtp_server_reply *reply)
817 {
818 	struct smtp_server_cmd_ctx *cmd = &reply->command->context;
819 	struct smtp_server_connection *conn = cmd->conn;
820 	enum smtp_capability caps = conn->set.capabilities;
821 	uoff_t cap_size = conn->set.max_message_size;
822 
823 	if ((caps & SMTP_CAPABILITY_SIZE) == 0)
824 		return;
825 
826 	if (cap_size > 0 && cap_size != UOFF_T_MAX) {
827 		smtp_server_reply_ehlo_add_param(reply,
828 			"SIZE", "%"PRIuUOFF_T, cap_size);
829 	} else {
830 		smtp_server_reply_ehlo_add(reply, "SIZE");
831 	}
832 }
833 
smtp_server_reply_ehlo_add_starttls(struct smtp_server_reply * reply)834 void smtp_server_reply_ehlo_add_starttls(struct smtp_server_reply *reply)
835 {
836 	struct smtp_server_cmd_ctx *cmd = &reply->command->context;
837 	struct smtp_server_connection *conn = cmd->conn;
838 	enum smtp_capability caps = conn->set.capabilities;
839 
840 	if ((caps & SMTP_CAPABILITY_STARTTLS) == 0)
841 		return;
842 	smtp_server_reply_ehlo_add(reply, "STARTTLS");
843 }
844 
smtp_server_reply_ehlo_add_vrfy(struct smtp_server_reply * reply)845 void smtp_server_reply_ehlo_add_vrfy(struct smtp_server_reply *reply)
846 {
847 	struct smtp_server_cmd_ctx *cmd = &reply->command->context;
848 	struct smtp_server_connection *conn = cmd->conn;
849 	enum smtp_capability caps = conn->set.capabilities;
850 
851 	if ((caps & SMTP_CAPABILITY_VRFY) == 0)
852 		return;
853 	smtp_server_reply_ehlo_add(reply, "VRFY");
854 }
855 
smtp_server_reply_ehlo_add_xclient(struct smtp_server_reply * reply)856 void smtp_server_reply_ehlo_add_xclient(struct smtp_server_reply *reply)
857 {
858 	static const char *base_fields =
859 		"ADDR PORT PROTO HELO LOGIN TTL TIMEOUT";
860 	struct smtp_server_cmd_ctx *cmd = &reply->command->context;
861 	struct smtp_server_connection *conn = cmd->conn;
862 
863 	if (!smtp_server_connection_is_trusted(conn))
864 		return;
865 	if (conn->set.xclient_extensions == NULL ||
866 	    *conn->set.xclient_extensions == NULL) {
867 		smtp_server_reply_ehlo_add_param(reply, "XCLIENT", "%s",
868 			base_fields);
869 		return;
870 	}
871 
872 	smtp_server_reply_ehlo_add_param(reply, "XCLIENT", "%s",
873 		t_strconcat(base_fields, " ",
874 			t_strarray_join(conn->set.xclient_extensions, " "),
875 			NULL));
876 }
877