summaryrefslogtreecommitdiffstats
path: root/src/lib-smtp/smtp-server-cmd-xclient.c
blob: d926b45095a202e9b604218e13c5c1924f222e83 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "istream.h"
#include "smtp-syntax.h"
#include "smtp-reply.h"

#include "smtp-server-private.h"

/* XCLIENT command (http://www.postfix.org/XCLIENT_README.html) */

static bool
cmd_xclient_check_state(struct smtp_server_cmd_ctx *cmd)
{
	struct smtp_server_connection *conn = cmd->conn;

	/* http://www.postfix.org/XCLIENT_README.html:

	   The XCLIENT command may be sent at any time, except in the middle
	   of a mail delivery transaction (i.e. between MAIL and DOT, or MAIL
	   and RSET). */
	if (conn->state.trans != NULL) {
		smtp_server_reply(cmd, 503, "5.5.0",
			"XCLIENT not permitted during a mail transaction");
		return FALSE;
	}
	return TRUE;
}

static void
cmd_xclient_completed(struct smtp_server_cmd_ctx *cmd,
		      struct smtp_proxy_data *proxy_data)
{
	struct smtp_server_connection *conn = cmd->conn;
	struct smtp_server_command *command = cmd->cmd;

	i_assert(smtp_server_command_is_replied(command));
	if (!smtp_server_command_replied_success(command)) {
		/* failure */
		return;
	}

	/* success */
	smtp_server_connection_reset_state(conn);
	smtp_server_connection_set_proxy_data(conn, proxy_data);
}

static void
cmd_xclient_recheck(struct smtp_server_cmd_ctx *cmd,
		    struct smtp_proxy_data *proxy_data ATTR_UNUSED)
{
	struct smtp_server_connection *conn = cmd->conn;

	/* all preceeding commands have finished and now the transaction state is
	   clear. This provides the opportunity to re-check the protocol state */
	if (!cmd_xclient_check_state(cmd))
		return;
	smtp_server_connection_set_state(conn, SMTP_SERVER_STATE_XCLIENT, NULL);

	/* succes; send greeting */
	smtp_server_reply(cmd, 220, NULL, "%s %s",
		conn->set.hostname, conn->set.login_greeting);
	return;
}

static void
smtp_server_cmd_xclient_extra_field(struct smtp_server_connection *conn,
				    pool_t pool, const struct smtp_param *param,
				    ARRAY_TYPE(smtp_proxy_data_field) *fields)
{
	struct smtp_proxy_data_field *field;

	if (conn->set.xclient_extensions == NULL ||
	    !str_array_icase_find(conn->set.xclient_extensions, param->keyword))
		return;

	if (!array_is_created(fields))
		p_array_init(fields, pool, 8);
	field = array_append_space(fields);
	field->name = p_strdup(pool, param->keyword);
	field->value = p_strdup(pool, param->value);
}

