summaryrefslogtreecommitdiffstats
path: root/src/pop3-login/client.c
blob: d57d284b135e037dea055e83ca746e47b8895aae (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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */

#include "login-common.h"
#include "base64.h"
#include "buffer.h"
#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
#include "randgen.h"
#include "hostpid.h"
#include "safe-memset.h"
#include "str.h"
#include "strescape.h"
#include "master-service.h"
#include "client.h"
#include "client-authenticate.h"
#include "auth-client.h"
#include "pop3-proxy.h"
#include "pop3-login-settings.h"

#include <ctype.h>

/* Disconnect client when it sends too many bad commands */
#define CLIENT_MAX_BAD_COMMANDS 3
#define CLIENT_MAX_CMD_LEN 8

static bool cmd_stls(struct pop3_client *client)
{
	client_cmd_starttls(&client->common);	
	return TRUE;
}

static bool cmd_quit(struct pop3_client *client)
{
	client_send_reply(&client->common, POP3_CMD_REPLY_OK, "Logging out");
	client_destroy(&client->common, CLIENT_UNAUTHENTICATED_LOGOUT_MSG);
	return TRUE;
}

static bool cmd_xclient(struct pop3_client *client, const char *args)
{
	const char *const *tmp;
	in_port_t remote_port;
	bool args_ok = TRUE;

	if (!client->common.trusted) {
		client_send_reply(&client->common, POP3_CMD_REPLY_OK,
				  "You are not from trusted IP - ignoring");
		return TRUE;
	}
	for (tmp = t_strsplit(args, " "); *tmp != NULL; tmp++) {
		if (strncasecmp(*tmp, "ADDR=", 5) == 0) {
			if (net_addr2ip(*tmp + 5, &client->common.ip) < 0)
				args_ok = FALSE;
		} else if (strncasecmp(*tmp, "PORT=", 5) == 0) {
			if (net_str2port(*tmp + 5, &remote_port) < 0)
				args_ok = FALSE;
			else
				client->common.remote_port = remote_port;
		} else if (strncasecmp(*tmp, "SESSION=", 8) == 0) {
			const char *value = *tmp + 8;

			if (strlen(value) <= LOGIN_MAX_SESSION_ID_LEN) {
				client->common.session_id =
					p_strdup(client->common.pool, value);
			}
		} else if (strncasecmp(*tmp, "TTL=", 4) == 0) {
			if (str_to_uint(*tmp + 4, &client->common.proxy_ttl) < 0)
				args_ok = FALSE;
		} else if (strncasecmp(*tmp, "FORWARD=", 8) == 0) {
			size_t value_len = strlen((*tmp)+8);
			client->common.forward_fields =
				str_new(client->common.preproxy_pool,
					MAX_BASE64_DECODED_SIZE(value_len));
			if (base64_decode((*tmp)+8, value_len, NULL,
					  client->common.forward_fields) < 0)
				args_ok = FALSE;
		}
	}
	if (!args_ok) {
		client_send_reply(&client->common, POP3_CMD_REPLY_ERROR,
				  "Invalid parameters");
		return TRUE;
	}

	/* args ok, set them and reset the state */
	client_send_reply(&client->common, POP3_CMD_REPLY_OK, "Updated");
	return TRUE;
}

static bool client_command_execute(struct pop3_client *client, const char *cmd,
				   const char *args)
{
	if (strcmp(cmd, "CAPA") == 0)
		return cmd_capa(client, args);
	if (strcmp(cmd, "USER") == 0)
		return cmd_user(client, args);
	if (strcmp(cmd, "PASS") == 0)
		return cmd_pass(client, args);
	if (strcmp(cmd, "APOP") == 0)
		return cmd_apop(client, args);
	if (strcmp(cmd, "STLS") == 0)
		return cmd_stls(client);
	if (strcmp(cmd, "QUIT") == 0)
		return cmd_quit(client);
	if (strcmp(cmd, "XCLIENT") == 0)
		return cmd_xclient(client, args);
	if (strcmp(cmd, "XOIP") == 0) {
		/* Compatibility with Zimbra's patched nginx */
		return cmd_xclient(client, t_strconcat("ADDR=", args, NULL));
	}

	client_send_reply(&client->common, POP3_CMD_REPLY_ERROR,
			  "Unknown command.");
	return FALSE;
}

static void pop3_client_input(struct client *client)
{
	i_assert(!client->authenticating);

	if (!client_read(client))
		return;

	client_ref(client);

	o_stream_cork(client->output);
	/* if a command starts an authentication, stop processing further
	   commands until the authentication is finished. */
	while (!client->output->closed && !client->authenticating &&
	       auth_client_is_connected(auth_client)) {
		if (!client->v.input_next_cmd(client))
			break;
	}

	if (auth_client != NULL && !auth_client_is_connected(auth_client))
		client->input_blocked = TRUE;

	o_stream_uncork(client->output);
	client_unref(&client);
}

static bool client_read_cmd_name(struct client *client, const char **cmd_r)
{
	const unsigned char *data;
	size_t size, i;
	string_t *cmd = t_str_new(CLIENT_MAX_CMD_LEN);
	if (i_stream_read_more(client->input, &data, &size) <= 0)
		return FALSE;
	for(i = 0; i < size; i++) {
		if (data[i] == '\r') continue;
		if (data[i] == ' ' ||
		    data[i] == '\n' ||
		    data[i] == '\0' ||
		    i >= CLIENT_MAX_CMD_LEN) {
			*cmd_r = str_c(cmd);
			/* only skip ws */
			i_stream_skip(client->input, i + (data[i] == ' ' ? 1 : 0));
			return TRUE;
		}
		str_append_c(cmd, i_toupper(data[i]));
	}
	return FALSE;
}

static bool pop3_client_input_next_cmd(struct client *client)
{
	struct pop3_client *pop3_client = (struct pop3_client *)client;
	const char *cmd, *args;

	if (pop3_client->current_cmd == NULL) {
		if (!client_read_cmd_name(client, &cmd))
			return FALSE;
		pop3_client->current_cmd = i_strdup(cmd);
	}

	if (strcmp(pop3_client->current_cmd, "AUTH") == 0) {
		if (cmd_auth(pop3_client) <= 0) {
			/* Need more input / destroyed. We also get here when
			   SASL authentication is actually started. */
			return FALSE;
		}
		/* AUTH command finished already (SASL probe or ERR reply) */
		i_free(pop3_client->current_cmd);
		return TRUE;
	}

	if ((args = i_stream_next_line(client->input)) == NULL)
		return FALSE;

	if (client_command_execute(pop3_client, pop3_client->current_cmd, args))
		client->bad_counter = 0;
	else if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
		client_send_reply(client, POP3_CMD_REPLY_ERROR,
				  "Too many invalid bad commands.");
		client_destroy(client,
			       "Disconnected: Too many bad commands");
		return FALSE;
	}
	i_free(pop3_client->current_cmd);
	return TRUE;
}

