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