1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2 
3 /*
4 The imap-urlauth service provides URLAUTH access between different accounts. If
5 user A has an URLAUTH that references a mail from user B, it makes a connection
6 to the imap-urlauth service to access user B's mail store to retrieve the
7 mail.
8 
9 The authentication and authorization of the URLAUTH is performed within
10 this service. Because access to the mailbox and the associated mailbox keys is
11 necessary to retrieve the message and for verification of the URLAUTH, the
12 urlauth services need root privileges. To mitigate security concerns, the
13 retrieval and verification of the URLs is performed in a worker service that
14 drops root privileges and acts as target user B.
15 
16 The imap-urlauth service thus consists of three separate stages:
17 
18 - imap-urlauth-login:
19   This is the login service which operates identical to imap-login and
20   pop3-login equivalents, except for the fact that only token authentication is
21   allowed. It verifies that the connecting client is an IMAP service acting on
22   behaf of an authenticated user.
23 
24 - imap-urlauth:
25   Once the client is authenticated, the connection gets passed to the
26   imap-urlauth service (as implemented here). The goal of this stage is
27   to prevent the need for re-authenticating to the imap-urlauth service when
28   the clients wants to switch to a different target user. It normally runs as
29   $default_internal_user and starts workers to perform the actual work. To start
30   a worker, the imap-urlauth service establishes a control connection to the
31   imap-urlauth-worker service. In the handshake phase of the control protocol,
32   the connection of the client is passed to the worker. Once the worker
33   finishes, a new worker is started and the client connection is transfered to
34   it, unless the client is disconnected.
35 
36 - imap-urlauth-worker:
37   The worker handles the URLAUTH requests from the client, so this is where the
38   mail store of the target user is accessed. The worker starts as root. In the
39   protocol interaction the client first indicates what the target user is.
40   The worker then performs a userdb lookup and drops privileges. The client can
41   then submit URLAUTH requests, which are limited to that user. Once the client
42   wants to access a different user, the worker terminates and the imap-urlauth
43   service starts a new worker for the next target user.
44 */
45 
46 #include "imap-urlauth-common.h"
47 #include "lib-signals.h"
48 #include "ioloop.h"
49 #include "buffer.h"
50 #include "array.h"
51 #include "istream.h"
52 #include "ostream.h"
53 #include "path-util.h"
54 #include "base64.h"
55 #include "str.h"
56 #include "process-title.h"
57 #include "auth-master.h"
58 #include "master-service.h"
59 #include "master-service-settings.h"
60 #include "master-login.h"
61 #include "master-interface.h"
62 #include "var-expand.h"
63 
64 #include <stdio.h>
65 #include <unistd.h>
66 
67 #define IS_STANDALONE() \
68         (getenv(MASTER_IS_PARENT_ENV) == NULL)
69 
70 bool verbose_proctitle = FALSE;
71 static struct master_login *master_login = NULL;
72 
73 static const struct imap_urlauth_settings *imap_urlauth_settings;
74 
imap_urlauth_refresh_proctitle(void)75 void imap_urlauth_refresh_proctitle(void)
76 {
77 	struct client *client;
78 	string_t *title = t_str_new(128);
79 
80 	if (!verbose_proctitle)
81 		return;
82 
83 	str_append_c(title, '[');
84 	switch (imap_urlauth_client_count) {
85 	case 0:
86 		str_append(title, "idling");
87 		break;
88 	case 1:
89 		client = imap_urlauth_clients;
90 		str_append(title, client->username);
91 		break;
92 	default:
93 		str_printfa(title, "%u connections", imap_urlauth_client_count);
94 		break;
95 	}
96 	str_append_c(title, ']');
97 	process_title_set(str_c(title));
98 }
99 
imap_urlauth_die(void)100 static void imap_urlauth_die(void)
101 {
102 	/* do nothing. imap_urlauth connections typically die pretty quick anyway. */
103 }
104 
105 static int
client_create_from_input(const char * service,const char * username,int fd_in,int fd_out)106 client_create_from_input(const char *service, const char *username,
107 		int fd_in, int fd_out)
108 {
109 	struct client *client;
110 
111 	if (client_create(service, username, fd_in, fd_out,
112 			  imap_urlauth_settings, &client) < 0)
113 		return -1;
114 
115 	if (!IS_STANDALONE())
116 		client_send_line(client, "OK");
117 	return 0;
118 }
119 
main_stdio_run(const char * username)120 static void main_stdio_run(const char *username)
121 {
122 	username = username != NULL ? username : getenv("USER");
123 	if (username == NULL && IS_STANDALONE())
124 		username = getlogin();
125 	if (username == NULL)
126 		i_fatal("USER environment missing");
127 
128 	(void)client_create_from_input("", username, STDIN_FILENO, STDOUT_FILENO);
129 }
130 
131 static void
login_client_connected(const struct master_login_client * client,const char * username,const char * const * extra_fields)132 login_client_connected(const struct master_login_client *client,
133 		       const char *username, const char *const *extra_fields)
134 {
135 	const char *msg = "NO\n";
136 	struct auth_user_reply reply;
137 	struct net_unix_cred cred;
138 	const char *const *fields;
139 	const char *service = NULL;
140 	unsigned int count, i;
141 
142 	auth_user_fields_parse(extra_fields, pool_datastack_create(), &reply);
143 
144 	/* check peer credentials if possible */
145 	if (reply.uid != (uid_t)-1 && net_getunixcred(client->fd, &cred) == 0 &&
146 		reply.uid != cred.uid) {
147 		i_error("Peer's credentials (uid=%ld) do not match "
148 			"the user that logged in (uid=%ld).",
149 			(long)cred.uid, (long)reply.uid);
150 		if (write(client->fd, msg, strlen(msg)) < 0) {
151 			/* ignored */
152 		}
153 		net_disconnect(client->fd);
154 		return;
155 	}
156 
157 	fields = array_get(&reply.extra_fields, &count);
158 	for (i = 0; i < count; i++) {
159 		if (str_begins(fields[i], "client_service=")) {
160 			service = fields[i] + 15;
161 			break;
162 		}
163 	}
164 
165 	if (service == NULL) {
166 		i_error("Auth did not yield required client_service field (BUG).");
167 		if (write(client->fd, msg, strlen(msg)) < 0) {
168 			/* ignored */
169 		}
170 		net_disconnect(client->fd);
171 		return;
172 	}
173 
174 	if (reply.anonymous)
175 		username = NULL;
176 
177 	if (client_create_from_input(service, username, client->fd, client->fd) < 0)
178 		net_disconnect(client->fd);
179 }
180 
login_client_failed(const struct master_login_client * client,const char * errormsg ATTR_UNUSED)181 static void login_client_failed(const struct master_login_client *client,
182 				const char *errormsg ATTR_UNUSED)
183 {
184 	const char *msg = "NO\n";
185 	if (write(client->fd, msg, strlen(msg)) < 0) {
186 		/* ignored */
187 	}
188 }
189 
client_connected(struct master_service_connection * conn)190 static void client_connected(struct master_service_connection *conn)
191 {
192 	/* when running standalone, we shouldn't even get here */
193 	i_assert(master_login != NULL);
194 
195 	master_service_client_connection_accept(conn);
196 	master_login_add(master_login, conn->fd);
197 }
198 
main(int argc,char * argv[])199 int main(int argc, char *argv[])
200 {
201 	static const struct setting_parser_info *set_roots[] = {
202 		&imap_urlauth_setting_parser_info,
203 		NULL
204 	};
205 	struct master_login_settings login_set;
206 	struct master_service_settings_input input;
207 	struct master_service_settings_output output;
208 	void **sets;
209 	enum master_service_flags service_flags = 0;
210 	const char *error = NULL, *username = NULL;
211 	const char *auth_socket_path = "auth-master";
212 	int c;
213 
214 	i_zero(&login_set);
215 	login_set.postlogin_timeout_secs = MASTER_POSTLOGIN_TIMEOUT_DEFAULT;
216 
217 	if (IS_STANDALONE() && getuid() == 0 &&
218 	    net_getpeername(1, NULL, NULL) == 0) {
219 		printf("NO imap_urlauth binary must not be started from "
220 		       "inetd, use imap-urlauth-login instead.\n");
221 		return 1;
222 	}
223 
224 	if (IS_STANDALONE()) {
225 		service_flags |= MASTER_SERVICE_FLAG_STANDALONE |
226 			MASTER_SERVICE_FLAG_STD_CLIENT;
227 	} else {
228 		service_flags |= MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN;
229 	}
230 
231 	master_service = master_service_init("imap-urlauth", service_flags,
232 					     &argc, &argv, "a:");
233 	while ((c = master_getopt(master_service)) > 0) {
234 		switch (c) {
235 		case 'a':
236 			auth_socket_path = optarg;
237 			break;
238 		default:
239 			return FATAL_DEFAULT;
240 		}
241 	}
242 	master_service_init_log(master_service);
243 
244 	i_zero(&input);
245 	input.roots = set_roots;
246 	input.module = "imap-urlauth";
247 	input.service = "imap-urlauth";
248 	if (master_service_settings_read(master_service, &input, &output,
249 						&error) < 0)
250 		i_fatal("Error reading configuration: %s", error);
251 
252 	sets = master_service_settings_get_others(master_service);
253 	imap_urlauth_settings = sets[0];
254 
255 	if (imap_urlauth_settings->verbose_proctitle)
256 		verbose_proctitle = TRUE;
257 
258 	if (t_abspath(auth_socket_path, &login_set.auth_socket_path, &error) < 0) {
259 		i_fatal("t_abspath(%s) failed: %s", auth_socket_path, error);
260 	}
261 	login_set.callback = login_client_connected;
262 	login_set.failure_callback = login_client_failed;
263 
264 	master_service_init_finish(master_service);
265 	master_service_set_die_callback(master_service, imap_urlauth_die);
266 
267 	/* fake that we're running, so we know if client was destroyed
268 	   while handling its initial input */
269 	io_loop_set_running(current_ioloop);
270 
271 	if (IS_STANDALONE()) {
272 		T_BEGIN {
273 			main_stdio_run(username);
274 		} T_END;
275 	} else {
276 		master_login = master_login_init(master_service, &login_set);
277 		io_loop_set_running(current_ioloop);
278 	}
279 
280 	if (io_loop_is_running(current_ioloop))
281 		master_service_run(master_service, client_connected);
282 	clients_destroy_all();
283 
284 	if (master_login != NULL)
285 		master_login_deinit(&master_login);
286 	master_service_deinit(&master_service);
287 	return 0;
288 }
289