static struct client *pop3_client_alloc(pool_t pool)
{
	struct pop3_client *pop3_client;

	pop3_client = p_new(pool, struct pop3_client, 1);
	return &pop3_client->common;
}

static void pop3_client_create(struct client *client ATTR_UNUSED,
			       void **other_sets ATTR_UNUSED)
{
}

static void pop3_client_destroy(struct client *client)
{
	struct pop3_client *pop3_client = (struct pop3_client *)client;

	i_free_and_null(pop3_client->current_cmd);
	i_free_and_null(pop3_client->last_user);
	i_free_and_null(pop3_client->apop_challenge);
}

static char *get_apop_challenge(struct pop3_client *client)
{
	unsigned char buffer[16];
	unsigned char buffer_base64[MAX_BASE64_ENCODED_SIZE(sizeof(buffer)) + 1];
	buffer_t buf;

	if (sasl_server_find_available_mech(&client->common, "APOP") == NULL) {
		/* disabled, no need to present the challenge */
		return NULL;
	}

	auth_client_get_connect_id(auth_client, &client->apop_server_pid,
				   &client->apop_connect_uid);

	random_fill(buffer, sizeof(buffer));
	buffer_create_from_data(&buf, buffer_base64, sizeof(buffer_base64));
	base64_encode(buffer, sizeof(buffer), &buf);
	buffer_append_c(&buf, '\0');

	return i_strdup_printf("<%x.%x.%lx.%s@%s>",
			       client->apop_server_pid,
			       client->apop_connect_uid,
			       (unsigned long)ioloop_time,
			       (const char *)buf.data, my_hostname);
}

