Coverage Report

Created: 2026-07-30 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-smtp/smtp-server-command.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "llist.h"
5
#include "array.h"
6
7
#include "smtp-reply.h"
8
#include "smtp-server-private.h"
9
10
/*
11
 * Command registry
12
 */
13
14
void smtp_server_command_register(struct smtp_server *server, const char *name,
15
          smtp_server_cmd_start_func_t *func,
16
          enum smtp_server_command_flags flags)
17
78.8k
{
18
78.8k
  struct smtp_server_command_reg cmd;
19
20
78.8k
  i_zero(&cmd);
21
78.8k
  cmd.name = name;
22
78.8k
  cmd.func = func;
23
78.8k
  cmd.flags = flags;
24
78.8k
  array_push_back(&server->commands_reg, &cmd);
25
26
78.8k
  server->commands_unsorted = TRUE;
27
78.8k
}
28
29
static bool ATTR_NOWARN_UNUSED_RESULT
30
smtp_server_command_do_unregister(struct smtp_server *server,
31
          const char *name)
32
0
{
33
0
  const struct smtp_server_command_reg *cmd;
34
0
  unsigned int i, count;
35
36
0
  cmd = array_get(&server->commands_reg, &count);
37
0
  for (i = 0; i < count; i++) {
38
0
    if (strcasecmp(cmd[i].name, name) == 0) {
39
0
      array_delete(&server->commands_reg, i, 1);
40
0
      return TRUE;
41
0
    }
42
0
  }
43
44
0
  return FALSE;
45
0
}
46
47
void smtp_server_command_unregister(struct smtp_server *server,
48
            const char *name)
49
0
{
50
0
  if (smtp_server_command_do_unregister(server, name))
51
0
    return;
52
0
  i_panic("smtp-server: Trying to unregister unknown command '%s'", name);
53
0
}
54
55
void smtp_server_command_override(struct smtp_server *server, const char *name,
56
          smtp_server_cmd_start_func_t *func,
57
          enum smtp_server_command_flags flags)
58
0
{
59
0
  smtp_server_command_do_unregister(server, name);
60
0
  smtp_server_command_register(server, name, func, flags);
61
0
}
62
63
static int
64
smtp_server_command_cmp(const struct smtp_server_command_reg *c1,
65
      const struct smtp_server_command_reg *c2)
66
188k
{
67
188k
  return strcasecmp(c1->name, c2->name);
68
188k
}
69
70
static int
71
smtp_server_command_bsearch(const char *name,
72
          const struct smtp_server_command_reg *cmd)
73
312k
{
74
312k
  return strcasecmp(name, cmd->name);
75
312k
}
76
77
static const struct smtp_server_command_reg *
78
smtp_server_command_find(struct smtp_server *server, const char *name)
79
97.9k
{
80
97.9k
  if (server->commands_unsorted) {
81
5.70k
    array_sort(&server->commands_reg, smtp_server_command_cmp);
82
5.70k
    server->commands_unsorted = FALSE;
83
5.70k
  }
84
85
97.9k
  return array_bsearch(&server->commands_reg,
86
97.9k
    name, smtp_server_command_bsearch);
87
97.9k
}
88
89
void smtp_server_commands_init(struct smtp_server *server)
90
6.06k
{
91
6.06k
  p_array_init(&server->commands_reg, server->pool, 16);
92
93
6.06k
  switch (server->set.protocol) {
94
6.06k
  case SMTP_PROTOCOL_SMTP:
95
6.06k
    smtp_server_command_register(
96
6.06k
      server, "EHLO", smtp_server_cmd_ehlo,
97
6.06k
      SMTP_SERVER_CMD_FLAG_PRETLS |
98
6.06k
      SMTP_SERVER_CMD_FLAG_PREAUTH);
99
6.06k
    smtp_server_command_register(
100
6.06k
      server, "HELO", smtp_server_cmd_helo,
101
6.06k
      SMTP_SERVER_CMD_FLAG_PREAUTH);
102
6.06k
    break;
103
0
  case SMTP_PROTOCOL_LMTP:
104
0
    smtp_server_command_register(
105
0
      server, "LHLO", smtp_server_cmd_ehlo,
106
0
      SMTP_SERVER_CMD_FLAG_PRETLS |
107
0
      SMTP_SERVER_CMD_FLAG_PREAUTH);
108
0
    break;
109
6.06k
  }
110
111
6.06k
  smtp_server_command_register(
112
6.06k
    server, "AUTH", smtp_server_cmd_auth,
113
6.06k
    SMTP_SERVER_CMD_FLAG_PREAUTH);
114
6.06k
  smtp_server_command_register(
115
6.06k
    server, "STARTTLS", smtp_server_cmd_starttls,
116
6.06k
    SMTP_SERVER_CMD_FLAG_PRETLS | SMTP_SERVER_CMD_FLAG_PREAUTH);
117
6.06k
  smtp_server_command_register(
118
6.06k
    server, "MAIL", smtp_server_cmd_mail, 0);
119
6.06k
  smtp_server_command_register(server, "RCPT", smtp_server_cmd_rcpt, 0);
120
6.06k
  smtp_server_command_register(server, "DATA", smtp_server_cmd_data, 0);
121
6.06k
  smtp_server_command_register(server, "BDAT", smtp_server_cmd_bdat, 0);
122
6.06k
  smtp_server_command_register(
123
6.06k
    server, "RSET", smtp_server_cmd_rset,
124
6.06k
    SMTP_SERVER_CMD_FLAG_PREAUTH);
125
6.06k
  smtp_server_command_register(server, "VRFY", smtp_server_cmd_vrfy, 0);
126
6.06k
  smtp_server_command_register(
127
6.06k
    server, "NOOP", smtp_server_cmd_noop,
128
6.06k
    SMTP_SERVER_CMD_FLAG_PRETLS | SMTP_SERVER_CMD_FLAG_PREAUTH);
129
6.06k
  smtp_server_command_register(
130
6.06k
    server, "QUIT", smtp_server_cmd_quit,
131
6.06k
    SMTP_SERVER_CMD_FLAG_PRETLS | SMTP_SERVER_CMD_FLAG_PREAUTH);
132
133
6.06k
  smtp_server_command_register(
134
6.06k
    server, "XCLIENT", smtp_server_cmd_xclient,
135
6.06k
    SMTP_SERVER_CMD_FLAG_PREAUTH);
136
6.06k
}
137
138
/*
139
 *
140
 */