void smtp_server_cmd_xclient(struct smtp_server_cmd_ctx *cmd,
			     const char *params)
{
	struct smtp_server_connection *conn = cmd->conn;
	struct smtp_server_command *command = cmd->cmd;
	const struct smtp_server_callbacks *callbacks = conn->callbacks;
	struct smtp_proxy_data *proxy_data;
	ARRAY_TYPE(smtp_proxy_data_field) extra_fields = ARRAY_INIT;
	const char *const *argv;

	/* xclient-command = XCLIENT 1*( SP attribute-name"="attribute-value )
	   attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | LOGIN )
	   attribute-value = xtext
	 */

	if ((conn->set.capabilities & SMTP_CAPABILITY_XCLIENT) == 0) {
		smtp_server_reply(cmd,
			502, "5.5.1", "Unsupported command");
		return;
	}

	/* check transaction state as far as possible */
	if (!cmd_xclient_check_state(cmd))
		return;

	/* check whether client is trusted */
	if (!smtp_server_connection_is_trusted(conn)) {
		smtp_server_reply(cmd, 550, "5.7.14",
			"You are not from trusted IP");
		return;
	}

	proxy_data = p_new(cmd->pool, struct smtp_proxy_data, 1);

	argv = t_strsplit(params, " ");
	for (; *argv != NULL; argv++) {
		struct smtp_param param;
		const char *error;

		if (smtp_param_parse(pool_datastack_create(), *argv,
			&param, &error) < 0) {
			smtp_server_reply(cmd, 501, "5.5.4",
				"Invalid parameter: %s", error);
			return;
		}

		param.keyword = t_str_ucase(param.keyword);

		if (smtp_xtext_parse(param.value, &param.value, &error) < 0) {
			smtp_server_reply(cmd, 501, "5.5.4",
				"Invalid %s parameter: %s",
				param.keyword, error);
			return;
		}

		if (strcmp(param.keyword, "ADDR") == 0) {
			bool ipv6 = FALSE;
			if (strcasecmp(param.value, "[UNAVAILABLE]") == 0)
				continue;
			if (strncasecmp(param.value, "IPV6:", 5) == 0) {
				ipv6 = TRUE;
				param.value += 5;
			}
			if (net_addr2ip(param.value, &proxy_data->source_ip) < 0 ||
				(ipv6 && proxy_data->source_ip.family != AF_INET6)) {
				smtp_server_reply(cmd, 501, "5.5.4",
					"Invalid ADDR parameter");
				return;
			}
		} else if (strcmp(param.keyword, "HELO") == 0) {
			if (strcasecmp(param.value, "[UNAVAILABLE]") == 0)
				continue;
			if (smtp_helo_domain_parse
				(param.value, TRUE, &proxy_data->helo) >= 0)
				proxy_data->helo =
					p_strdup(cmd->pool, proxy_data->helo);
		} else if (strcmp(param.keyword, "LOGIN") == 0) {
			if (strcasecmp(param.value, "[UNAVAILABLE]") == 0)
				continue;
			proxy_data->login = p_strdup(cmd->pool, param.value);
		} else if (strcmp(param.keyword, "PORT") == 0) {
			if (strcasecmp(param.value, "[UNAVAILABLE]") == 0)
				continue;
			if (net_str2port(param.value, &proxy_data->source_port) < 0) {
				smtp_server_reply(cmd, 501, "5.5.4",
					"Invalid PORT parameter");
				return;
			}
		} else if (strcmp(param.keyword, "PROTO") == 0) {
			param.value = t_str_ucase(param.value);
			if (strcmp(param.value, "SMTP") == 0)
				proxy_data->proto = SMTP_PROXY_PROTOCOL_SMTP;
			else if (strcmp(param.value, "ESMTP") == 0)
				proxy_data->proto = SMTP_PROXY_PROTOCOL_ESMTP;
			else if (strcmp(param.value, "LMTP") == 0)
				proxy_data->proto = SMTP_PROXY_PROTOCOL_LMTP;
			else {
				smtp_server_reply(cmd, 501, "5.5.4",
					"Invalid PROTO parameter");
				return;
			}
		} else if (strcmp(param.keyword, "SESSION") == 0) {
			if (strcasecmp(param.value, "[UNAVAILABLE]") == 0)
				continue;
			proxy_data->session = p_strdup(cmd->pool, param.value);
		} else if (strcmp(param.keyword, "TIMEOUT") == 0) {
			if (str_to_uint(param.value,
				&proxy_data->timeout_secs) < 0) {
				smtp_server_reply(cmd, 501, "5.5.4",
					"Invalid TIMEOUT parameter");
				return;
			}
		} else if (strcmp(param.keyword, "TTL") == 0) {
			if (str_to_uint(param.value,
				&proxy_data->ttl_plus_1) < 0) {
				smtp_server_reply(cmd, 501, "5.5.4",
					"Invalid TTL parameter");
				return;
			}
			proxy_data->ttl_plus_1++;
		} else {
			smtp_server_cmd_xclient_extra_field(conn,
				cmd->pool, &param, &extra_fields);
		}
	}

	if (array_is_created(&extra_fields)) {
		proxy_data->extra_fields = array_get(&extra_fields,
			&proxy_data->extra_fields_count);
	}

	smtp_server_command_input_lock(cmd);

	smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_NEXT,
				     cmd_xclient_recheck, proxy_data);
	smtp_server_command_add_hook(command, SMTP_SERVER_COMMAND_HOOK_COMPLETED,
				     cmd_xclient_completed, proxy_data);

	if (conn->state.state == SMTP_SERVER_STATE_GREETING) {
		smtp_server_connection_set_state(
			conn, SMTP_SERVER_STATE_XCLIENT, NULL);
	}

	smtp_server_command_ref(command);
	if (callbacks != NULL && callbacks->conn_cmd_xclient != NULL) {
		/* specific implementation of XCLIENT command */
		callbacks->conn_cmd_xclient(conn->context, cmd, proxy_data);
	}
	smtp_server_command_unref(&command);
}