1 /* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "login-common.h"
4 #include "ioloop.h"
5 #include "istream.h"
6 #include "ostream.h"
7 #include "base64.h"
8 #include "safe-memset.h"
9 #include "str.h"
10 #include "str-sanitize.h"
11 #include "strescape.h"
12 #include "dsasl-client.h"
13 #include "client.h"
14 #include "pop3-proxy.h"
15 
16 static const char *pop3_proxy_state_names[POP3_PROXY_STATE_COUNT] = {
17 	"banner", "starttls", "xclient", "login1", "login2"
18 };
19 
proxy_send_login(struct pop3_client * client,struct ostream * output)20 static int proxy_send_login(struct pop3_client *client, struct ostream *output)
21 {
22 	struct dsasl_client_settings sasl_set;
23 	const unsigned char *sasl_output;
24 	size_t len;
25 	const char *mech_name, *error;
26 	string_t *str = t_str_new(128);
27 
28 	i_assert(client->common.proxy_ttl > 1);
29 	if (client->proxy_xclient &&
30 	    !client->common.proxy_not_trusted) {
31 		string_t *fwd = t_str_new(128);
32                 for(const char *const *ptr = client->common.auth_passdb_args;*ptr != NULL; ptr++) {
33                         if (strncasecmp(*ptr, "forward_", 8) == 0) {
34                                 if (str_len(fwd) > 0)
35                                         str_append_c(fwd, '\t');
36                                 str_append_tabescaped(fwd, (*ptr)+8);
37                         }
38 		}
39 
40 		str_printfa(str, "XCLIENT ADDR=%s PORT=%u SESSION=%s TTL=%u",
41 			    net_ip2addr(&client->common.ip),
42 			    client->common.remote_port,
43 			    client_get_session_id(&client->common),
44 			    client->common.proxy_ttl - 1);
45 		if (str_len(fwd) > 0) {
46 			str_append(str, " FORWARD=");
47 			base64_encode(str_data(fwd), str_len(fwd), str);
48 		}
49 		str_append(str, "\r\n");
50 		/* remote supports XCLIENT, send it */
51 		o_stream_nsend(output, str_data(str), str_len(str));
52 		client->proxy_state = POP3_PROXY_XCLIENT;
53 	} else {
54 		client->proxy_state = POP3_PROXY_LOGIN1;
55 	}
56 
57 	str_truncate(str, 0);
58 
59 	if (client->common.proxy_mech == NULL) {
60 		/* send USER command */
61 		str_append(str, "USER ");
62 		str_append(str, client->common.proxy_user);
63 		str_append(str, "\r\n");
64 		o_stream_nsend(output, str_data(str), str_len(str));
65 		return 0;
66 	}
67 
68 	i_assert(client->common.proxy_sasl_client == NULL);
69 	i_zero(&sasl_set);
70 	sasl_set.authid = client->common.proxy_master_user != NULL ?
71 		client->common.proxy_master_user : client->common.proxy_user;
72 	sasl_set.authzid = client->common.proxy_user;
73 	sasl_set.password = client->common.proxy_password;
74 	client->common.proxy_sasl_client =
75 		dsasl_client_new(client->common.proxy_mech, &sasl_set);
76 	mech_name = dsasl_client_mech_get_name(client->common.proxy_mech);
77 
78 	str_printfa(str, "AUTH %s ", mech_name);
79 	if (dsasl_client_output(client->common.proxy_sasl_client,
80 				&sasl_output, &len, &error) < 0) {
81 		const char *reason = t_strdup_printf(
82 			"SASL mechanism %s init failed: %s",
83 			mech_name, error);
84 		login_proxy_failed(client->common.login_proxy,
85 			login_proxy_get_event(client->common.login_proxy),
86 			LOGIN_PROXY_FAILURE_TYPE_INTERNAL, reason);
87 		return -1;
88 	}
89 	if (len == 0)
90 		str_append_c(str, '=');
91 	else
92 		base64_encode(sasl_output, len, str);
93 	str_append(str, "\r\n");
94 	o_stream_nsend(output, str_data(str), str_len(str));
95 
96 	if (client->proxy_state != POP3_PROXY_XCLIENT)
97 		client->proxy_state = POP3_PROXY_LOGIN2;
98 	return 0;
99 }
100 
101 static int
pop3_proxy_continue_sasl_auth(struct client * client,struct ostream * output,const char * line)102 pop3_proxy_continue_sasl_auth(struct client *client, struct ostream *output,
103 			      const char *line)
104 {
105 	string_t *str;
106 	const unsigned char *data;
107 	size_t data_len;
108 	const char *error;
109 	int ret;
110 
111 	str = t_str_new(128);
112 	if (base64_decode(line, strlen(line), NULL, str) < 0) {
113 		const char *reason = t_strdup_printf(
114 			"Invalid base64 data in AUTH response");
115 		login_proxy_failed(client->login_proxy,
116 			login_proxy_get_event(client->login_proxy),
117 			LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason);
118 		return -1;
119 	}
120 	ret = dsasl_client_input(client->proxy_sasl_client,
121 				 str_data(str), str_len(str), &error);
122 	if (ret == 0) {
123 		ret = dsasl_client_output(client->proxy_sasl_client,
124 					  &data, &data_len, &error);
125 	}
126 	if (ret < 0) {
127 		const char *reason = t_strdup_printf(
128 			"Invalid authentication data: %s", error);
129 		login_proxy_failed(client->login_proxy,
130 				   login_proxy_get_event(client->login_proxy),
131 				   LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason);
132 		return -1;
133 	}
134 	i_assert(ret == 0);
135 
136 	str_truncate(str, 0);
137 	base64_encode(data, data_len, str);
138 	str_append(str, "\r\n");
139 
140 	o_stream_nsend(output, str_data(str), str_len(str));
141 	return 0;
142 }
143 
pop3_proxy_parse_line(struct client * client,const char * line)144 int pop3_proxy_parse_line(struct client *client, const char *line)
145 {
146 	struct pop3_client *pop3_client = (struct pop3_client *)client;
147 	struct ostream *output;
148 	enum login_proxy_ssl_flags ssl_flags;
149 
150 	i_assert(!client->destroyed);
151 
152 	output = login_proxy_get_ostream(client->login_proxy);
153 	switch (pop3_client->proxy_state) {
154 	case POP3_PROXY_BANNER:
155 		/* this is a banner */
156 		if (!str_begins(line, "+OK")) {
157 			const char *reason = t_strdup_printf(
158 				"Invalid banner: %s", str_sanitize(line, 160));
159 			login_proxy_failed(client->login_proxy,
160 				login_proxy_get_event(client->login_proxy),
161 				LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason);
162 			return -1;
163 		}
164 		pop3_client->proxy_xclient =
165 			str_begins(line+3, " [XCLIENT]");
166 
167 		ssl_flags = login_proxy_get_ssl_flags(client->login_proxy);
168 		if ((ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0) {
169 			if (proxy_send_login(pop3_client, output) < 0)
170 				return -1;
171 		} else {
172 			o_stream_nsend_str(output, "STLS\r\n");
173 			pop3_client->proxy_state = POP3_PROXY_STARTTLS;
174 		}
175 		return 0;
176 	case POP3_PROXY_STARTTLS:
177 		if (!str_begins(line, "+OK")) {
178 			const char *reason = t_strdup_printf(
179 				"STLS failed: %s", str_sanitize(line, 160));
180 			login_proxy_failed(client->login_proxy,
181 				login_proxy_get_event(client->login_proxy),
182 				LOGIN_PROXY_FAILURE_TYPE_REMOTE, reason);
183 			return -1;
184 		}
185 		if (login_proxy_starttls(client->login_proxy) < 0)
186 			return -1;
187 		/* i/ostreams changed. */
188 		output = login_proxy_get_ostream(client->login_proxy);
189 		if (proxy_send_login(pop3_client, output) < 0)
190 			return -1;
191 		return 1;
192 	case POP3_PROXY_XCLIENT:
193 		if (!str_begins(line, "+OK")) {
194 			const char *reason = t_strdup_printf(
195 				"XCLIENT failed: %s", str_sanitize(line, 160));
196 			login_proxy_failed(client->login_proxy,
197 				login_proxy_get_event(client->login_proxy),
198 				LOGIN_PROXY_FAILURE_TYPE_REMOTE, reason);
199 			return -1;
200 		}
201 		pop3_client->proxy_state = client->proxy_sasl_client == NULL ?
202 			POP3_PROXY_LOGIN1 : POP3_PROXY_LOGIN2;
203 		return 0;
204 	case POP3_PROXY_LOGIN1:
205 		i_assert(client->proxy_sasl_client == NULL);
206 		if (!str_begins(line, "+OK"))
207 			break;
208 
209 		/* USER successful, send PASS */
210 		o_stream_nsend_str(output, t_strdup_printf(
211 			"PASS %s\r\n", client->proxy_password));
212 		pop3_client->proxy_state = POP3_PROXY_LOGIN2;
213 		return 0;
214 	case POP3_PROXY_LOGIN2:
215 		if (str_begins(line, "+ ") &&
216 		    client->proxy_sasl_client != NULL) {
217 			/* continue SASL authentication */
218 			if (pop3_proxy_continue_sasl_auth(client, output,
219 							  line+2) < 0)
220 				return -1;
221 			return 0;
222 		}
223 		if (!str_begins(line, "+OK"))
224 			break;
225 
226 		/* Login successful. Send this line to client. */
227 		line = t_strconcat(line, "\r\n", NULL);
228 		o_stream_nsend_str(client->output, line);
229 
230 		client_proxy_finish_destroy_client(client);
231 		return 1;
232 	case POP3_PROXY_STATE_COUNT:
233 		i_unreached();
234 	}
235 
236 	/* Login failed. Pass through the error message to client.
237 
238 	   If the backend server isn't Dovecot, the error message may
239 	   be different from Dovecot's "user doesn't exist" error. This
240 	   would allow an attacker to find out what users exist in the
241 	   system.
242 
243 	   The optimal way to handle this would be to replace the
244 	   backend's "password failed" error message with Dovecot's
245 	   AUTH_FAILED_MSG, but this would require a new setting and
246 	   the sysadmin to actually bother setting it properly.
247 
248 	   So for now we'll just forward the error message. This
249 	   shouldn't be a real problem since of course everyone will
250 	   be using only Dovecot as their backend :) */
251 	enum login_proxy_failure_type failure_type =
252 		LOGIN_PROXY_FAILURE_TYPE_AUTH;
253 	if (!str_begins(line, "-ERR ")) {
254 		client_send_reply(client, POP3_CMD_REPLY_ERROR,
255 				  AUTH_FAILED_MSG);
256 	} else if (str_begins(line, "-ERR [SYS/TEMP]")) {
257 		/* delay sending the reply until we know if we reconnect */
258 		failure_type = LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL;
259 		line += 5;
260 	} else {
261 		client_send_raw(client, t_strconcat(line, "\r\n", NULL));
262 		line += 5;
263 	}
264 
265 	login_proxy_failed(client->login_proxy,
266 			   login_proxy_get_event(client->login_proxy),
267 			   failure_type, line);
268 	return -1;
269 }
270 
pop3_proxy_reset(struct client * client)271 void pop3_proxy_reset(struct client *client)
272 {
273 	struct pop3_client *pop3_client = (struct pop3_client *)client;
274 
275 	pop3_client->proxy_state = POP3_PROXY_BANNER;
276 }
277 
278 static void
pop3_proxy_send_failure_reply(struct client * client,enum login_proxy_failure_type type,const char * reason)279 pop3_proxy_send_failure_reply(struct client *client,
280 			      enum login_proxy_failure_type type,
281 			      const char *reason)
282 {
283 	switch (type) {
284 	case LOGIN_PROXY_FAILURE_TYPE_CONNECT:
285 	case LOGIN_PROXY_FAILURE_TYPE_INTERNAL:
286 	case LOGIN_PROXY_FAILURE_TYPE_REMOTE:
287 	case LOGIN_PROXY_FAILURE_TYPE_PROTOCOL:
288 		client_send_reply(client, POP3_CMD_REPLY_TEMPFAIL,
289 				  LOGIN_PROXY_FAILURE_MSG);
290 		break;
291 	case LOGIN_PROXY_FAILURE_TYPE_INTERNAL_CONFIG:
292 	case LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG:
293 		client_send_reply(client, POP3_CMD_REPLY_ERROR,
294 				  LOGIN_PROXY_FAILURE_MSG);
295 		break;
296 	case LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL:
297 		/* [SYS/TEMP] prefix is already in the reason string */
298 		client_send_reply(client, POP3_CMD_REPLY_ERROR, reason);
299 		break;
300 	case LOGIN_PROXY_FAILURE_TYPE_AUTH:
301 		/* reply was already sent */
302 		break;
303 	}
304 }
305 
pop3_proxy_failed(struct client * client,enum login_proxy_failure_type type,const char * reason,bool reconnecting)306 void pop3_proxy_failed(struct client *client,
307 		       enum login_proxy_failure_type type,
308 		       const char *reason, bool reconnecting)
309 {
310 	if (!reconnecting)
311 		pop3_proxy_send_failure_reply(client, type, reason);
312 	client_common_proxy_failed(client, type, reason, reconnecting);
313 }
314 
pop3_proxy_get_state(struct client * client)315 const char *pop3_proxy_get_state(struct client *client)
316 {
317 	struct pop3_client *pop3_client = (struct pop3_client *)client;
318 
319 	return pop3_proxy_state_names[pop3_client->proxy_state];
320 }
321