141
142
static void smtp_server_command_update_event(struct smtp_server_command *cmd)
143
112k
{
144
112k
  struct event *event = cmd->context.event;
145
112k
  const char *label = (cmd->context.name == NULL ?
146
14.5k
           "[unknown]" :
147
112k
           t_str_ucase(cmd->context.name));
148
149
112k
  if (cmd->reg != NULL)
150
76.2k
    event_add_str(event, "cmd_name", cmd->reg->name);
151
36.2k
  else
152
36.2k
    event_add_str(event, "cmd_name", "unknown");
153
112k
  event_add_str(event, "cmd_input_name", cmd->context.name);
154
112k
  event_set_append_log_prefix(event,
155
112k
            t_strdup_printf("command %s: ", label));
156
112k
}
157
158
static struct smtp_server_command *
159
smtp_server_command_alloc(struct smtp_server_connection *conn)
160
112k
{
161
112k
  struct smtp_server_command *cmd;
162
112k
  pool_t pool;
163
164
112k
  pool = pool_alloconly_create("smtp_server_command", 1024);
165
112k
  cmd = p_new(pool, struct smtp_server_command, 1);
166
112k
  cmd->context.pool = pool;
167
112k
  cmd->context.cmd = cmd;
168
112k
  cmd->context.event = event_create(conn->event);
169
112k
  cmd->refcount = 1;
170
112k
  cmd->context.conn = conn;
171
112k
  cmd->context.server = conn->server;
172
112k
  cmd->replies_expected = 1;
173
174
112k
  DLLIST2_APPEND(&conn->command_queue_head,
175
112k
           &conn->command_queue_tail, cmd);
176
112k
  conn->command_queue_count++;
177
178
112k
  return cmd;
179
112k
}
180
181
struct smtp_server_command *
182
smtp_server_command_new_invalid(struct smtp_server_connection *conn)
183
14.5k
{
184
14.5k
  struct smtp_server_command *cmd;
185
186
14.5k
  cmd = smtp_server_command_alloc(conn);
187
14.5k
  smtp_server_command_update_event(cmd);
188
189
14.5k
  e_debug(cmd->context.event, "Invalid command");
190
191
14.5k
  return cmd;
192
14.5k
}
193
194
struct smtp_server_command *
195
smtp_server_command_new(struct smtp_server_connection *conn,
196
      const char *name)
197
97.9k
{
198
97.9k
  struct smtp_server *server = conn->server;
199
97.9k
  struct smtp_server_command *cmd;
200
201
97.9k
  cmd = smtp_server_command_alloc(conn);
202
97.9k
  cmd->context.name = p_strdup(cmd->context.pool, name);
203
97.9k
  cmd->reg = smtp_server_command_find(server, name);
204
205
97.9k
  smtp_server_command_update_event(cmd);
206
207
97.9k
  e_debug(cmd->context.event, "New command");
208
209
97.9k
  return cmd;
210
97.9k
}
211
212
void smtp_server_command_execute(struct smtp_server_command *cmd,
213
         const char *params)
