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