static void pop3_client_notify_auth_ready(struct client *client)
{
	struct pop3_client *pop3_client = (struct pop3_client *)client;
	string_t *str;

	client->io = io_add_istream(client->input, client_input, client);

	str = t_str_new(128);
	if (client->trusted) {
		/* Dovecot extension to avoid extra roundtrip for CAPA */
		str_append(str, "[XCLIENT] ");
	}
	str_append(str, client->set->login_greeting);

	pop3_client->apop_challenge = get_apop_challenge(pop3_client);
	if (pop3_client->apop_challenge != NULL)
		str_printfa(str, " %s", pop3_client->apop_challenge);
	client_send_reply(client, POP3_CMD_REPLY_OK, str_c(str));

	client->banner_sent = TRUE;
}

static void
pop3_client_notify_starttls(struct client *client,
			    bool success, const char *text)
{
	if (success)
		client_send_reply(client, POP3_CMD_REPLY_OK, text);
	else
		client_send_reply(client, POP3_CMD_REPLY_ERROR, text);
}

static void pop3_client_starttls(struct client *client ATTR_UNUSED)
{
}

void client_send_reply(struct client *client, enum pop3_cmd_reply reply,
		       const char *text)
{
	const char *prefix = "-ERR";

	switch (reply) {
	case POP3_CMD_REPLY_OK:
		prefix = "+OK";
		break;
	case POP3_CMD_REPLY_TEMPFAIL:
		prefix = "-ERR [SYS/TEMP]";
		break;
	case POP3_CMD_REPLY_AUTH_ERROR:
		if (text[0] == '[')
			prefix = "-ERR";
		else
			prefix = "-ERR [AUTH]";
		break;
	case POP3_CMD_REPLY_ERROR:
		break;
	}

	T_BEGIN {
		string_t *line = t_str_new(256);

		str_append(line, prefix);
		str_append_c(line, ' ');
		str_append(line, text);
		str_append(line, "\r\n");

		client_send_raw_data(client, str_data(line), str_len(line));
	} T_END;
}

static void 
pop3_client_notify_disconnect(struct client *client,
			      enum client_disconnect_reason reason,
			      const char *text)
{
	if (reason == CLIENT_DISCONNECT_INTERNAL_ERROR)
		client_send_reply(client, POP3_CMD_REPLY_TEMPFAIL, text);
	else
		client_send_reply(client, POP3_CMD_REPLY_ERROR, text);
}

static void pop3_login_die(void)
{
	/* do nothing. pop3 connections typically die pretty quick anyway. */
}

static void pop3_login_preinit(void)
{
	login_set_roots = pop3_login_setting_roots;
}

static void pop3_login_init(void)
{
	/* override the default login_die() */
	master_service_set_die_callback(master_service, pop3_login_die);
}

static void pop3_login_deinit(void)
{
	clients_destroy_all();
}

static struct client_vfuncs pop3_client_vfuncs = {
	.alloc = pop3_client_alloc,
	.create = pop3_client_create,
	.destroy = pop3_client_destroy,
	.notify_auth_ready = pop3_client_notify_auth_ready,
	.notify_disconnect = pop3_client_notify_disconnect,
	.notify_starttls = pop3_client_notify_starttls,
	.starttls = pop3_client_starttls,
	.input = pop3_client_input,
	.auth_result = pop3_client_auth_result,
	.proxy_reset = pop3_proxy_reset,
	.proxy_parse_line = pop3_proxy_parse_line,
	.proxy_failed = pop3_proxy_failed,
	.proxy_get_state = pop3_proxy_get_state,
	.send_raw_data = client_common_send_raw_data,
	.input_next_cmd  = pop3_client_input_next_cmd,
	.free = client_common_default_free,
};

static struct login_binary pop3_login_binary = {
	.protocol = "pop3",
	.process_name = "pop3-login",
	.default_port = 110,
	.default_ssl_port = 995,

	.event_category = {
		.name = "pop3",
	},

	.client_vfuncs = &pop3_client_vfuncs,
	.preinit = pop3_login_preinit,
	.init = pop3_login_init,
	.deinit = pop3_login_deinit,

	.sasl_support_final_reply = FALSE,
	.anonymous_login_acceptable = TRUE,
};

int main(int argc, char *argv[])
{
	return login_binary_run(&pop3_login_binary, argc, argv);
}