214
97.9k
{
215
97.9k
  struct smtp_server_connection *conn = cmd->context.conn;
216
217
97.9k
  event_add_str(cmd->context.event, "cmd_args", params);
218
97.9k
  event_add_str(cmd->context.event, "cmd_human_args", params);
219
220
97.9k
  struct event_passthrough *e =
221
97.9k
    event_create_passthrough(cmd->context.event)->
222
97.9k
    set_name("smtp_server_command_started");
223
97.9k
  e_debug(e->event(), "Execute command");
224
225
97.9k
  if (cmd->reg == NULL) {
226
    /* RFC 5321, Section 4.2.4: Reply Code 502
227
228
       Questions have been raised as to when reply code 502 (Command
229
       not implemented) SHOULD be returned in preference to other
230
       codes. 502 SHOULD be used when the command is actually
231
       recognized by the SMTP server, but not implemented. If the
232
       command is not recognized, code 500 SHOULD be returned.
233
     */
234
21.6k
    smtp_server_command_fail(cmd,
235
21.6k
      500, "5.5.1", "Unknown command");
236
237
76.2k
  } else if (!conn->ssl_secured && conn->set.tls_required &&
238
0
       (cmd->reg->flags & SMTP_SERVER_CMD_FLAG_PRETLS) == 0) {
239
    /* RFC 3207, Section 4:
240
241
       A SMTP server that is not publicly referenced may choose to
242
       require that the client perform a TLS negotiation before
243
       accepting any commands. In this case, the server SHOULD
244
       return the reply code:
245
246
       530 Must issue a STARTTLS command first
247
248
       to every command other than NOOP, EHLO, STARTTLS, or QUIT. If
249
       the client and server are using the ENHANCEDSTATUSCODES ESMTP
250
       extension [RFC2034], the status code to be returned SHOULD be
251
       5.7.0.
252
     */
253
0
    smtp_server_command_fail(cmd,
254
0
      530, "5.7.0", "TLS required.");
255
256
76.2k
  } else if (!conn->authenticated && !conn->set.auth_optional &&
257
0
       (cmd->reg->flags & SMTP_SERVER_CMD_FLAG_PREAUTH) == 0) {
258
    /* RFC 4954, Section 6: Status Codes
259
260
       530 5.7.0  Authentication required
261
262
       This response SHOULD be returned by any command other than
263
       AUTH, EHLO, HELO, NOOP, RSET, or QUIT when server policy
264
       requires authentication in order to perform the requested
265
       action and authentication is not currently in force.
266
     */
267
0
    smtp_server_command_fail(cmd,
268
0
      530, "5.7.0", "Authentication required.");
269
270
76.2k
  } else {
271
76.2k
    struct smtp_server_command *tmp_cmd = cmd;
272
273
76.2k
    i_assert(cmd->reg->func != NULL);
274
76.2k
    smtp_server_command_ref(tmp_cmd);
275
76.2k
    cmd->reg->func(&tmp_cmd->context, params);
276
76.2k
    if (tmp_cmd->state == SMTP_SERVER_COMMAND_STATE_NEW)
277
4.53k
      tmp_cmd->state = SMTP_SERVER_COMMAND_STATE_PROCESSING;
278
76.2k
    if (!smtp_server_command_unref(&tmp_cmd))
279
0
      cmd = NULL;
280
76.2k
  }
281
97.9k
}
282
283
void smtp_server_command_ref(struct smtp_server_command *cmd)
284
794k
{
285
794k
  if (cmd->destroying)
286
0
    return;
287
794k
  cmd->refcount++;
288
794k
}
289
290
bool smtp_server_command_unref(struct smtp_server_command **_cmd)
291
907k
{
292
907k
  struct smtp_server_command *cmd = *_cmd;
293
907k
  struct smtp_server_connection *conn = cmd->context.conn;
294
295
907k
  *_cmd = NULL;
296
297
907k
  if (cmd->destroying)
298
0
    return FALSE;
299
300
907k
  i_assert(cmd->refcount > 0);
301
907k
  if (--cmd->refcount > 0)
302
794k
    return TRUE;
303
112k
  cmd->destroying = TRUE;
304
305
112k
  if (cmd->state >= SMTP_SERVER_COMMAND_STATE_FINISHED) {
306
112k
    e_debug(cmd->context.event, "Destroy");
307
112k
  } else {
308
0
    struct event_passthrough *e =
309
0
      event_create_passthrough(cmd->context.event)->
310
0
      set_name("smtp_server_command_finished");
311
0
    e->add_int("status_code", 9000);
312
0
    e->add_str("enhanced_code", "9.0.0");
313
0
    e->add_str("error", "Aborted");
314
0
    e_debug(e->event(), "Destroy");
315
316
0
    cmd->state = SMTP_SERVER_COMMAND_STATE_ABORTED;
317
0
    DLLIST2_REMOVE(&conn->command_queue_head,
318
0
      &conn->command_queue_tail, cmd);
319
0
    conn->command_queue_count--;
320
0
  }
321
322
  /* Execute hooks */
323
112k
  if (!smtp_server_command_call_hooks(
324
112k
    &cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, TRUE))
325
0
    i_unreached();
326
327
112k
  smtp_server_command_pipeline_unblock(&cmd->context);
328
329
112k
  smtp_server_reply_free(cmd);
330
112k
  event_unref(&cmd->context.event);
331
112k
  pool_unref(&cmd->context.pool);
332
112k
  return FALSE;
333
112k
}
334
335
void smtp_server_command_abort(struct smtp_server_command **_cmd)
336
3.86k
{
337
3.86k
  struct smtp_server_command *cmd = *_cmd;
338
3.86k
  struct smtp_server_connection *conn = cmd->context.conn;
339
340
  /* Preemptively remove command from queue (references may still exist)
341
   */
342
3.86k
  if (cmd->state >= SMTP_SERVER_COMMAND_STATE_FINISHED) {
343
0
    e_debug(cmd->context.event, "Abort");
344
3.86k
  } else {
345
3.86k
    struct event_passthrough *e =
346
3.86k
      event_create_passthrough(cmd->context.event)->
347
3.86k
      set_name("smtp_server_command_finished");
348
3.86k
    e->add_int("status_code", 9000);
349
3.86k
    e->add_str("enhanced_code", "9.0.0");
350
3.86k
    e->add_str("error", "Aborted");
351
3.86k
    e_debug(e->event(), "Abort");
352
353
3.86k
    cmd->state = SMTP_SERVER_COMMAND_STATE_ABORTED;
354
3.86k
    DLLIST2_REMOVE(&conn->command_queue_head,
355
3.86k
      &conn->command_queue_tail, cmd);
356
3.86k
    conn->command_queue_count--;
357
3.86k
  }
358
3.86k
  smtp_server_reply_free(cmd);
359
360
3.86k
  smtp_server_command_pipeline_unblock(&cmd->context);
361
3.86k
  smtp_server_command_unref(_cmd);
362
3.86k
}
363
364
#undef smtp_server_command_add_hook
365
void smtp_server_command_add_hook(struct smtp_server_command *cmd,
366
          enum smtp_server_command_hook_type type,
367
          smtp_server_cmd_func_t func,
368
          void *context)
