1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "array.h"
5 #include "ioloop.h"
6 #include "smtp-parser.h"
7 #include "smtp-syntax.h"
8 #include "smtp-address.h"
9 
10 #include "smtp-server-private.h"
11 
12 /* MAIL command */
13 
cmd_mail_check_state(struct smtp_server_cmd_ctx * cmd)14 static bool cmd_mail_check_state(struct smtp_server_cmd_ctx *cmd)
15 {
16 	struct smtp_server_connection *conn = cmd->conn;
17 	struct smtp_server_command *command = cmd->cmd;
18 
19 	if (smtp_server_command_is_replied(command) &&
20 	    !smtp_server_command_replied_success(command) &&
21 	    !smtp_server_command_reply_is_forwarded(command))
22 		return FALSE;
23 
24 	if (conn->state.trans != NULL) {
25 		smtp_server_reply(cmd, 503, "5.5.0", "MAIL already given");
26 		return FALSE;
27 	}
28 	return TRUE;
29 }
30 
31 static void
cmd_mail_completed(struct smtp_server_cmd_ctx * cmd,struct smtp_server_cmd_mail * data)32 cmd_mail_completed(struct smtp_server_cmd_ctx *cmd,
33 		   struct smtp_server_cmd_mail *data)
34 {
35 	struct smtp_server_connection *conn = cmd->conn;
36 	struct smtp_server_command *command = cmd->cmd;
37 
38 	i_assert(conn->state.pending_mail_cmds > 0);
39 	conn->state.pending_mail_cmds--;
40 
41 	i_assert(smtp_server_command_is_replied(command));
42 	i_assert(conn->state.state == SMTP_SERVER_STATE_MAIL_FROM ||
43 		 !smtp_server_command_replied_success(command));
44 
45 	if (!smtp_server_command_replied_success(command)) {
46 		/* Failure */
47 		return;
48 	}
49 
50 	/* Success */
51 	conn->state.trans = smtp_server_transaction_create(conn, data);
52 }
53 
54 static void
cmd_mail_recheck(struct smtp_server_cmd_ctx * cmd,struct smtp_server_cmd_mail * data)55 cmd_mail_recheck(struct smtp_server_cmd_ctx *cmd,
56 		 struct smtp_server_cmd_mail *data)
57 {
58 	struct smtp_server_connection *conn = cmd->conn;
59 
60 	/* All preceeding commands have finished and now the transaction state
61 	   is clear. This provides the opportunity to re-check the transaction
62 	   state */
63 	if (!cmd_mail_check_state(cmd))
64 		return;
65 
66 	/* Advance state */
67 	smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_MAIL_FROM,
68 					 smtp_address_encode(data->path));
69 }
70 
smtp_server_cmd_mail(struct smtp_server_cmd_ctx * cmd,const char * params)71 void smtp_server_cmd_mail(struct smtp_server_cmd_ctx *cmd,
72 			  const char *params)
73 {
74 	struct smtp_server_connection *conn = cmd->conn;
75 	const struct smtp_server_settings *set = &conn->set;
76 	enum smtp_capability caps = set->capabilities;
77 	const struct smtp_server_callbacks *callbacks = conn->callbacks;
78 	struct smtp_server_command *command = cmd->cmd;
79 	struct smtp_server_cmd_mail *mail_data;
80 	enum smtp_address_parse_flags path_parse_flags;
81 	const char *const *param_extensions = NULL;
82 	struct smtp_address *path = NULL;
83 	enum smtp_param_parse_error pperror;
84 	const char *error;
85 	int ret;
86 
87 	/* mail         = "MAIL FROM:" Reverse-path [SP Mail-parameters] CRLF
88 	   Reverse-path = Path / "<>"
89 	 */
90 
91 	/* Check transaction state as far as possible */
92 	if (!cmd_mail_check_state(cmd))
93 		return;
94 
95 	/* Reverse-path */
96 	if (params == NULL || strncasecmp(params, "FROM:", 5) != 0) {
97 		smtp_server_reply(cmd, 501, "5.5.4", "Invalid parameters");
98 		return;
99 	}
100 	if (params[5] != ' ' && params[5] != '\t') {
101 		params += 5;
102 	} else if ((set->workarounds &
103 		    SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH) != 0) {
104 		params += 5;
105 		while (*params == ' ' || *params == '\t')
106 			params++;
107 	} else {
108 		smtp_server_reply(cmd, 501, "5.5.4",
109 				  "Invalid FROM: "
110 				  "Unexpected whitespace before path");
111 		return;
112 	}
113 	path_parse_flags =
114 		SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY |
115 		SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW;
116 	if (*params != '\0' &&
117 	    (set->workarounds & SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH) != 0)
118 		path_parse_flags |= SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL;
119 	if (set->mail_path_allow_broken) {
120 		path_parse_flags |=
121 			SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART |
122 			SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN;
123 	}
124 	ret = smtp_address_parse_path_full(pool_datastack_create(), params,
125 					   path_parse_flags, &path, &error,
126 					   &params);
127 	if (ret < 0 && !smtp_address_is_broken(path)) {
128 		smtp_server_reply(cmd, 501, "5.5.4",
129 				  "Invalid FROM: %s", error);
130 		return;
131 	}
132 	if (*params == ' ')
133 		params++;
134 	else if (*params != '\0') {
135 		smtp_server_reply(
136 			cmd, 501, "5.5.4",
137 			"Invalid FROM: Invalid character in path");
138 		return;
139 	}
140 	if (ret < 0) {
141 		i_assert(set->mail_path_allow_broken);
142 		e_debug(conn->event, "Invalid FROM: %s "
143 			"(proceeding with <> as sender)", error);
144 	}
145 
146 	mail_data = p_new(cmd->pool, struct smtp_server_cmd_mail, 1);
147 
148 	if (conn->set.protocol == SMTP_PROTOCOL_LMTP)
149 		mail_data->flags |= SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT;
150 
151 	/* [SP Mail-parameters] */
152 	if (array_is_created(&conn->mail_param_extensions))
153 		param_extensions = array_front(&conn->mail_param_extensions);
154 	if (smtp_params_mail_parse(cmd->pool, params, caps, param_extensions,
155 				   NULL, &mail_data->params, &pperror,
156 				   &error) < 0) {
157 		switch (pperror) {
158 		case SMTP_PARAM_PARSE_ERROR_BAD_SYNTAX:
159 			smtp_server_reply(cmd, 501, "5.5.4", "%s", error);
160 			break;
161 		case SMTP_PARAM_PARSE_ERROR_NOT_SUPPORTED:
162 			smtp_server_reply(cmd, 555, "5.5.4", "%s", error);
163 			break;
164 		default:
165 			i_unreached();
166 		}
167 		return;
168 	}
169 
170 	if ((caps & SMTP_CAPABILITY_SIZE) != 0 && set->max_message_size > 0 &&
171 	    mail_data->params.size > set->max_message_size) {
172 		smtp_server_reply(cmd, 552, "5.2.3",
173 			"Message size exceeds administrative limit");
174 		return;
175 	}
176 
177 	mail_data->path = smtp_address_clone(cmd->pool, path);
178 	mail_data->timestamp = ioloop_timeval;
179 
180 	smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_NEXT,
181 				     cmd_mail_recheck, mail_data);
182 	smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_COMPLETED,
183 				     cmd_mail_completed, mail_data);
184 
185 	smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_MAIL_FROM,
186 					 smtp_address_encode(mail_data->path));
187 	conn->state.pending_mail_cmds++;
188 
189 	smtp_server_command_ref(command);
190 	if (callbacks != NULL && callbacks->conn_cmd_mail != NULL) {
191 		/* Specific implementation of MAIL command */
192 		ret = callbacks->conn_cmd_mail(conn->context, cmd, mail_data);
193 		if (ret <= 0) {
194 			i_assert(ret == 0 ||
195 				 smtp_server_command_is_replied(command));
196 			/* Command is waiting for external event or it failed */
197 			smtp_server_command_unref(&command);
198 			return;
199 		}
200 	}
201 	if (!smtp_server_command_is_replied(command)) {
202 		/* Set generic MAIL success reply if none is provided */
203 		smtp_server_cmd_mail_reply_success(cmd);
204 	}
205 	smtp_server_command_unref(&command);
206 }
207 
smtp_server_cmd_mail_reply_success(struct smtp_server_cmd_ctx * cmd)208 void smtp_server_cmd_mail_reply_success(struct smtp_server_cmd_ctx *cmd)
209 {
210 	i_assert(cmd->cmd->reg->func == smtp_server_cmd_mail);
211 
212 	smtp_server_reply(cmd, 250, "2.1.0", "OK");
213 }
214