/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 | 10.9k | { |
24 | 10.9k | smtp_server_recipient_destroy(&data->rcpt); |
25 | 10.9k | } |
26 | | |
27 | | static bool |
28 | | cmd_rcpt_check_state(struct smtp_server_cmd_ctx *cmd, bool next_to_reply) |
29 | 28.3k | { |
30 | 28.3k | struct smtp_server_connection *conn = cmd->conn; |
31 | 28.3k | struct smtp_server_command *command = cmd->cmd; |
32 | 28.3k | struct smtp_server_transaction *trans = conn->state.trans; |
33 | | |
34 | 28.3k | if (smtp_server_command_is_replied(command) && |
35 | 10.9k | !smtp_server_command_replied_success(command) && |
36 | 0 | !smtp_server_command_reply_is_forwarded(command)) |
37 | 0 | return FALSE; |
38 | | |
39 | 28.3k | if (trans == NULL && |
40 | 7.49k | (conn->state.pending_mail_cmds == 0 || next_to_reply)) { |
41 | 437 | smtp_server_reply(cmd, |
42 | 437 | 503, "5.5.0", "MAIL needed first"); |
43 | 437 | return FALSE; |
44 | 437 | } |
45 | 28.3k | i_assert(conn->set.max_recipients != 0); |
46 | 27.9k | 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 | 27.9k | return TRUE; |
56 | 27.9k | } |
57 | | |
58 | | static void |
59 | | cmd_rcpt_replied(struct smtp_server_cmd_ctx *cmd, |
60 | | struct smtp_server_cmd_rcpt *data) |
61 | 10.9k | { |
62 | 10.9k | struct smtp_server_recipient *rcpt = data->rcpt; |
63 | | |
64 | 10.9k | smtp_server_recipient_replied( |
65 | 10.9k | rcpt, smtp_server_command_get_reply(cmd->cmd, 0)); |
66 | 10.9k | } |
67 | | |
68 | | static void |
69 | | cmd_rcpt_completed(struct smtp_server_cmd_ctx *cmd, |
70 | | struct smtp_server_cmd_rcpt *data) |
71 | 10.9k | { |
72 | 10.9k | struct smtp_server_connection *conn = cmd->conn; |
73 | 10.9k | struct smtp_server_command *command = cmd->cmd; |
74 | 10.9k | struct smtp_server_recipient *rcpt = data->rcpt; |
75 | | |
76 | 10.9k | i_assert(conn->state.pending_rcpt_cmds > 0); |
77 | 10.9k | conn->state.pending_rcpt_cmds--; |
78 | | |
79 | 10.9k | i_assert(smtp_server_command_is_replied(command)); |
80 | 10.9k | i_assert(conn->state.state == SMTP_SERVER_STATE_RCPT_TO || |
81 | 10.9k | !smtp_server_command_replied_success(command)); |
82 | | |
83 | 10.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 | 10.9k | data->rcpt = NULL; /* clear to prevent destruction */ |
93 | 10.9k | (void)smtp_server_recipient_approved(&rcpt); |
94 | 10.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 | 10.9k | { |
100 | 10.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 | 10.9k | if (!cmd_rcpt_check_state(cmd, TRUE)) |
107 | 0 | return; |
108 | | |
109 | | /* Advance state */ |
110 | 10.9k | smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_RCPT_TO, |
111 | 10.9k | smtp_address_encode(data->rcpt->path)); |
112 | 10.9k | } |
113 | | |
114 | | void smtp_server_cmd_rcpt(struct smtp_server_cmd_ctx *cmd, |
115 | | const char *params) |
116 | 17.4k | { |
117 | 17.4k | struct smtp_server_connection *conn = cmd->conn; |
118 | 17.4k | const struct smtp_server_settings *set = &conn->set; |
119 | 17.4k | enum smtp_capability caps = set->capabilities; |
120 | 17.4k | const struct smtp_server_callbacks *callbacks = conn->callbacks; |
121 | 17.4k | struct smtp_server_command *command = cmd->cmd; |
122 | 17.4k | struct smtp_server_cmd_rcpt *rcpt_data; |
123 | 17.4k | struct smtp_server_recipient *rcpt; |
124 | 17.4k | enum smtp_address_parse_flags path_parse_flags; |
125 | 17.4k | enum smtp_param_rcpt_parse_flags param_parse_flags; |
126 | 17.4k | const char *const *param_extensions = NULL; |
127 | 17.4k | struct smtp_address *path; |
128 | 17.4k | struct smtp_params_rcpt rcpt_params; |
129 | 17.4k | enum smtp_param_parse_error pperror; |
130 | 17.4k | const char *error; |
131 | 17.4k | 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 | 17.4k | if (!cmd_rcpt_check_state(cmd, FALSE)) |
140 | 437 | return; |
141 | | |
142 | | /* ( "<Postmaster@" Domain ">" / "<Postmaster>" / Forward-path ) */ |
143 | 17.0k | if (params == NULL || !str_begins_icase(params, "TO:", ¶ms)) { |
144 | 779 | smtp_server_reply(cmd, |
145 | 779 | 501, "5.5.4", "Invalid parameters"); |
146 | 779 | return; |
147 | 779 | } |
148 | 16.2k | if (params[0] != ' ' && params[0] != '\t') { |
149 | | /* no whitespace */ |
150 | 15.7k | } else if ((set->workarounds & |
151 | 442 | SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH) != 0) { |
152 | 0 | while (*params == ' ' || *params == '\t') |
153 | 0 | params++; |
154 | 442 | } else { |
155 | 442 | smtp_server_reply(cmd, 501, "5.5.4", |
156 | 442 | "Invalid TO: " |
157 | 442 | "Unexpected whitespace before path"); |
158 | 442 | return; |
159 | 442 | } |
160 | 15.7k | path_parse_flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART; |
161 | 15.7k | if ((set->workarounds & SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH) != 0) |
162 | 0 | path_parse_flags |= SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL; |
163 | 15.7k | if (smtp_address_parse_path_full(pool_datastack_create(), params, |
164 | 15.7k | path_parse_flags, &path, &error, |
165 | 15.7k | ¶ms) < 0) { |
166 | 3.41k | smtp_server_reply(cmd, |
167 | 3.41k | 501, "5.5.4", "Invalid TO: %s", error); |
168 | 3.41k | return; |
169 | 3.41k | } |
170 | 12.3k | if (*params == ' ') |
171 | 550 | params++; |
172 | 11.8k | else if (*params != '\0') { |
173 | 156 | smtp_server_reply(cmd, 501, "5.5.4", |
174 | 156 | "Invalid TO: Invalid character in path"); |
175 | 156 | return; |
176 | 156 | } |
177 | 12.2k | if (path->domain == NULL && !conn->set.rcpt_domain_optional && |
178 | 976 | strcasecmp(path->localpart, "postmaster") != 0) { |
179 | 719 | smtp_server_reply(cmd, |
180 | 719 | 501, "5.5.4", "Invalid TO: Missing domain"); |
181 | 719 | return; |
182 | 719 | } |
183 | | |
184 | | /* [SP Rcpt-parameters] */ |
185 | 11.4k | param_parse_flags = 0; |
186 | 11.4k | if (conn->set.rcpt_domain_optional) |
187 | 0 | param_parse_flags |= SMTP_PARAM_RCPT_FLAG_ORCPT_ALLOW_LOCALPART; |
188 | 11.4k | if (array_is_created(&conn->rcpt_param_extensions)) |
189 | 0 | param_extensions = array_front(&conn->rcpt_param_extensions); |
190 | 11.4k | if (smtp_params_rcpt_parse(pool_datastack_create(), params, |
191 | 11.4k | param_parse_flags, caps, param_extensions, |
192 | 11.4k | &rcpt_params, &pperror, &error) < 0) { |
193 | 539 | switch (pperror) { |
194 | 77 | case SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX: |
195 | 77 | smtp_server_reply(cmd, |
196 | 77 | 501, "5.5.4", "%s", error); |
197 | 77 | break; |
198 | 462 | case SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED: |
199 | 462 | smtp_server_reply(cmd, |
200 | 462 | 555, "5.5.4", "%s", error); |
201 | 462 | break; |
202 | 0 | default: |
203 | 0 | i_unreached(); |
204 | 539 | } |
205 | 539 | return; |
206 | 539 | } |
207 | | |
208 | 10.9k | rcpt = smtp_server_recipient_create(cmd, path, &rcpt_params); |
209 | | |
210 | 10.9k | rcpt_data = p_new(cmd->pool, struct smtp_server_cmd_rcpt, 1); |
211 | 10.9k | rcpt_data->rcpt = rcpt; |
212 | 10.9k | command->data = rcpt_data; |
213 | | |
214 | 10.9k | smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_NEXT, |
215 | 10.9k | cmd_rcpt_recheck, rcpt_data); |
216 | 10.9k | smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_REPLIED, |
217 | 10.9k | cmd_rcpt_replied, rcpt_data); |
218 | 10.9k | smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_COMPLETED, |
219 | 10.9k | cmd_rcpt_completed, rcpt_data); |
220 | 10.9k | smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_DESTROY, |
221 | 10.9k | cmd_rcpt_destroy, rcpt_data); |
222 | | |
223 | 10.9k | conn->state.pending_rcpt_cmds++; |
224 | | |
225 | 10.9k | smtp_server_command_ref(command); |
226 | 10.9k | i_assert(callbacks != NULL && callbacks->conn_cmd_rcpt != NULL); |
227 | | |
228 | 10.9k | struct event_reason *reason = |
229 | 10.9k | smtp_server_connection_reason_begin(conn, "cmd_rcpt"); |
230 | 10.9k | ret = callbacks->conn_cmd_rcpt(conn->context, cmd, rcpt); |
231 | 10.9k | event_reason_end(&reason); |
232 | 10.9k | 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 | 10.9k | if (!smtp_server_command_is_replied(command)) { |
239 | | /* Set generic RCPT success reply if none is provided */ |
240 | 10.9k | smtp_server_cmd_rcpt_reply_success(cmd); |
241 | 10.9k | } |
242 | 10.9k | smtp_server_command_unref(&command); |
243 | 10.9k | } |
244 | | |
245 | | bool smtp_server_command_is_rcpt(struct smtp_server_cmd_ctx *cmd) |
246 | 21.8k | { |
247 | 21.8k | return (cmd->cmd->reg->func == smtp_server_cmd_rcpt); |
248 | 21.8k | } |
249 | | |
250 | | void smtp_server_cmd_rcpt_reply_success(struct smtp_server_cmd_ctx *cmd) |
251 | 10.9k | { |
252 | 10.9k | struct smtp_server_cmd_rcpt *rcpt_data = cmd->cmd->data; |
253 | | |
254 | 10.9k | i_assert(smtp_server_command_is_rcpt(cmd)); |
255 | | |
256 | 10.9k | smtp_server_recipient_reply(rcpt_data->rcpt, 250, "2.1.5", "OK"); |
257 | 10.9k | } |