369
133k
{
370
133k
  struct smtp_server_command_hook *hook;
371
372
133k
  i_assert(func != NULL);
373
374
133k
  hook = cmd->hooks_head;
375
275k
  while (hook != NULL) {
376
    /* No double registrations */
377
141k
    i_assert(hook->type != type || hook->func != func);
378
379
141k
    hook = hook->next;
380
141k
  }
381
382
133k
  hook = p_new(cmd->context.pool, struct smtp_server_command_hook, 1);
383
133k
  hook->type = type;
384
133k
  hook->func = func;
385
133k
  hook->context = context;
386
387
133k
  DLLIST2_APPEND(&cmd->hooks_head, &cmd->hooks_tail, hook);
388
133k
}
389
390
#undef smtp_server_command_remove_hook
391
void smtp_server_command_remove_hook(struct smtp_server_command *cmd,
392
             enum smtp_server_command_hook_type type,
393
             smtp_server_cmd_func_t *func)
394
76
{
395
76
  struct smtp_server_command_hook *hook;
396
76
  bool found = FALSE;
397
398
76
  hook = cmd->hooks_head;
399
76
  while (hook != NULL) {
400
76
    struct smtp_server_command_hook *hook_next = hook->next;
401
402
76
    if (hook->type == type && hook->func == func) {
403
76
      DLLIST2_REMOVE(&cmd->hooks_head, &cmd->hooks_tail,
404
76
               hook);
405
76
      found = TRUE;
406
76
      break;
407
76
    }
408
409
0
    hook = hook_next;
410
0
  }
411
76
  i_assert(found);
412
76
}
413
414
bool smtp_server_command_call_hooks(struct smtp_server_command **_cmd,
415
            enum smtp_server_command_hook_type type,
416
            bool remove)
