Coverage Report

Created: 2026-09-03 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-smtp/smtp-server-cmd-rcpt.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "str.h"
5
#include "array.h"
6
#include "smtp-parser.h"
7
#include "smtp-address.h"
8
#include "smtp-reply.h"
9
#include "smtp-syntax.h"
10
11
#include "smtp-server-private.h"
12
#include "settings-consts.h"
13
14
/* RCPT command */
15
16
struct smtp_server_cmd_rcpt {
17
  struct smtp_server_recipient *rcpt;
18
};
19
20
static void
21
cmd_rcpt_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
22
     struct smtp_server_cmd_rcpt *data)
23
13.0k
{
24
13.0k
  smtp_server_recipient_destroy(&data->rcpt);
25
13.0k
}
26
27
static bool
28
cmd_rcpt_check_state(struct smtp_server_cmd_ctx *cmd, bool next_to_reply)
29
33.3k
{
30
33.3k
  struct smtp_server_connection *conn = cmd->conn;
31
33.3k
  struct smtp_server_command *command = cmd->cmd;
32
33.3k
  struct smtp_server_transaction *trans = conn->state.trans;
33
34
33.3k
  if (smtp_server_command_is_replied(command) &&
35
12.9k
      !smtp_server_command_replied_success(command) &&
36
0
      !smtp_server_command_reply_is_forwarded(command))
37
0
    return FALSE;
38
39
33.3k
  if (trans == NULL &&
40
8.67k
      (conn->state.pending_mail_cmds == 0 || next_to_reply)) {
41
429
    smtp_server_reply(cmd,
42
429
      503, "5.5.0", "MAIL needed first");
43
429
    return FALSE;
44
429
  }
45
33.3k
  i_assert(conn->set.max_recipients != 0);
46
32.8k
  if (conn->set.max_recipients != SET_UINT_UNLIMITED &&
47
0
      trans != NULL &&
48
0
      smtp_server_transaction_rcpt_count(trans) >=
49
0
      conn->set.max_recipients) {
50
0
    smtp_server_reply(cmd,
51
0
      451, "4.5.3", "Too many recipients");
52
0
    return FALSE;
53
0
  }
54
55
32.8k
  return TRUE;
56
32.8k
}
57
58
static void
59
cmd_rcpt_replied(struct smtp_server_cmd_ctx *cmd,
60
     struct smtp_server_cmd_rcpt *data)
61
13.0k
{
62
13.0k
  struct smtp_server_recipient *rcpt = data->rcpt;
63
64
13.0k
  smtp_server_recipient_replied(
65
13.0k
    rcpt, smtp_server_command_get_reply(cmd->cmd, 0));
66
13.0k
}
67
68
static void
69
cmd_rcpt_completed(struct smtp_server_cmd_ctx *cmd,
70
       struct smtp_server_cmd_rcpt *data)
71
12.9k
{
72
12.9k
  struct smtp_server_connection *conn = cmd->conn;
73
12.9k
  struct smtp_server_command *command = cmd->cmd;
74
12.9k
  struct smtp_server_recipient *rcpt = data->rcpt;
75
76
12.9k
  i_assert(conn->state.pending_rcpt_cmds > 0);
77
12.9k
  conn->state.pending_rcpt_cmds--;
78
79
12.9k
  i_assert(smtp_server_command_is_replied(command));
80
12.9k
  i_assert(conn->state.state == SMTP_SERVER_STATE_RCPT_TO ||
81
12.9k
     !smtp_server_command_replied_success(command));
82
83
12.9k
  if (!smtp_server_command_replied_success(command)) {
84
    /* Failure */
85
0
    conn->state.denied_rcpt_cmds++;
86
0
    smtp_server_recipient_denied(
87
0
      rcpt, smtp_server_command_get_reply(cmd->cmd, 0));
88
0
    return;
89
0
  }
90
91
  /* Success */
92
12.9k
  data->rcpt = NULL; /* clear to prevent destruction */
93
12.9k
  (void)smtp_server_recipient_approved(&rcpt);
94
12.9k
}
95
96
static void
97
cmd_rcpt_recheck(struct smtp_server_cmd_ctx *cmd,
98
     struct smtp_server_cmd_rcpt *data ATTR_UNUSED)
