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