417
578k
{
418
578k
  struct smtp_server_command *cmd = *_cmd;
419
578k
  struct smtp_server_command_hook *hook;
420
421
578k
  if (type != SMTP_SERVER_COMMAND_HOOK_DESTROY) {
422
466k
    if (cmd->state >= SMTP_SERVER_COMMAND_STATE_FINISHED)
423
0
      return FALSE;
424
466k
    smtp_server_command_ref(cmd);
425
466k
  }
426
427
578k
  hook = cmd->hooks_head;
428
1.05M
  while (hook != NULL) {
429
472k
    struct smtp_server_command_hook *hook_next = hook->next;
430
431
472k
    if (hook->type == type) {
432
131k
      if (remove) {
433
131k
        DLLIST2_REMOVE(&cmd->hooks_head,
434
131k
                 &cmd->hooks_tail, hook);
435
131k
      }
436
131k
      hook->func(&cmd->context, hook->context);
437
131k
    }
438
439
472k
    hook = hook_next;
440
472k
  }
441
442
578k
  if (type != SMTP_SERVER_COMMAND_HOOK_DESTROY) {
443
466k
    if (!smtp_server_command_unref(&cmd)) {
444
498
      *_cmd = NULL;
445
498
      return FALSE;
446
498
    }
447
466k
  }
448
578k
  return TRUE;
449
578k
}
450
451
void smtp_server_command_remove_hooks(struct smtp_server_command *cmd,
452
              enum smtp_server_command_hook_type type)
453
130k
{
454
130k
  struct smtp_server_command_hook *hook;
455
456
130k
  hook = cmd->hooks_head;
457
217k
  while (hook != NULL) {
458
87.2k
    struct smtp_server_command_hook *hook_next = hook->next;
459
460
87.2k
    if (hook->type == type) {
461
0
      DLLIST2_REMOVE(&cmd->hooks_head, &cmd->hooks_tail,
462
0
               hook);
463
0
    }
464
465
87.2k
    hook = hook_next;
466
87.2k
  }
467
130k
}
468
469
void smtp_server_command_set_reply_count(struct smtp_server_command *cmd,
470
  unsigned int count)
