1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "buffer.h"
5 #include "array.h"
6 #include "str.h"
7 #include "str-sanitize.h"
8 #include "llist.h"
9 #include "istream.h"
10 #include "ostream.h"
11 #include "ostream-dot.h"
12 #include "smtp-common.h"
13 #include "smtp-syntax.h"
14 #include "smtp-params.h"
15 #include "smtp-client-private.h"
16
17 static const char *
smtp_client_command_get_name(struct smtp_client_command * cmd)18 smtp_client_command_get_name(struct smtp_client_command *cmd)
19 {
20 const unsigned char *p, *pend;
21
22 if (cmd->name != NULL)
23 return cmd->name;
24
25 if (cmd->plug)
26 return NULL;
27 if (cmd->data == NULL || cmd->data->used == 0)
28 return NULL;
29
30 p = cmd->data->data;
31 pend = p + cmd->data->used;
32 for (;p < pend; p++) {
33 if (*p == ' ' || *p == '\r' || *p == '\n')
34 break;
35 }
36 cmd->name = p_strdup(cmd->pool,
37 t_str_ucase(t_strdup_until(cmd->data->data, p)));
38 return cmd->name;
39 }
40
41 static const char *
smtp_client_command_get_label(struct smtp_client_command * cmd)42 smtp_client_command_get_label(struct smtp_client_command *cmd)
43 {
44 if (cmd->plug)
45 return "[plug]";
46 if (cmd->data == NULL || cmd->data->used == 0) {
47 if (!cmd->has_stream)
48 return "[empty]";
49 return "[data]";
50 }
51 return smtp_client_command_get_name(cmd);
52 }
53
54 static void
smtp_client_command_update_event(struct smtp_client_command * cmd)55 smtp_client_command_update_event(struct smtp_client_command *cmd)
56 {
57 event_add_str(cmd->event, "cmd_name", smtp_client_command_get_name(cmd));
58 event_set_append_log_prefix(
59 cmd->event,
60 t_strdup_printf("command %s: ",
61 str_sanitize(smtp_client_command_get_label(cmd), 128)));
62 }
63
64 static struct smtp_client_command *
smtp_client_command_create(struct smtp_client_connection * conn,enum smtp_client_command_flags flags,smtp_client_command_callback_t * callback,void * context)65 smtp_client_command_create(struct smtp_client_connection *conn,
66 enum smtp_client_command_flags flags,
67 smtp_client_command_callback_t *callback,
68 void *context)
69 {
70 struct smtp_client_command *cmd;
71 pool_t pool;
72
73 pool = pool_alloconly_create("smtp client command", 2048);
74 cmd = p_new(pool, struct smtp_client_command, 1);
75 cmd->pool = pool;
76 cmd->refcount = 1;
77 cmd->conn = conn;
78 cmd->flags = flags;
79 cmd->replies_expected = 1;
80 cmd->callback = callback;
81 cmd->context = context;
82 cmd->event = event_create(conn->event);
83 smtp_client_command_update_event(cmd);
84 return cmd;
85 }
86
87 #undef smtp_client_command_new
88 struct smtp_client_command *
smtp_client_command_new(struct smtp_client_connection * conn,enum smtp_client_command_flags flags,smtp_client_command_callback_t * callback,void * context)89 smtp_client_command_new(struct smtp_client_connection *conn,
90 enum smtp_client_command_flags flags,
91 smtp_client_command_callback_t *callback,
92 void *context)
93 {
94 i_assert(callback != NULL);
95 return smtp_client_command_create(conn, flags, callback, context);
96 }
97
98 struct smtp_client_command *
smtp_client_command_plug(struct smtp_client_connection * conn,struct smtp_client_command * after)99 smtp_client_command_plug(struct smtp_client_connection *conn,
100 struct smtp_client_command *after)
101 {
102 struct smtp_client_command *cmd;
103
104 cmd = smtp_client_command_create(conn, 0, NULL, NULL);
105 cmd->plug = TRUE;
106 smtp_client_command_submit_after(cmd, after);
107 return cmd;
108 }
109
smtp_client_command_ref(struct smtp_client_command * cmd)110 void smtp_client_command_ref(struct smtp_client_command *cmd)
111 {
112 cmd->refcount++;
113 }
114
smtp_client_command_unref(struct smtp_client_command ** _cmd)115 bool smtp_client_command_unref(struct smtp_client_command **_cmd)
116 {
117 struct smtp_client_command *cmd = *_cmd;
118
119 *_cmd = NULL;
120
121 if (cmd == NULL)
122 return FALSE;
123
124 struct smtp_client_connection *conn = cmd->conn;
125
126 i_assert(cmd->refcount > 0);
127 if (--cmd->refcount > 0)
128 return TRUE;
129
130 e_debug(cmd->event, "Destroy (%u commands pending, %u commands queued)",
131 conn->cmd_wait_list_count, conn->cmd_send_queue_count);
132
133 i_assert(cmd->state >= SMTP_CLIENT_COMMAND_STATE_FINISHED);
134 i_assert(cmd != conn->cmd_streaming);
135
136 i_stream_unref(&cmd->stream);
137 event_unref(&cmd->event);
138 pool_unref(&cmd->pool);
139
140 return FALSE;
141 }
142
smtp_client_command_name_equals(struct smtp_client_command * cmd,const char * name)143 bool smtp_client_command_name_equals(struct smtp_client_command *cmd,
144 const char *name)
145 {
146 const unsigned char *data;
147 size_t name_len, data_len;
148
149 if (cmd->data == NULL)
150 return FALSE;
151
152 name_len = strlen(name);
153 data = cmd->data->data;
154 data_len = cmd->data->used;
155
156 if (data_len < name_len ||
157 i_memcasecmp(data, name, name_len) != 0)
158 return FALSE;
159 return (data_len == name_len ||
160 data[name_len] == ' ' || data[name_len] == '\r');
161 }
162
smtp_client_command_lock(struct smtp_client_command * cmd)163 void smtp_client_command_lock(struct smtp_client_command *cmd)
164 {
165 if (cmd->plug)
166 return;
167 cmd->locked = TRUE;
168 }
169
smtp_client_command_unlock(struct smtp_client_command * cmd)170 void smtp_client_command_unlock(struct smtp_client_command *cmd)
171 {
172 if (cmd->plug)
173 return;
174 if (cmd->locked) {
175 cmd->locked = FALSE;
176 if (!cmd->conn->corked)
177 smtp_client_connection_trigger_output(cmd->conn);
178 }
179 }
180
smtp_client_command_abort(struct smtp_client_command ** _cmd)181 void smtp_client_command_abort(struct smtp_client_command **_cmd)
182 {
183 struct smtp_client_command *cmd = *_cmd;
184 struct smtp_client_connection *conn = cmd->conn;
185 enum smtp_client_command_state state = cmd->state;
186 bool disconnected =
187 (conn->state == SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED);
188 bool was_locked =
189 (state >= SMTP_CLIENT_COMMAND_STATE_SUBMITTED) &&
190 (cmd->locked || cmd->plug);
191 bool was_sent =
192 (!disconnected && state > SMTP_CLIENT_COMMAND_STATE_SUBMITTED &&
193 state < SMTP_CLIENT_COMMAND_STATE_FINISHED);
194
195 *_cmd = NULL;
196
197 smtp_client_command_drop_callback(cmd);
198
199 if ((!disconnected && !cmd->plug && cmd->aborting) ||
200 state >= SMTP_CLIENT_COMMAND_STATE_FINISHED)
201 return;
202
203 struct event_passthrough *e = event_create_passthrough(cmd->event);
204 if (!cmd->event_finished) {
205 struct smtp_reply failure;
206
207 smtp_reply_init(&failure,
208 SMTP_CLIENT_COMMAND_ERROR_ABORTED,
209 "Aborted");
210 failure.enhanced_code = SMTP_REPLY_ENH_CODE(9, 0, 0);
211
212 e->set_name("smtp_client_command_finished");
213 smtp_reply_add_to_event(&failure, e);
214 cmd->event_finished = TRUE;
215 }
216 e_debug(e->event(), "Aborted%s",
217 (was_sent ? " (already sent)" : ""));
218
219 if (!was_sent) {
220 cmd->state = SMTP_CLIENT_COMMAND_STATE_ABORTED;
221 } else {
222 i_assert(state < SMTP_CLIENT_COMMAND_STATE_FINISHED);
223 cmd->aborting = TRUE;
224 }
225 cmd->locked = FALSE;
226
227 i_assert(!cmd->plug || state <= SMTP_CLIENT_COMMAND_STATE_SUBMITTED);
228
229 switch (state) {
230 case SMTP_CLIENT_COMMAND_STATE_NEW:
231 if (cmd->delaying_failure) {
232 DLLIST_REMOVE(&conn->cmd_fail_list, cmd);
233 if (conn->cmd_fail_list == NULL)
234 timeout_remove(&conn->to_cmd_fail);
235 }
236 break;
237 case SMTP_CLIENT_COMMAND_STATE_SENDING:
238 if (!disconnected) {
239 /* it is being sent; cannot truly abort it now */
240 break;
241 }
242 /* fall through */
243 case SMTP_CLIENT_COMMAND_STATE_SUBMITTED:
244 /* not yet sent */
245 e_debug(cmd->event, "Removed from send queue");
246 i_assert(conn->cmd_send_queue_count > 0);
247 DLLIST2_REMOVE(&conn->cmd_send_queue_head,
248 &conn->cmd_send_queue_tail, cmd);
249 i_assert(conn->cmd_send_queue_count > 1 ||
250 (cmd->prev == NULL && cmd->next == NULL));
251 conn->cmd_send_queue_count--;
252 break;
253 case SMTP_CLIENT_COMMAND_STATE_WAITING:
254 if (!disconnected) {
255 /* we're expecting a reply; cannot truly abort it now */
256 break;
257 }
258 e_debug(cmd->event, "Removed from wait list");
259 i_assert(conn->cmd_wait_list_count > 0);
260 DLLIST2_REMOVE(&conn->cmd_wait_list_head,
261 &conn->cmd_wait_list_tail, cmd);
262 conn->cmd_wait_list_count--;
263 break;
264 default:
265 i_unreached();
266 }
267
268 if (cmd->abort_callback != NULL) {
269 cmd->abort_callback(cmd->abort_context);
270 cmd->abort_callback = NULL;
271 }
272
273 if (disconnected || cmd->plug ||
274 state <= SMTP_CLIENT_COMMAND_STATE_SUBMITTED) {
275 /* can only destroy it when it is not pending */
276 smtp_client_command_unref(&cmd);
277 }
278
279 if (!disconnected && was_locked && !conn->corked)
280 smtp_client_connection_trigger_output(conn);
281 }
282
smtp_client_command_drop_callback(struct smtp_client_command * cmd)283 void smtp_client_command_drop_callback(struct smtp_client_command *cmd)
284 {
285 cmd->callback = NULL;
286 cmd->context = NULL;
287 }
288
smtp_client_command_fail_reply(struct smtp_client_command ** _cmd,const struct smtp_reply * reply)289 void smtp_client_command_fail_reply(struct smtp_client_command **_cmd,
290 const struct smtp_reply *reply)
291 {
292 struct smtp_client_command *cmd = *_cmd, *tmp_cmd;
293 struct smtp_client_connection *conn = cmd->conn;
294 enum smtp_client_command_state state = cmd->state;
295 smtp_client_command_callback_t *callback = cmd->callback;
296
297 *_cmd = NULL;
298
299 if (state >= SMTP_CLIENT_COMMAND_STATE_FINISHED)
300 return;
301
302 if (cmd->delay_failure) {
303 i_assert(cmd->delayed_failure == NULL);
304 i_assert(state < SMTP_CLIENT_COMMAND_STATE_SUBMITTED);
305
306 e_debug(cmd->event, "Fail (delay)");
307
308 cmd->delayed_failure = smtp_reply_clone(cmd->pool, reply);
309 cmd->delaying_failure = TRUE;
310 if (conn->to_cmd_fail == NULL) {
311 conn->to_cmd_fail = timeout_add_short(0,
312 smtp_client_commands_fail_delayed, conn);
313 }
314 DLLIST_PREPEND(&conn->cmd_fail_list, cmd);
315 return;
316 }
317
318 cmd->callback = NULL;
319
320 smtp_client_connection_ref(conn);
321 smtp_client_command_ref(cmd);
322
323 if (!cmd->aborting) {
324 cmd->failed = TRUE;
325
326 struct event_passthrough *e =
327 event_create_passthrough(cmd->event);
328 if (!cmd->event_finished) {
329 e->set_name("smtp_client_command_finished");
330 smtp_reply_add_to_event(reply, e);
331 cmd->event_finished = TRUE;
332 }
333 e_debug(e->event(), "Failed: %s", smtp_reply_log(reply));
334
335 if (callback != NULL)
336 (void)callback(reply, cmd->context);
337 }
338
339 tmp_cmd = cmd;
340 smtp_client_command_abort(&tmp_cmd);
341
342 smtp_client_command_unref(&cmd);
343 smtp_client_connection_unref(&conn);
344 }
345
smtp_client_command_fail(struct smtp_client_command ** _cmd,unsigned int status,const char * error)346 void smtp_client_command_fail(struct smtp_client_command **_cmd,
347 unsigned int status, const char *error)
348 {
349 struct smtp_reply reply;
350 const char *text_lines[] = {error, NULL};
351
352 i_zero(&reply);
353 reply.status = status;
354 reply.text_lines = text_lines;
355 reply.enhanced_code.x = 9;
356
357 smtp_client_command_fail_reply(_cmd, &reply);
358 }
359
360 static void
smtp_client_command_fail_delayed(struct smtp_client_command ** _cmd)361 smtp_client_command_fail_delayed(struct smtp_client_command **_cmd)
362 {
363 struct smtp_client_command *cmd = *_cmd;
364
365 e_debug(cmd->event, "Fail delayed");
366
367 i_assert(!cmd->delay_failure);
368 i_assert(cmd->state < SMTP_CLIENT_COMMAND_STATE_FINISHED);
369 smtp_client_command_fail_reply(_cmd, cmd->delayed_failure);
370 }
371
smtp_client_commands_list_abort(struct smtp_client_command * cmds_list,unsigned int cmds_list_count)372 void smtp_client_commands_list_abort(struct smtp_client_command *cmds_list,
373 unsigned int cmds_list_count)
374 {
375 struct smtp_client_command *cmd;
376 ARRAY(struct smtp_client_command *) cmds_arr;
377 struct smtp_client_command **cmds;
378 unsigned int count, i;
379
380 if (cmds_list == NULL)
381 return;
382 i_assert(cmds_list_count > 0);
383
384 /* copy the array and reference the commands to be robust against more
385 than one command disappearing from the list */
386 t_array_init(&cmds_arr, cmds_list_count);
387 for (cmd = cmds_list; cmd != NULL; cmd = cmd->next) {
388 smtp_client_command_ref(cmd);
389 array_push_back(&cmds_arr, &cmd);
390 }
391
392 cmds = array_get_modifiable(&cmds_arr, &count);
393 for (i = 0; i < count; i++) {
394 cmd = cmds[i];
395 /* fail the reply */
396 smtp_client_command_abort(&cmds[i]);
397 /* drop our reference */
398 smtp_client_command_unref(&cmd);
399 }
400 }
401
smtp_client_commands_list_fail_reply(struct smtp_client_command * cmds_list,unsigned int cmds_list_count,const struct smtp_reply * reply)402 void smtp_client_commands_list_fail_reply(struct smtp_client_command *cmds_list,
403 unsigned int cmds_list_count,
404 const struct smtp_reply *reply)
405 {
406 struct smtp_client_command *cmd;
407 ARRAY(struct smtp_client_command *) cmds_arr;
408 struct smtp_client_command **cmds;
409 unsigned int count, i;
410
411 if (cmds_list == NULL)
412 return;
413 i_assert(cmds_list_count > 0);
414
415 /* copy the array and reference the commands to be robust against more
416 than one command disappearing from the list */
417 t_array_init(&cmds_arr, cmds_list_count);
418 for (cmd = cmds_list; cmd != NULL; cmd = cmd->next) {
419 smtp_client_command_ref(cmd);
420 array_push_back(&cmds_arr, &cmd);
421 }
422
423 cmds = array_get_modifiable(&cmds_arr, &count);
424 for (i = 0; i < count; i++) {
425 cmd = cmds[i];
426 /* fail the reply */
427 smtp_client_command_fail_reply(&cmds[i], reply);
428 /* drop our reference */
429 smtp_client_command_unref(&cmd);
430 }
431 }
432
smtp_client_commands_abort_delayed(struct smtp_client_connection * conn)433 void smtp_client_commands_abort_delayed(struct smtp_client_connection *conn)
434 {
435 struct smtp_client_command *cmd;
436
437 timeout_remove(&conn->to_cmd_fail);
438
439 cmd = conn->cmd_fail_list;
440 conn->cmd_fail_list = NULL;
441 while (cmd != NULL) {
442 struct smtp_client_command *cmd_next = cmd->next;
443
444 cmd->delaying_failure = FALSE;
445 smtp_client_command_abort(&cmd);
446 cmd = cmd_next;
447 }
448 }
449
smtp_client_commands_fail_delayed(struct smtp_client_connection * conn)450 void smtp_client_commands_fail_delayed(struct smtp_client_connection *conn)
451 {
452 struct smtp_client_command *cmd;
453
454 timeout_remove(&conn->to_cmd_fail);
455
456 cmd = conn->cmd_fail_list;
457 conn->cmd_fail_list = NULL;
458 while (cmd != NULL) {
459 struct smtp_client_command *cmd_next = cmd->next;
460
461 cmd->delaying_failure = FALSE;
462 smtp_client_command_fail_delayed(&cmd);
463 cmd = cmd_next;
464 }
465 }
466
smtp_client_command_set_abort_callback(struct smtp_client_command * cmd,void (* callback)(void * context),void * context)467 void smtp_client_command_set_abort_callback(struct smtp_client_command *cmd,
468 void (*callback)(void *context),
469 void *context)
470 {
471 cmd->abort_callback = callback;
472 cmd->abort_context = context;
473 }
474
smtp_client_command_set_sent_callback(struct smtp_client_command * cmd,void (* callback)(void * context),void * context)475 void smtp_client_command_set_sent_callback(struct smtp_client_command *cmd,
476 void (*callback)(void *context),
477 void *context)
478 {
479 cmd->sent_callback = callback;
480 cmd->sent_context = context;
481 }
482
smtp_client_command_set_replies(struct smtp_client_command * cmd,unsigned int replies)483 void smtp_client_command_set_replies(struct smtp_client_command *cmd,
484 unsigned int replies)
485 {
486 i_assert(cmd->replies_expected == 1 ||
487 cmd->replies_expected == replies);
488 i_assert(replies > 0);
489 i_assert(cmd->replies_seen <= 1);
490 cmd->replies_expected = replies;
491 }
492
smtp_client_command_sent(struct smtp_client_command * cmd)493 static void smtp_client_command_sent(struct smtp_client_command *cmd)
494 {
495 struct event_passthrough *e;
496
497 e = event_create_passthrough(cmd->event)->
498 set_name("smtp_client_command_sent");
499
500 if (cmd->data == NULL)
501 e_debug(e->event(), "Sent");
502 else {
503 i_assert(str_len(cmd->data) > 2);
504 str_truncate(cmd->data, str_len(cmd->data)-2);
505 e_debug(e->event(), "Sent: %s", str_c(cmd->data));
506 }
507
508 if (smtp_client_command_name_equals(cmd, "QUIT"))
509 cmd->conn->sent_quit = TRUE;
510
511 if (cmd->sent_callback != NULL) {
512 cmd->sent_callback(cmd->sent_context);
513 cmd->sent_callback = NULL;
514 }
515 }
516
517 static int
smtp_client_command_finish_dot_stream(struct smtp_client_command * cmd)518 smtp_client_command_finish_dot_stream(struct smtp_client_command *cmd)
519 {
520 struct smtp_client_connection *conn = cmd->conn;
521 int ret;
522
523 i_assert(cmd->stream_dot);
524 i_assert(conn->dot_output != NULL);
525
526 /* this concludes the dot stream with CRLF.CRLF */
527 if ((ret = o_stream_finish(conn->dot_output)) < 0) {
528 o_stream_unref(&conn->dot_output);
529 smtp_client_connection_handle_output_error(conn);
530 return -1;
531 }
532 if (ret == 0)
533 return 0;
534 o_stream_unref(&conn->dot_output);
535 return 1;
536 }
537
smtp_client_command_payload_input(struct smtp_client_command * cmd)538 static void smtp_client_command_payload_input(struct smtp_client_command *cmd)
539 {
540 struct smtp_client_connection *conn = cmd->conn;
541
542 io_remove(&conn->io_cmd_payload);
543
544 smtp_client_connection_trigger_output(conn);
545 }
546
smtp_client_command_send_stream(struct smtp_client_command * cmd)547 static int smtp_client_command_send_stream(struct smtp_client_command *cmd)
548 {
549 struct smtp_client_connection *conn = cmd->conn;
550 struct istream *stream = cmd->stream;
551 struct ostream *output = conn->conn.output;
552 enum ostream_send_istream_result res;
553 int ret;
554
555 io_remove(&conn->io_cmd_payload);
556
557 if (cmd->stream_finished) {
558 if ((ret = smtp_client_command_finish_dot_stream(cmd)) <= 0)
559 return ret;
560 /* done sending payload */
561 e_debug(cmd->event, "Finished sending payload");
562 i_stream_unref(&cmd->stream);
563 return 1;
564 }
565 if (cmd->stream_dot) {
566 if (conn->dot_output == NULL)
567 conn->dot_output = o_stream_create_dot(output, FALSE);
568 output = conn->dot_output;
569 }
570
571 /* we're sending the stream now */
572 o_stream_set_max_buffer_size(output, IO_BLOCK_SIZE);
573 res = o_stream_send_istream(output, stream);
574 o_stream_set_max_buffer_size(output, SIZE_MAX);
575
576 switch (res) {
577 case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
578 i_assert(cmd->stream_size == 0 ||
579 stream->v_offset == cmd->stream_size);
580 /* finished with the stream */
581 e_debug(cmd->event, "Finished reading payload stream");
582 cmd->stream_finished = TRUE;
583 if (cmd->stream_dot) {
584 ret = smtp_client_command_finish_dot_stream(cmd);
585 if (ret <= 0)
586 return ret;
587 }
588 /* done sending payload */
589 e_debug(cmd->event, "Finished sending payload");
590 i_stream_unref(&cmd->stream);
591 return 1;
592 case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
593 /* input is blocking (client needs to act; disable timeout) */
594 conn->io_cmd_payload = io_add_istream(
595 stream, smtp_client_command_payload_input, cmd);
596 return 0;
597 case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
598 e_debug(cmd->event, "Partially sent payload");
599 i_assert(cmd->stream_size == 0 ||
600 stream->v_offset < cmd->stream_size);
601 return 0;
602 case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
603
604 /* the provided payload stream is broken;
605 fail this command separately */
606 e_error(cmd->event, "read(%s) failed: %s",
607 i_stream_get_name(stream), i_stream_get_error(stream));
608 smtp_client_command_fail(
609 &cmd, SMTP_CLIENT_COMMAND_ERROR_BROKEN_PAYLOAD,
610 "Broken payload stream");
611 /* we're in the middle of sending a command, so the connection
612 will also have to be aborted */
613 o_stream_unref(&conn->dot_output);
614 smtp_client_connection_fail(
615 conn, SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST,
616 "Broken payload stream");
617 return -1;
618 case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
619 /* normal connection failure */
620 o_stream_unref(&conn->dot_output);
621 smtp_client_connection_handle_output_error(conn);
622 return -1;
623 }
624 i_unreached();
625 }
626
smtp_client_command_send_line(struct smtp_client_command * cmd)627 static int smtp_client_command_send_line(struct smtp_client_command *cmd)
628 {
629 struct smtp_client_connection *conn = cmd->conn;
630 const char *data;
631 size_t size;
632 ssize_t sent;
633
634 if (cmd->data == NULL)
635 return 1;
636
637 while (cmd->send_pos < cmd->data->used) {
638 data = CONST_PTR_OFFSET(cmd->data->data, cmd->send_pos);
639 size = cmd->data->used - cmd->send_pos;
640
641 sent = o_stream_send(conn->conn.output, data, size);
642 if (sent <= 0) {
643 if (sent < 0) {
644 smtp_client_connection_handle_output_error(conn);
645 return -1;
646 }
647 e_debug(cmd->event, "Blocked while sending");
648 return 0;
649 }
650 cmd->send_pos += sent;
651 }
652
653 i_assert(cmd->send_pos == cmd->data->used);
654 return 1;
655 }
656
657 static bool
smtp_client_command_pipeline_is_open(struct smtp_client_connection * conn)658 smtp_client_command_pipeline_is_open(struct smtp_client_connection *conn)
659 {
660 struct smtp_client_command *cmd = conn->cmd_send_queue_head;
661
662 if (cmd == NULL)
663 return TRUE;
664
665 if (cmd->plug) {
666 e_debug(cmd->event, "Pipeline is plugged");
667 return FALSE;
668 }
669
670 if (conn->state < SMTP_CLIENT_CONNECTION_STATE_READY &&
671 (cmd->flags & SMTP_CLIENT_COMMAND_FLAG_PRELOGIN) == 0) {
672 /* wait until we're fully connected */
673 e_debug(cmd->event, "Connection not ready [state=%s]",
674 smtp_client_connection_state_names[conn->state]);
675 return FALSE;
676 }
677
678 cmd = conn->cmd_wait_list_head;
679 if (cmd != NULL &&
680 (conn->caps.standard & SMTP_CAPABILITY_PIPELINING) == 0) {
681 /* cannot pipeline; wait for reply */
682 e_debug(cmd->event, "Pipeline occupied");
683 return FALSE;
684 }
685 while (cmd != NULL) {
686 if ((conn->caps.standard & SMTP_CAPABILITY_PIPELINING) == 0 ||
687 (cmd->flags & SMTP_CLIENT_COMMAND_FLAG_PIPELINE) == 0 ||
688 cmd->locked) {
689 /* cannot pipeline with previous command;
690 wait for reply */
691 e_debug(cmd->event, "Pipeline blocked");
692 return FALSE;
693 }
694 cmd = cmd->next;
695 }
696
697 return TRUE;
698 }
699
smtp_cient_command_wait(struct smtp_client_command * cmd)700 static void smtp_cient_command_wait(struct smtp_client_command *cmd)
701 {
702 struct smtp_client_connection *conn = cmd->conn;
703
704 /* move command to wait list. */
705 i_assert(conn->cmd_send_queue_count > 0);
706 i_assert(conn->cmd_send_queue_count > 1 ||
707 (cmd->prev == NULL && cmd->next == NULL));
708 DLLIST2_REMOVE(&conn->cmd_send_queue_head,
709 &conn->cmd_send_queue_tail, cmd);
710 conn->cmd_send_queue_count--;
711 DLLIST2_APPEND(&conn->cmd_wait_list_head,
712 &conn->cmd_wait_list_tail, cmd);
713 conn->cmd_wait_list_count++;
714 }
715
smtp_client_command_do_send_more(struct smtp_client_connection * conn)716 static int smtp_client_command_do_send_more(struct smtp_client_connection *conn)
717 {
718 struct smtp_client_command *cmd;
719 int ret;
720
721 if (conn->cmd_streaming != NULL) {
722 cmd = conn->cmd_streaming;
723 i_assert(cmd->stream != NULL);
724 } else {
725 /* check whether we can send anything */
726 cmd = conn->cmd_send_queue_head;
727 if (cmd == NULL)
728 return 0;
729 if (!smtp_client_command_pipeline_is_open(conn))
730 return 0;
731
732 cmd->state = SMTP_CLIENT_COMMAND_STATE_SENDING;
733 conn->sending_command = TRUE;
734
735 if ((ret = smtp_client_command_send_line(cmd)) <= 0)
736 return ret;
737
738 /* command line sent. move command to wait list. */
739 smtp_cient_command_wait(cmd);
740 cmd->state = SMTP_CLIENT_COMMAND_STATE_WAITING;
741 }
742
743 if (cmd->stream != NULL &&
744 (ret = smtp_client_command_send_stream(cmd)) <= 0) {
745 if (ret < 0)
746 return -1;
747 e_debug(cmd->event, "Blocked while sending payload");
748 if (conn->cmd_streaming != cmd) {
749 i_assert(conn->cmd_streaming == NULL);
750 conn->cmd_streaming = cmd;
751 smtp_client_command_ref(cmd);
752 }
753 return 0;
754 }
755
756 conn->sending_command = FALSE;
757 if (conn->cmd_streaming != cmd ||
758 smtp_client_command_unref(&conn->cmd_streaming))
759 smtp_client_command_sent(cmd);
760 return 1;
761 }
762
smtp_client_command_send_more(struct smtp_client_connection * conn)763 int smtp_client_command_send_more(struct smtp_client_connection *conn)
764 {
765 int ret;
766
767 while ((ret = smtp_client_command_do_send_more(conn)) > 0);
768 if (ret < 0)
769 return -1;
770
771 smtp_client_connection_update_cmd_timeout(conn);
772 return ret;
773 }
774
775 static void
smtp_client_command_disconnected(struct smtp_client_connection * conn)776 smtp_client_command_disconnected(struct smtp_client_connection *conn)
777 {
778 smtp_client_connection_fail(
779 conn, SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST,
780 "Disconnected");
781 }
782
783 static void
smtp_client_command_insert_prioritized(struct smtp_client_command * cmd,enum smtp_client_command_flags flag)784 smtp_client_command_insert_prioritized(struct smtp_client_command *cmd,
785 enum smtp_client_command_flags flag)
786 {
787 struct smtp_client_connection *conn = cmd->conn;
788 struct smtp_client_command *cmd_cur, *cmd_prev;
789
790 cmd_cur = conn->cmd_send_queue_head;
791 if (cmd_cur == NULL || (cmd_cur->flags & flag) == 0) {
792 DLLIST2_PREPEND(&conn->cmd_send_queue_head,
793 &conn->cmd_send_queue_tail, cmd);
794 conn->cmd_send_queue_count++;
795 } else {
796 cmd_prev = cmd_cur;
797 cmd_cur = cmd_cur->next;
798 while (cmd_cur != NULL && (cmd_cur->flags & flag) != 0) {
799 cmd_prev = cmd_cur;
800 cmd_cur = cmd_cur->next;
801 }
802 DLLIST2_INSERT_AFTER(&conn->cmd_send_queue_head,
803 &conn->cmd_send_queue_tail, cmd_prev, cmd);
804 conn->cmd_send_queue_count++;
805 }
806 }
807
smtp_client_command_submit_after(struct smtp_client_command * cmd,struct smtp_client_command * after)808 void smtp_client_command_submit_after(struct smtp_client_command *cmd,
809 struct smtp_client_command *after)
810 {
811 struct smtp_client_connection *conn = cmd->conn;
812 struct event_passthrough *e;
813
814 i_assert(after == NULL || cmd->conn == after->conn);
815
816 smtp_client_command_update_event(cmd);
817 e = event_create_passthrough(cmd->event)->
818 set_name("smtp_client_command_started");
819
820 cmd->state = SMTP_CLIENT_COMMAND_STATE_SUBMITTED;
821
822 if (smtp_client_command_name_equals(cmd, "EHLO"))
823 cmd->ehlo = TRUE;
824
825 if (conn->state == SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED) {
826 /* Add commands to send queue for delayed failure reply
827 from ioloop */
828 DLLIST2_APPEND(&conn->cmd_send_queue_head,
829 &conn->cmd_send_queue_tail, cmd);
830 conn->cmd_send_queue_count++;
831 if (conn->to_commands == NULL) {
832 conn->to_commands = timeout_add_short(
833 0, smtp_client_command_disconnected, conn);
834 }
835 e_debug(e->event(), "Submitted, but disconnected");
836 return;
837 }
838
839 if (cmd->data != NULL)
840 str_append(cmd->data, "\r\n");
841
842 if ((cmd->flags & SMTP_CLIENT_COMMAND_FLAG_PRELOGIN) != 0 &&
843 conn->state < SMTP_CLIENT_CONNECTION_STATE_READY) {
844 /* pre-login commands get inserted before everything else */
845 smtp_client_command_insert_prioritized(
846 cmd, SMTP_CLIENT_COMMAND_FLAG_PRELOGIN);
847 if (!conn->corked)
848 smtp_client_connection_trigger_output(conn);
849 e_debug(e->event(), "Submitted with priority");
850 return;
851 }
852
853 if (after != NULL) {
854 if (after->state >= SMTP_CLIENT_COMMAND_STATE_WAITING) {
855 /* not in the send queue anymore; just prepend */
856 DLLIST2_PREPEND(&conn->cmd_send_queue_head,
857 &conn->cmd_send_queue_tail, cmd);
858 conn->cmd_send_queue_count++;
859 } else {
860 /* insert after indicated command */
861 DLLIST2_INSERT_AFTER(&conn->cmd_send_queue_head,
862 &conn->cmd_send_queue_tail,
863 after, cmd);
864 conn->cmd_send_queue_count++;
865 }
866 } else if ((cmd->flags & SMTP_CLIENT_COMMAND_FLAG_PRIORITY) != 0) {
867 /* insert at beginning of queue for priority commands */
868 smtp_client_command_insert_prioritized(
869 cmd, SMTP_CLIENT_COMMAND_FLAG_PRIORITY);
870 } else {
871 /* just append at end of queue */
872 DLLIST2_APPEND(&conn->cmd_send_queue_head,
873 &conn->cmd_send_queue_tail, cmd);
874 conn->cmd_send_queue_count++;
875 }
876
877 if (conn->state >= SMTP_CLIENT_CONNECTION_STATE_READY)
878 smtp_client_connection_start_cmd_timeout(conn);
879
880 if (!conn->corked)
881 smtp_client_connection_trigger_output(conn);
882 e_debug(e->event(), "Submitted");
883 }
884
smtp_client_command_submit(struct smtp_client_command * cmd)885 void smtp_client_command_submit(struct smtp_client_command *cmd)
886 {
887 smtp_client_command_submit_after(cmd, NULL);
888 }
889
smtp_client_command_set_flags(struct smtp_client_command * cmd,enum smtp_client_command_flags flags)890 void smtp_client_command_set_flags(struct smtp_client_command *cmd,
891 enum smtp_client_command_flags flags)
892 {
893 cmd->flags = flags;
894 }
895
smtp_client_command_write(struct smtp_client_command * cmd,const char * cmd_str)896 void smtp_client_command_write(struct smtp_client_command *cmd,
897 const char *cmd_str)
898 {
899 unsigned int len = strlen(cmd_str);
900
901 i_assert(cmd->state < SMTP_CLIENT_COMMAND_STATE_SUBMITTED);
902 if (cmd->data == NULL)
903 cmd->data = str_new(cmd->pool, len + 2);
904 str_append(cmd->data, cmd_str);
905 }
906
smtp_client_command_printf(struct smtp_client_command * cmd,const char * cmd_fmt,...)907 void smtp_client_command_printf(struct smtp_client_command *cmd,
908 const char *cmd_fmt, ...)
909 {
910 va_list args;
911
912 va_start(args, cmd_fmt);
913 smtp_client_command_vprintf(cmd, cmd_fmt, args);
914 va_end(args);
915 }
916
smtp_client_command_vprintf(struct smtp_client_command * cmd,const char * cmd_fmt,va_list args)917 void smtp_client_command_vprintf(struct smtp_client_command *cmd,
918 const char *cmd_fmt, va_list args)
919 {
920 if (cmd->data == NULL)
921 cmd->data = str_new(cmd->pool, 128);
922 str_vprintfa(cmd->data, cmd_fmt, args);
923 }
924
smtp_client_command_set_stream(struct smtp_client_command * cmd,struct istream * input,bool dot)925 void smtp_client_command_set_stream(struct smtp_client_command *cmd,
926 struct istream *input, bool dot)
927 {
928 int ret;
929
930 cmd->stream = input;
931 i_stream_ref(input);
932
933 if ((ret = i_stream_get_size(input, TRUE, &cmd->stream_size)) <= 0) {
934 if (ret < 0) {
935 e_error(cmd->event, "i_stream_get_size(%s) failed: %s",
936 i_stream_get_name(input),
937 i_stream_get_error(input));
938 }
939 /* size must be known if stream is to be sent in chunks */
940 i_assert(dot);
941 cmd->stream_size = 0;
942 }
943
944 cmd->stream_dot = dot;
945 cmd->has_stream = TRUE;
946 }
947
smtp_client_command_input_reply(struct smtp_client_command * cmd,const struct smtp_reply * reply)948 int smtp_client_command_input_reply(struct smtp_client_command *cmd,
949 const struct smtp_reply *reply)
950 {
951 struct smtp_client_connection *conn = cmd->conn;
952 bool finished;
953
954 i_assert(cmd->replies_seen < cmd->replies_expected);
955 finished = (++cmd->replies_seen == cmd->replies_expected);
956
957 /* Finish command event at final reply or first failure */
958 struct event_passthrough *e = event_create_passthrough(cmd->event);
959 if (!cmd->event_finished &&
960 (finished || !smtp_reply_is_success(reply))) {
961 e->set_name("smtp_client_command_finished");
962 smtp_reply_add_to_event(reply, e);
963 cmd->event_finished = TRUE;
964 }
965 e_debug(e->event(), "Got reply (%u/%u): %s "
966 "(%u commands pending, %u commands queued)",
967 cmd->replies_seen, cmd->replies_expected,
968 smtp_reply_log(reply), conn->cmd_wait_list_count,
969 conn->cmd_send_queue_count);
970
971 if (finished) {
972 i_assert(conn->cmd_wait_list_count > 0);
973 DLLIST2_REMOVE(&conn->cmd_wait_list_head,
974 &conn->cmd_wait_list_tail, cmd);
975 conn->cmd_wait_list_count--;
976 if (cmd->aborting)
977 cmd->state = SMTP_CLIENT_COMMAND_STATE_ABORTED;
978 else if (cmd->state != SMTP_CLIENT_COMMAND_STATE_ABORTED)
979 cmd->state = SMTP_CLIENT_COMMAND_STATE_FINISHED;
980
981 smtp_client_connection_update_cmd_timeout(conn);
982 }
983
984 if (!cmd->aborting && cmd->callback != NULL)
985 cmd->callback(reply, cmd->context);
986
987 if (finished) {
988 smtp_client_command_drop_callback(cmd);
989 smtp_client_command_unref(&cmd);
990 smtp_client_connection_trigger_output(conn);
991 }
992 return 1;
993 }
994
995 enum smtp_client_command_state
smtp_client_command_get_state(struct smtp_client_command * cmd)996 smtp_client_command_get_state(struct smtp_client_command *cmd)
997 {
998 return cmd->state;
999 }
1000
1001 /*
1002 * Standard commands
1003 */
1004
1005 /* NOTE: Pipelining is only enabled for certain commands:
1006
1007 From RFC 2920, Section 3.1:
1008
1009 Once the client SMTP has confirmed that support exists for the
1010 pipelining extension, the client SMTP may then elect to transmit
1011 groups of SMTP commands in batches without waiting for a response to
1012 each individual command. In particular, the commands RSET, MAIL FROM,
1013 SEND FROM, SOML FROM, SAML FROM, and RCPT TO can all appear anywhere
1014 in a pipelined command group. The EHLO, DATA, VRFY, EXPN, TURN,
1015 QUIT, and NOOP commands can only appear as the last command in a
1016 group since their success or failure produces a change of state which
1017 the client SMTP must accommodate. (NOOP is included in this group so
1018 it can be used as a synchronization point.)
1019
1020 Additional commands added by other SMTP extensions may only appear as
1021 the last command in a group unless otherwise specified by the
1022 extensions that define the commands.
1023 */
1024
1025 /* NOOP */
1026
1027 #undef smtp_client_command_noop_submit_after
1028 struct smtp_client_command *
smtp_client_command_noop_submit_after(struct smtp_client_connection * conn,enum smtp_client_command_flags flags,struct smtp_client_command * after,smtp_client_command_callback_t * callback,void * context)1029 smtp_client_command_noop_submit_after(struct smtp_client_connection *conn,
1030 enum smtp_client_command_flags flags,
1031 struct smtp_client_command *after,
1032 smtp_client_command_callback_t *callback,
1033 void *context)
1034 {
1035 struct smtp_client_command *cmd;
1036
1037 cmd = smtp_client_command_new(conn, flags, callback, context);
1038 smtp_client_command_write(cmd, "NOOP");
1039 smtp_client_command_submit_after(cmd, after);
1040 return cmd;
1041 }
1042
1043 #undef smtp_client_command_noop_submit
1044 struct smtp_client_command *
smtp_client_command_noop_submit(struct smtp_client_connection * conn,enum smtp_client_command_flags flags,smtp_client_command_callback_t * callback,void * context)1045 smtp_client_command_noop_submit(struct smtp_client_connection *conn,
1046 enum smtp_client_command_flags flags,
1047 smtp_client_command_callback_t *callback,
1048 void *context)
1049 {
1050 return smtp_client_command_noop_submit_after(conn, flags, NULL,
1051 callback, context);
1052 }
1053
1054 /* VRFY */
1055
1056 #undef smtp_client_command_vrfy_submit_after
1057 struct smtp_client_command *
smtp_client_command_vrfy_submit_after(struct smtp_client_connection * conn,enum smtp_client_command_flags flags,struct smtp_client_command * after,const char * param,smtp_client_command_callback_t * callback,void * context)1058 smtp_client_command_vrfy_submit_after(struct smtp_client_connection *conn,
1059 enum smtp_client_command_flags flags,
1060 struct smtp_client_command *after,
1061 const char *param,
1062 smtp_client_command_callback_t *callback,
1063 void *context)
1064 {
1065 struct smtp_client_command *cmd;
1066
1067 cmd = smtp_client_command_new(conn, flags, callback, context);
1068 smtp_client_command_write(cmd, "VRFY ");
1069 smtp_string_write(cmd->data, param);
1070 smtp_client_command_submit_after(cmd, after);
1071 return cmd;
1072 }
1073
1074 #undef smtp_client_command_vrfy_submit
1075 struct smtp_client_command *
smtp_client_command_vrfy_submit(struct smtp_client_connection * conn,enum smtp_client_command_flags flags,const char * param,smtp_client_command_callback_t * callback,void * context)1076 smtp_client_command_vrfy_submit(struct smtp_client_connection *conn,
1077 enum smtp_client_command_flags flags,
1078 const char *param,
1079 smtp_client_command_callback_t *callback,
1080 void *context)
1081 {
1082 return smtp_client_command_vrfy_submit_after(conn, flags, NULL, param,
1083 callback, context);
1084 }
1085
1086 /* RSET */
1087
1088 #undef smtp_client_command_rset_submit_after
1089 struct smtp_client_command *
smtp_client_command_rset_submit_after(struct smtp_client_connection * conn,enum smtp_client_command_flags flags,struct smtp_client_command * after,smtp_client_command_callback_t * callback,void * context)1090 smtp_client_command_rset_submit_after(struct smtp_client_connection *conn,
1091 enum smtp_client_command_flags flags,
1092 struct smtp_client_command *after,
1093 smtp_client_command_callback_t *callback,
1094 void *context)
1095 {
1096 struct smtp_client_command *cmd;
1097
1098 cmd = smtp_client_command_new(conn,
1099 flags | SMTP_CLIENT_COMMAND_FLAG_PIPELINE,
1100 callback, context);
1101 smtp_client_command_write(cmd, "RSET");
1102 smtp_client_command_submit_after(cmd, after);
1103 return cmd;
1104 }
1105
1106 #undef smtp_client_command_rset_submit
1107 struct smtp_client_command *
smtp_client_command_rset_submit(struct smtp_client_connection * conn,enum smtp_client_command_flags flags,smtp_client_command_callback_t * callback,void * context)1108 smtp_client_command_rset_submit(struct smtp_client_connection *conn,
1109 enum smtp_client_command_flags flags,
1110 smtp_client_command_callback_t *callback,
1111 void *context)
1112 {
1113 return smtp_client_command_rset_submit_after(conn, flags, NULL,
1114 callback, context);
1115 }
1116
1117 /* MAIL FROM: */
1118
1119 #undef smtp_client_command_mail_submit
1120 struct smtp_client_command *
smtp_client_command_mail_submit(struct smtp_client_connection * conn,enum smtp_client_command_flags flags,const struct smtp_address * from,const struct smtp_params_mail * params,smtp_client_command_callback_t * callback,void * context)1121 smtp_client_command_mail_submit(struct smtp_client_connection *conn,
1122 enum smtp_client_command_flags flags,
1123 const struct smtp_address *from,
1124 const struct smtp_params_mail *params,
1125 smtp_client_command_callback_t *callback,
1126 void *context)
1127 {
1128 struct smtp_client_command *cmd;
1129
1130 smtp_client_connection_send_xclient(conn);
1131
1132 flags |= SMTP_CLIENT_COMMAND_FLAG_PIPELINE;
1133 cmd = smtp_client_command_new(conn, flags, callback, context);
1134 if (!conn->set.mail_send_broken_path || !smtp_address_is_broken(from)) {
1135 /* Compose MAIL command with normalized path. */
1136 smtp_client_command_printf(cmd, "MAIL FROM:<%s>",
1137 smtp_address_encode(from));
1138 } else {
1139 /* Compose MAIL command with broken path (for proxy). */
1140 smtp_client_command_printf(cmd, "MAIL FROM:<%s>",
1141 smtp_address_encode_raw(from));
1142 }
1143 if (params != NULL) {
1144 size_t orig_len = str_len(cmd->data);
1145 const char *const *extensions = NULL;
1146
1147 if (array_is_created(&conn->caps.mail_param_extensions)) {
1148 extensions =
1149 array_front(&conn->caps.mail_param_extensions);
1150 }
1151
1152 str_append_c(cmd->data, ' ');
1153 smtp_params_mail_write(cmd->data, conn->caps.standard,
1154 extensions, params);
1155 if (str_len(cmd->data) == orig_len + 1)
1156 str_truncate(cmd->data, orig_len);
1157 }
1158 smtp_client_command_submit(cmd);
1159 return cmd;
1160 }
1161
1162 /* RCPT TO: */
1163
1164 #undef smtp_client_command_rcpt_submit_after
1165 struct smtp_client_command *
smtp_client_command_rcpt_submit_after(struct smtp_client_connection * conn,enum smtp_client_command_flags flags,struct smtp_client_command * after,const struct smtp_address * to,const struct smtp_params_rcpt * params,smtp_client_command_callback_t * callback,void * context)1166 smtp_client_command_rcpt_submit_after(struct smtp_client_connection *conn,
1167 enum smtp_client_command_flags flags,
1168 struct smtp_client_command *after,
1169 const struct smtp_address *to,
1170 const struct smtp_params_rcpt *params,
1171 smtp_client_command_callback_t *callback,
1172 void *context)
1173 {
1174 struct smtp_client_command *cmd;
1175
1176 flags |= SMTP_CLIENT_COMMAND_FLAG_PIPELINE;
1177 cmd = smtp_client_command_new(conn, flags, callback, context);
1178 smtp_client_command_printf(cmd, "RCPT TO:<%s>",
1179 smtp_address_encode(to));
1180 if (params != NULL) {
1181 size_t orig_len = str_len(cmd->data);
1182 const char *const *extensions = NULL;
1183
1184 if (array_is_created(&conn->caps.rcpt_param_extensions)) {
1185 extensions =
1186 array_front(&conn->caps.rcpt_param_extensions);
1187 }
1188
1189 str_append_c(cmd->data, ' ');
1190 smtp_params_rcpt_write(cmd->data, conn->caps.standard,
1191 extensions, params);
1192 if (str_len(cmd->data) == orig_len + 1)
1193 str_truncate(cmd->data, orig_len);
1194 }
1195 smtp_client_command_submit_after(cmd, after);
1196 return cmd;
1197 }
1198
1199 #undef smtp_client_command_rcpt_submit
1200 struct smtp_client_command *
smtp_client_command_rcpt_submit(struct smtp_client_connection * conn,enum smtp_client_command_flags flags,const struct smtp_address * from,const struct smtp_params_rcpt * params,smtp_client_command_callback_t * callback,void * context)1201 smtp_client_command_rcpt_submit(struct smtp_client_connection *conn,
1202 enum smtp_client_command_flags flags,
1203 const struct smtp_address *from,
1204 const struct smtp_params_rcpt *params,
1205 smtp_client_command_callback_t *callback,
1206 void *context)
1207 {
1208 return smtp_client_command_rcpt_submit_after(conn, flags, NULL, from,
1209 params, callback, context);
1210 }
1211
1212 /* DATA or BDAT */
1213
1214 struct _cmd_data_context {
1215 struct smtp_client_connection *conn;
1216 pool_t pool;
1217
1218 struct smtp_client_command *cmd_data, *cmd_first;
1219 ARRAY(struct smtp_client_command *) cmds;
1220
1221 struct istream *data;
1222 uoff_t data_offset, data_left;
1223 };
1224
1225 static void
1226 _cmd_bdat_send_chunks(struct _cmd_data_context *ctx,
1227 struct smtp_client_command *after);
1228
_cmd_data_context_free(struct _cmd_data_context * ctx)1229 static void _cmd_data_context_free(struct _cmd_data_context *ctx)
1230 {
1231 if (ctx->cmd_data != NULL) {
1232 /* abort the main (possibly unsubmitted) data command */
1233 smtp_client_command_set_abort_callback(ctx->cmd_data,
1234 NULL, NULL);
1235 ctx->cmd_data = NULL;
1236 }
1237 i_stream_unref(&ctx->data);
1238 }
1239
_cmd_data_abort(struct _cmd_data_context * ctx)1240 static void _cmd_data_abort(struct _cmd_data_context *ctx)
1241 {
1242 struct smtp_client_command **cmds;
1243 unsigned int count, i;
1244
1245 /* drop all pending commands */
1246 cmds = array_get_modifiable(&ctx->cmds, &count);
1247 for (i = 0; i < count; i++) {
1248 smtp_client_command_set_abort_callback(cmds[i], NULL, NULL);
1249 smtp_client_command_abort(&cmds[i]);
1250 }
1251 }
1252
_cmd_data_abort_cb(void * context)1253 static void _cmd_data_abort_cb(void *context)
1254 {
1255 struct _cmd_data_context *ctx = (struct _cmd_data_context *)context;
1256
1257 /* the main (possibly unsubmitted) data command got aborted */
1258 _cmd_data_abort(ctx);
1259 _cmd_data_context_free(ctx);
1260 }
1261
1262 static void
_cmd_data_error(struct _cmd_data_context * ctx,const struct smtp_reply * reply)1263 _cmd_data_error(struct _cmd_data_context *ctx, const struct smtp_reply *reply)
1264 {
1265 struct smtp_client_command *cmd = ctx->cmd_data;
1266
1267 if (cmd != NULL) {
1268 /* fail the main (possibly unsubmitted) data command so that
1269 the caller gets notified */
1270 smtp_client_command_fail_reply(&cmd, reply);
1271 }
1272 }
1273
1274 static void
_cmd_data_cb(const struct smtp_reply * reply,void * context)1275 _cmd_data_cb(const struct smtp_reply *reply, void *context)
1276 {
1277 struct _cmd_data_context *ctx = (struct _cmd_data_context *)context;
1278 struct smtp_client_command *const *cmds, *cmd;
1279 unsigned int count;
1280
1281 /* got DATA reply; one command must be pending */
1282 cmds = array_get(&ctx->cmds, &count);
1283 i_assert(count > 0);
1284
1285 if (reply->status == 354) {
1286 /* submit second stage: which is a command with only a stream */
1287 cmd = ctx->cmd_data;
1288 smtp_client_command_submit_after(cmd, cmds[0]);
1289
1290 /* nothing else to do, so drop the context already */
1291 _cmd_data_context_free(ctx);
1292 } else {
1293 /* error */
1294 _cmd_data_error(ctx, reply);
1295 }
1296 }
1297
1298 static void
_cmd_bdat_cb(const struct smtp_reply * reply,void * context)1299 _cmd_bdat_cb(const struct smtp_reply *reply, void *context)
1300 {
1301 struct _cmd_data_context *ctx = (struct _cmd_data_context *)context;
1302
1303 /* got BDAT reply, so there must be ones pending */
1304 i_assert(array_count(&ctx->cmds) > 0);
1305
1306 if ((reply->status / 100) != 2) {
1307 /* error */
1308 _cmd_data_error(ctx, reply);
1309 return;
1310 }
1311
1312 /* drop the command from the list */
1313 array_pop_front(&ctx->cmds);
1314
1315 /* send more BDAT commands if necessary */
1316 (void)_cmd_bdat_send_chunks(ctx, NULL);
1317
1318 if (array_count(&ctx->cmds) == 0) {
1319 /* all of the BDAT commands finished already */
1320 _cmd_data_context_free(ctx);
1321 }
1322 }
1323
_cmd_bdat_sent_cb(void * context)1324 static void _cmd_bdat_sent_cb(void *context)
1325 {
1326 struct _cmd_data_context *ctx = (struct _cmd_data_context *)context;
1327
1328 /* send more BDAT commands if possible */
1329 (void)_cmd_bdat_send_chunks(ctx, NULL);
1330 }
1331
1332 static int
_cmd_bdat_read_data(struct _cmd_data_context * ctx,size_t * data_size_r)1333 _cmd_bdat_read_data(struct _cmd_data_context *ctx, size_t *data_size_r)
1334 {
1335 int ret;
1336
1337 while ((ret = i_stream_read(ctx->data)) > 0);
1338
1339 if (ret < 0) {
1340 if (ret != -2 && ctx->data->stream_errno != 0) {
1341 e_error(ctx->cmd_data->event,
1342 "Failed to read DATA stream: %s",
1343 i_stream_get_error(ctx->data));
1344 smtp_client_command_fail(
1345 &ctx->cmd_data,
1346 SMTP_CLIENT_COMMAND_ERROR_BROKEN_PAYLOAD,
1347 "Broken payload stream");
1348 return -1;
1349 }
1350 }
1351
1352 *data_size_r = i_stream_get_data_size(ctx->data);
1353 return 0;
1354 }
1355
1356 static void
_cmd_bdat_send_chunks(struct _cmd_data_context * ctx,struct smtp_client_command * after)1357 _cmd_bdat_send_chunks(struct _cmd_data_context *ctx,
1358 struct smtp_client_command *after)
1359 {
1360 struct smtp_client_connection *conn = ctx->conn;
1361 const struct smtp_client_settings *set = &conn->set;
1362 struct smtp_client_command *const *cmds, *cmd, *cmd_prev;
1363 unsigned int count;
1364 struct istream *chunk;
1365 size_t data_size, max_chunk_size;
1366
1367 if (smtp_client_command_get_state(ctx->cmd_data) >=
1368 SMTP_CLIENT_COMMAND_STATE_SUBMITTED) {
1369 /* finished or aborted */
1370 return;
1371 }
1372
1373 /* pipeline management: determine where to submit the next command */
1374 cmds = array_get(&ctx->cmds, &count);
1375 cmd_prev = NULL;
1376 if (after != NULL) {
1377 i_assert(count == 0);
1378 cmd_prev = after;
1379 } else if (count > 0) {
1380 cmd_prev = cmds[count-1];
1381 smtp_client_command_unlock(cmd_prev);
1382 }
1383
1384 data_size = ctx->data_left;
1385 if (data_size > 0) {
1386 max_chunk_size = set->max_data_chunk_size;
1387 } else {
1388 if (ctx->data->v_offset < ctx->data_offset) {
1389 /* previous BDAT command not completely sent */
1390 return;
1391 }
1392 max_chunk_size = i_stream_get_max_buffer_size(ctx->data);
1393 if (set->max_data_chunk_size < max_chunk_size)
1394 max_chunk_size = set->max_data_chunk_size;
1395 if (_cmd_bdat_read_data(ctx, &data_size) < 0)
1396 return;
1397 }
1398
1399 /* Keep sending more chunks until pipeline is filled to the limit */
1400 cmd = NULL;
1401 while (data_size > max_chunk_size ||
1402 (data_size == max_chunk_size && !ctx->data->eof)) {
1403 enum smtp_client_command_flags flags = ctx->cmd_data->flags;
1404 size_t size = (data_size > set->max_data_chunk_size ?
1405 set->max_data_chunk_size : data_size);
1406 chunk = i_stream_create_range(ctx->data, ctx->data_offset,
1407 size);
1408
1409 flags |= SMTP_CLIENT_COMMAND_FLAG_PIPELINE;
1410 cmd = smtp_client_command_new(conn, flags, _cmd_bdat_cb, ctx);
1411 smtp_client_command_set_abort_callback(cmd, _cmd_data_abort_cb,
1412 ctx);
1413 smtp_client_command_set_stream(cmd, chunk, FALSE);
1414 i_stream_unref(&chunk);
1415 smtp_client_command_printf(cmd, "BDAT %"PRIuUOFF_T,
1416 (uoff_t)size);
1417 smtp_client_command_submit_after(cmd, cmd_prev);
1418 array_push_back(&ctx->cmds, &cmd);
1419
1420 ctx->data_offset += size;
1421 data_size -= size;
1422
1423 if (array_count(&ctx->cmds) >= set->max_data_chunk_pipeline) {
1424 /* pipeline full */
1425 if (ctx->data_left != 0) {
1426 /* data stream size known:
1427 record where we left off */
1428 ctx->data_left = data_size;
1429 }
1430 smtp_client_command_lock(cmd);
1431 return;
1432 }
1433
1434 cmd_prev = cmd;
1435 }
1436
1437 if (ctx->data_left != 0) {
1438 /* data stream size known:
1439 record where we left off */
1440 ctx->data_left = data_size;
1441 } else if (!ctx->data->eof) {
1442 /* more to read */
1443 if (cmd != NULL) {
1444 smtp_client_command_set_sent_callback(
1445 cmd, _cmd_bdat_sent_cb, ctx);
1446 }
1447 return;
1448 }
1449
1450 /* the last chunk, which may actually be empty */
1451 chunk = i_stream_create_range(ctx->data, ctx->data_offset, data_size);
1452
1453 /* submit final command */
1454 cmd = ctx->cmd_data;
1455 smtp_client_command_set_stream(cmd, chunk, FALSE);
1456 i_stream_unref(&chunk);
1457 smtp_client_command_printf(cmd, "BDAT %zu LAST", data_size);
1458 smtp_client_command_submit_after(cmd, cmd_prev);
1459
1460 if (array_count(&ctx->cmds) == 0) {
1461 /* all of the previous BDAT commands got replies already */
1462 _cmd_data_context_free(ctx);
1463 }
1464 }
1465
1466 #undef smtp_client_command_data_submit_after
1467 struct smtp_client_command *
smtp_client_command_data_submit_after(struct smtp_client_connection * conn,enum smtp_client_command_flags flags,struct smtp_client_command * after,struct istream * data,smtp_client_command_callback_t * callback,void * context)1468 smtp_client_command_data_submit_after(struct smtp_client_connection *conn,
1469 enum smtp_client_command_flags flags,
1470 struct smtp_client_command *after,
1471 struct istream *data,
1472 smtp_client_command_callback_t *callback,
1473 void *context)
1474 {
1475 const struct smtp_client_settings *set = &conn->set;
1476 struct _cmd_data_context *ctx;
1477 struct smtp_client_command *cmd, *cmd_data;
1478
1479 /* create the final command early for reference by the caller;
1480 it will not be submitted for now. The DATA command is handled in
1481 two stages (== command submissions), the BDAT command in one or more. */
1482 cmd = cmd_data = smtp_client_command_create(conn, flags,
1483 callback, context);
1484
1485 /* protect against race conditions */
1486 cmd_data->delay_failure = TRUE;
1487
1488 /* create context in the final command's pool */
1489 ctx = p_new(cmd->pool, struct _cmd_data_context, 1);
1490 ctx->conn = conn;
1491 ctx->pool = cmd->pool;
1492 ctx->cmd_data = cmd;
1493
1494 /* capture abort event with our context */
1495 smtp_client_command_set_abort_callback(cmd, _cmd_data_abort_cb, ctx);
1496
1497 ctx->data = data;
1498 i_stream_ref(data);
1499
1500 if ((conn->caps.standard & SMTP_CAPABILITY_CHUNKING) == 0) {
1501 /* DATA */
1502 p_array_init(&ctx->cmds, ctx->pool, 1);
1503
1504 /* Data stream is sent in one go in the second stage. Since the data
1505 is sent in a '<CRLF>.<CRLF>'-terminated stream, it size is not
1506 relevant here. */
1507 smtp_client_command_set_stream(cmd, ctx->data, TRUE);
1508
1509 /* Submit the initial DATA command */
1510 cmd = smtp_client_command_new(conn, flags, _cmd_data_cb, ctx);
1511 smtp_client_command_set_abort_callback(cmd, _cmd_data_abort_cb,
1512 ctx);
1513 smtp_client_command_write(cmd, "DATA");
1514 smtp_client_command_submit_after(cmd, after);
1515 array_push_back(&ctx->cmds, &cmd);
1516 } else {
1517 /* BDAT */
1518 p_array_init(&ctx->cmds, ctx->pool,
1519 conn->set.max_data_chunk_pipeline);
1520
1521 /* The data stream is sent in multiple chunks. Either the size of the
1522 data stream is known or it is not. These cases are handled a little
1523 differently. */
1524 if (i_stream_get_size(data, TRUE, &ctx->data_left) > 0) {
1525 /* size is known */
1526 i_assert(ctx->data_left >= data->v_offset);
1527 ctx->data_left -= data->v_offset;
1528 } else {
1529 /* size is unknown */
1530 ctx->data_left = 0;
1531
1532 /* Make sure we can send chunks of sufficient size by
1533 making the data stream buffer size limit at least
1534 equally large. */
1535 if (i_stream_get_max_buffer_size(ctx->data) <
1536 set->max_data_chunk_size) {
1537 i_stream_set_max_buffer_size(
1538 ctx->data, set->max_data_chunk_size);
1539 }
1540 }
1541
1542 /* Send the first BDAT command(s) */
1543 ctx->data_offset = data->v_offset;
1544 _cmd_bdat_send_chunks(ctx, after);
1545 }
1546
1547 cmd_data->delay_failure = FALSE;
1548 return cmd_data;
1549 }
1550
1551 #undef smtp_client_command_data_submit
1552 struct smtp_client_command *
smtp_client_command_data_submit(struct smtp_client_connection * conn,enum smtp_client_command_flags flags,struct istream * data,smtp_client_command_callback_t * callback,void * context)1553 smtp_client_command_data_submit(struct smtp_client_connection *conn,
1554 enum smtp_client_command_flags flags,
1555 struct istream *data,
1556 smtp_client_command_callback_t *callback,
1557 void *context)
1558 {
1559 return smtp_client_command_data_submit_after(conn, flags, NULL, data,
1560 callback, context);
1561 }
1562