99
12.9k
{
100
12.9k
  struct smtp_server_connection *conn = cmd->conn;
101
102
  /* All preceding commands have finished and now the transaction state
103
     is clear. This provides the opportunity to re-check the transaction
104
     state and abort the pending proxied mail command if it is bound to
105
     fail */
106
12.9k
  if (!cmd_rcpt_check_state(cmd, TRUE))
107
0
    return;
108
109
  /* Advance state */
110
12.9k
  smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_RCPT_TO,
111
12.9k
           smtp_address_encode(data->rcpt->path));
112
12.9k
}
113
114
void smtp_server_cmd_rcpt(struct smtp_server_cmd_ctx *cmd,
115
        const char *params)
116
20.3k
{
117
20.3k
  struct smtp_server_connection *conn = cmd->conn;
118
20.3k
  const struct smtp_server_settings *set = &conn->set;
119
20.3k
  enum smtp_capability caps = set->capabilities;
120
20.3k
  const struct smtp_server_callbacks *callbacks = conn->callbacks;
121
20.3k
  struct smtp_server_command *command = cmd->cmd;
122
20.3k
  struct smtp_server_cmd_rcpt *rcpt_data;
123
20.3k
  struct smtp_server_recipient *rcpt;
124
20.3k
  enum smtp_address_parse_flags path_parse_flags;
125
20.3k
  enum smtp_param_rcpt_parse_flags param_parse_flags;
126
20.3k
  const char *const *param_extensions = NULL;
127
20.3k
  struct smtp_address *path;
128
20.3k
  struct smtp_params_rcpt rcpt_params;
129
20.3k
  enum smtp_param_parse_error pperror;
130
20.3k
  const char *error;
131
20.3k
  int ret;
132
133
  /* rcpt         = "RCPT TO:" ( "<Postmaster@" Domain ">" /
134
        "<Postmaster>" / Forward-path ) [SP Rcpt-parameters] CRLF
135
     Forward-path = Path
136
   */
137
138
  /* Check transaction state as far as possible */
139
20.3k
  if (!cmd_rcpt_check_state(cmd, FALSE))
140
429
    return;
141
142
  /* ( "<Postmaster@" Domain ">" / "<Postmaster>" / Forward-path ) */
143
19.9k
  if (params == NULL || !str_begins_icase(params, "TO:", &params)) {
144
1.28k
    smtp_server_reply(cmd,
145
1.28k
      501, "5.5.4", "Invalid parameters");
146
1.28k
    return;
147
1.28k
  }
148
18.6k
  if (params[0] != ' ' && params[0] != '\t') {
149
    /* no whitespace */
150
18.2k
  } else if ((set->workarounds &
151
401
        SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH) != 0) {
152
0
    while (*params == ' ' || *params == '\t')
153
0
      params++;
154
401
  } else {
155
401
    smtp_server_reply(cmd, 501, "5.5.4",
156
401
          "Invalid TO: "
157
401
          "Unexpected whitespace before path");
158
401
    return;
159
401
  }
160
18.2k
  path_parse_flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART;
161
18.2k
  if ((set->workarounds & SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH) != 0)
162
0
    path_parse_flags |= SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL;
163
18.2k
  if (smtp_address_parse_path_full(pool_datastack_create(), params,
164
18.2k
           path_parse_flags, &path, &error,
165
18.2k
           &params) < 0) {
166
3.91k
    smtp_server_reply(cmd,
167
3.91k
      501, "5.5.4", "Invalid TO: %s", error);
168
3.91k
    return;
169
3.91k
  }
170
14.3k
  if (*params == ' ')
171
495
    params++;
172
13.8k
  else if (*params != '\0') {
173
149
    smtp_server_reply(cmd, 501, "5.5.4",
174
149
      "Invalid TO: Invalid character in path");
175
149
    return;
176
149
  }
177
14.2k
  if (path->domain == NULL && !conn->set.rcpt_domain_optional &&
178
989
    strcasecmp(path->localpart, "postmaster") != 0) {
179
728
    smtp_server_reply(cmd,
180
728
      501, "5.5.4", "Invalid TO: Missing domain");
181
728
    return;
182
728
  }
183
184
  /* [SP Rcpt-parameters] */
185
13.4k
  param_parse_flags = 0;
186
13.4k
  if (conn->set.rcpt_domain_optional)
187
0
    param_parse_flags |= SMTP_PARAM_RCPT_FLAG_ORCPT_ALLOW_LOCALPART;
188
13.4k
  if (array_is_created(&conn->rcpt_param_extensions))
189
0
    param_extensions = array_front(&conn->rcpt_param_extensions);
190
13.4k
  if (smtp_params_rcpt_parse(pool_datastack_create(), params,
191
13.4k
           param_parse_flags, caps, param_extensions,
192
13.4k
           &rcpt_params, &pperror, &error) < 0) {
193
485
    switch (pperror) {
194
70
    case SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX:
195
70
      smtp_server_reply(cmd,
196
70
        501, "5.5.4", "%s", error);
197
70
      break;
198
415
    case SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED:
199
415
      smtp_server_reply(cmd,
200
415
        555, "5.5.4", "%s", error);
201
415
      break;
202
0
    default:
203
0
      i_unreached();
204
485
    }
205
485
    return;
206
485
  }
207
208
13.0k
  rcpt = smtp_server_recipient_create(cmd, path, &rcpt_params);
209
210
13.0k
  rcpt_data = p_new(cmd->pool, struct smtp_server_cmd_rcpt, 1);
211
13.0k
  rcpt_data->rcpt = rcpt;
212
13.0k
  command->data = rcpt_data;
213
214
13.0k
  smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_NEXT,
215
13.0k
             cmd_rcpt_recheck, rcpt_data);