471
0
{
472
0
  i_assert(count > 0);
473
0
  i_assert(!array_is_created(&cmd->replies));
474
0
  cmd->replies_expected = count;
475
0
}
476
477
unsigned int
478
smtp_server_command_get_reply_count(struct smtp_server_command *cmd)
479
0
{
480
0
  i_assert(cmd->replies_expected > 0);
481
0
  return cmd->replies_expected;
482
0
}
483
484
bool smtp_server_command_next_to_reply(struct smtp_server_command **_cmd)
485
131k
{
486
131k
  struct smtp_server_command *cmd = *_cmd;
487
488
131k
  e_debug(cmd->context.event, "Next to reply");
489
490
131k
  if (!smtp_server_command_call_hooks(
491
131k
    _cmd, SMTP_SERVER_COMMAND_HOOK_NEXT, TRUE))
492
498
    return FALSE;
493
494
130k
  smtp_server_command_remove_hooks(cmd, SMTP_SERVER_COMMAND_HOOK_NEXT);
495
130k
  return TRUE;
496
131k
}
497
498
void smtp_server_command_ready_to_reply(struct smtp_server_command *cmd)
499
111k
{
500
111k
  cmd->state = SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY;
501
111k
  e_debug(cmd->context.event, "Ready to reply");
502
111k
  smtp_server_connection_trigger_output(cmd->context.conn);
503
111k
}
504
505
static bool
506
smtp_server_command_replied(struct smtp_server_command **_cmd)
507
113k
{
508
113k
  struct smtp_server_command *cmd = *_cmd;
509
510
113k
  if (cmd->replies_submitted < cmd->replies_expected) {
511
0
    e_debug(cmd->context.event, "Replied (one)");
512
513
0
    return smtp_server_command_call_hooks(
514
0
      _cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED_ONE, FALSE);
515
0
  }
516
517
113k
  e_debug(cmd->context.event, "Replied");
518
519
113k
  return (smtp_server_command_call_hooks(
520
113k
      _cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED_ONE, TRUE) &&
521
113k
    smtp_server_command_call_hooks(
522
113k
      _cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED, TRUE));
523
113k
}
524
525
bool smtp_server_command_completed(struct smtp_server_command **_cmd)
526
108k
{
527
108k
  struct smtp_server_command *cmd = *_cmd;
528
529
108k
  if (cmd->replies_submitted < cmd->replies_expected)
530
0
    return TRUE;
531
532
108k
  e_debug(cmd->context.event, "Completed");
533
534
108k
  if (cmd->pipeline_blocked)
535
15.5k
    smtp_server_command_pipeline_unblock(&cmd->context);
536
537
108k
  return smtp_server_command_call_hooks(
538
108k
    _cmd, SMTP_SERVER_COMMAND_HOOK_COMPLETED, TRUE);
539
108k
}
540
541
static bool
542
smtp_server_command_handle_reply(struct smtp_server_command *cmd)
543
113k
{
544
113k
  struct smtp_server_connection *conn = cmd->context.conn;
545
546
113k
  smtp_server_connection_ref(conn);
547
548
113k
  if (!smtp_server_command_replied(&cmd))
549
0
    return smtp_server_connection_unref(&conn);
550
551
113k
  if (cmd->input_locked)
552
12.5k
    smtp_server_command_input_unlock(&cmd->context);
553
554
  /* Submit reply */
555
113k
  switch (cmd->state) {
556
107k
  case SMTP_SERVER_COMMAND_STATE_NEW:
557
111k
  case SMTP_SERVER_COMMAND_STATE_PROCESSING:
558
111k
    if (!smtp_server_command_is_complete(cmd)) {
559
2.84k
      e_debug(cmd->context.event, "Not ready to reply");
560
2.84k
      cmd->state = SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY;
561
2.84k
      break;
562
2.84k
    }
563
109k
    smtp_server_command_ready_to_reply(cmd);
564
109k
    break;
565
1.35k
  case SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY:
566
1.35k
  case SMTP_SERVER_COMMAND_STATE_ABORTED:
567
1.35k
    break;
568
0
  default:
569
0
    i_unreached();
570
113k
  }
571
572
113k
  return smtp_server_connection_unref(&conn);
573
113k
}
574
575
void smtp_server_command_submit_reply(struct smtp_server_command *cmd)
576
113k
{
577
113k
  struct smtp_server_connection *conn = cmd->context.conn;
578
113k
  unsigned int i, submitted;
579
113k
  bool is_bad = FALSE;
580
581
113k
  i_assert(conn != NULL && array_is_created(&cmd->replies));
582
583
113k
  submitted = 0;
584
226k
  for (i = 0; i < cmd->replies_expected; i++) {
585
113k
    const struct smtp_server_reply *reply =
586
113k
      array_idx(&cmd->replies, i);
587
113k
    if (!reply->submitted)
588
0
      continue;
589
113k
    submitted++;
590
591
113k
    i_assert(reply->content != NULL);
592
113k
    switch (reply->content->status) {
593
36.2k
    case 500:
594
57.3k
    case 501:
595
64.2k
    case 503:
596
64.2k
      is_bad = TRUE;
597
64.2k
      break;
598
113k
    }
599
113k
  }
600
601
113k
  i_assert(submitted == cmd->replies_submitted);
602
603
  /* Limit number of consecutive bad commands */
604
113k
  if (is_bad)
605
64.2k
    conn->bad_counter++;
606
48.9k
  else if (cmd->replies_submitted == cmd->replies_expected)
607
48.9k
    conn->bad_counter = 0;
608
609
113k
  if (!smtp_server_command_handle_reply(cmd))
610
0
    return;
611
612
113k
  if (conn != NULL && conn->bad_counter > conn->set.max_bad_commands) {
613
258
    smtp_server_connection_terminate(&conn,
614
258
      "4.7.0", "Too many invalid commands.");
615
258
    return;
616
258
  }
617
113k
}
618
619
bool smtp_server_command_is_replied(struct smtp_server_command *cmd)
620
140k
{
621
140k
  unsigned int i;
622
623
140k
  if (!array_is_created(&cmd->replies))
624
78.7k
    return FALSE;
625
626
123k
  for (i = 0; i < cmd->replies_expected; i++) {
627
61.9k
    const struct smtp_server_reply *reply =
628
61.9k
      array_idx(&cmd->replies, i);
629
61.9k
    if (!reply->submitted)
630
0
      return FALSE;
631
61.9k
  }
632
633
61.9k
  return TRUE;
634
61.9k
}
635
636
bool smtp_server_command_reply_is_forwarded(struct smtp_server_command *cmd)
637
0
{
638
0
  unsigned int i;
639
640
0
  if (!array_is_created(&cmd->replies))
641
0
    return FALSE;
642
643
0
  for (i = 0; i < cmd->replies_expected; i++) {
644
0
    const struct smtp_server_reply *reply =
645
0
      array_idx(&cmd->replies, i);
646
0
    if (!reply->submitted)
647
0
      return FALSE;
648
0
    if (reply->forwarded)
649
0
      return TRUE;
650
0
  }
651
652
0
  return FALSE;
653
0
}
654
655
struct smtp_server_reply *
656
smtp_server_command_get_reply(struct smtp_server_command *cmd,
657
            unsigned int idx)
658
17.5k
{
659
17.5k
  struct smtp_server_reply *reply;
660
661
17.5k
  i_assert(idx < cmd->replies_expected);
662
663
17.5k
  if (!array_is_created(&cmd->replies))
664
0
    return NULL;
665
666
17.5k
  reply = array_idx_get_space(&cmd->replies, idx);
667
17.5k
  if (!reply->submitted)
668
0
    return NULL;
669
17.5k
  return reply;
670
17.5k
}
671
672
bool smtp_server_command_reply_status_equals(struct smtp_server_command *cmd,
673
               unsigned int status)
674
0
{
675
0
  struct smtp_server_reply *reply;
676
677
0
  i_assert(cmd->replies_expected == 1);
678
0
  reply = smtp_server_command_get_reply(cmd, 0);
679
680
0
  return (reply->content != NULL && reply->content->status == status);
681
0
}
682
683
bool smtp_server_command_replied_success(struct smtp_server_command *cmd)
684
89.0k
{
685
89.0k
  bool success = FALSE;
686
89.0k
  unsigned int i;
687
688
89.0k
  if (!array_is_created(&cmd->replies))
689
0
    return FALSE;
690
691
178k
  for (i = 0; i < cmd->replies_expected; i++) {
692
89.0k
    const struct smtp_server_reply *reply =
693
89.0k
      array_idx(&cmd->replies, i);
694
89.0k
    if (!reply->submitted)
695
0
      return FALSE;
696
89.0k
    if (smtp_server_reply_is_success(reply))
697
71.1k
      success = TRUE;
698
89.0k
  }
699
700
89.0k
  return success;
701
89.0k
}
702
703
static int
704
smtp_server_command_send_more_replies(struct smtp_server_command *cmd)
705
108k
{
706
108k
  unsigned int i;
707
108k
  int ret = 1;
708
709
108k
  smtp_server_command_ref(cmd);
710
711
  // FIXME: handle LMTP DATA command with enormous number of recipients;
712
  // i.e. don't keep filling output stream with replies indefinitely.
713
217k
  for (i = 0; i < cmd->replies_expected; i++) {
714
108k
    struct smtp_server_reply *reply;
715
716
108k
    reply = array_idx_modifiable(&cmd->replies, i);
717
718
108k
    if (!reply->submitted) {
719
0
      i_assert(!reply->sent);
720
0
      ret = 0;
721
0
      break;
722
0
    }
723
108k
    if (smtp_server_reply_send(reply) < 0) {
724
0
      ret = -1;
725
0
      break;
726
0
    }
727
108k
  }
728
729
108k
  if (!smtp_server_command_unref(&cmd))
730
0
    return -1;
731
108k
  return ret;
732
108k
}
733
734
bool smtp_server_command_send_replies(struct smtp_server_command *cmd)
735
109k
{
736
109k
  int ret;
737
738
109k
  if (!smtp_server_command_next_to_reply(&cmd))
739
498
    return FALSE;
740
109k
  if (cmd->state < SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY)
741
475
    return FALSE;
742
743
109k
  i_assert(cmd->state == SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY &&
744
108k
     array_is_created(&cmd->replies));
745
746
108k
  if (!smtp_server_command_completed(&cmd))
747
0
    return TRUE;
748
749
  /* Send command replies */
750
108k
  ret = smtp_server_command_send_more_replies(cmd);
751
108k
  if (ret < 0)
752
0
    return FALSE;
753
108k
  if (ret == 0) {
754
0
    cmd->state = SMTP_SERVER_COMMAND_STATE_PROCESSING;
755
0
    return FALSE;
756
0
  }
757
758
108k
  smtp_server_command_finished(cmd);
759
108k
  return TRUE;
760
108k
}
761
762
void smtp_server_command_finished(struct smtp_server_command *cmd)
763
108k
{
764
108k
  struct smtp_server_connection *conn = cmd->context.conn;
765
108k
  struct smtp_server_reply *reply;
766
767
108k
  i_assert(cmd->state < SMTP_SERVER_COMMAND_STATE_FINISHED);
768
108k
  cmd->state = SMTP_SERVER_COMMAND_STATE_FINISHED;
769
770
108k
  DLLIST2_REMOVE(&conn->command_queue_head,
771
108k
           &conn->command_queue_tail, cmd);
772
108k
  conn->command_queue_count--;
773
108k
  conn->stats.reply_count++;
774
775
108k
  i_assert(array_is_created(&cmd->replies));
776
108k
  reply = array_front_modifiable(&cmd->replies);
777
108k
  i_assert(reply->content != NULL);
778
779
108k
  struct event_passthrough *e =
780
108k
    event_create_passthrough(cmd->context.event)->
781
108k
    set_name("smtp_server_command_finished");
782
108k
  smtp_server_reply_add_to_event(reply, e);
783
108k
  e_debug(e->event(), "Finished");
784
785
108k
  if (reply->content->status == 221 || reply->content->status == 421) {
786
51
    i_assert(cmd->replies_expected == 1);
787
51
    if (reply->content->status == 421) {
788
0
      smtp_server_connection_close(&conn, t_strdup_printf(
789
0
        "Server closed the connection: %s",
790
0
        smtp_server_reply_get_one_line(reply)));
791
51
    } else {
792
51
      smtp_server_connection_close(&conn, "Logged out");
793
51
    }
794
51
    smtp_server_command_unref(&cmd);
795
51
    return;
796
51
  }
797
108k
  if (cmd->input_locked)
798
1.17k
    smtp_server_command_input_unlock(&cmd->context);
799
108k
  if (cmd->pipeline_blocked)
800
0
    smtp_server_command_pipeline_unblock(&cmd->context);
801
802
108k
  smtp_server_command_unref(&cmd);
803
108k
  smtp_server_connection_trigger_output(conn);
804
108k
}
805
806
void smtp_server_command_fail(struct smtp_server_command *cmd,
807
            unsigned int status, const char *enh_code,
808
            const char *fmt, ...)