216
13.0k
  smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_REPLIED,
217
13.0k
             cmd_rcpt_replied, rcpt_data);
218
13.0k
  smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_COMPLETED,
219
13.0k
             cmd_rcpt_completed, rcpt_data);
220
13.0k
  smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_DESTROY,
221
13.0k
             cmd_rcpt_destroy, rcpt_data);
222
223
13.0k
  conn->state.pending_rcpt_cmds++;
224
225
13.0k
  smtp_server_command_ref(command);
226
13.0k
  i_assert(callbacks != NULL && callbacks->conn_cmd_rcpt != NULL);
227
228
13.0k
  struct event_reason *reason =
229
13.0k
    smtp_server_connection_reason_begin(conn, "cmd_rcpt");
230
13.0k
  ret = callbacks->conn_cmd_rcpt(conn->context, cmd, rcpt);
231
13.0k
  event_reason_end(&reason);
232
13.0k
  if (ret <= 0) {
233
0
    i_assert(ret == 0 || smtp_server_command_is_replied(command));
234
    /* Command is waiting for external event or it failed */
235
0
    smtp_server_command_unref(&command);
236
0
    return;
237
0
  }
238
13.0k
  if (!smtp_server_command_is_replied(command)) {
239
    /* Set generic RCPT success reply if none is provided */
240
13.0k
    smtp_server_cmd_rcpt_reply_success(cmd);
241
13.0k
  }
242
13.0k
  smtp_server_command_unref(&command);
243
13.0k
}
244
245
bool smtp_server_command_is_rcpt(struct smtp_server_cmd_ctx *cmd)
246
26.0k
{
247
26.0k
  return (cmd->cmd->reg->func == smtp_server_cmd_rcpt);
248
26.0k
}
249
250
void smtp_server_cmd_rcpt_reply_success(struct smtp_server_cmd_ctx *cmd)
251
13.0k
{
252
13.0k
  struct smtp_server_cmd_rcpt *rcpt_data = cmd->cmd->data;
253
254
13.0k
  i_assert(smtp_server_command_is_rcpt(cmd));
255
256
13.0k
  smtp_server_recipient_reply(rcpt_data->rcpt, 250, "2.1.5", "OK");
257
13.0k
}