809
41.7k
{
810
41.7k
  unsigned int i;
811
41.7k
  va_list args;
812
813
41.7k
  i_assert(status / 100 > 2);
814
815
41.7k
  va_start(args, fmt);
816
41.7k
  if (cmd->replies_expected == 1) {
817
41.7k
    smtp_server_reply_indexv(&cmd->context, 0,
818
41.7k
           status, enh_code, fmt, args);
819
41.7k
  } else for (i = 0; i < cmd->replies_expected; i++) {
820
0
    bool sent = FALSE;
821
822
0
    if (array_is_created(&cmd->replies)) {
823
0
      const struct smtp_server_reply *reply =
824
0
        array_idx(&cmd->replies, i);
825
0
      sent = reply->sent;
826
0
    }
827
828
    /* Send the same reply for all */
829
0
    if (!sent) {
830
0
      va_list args_copy;
831
0
      VA_COPY(args_copy, args);
832
0
      smtp_server_reply_indexv(&cmd->context, i,
833
0
        status, enh_code, fmt, args_copy);
834
0
      va_end(args_copy);
835
0
    }
836
0
  }
837
41.7k
  va_end(args);
838
41.7k
}
839
840
void smtp_server_command_input_lock(struct smtp_server_cmd_ctx *cmd)
841
29.5k
{
842
29.5k
  struct smtp_server_command *command = cmd->cmd;
843
29.5k
  struct smtp_server_connection *conn = cmd->conn;
844
845
29.5k
  command->input_locked = TRUE;
846
29.5k
  smtp_server_connection_input_halt(conn);
847
29.5k
}
848
849
void smtp_server_command_input_unlock(struct smtp_server_cmd_ctx *cmd)
850
15.6k
{
851
15.6k
  struct smtp_server_command *command = cmd->cmd;
852
15.6k
  struct smtp_server_connection *conn = cmd->conn;
853
854
15.6k
  command->input_locked = FALSE;
855
15.6k
  if (command->input_captured) {
856
9.89k
    command->input_captured = FALSE;
857
9.89k
    smtp_server_connection_input_halt(conn);
858
9.89k
  }
859
15.6k
  smtp_server_connection_input_resume(conn);
860
15.6k
}
861
862
void smtp_server_command_input_capture(
863
  struct smtp_server_cmd_ctx *cmd,
864
  smtp_server_cmd_input_callback_t *callback)
865
10.5k
{
866
10.5k
  struct smtp_server_command *command = cmd->cmd;
867
10.5k
  struct smtp_server_connection *conn = cmd->conn;
868
869
10.5k
  smtp_server_connection_input_capture(conn, *callback, cmd);
870
10.5k
  command->input_locked = TRUE;
871
10.5k
  command->input_captured = TRUE;
872
10.5k
}
873
874
void smtp_server_command_pipeline_block(struct smtp_server_cmd_ctx *cmd)
875
16.0k
{
876
16.0k
  struct smtp_server_command *command = cmd->cmd;
877
16.0k
  struct smtp_server_connection *conn = cmd->conn;
878
879
16.0k
  e_debug(cmd->event, "Pipeline blocked");
880
881
16.0k
  command->pipeline_blocked = TRUE;
882
16.0k
  smtp_server_connection_input_halt(conn);
883
16.0k
}
884
885
void smtp_server_command_pipeline_unblock(struct smtp_server_cmd_ctx *cmd)
886
131k
{
887
131k
  struct smtp_server_command *command = cmd->cmd;
888
131k
  struct smtp_server_connection *conn = cmd->conn;
889
890
131k
  if (!command->pipeline_blocked)
891
115k
    return;
892
893
131k
  e_debug(cmd->event, "Pipeline unblocked");
894
895
16.0k
  command->pipeline_blocked = FALSE;
896
16.0k
  smtp_server_connection_input_resume(conn);
897
16.0k
}