1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2
3 #include "imap-urlauth-common.h"
4 #include "array.h"
5 #include "ioloop.h"
6 #include "net.h"
7 #include "fdpass.h"
8 #include "istream.h"
9 #include "ostream.h"
10 #include "str.h"
11 #include "strescape.h"
12 #include "eacces-error.h"
13 #include "llist.h"
14 #include "hostpid.h"
15 #include "execv-const.h"
16 #include "env-util.h"
17 #include "var-expand.h"
18 #include "restrict-access.h"
19 #include "master-service.h"
20 #include "master-interface.h"
21
22 #include <unistd.h>
23 #include <sys/wait.h>
24
25 #define IMAP_URLAUTH_PROTOCOL_MAJOR_VERSION 1
26 #define IMAP_URLAUTH_PROTOCOL_MINOR_VERSION 0
27
28 #define IMAP_URLAUTH_WORKER_SOCKET "imap-urlauth-worker"
29
30 /* max. length of input lines (URLs) */
31 #define MAX_INBUF_SIZE 2048
32
33 /* Disconnect client after idling this many milliseconds */
34 #define CLIENT_IDLE_TIMEOUT_MSECS (10*60*1000)
35
36 #define USER_EXECUTABLE "imap-urlauth-worker"
37
38 #define IS_STANDALONE() \
39 (getenv(MASTER_IS_PARENT_ENV) == NULL)
40
41 static struct event_category event_category_urlauth = {
42 .name = "imap-urlauth",
43 };
44
45 struct client *imap_urlauth_clients;
46 unsigned int imap_urlauth_client_count;
47
48 static int client_worker_connect(struct client *client);
49 static void client_worker_disconnect(struct client *client);
50 static void client_worker_input(struct client *client);
51
client_create(const char * service,const char * username,int fd_in,int fd_out,const struct imap_urlauth_settings * set,struct client ** client_r)52 int client_create(const char *service, const char *username,
53 int fd_in, int fd_out, const struct imap_urlauth_settings *set,
54 struct client **client_r)
55 {
56 struct client *client;
57 const char *app;
58
59 /* always use nonblocking I/O */
60 net_set_nonblock(fd_in, TRUE);
61 net_set_nonblock(fd_out, TRUE);
62
63 client = i_new(struct client, 1);
64 client->fd_in = fd_in;
65 client->fd_out = fd_out;
66 client->fd_ctrl = -1;
67 client->set = set;
68
69 client->event = event_create(NULL);
70 event_set_forced_debug(client->event, set->mail_debug);
71 event_add_category(client->event, &event_category_urlauth);
72 event_set_append_log_prefix(client->event, t_strdup_printf(
73 "user %s: ", username));
74
75 if (client_worker_connect(client) < 0) {
76 event_unref(&client->event);
77 i_free(client);
78 return -1;
79 }
80
81 /* determine user's special privileges */
82 i_array_init(&client->access_apps, 4);
83 if (username != NULL) {
84 if (set->imap_urlauth_submit_user != NULL &&
85 strcmp(set->imap_urlauth_submit_user, username) == 0) {
86 e_debug(client->event, "User has URLAUTH submit access");
87 app = "submit+";
88 array_push_back(&client->access_apps, &app);
89 }
90 if (set->imap_urlauth_stream_user != NULL &&
91 strcmp(set->imap_urlauth_stream_user, username) == 0) {
92 e_debug(client->event, "User has URLAUTH stream access");
93 app = "stream";
94 array_push_back(&client->access_apps, &app);
95 }
96 }
97
98 client->username = i_strdup(username);
99 client->service = i_strdup(service);
100
101 client->output = o_stream_create_fd(fd_out, SIZE_MAX);
102
103 imap_urlauth_client_count++;
104 DLLIST_PREPEND(&imap_urlauth_clients, client);
105
106 imap_urlauth_refresh_proctitle();
107 *client_r = client;
108 return 0;
109 }
110
client_send_line(struct client * client,const char * fmt,...)111 void client_send_line(struct client *client, const char *fmt, ...)
112 {
113 va_list va;
114 ssize_t ret;
115
116 if (client->output->closed)
117 return;
118
119 va_start(va, fmt);
120
121 T_BEGIN {
122 string_t *str;
123
124 str = t_str_new(256);
125 str_vprintfa(str, fmt, va);
126 str_append(str, "\n");
127
128 ret = o_stream_send(client->output,
129 str_data(str), str_len(str));
130 i_assert(ret < 0 || (size_t)ret == str_len(str));
131 } T_END;
132
133 va_end(va);
134 }
135
client_worker_connect(struct client * client)136 static int client_worker_connect(struct client *client)
137 {
138 static const char handshake[] = "VERSION\timap-urlauth-worker\t2\t0\n";
139 const char *socket_path;
140 ssize_t ret;
141 unsigned char data;
142
143 socket_path = t_strconcat(client->set->base_dir,
144 "/"IMAP_URLAUTH_WORKER_SOCKET, NULL);
145
146 e_debug(client->event, "Connecting to worker socket %s", socket_path);
147
148 client->fd_ctrl = net_connect_unix_with_retries(socket_path, 1000);
149 if (client->fd_ctrl < 0) {
150 if (errno == EACCES) {
151 e_error(client->event, "imap-urlauth-client: %s",
152 eacces_error_get("net_connect_unix",
153 socket_path));
154 } else {
155 e_error(client->event, "imap-urlauth-client: "
156 "net_connect_unix(%s) failed: %m",
157 socket_path);
158 }
159 return -1;
160 }
161
162 /* transfer one or two fds */
163 data = (client->fd_in == client->fd_out ? '0' : '1');
164 ret = fd_send(client->fd_ctrl, client->fd_in, &data, sizeof(data));
165 if (ret > 0 && client->fd_in != client->fd_out) {
166 data = '0';
167 ret = fd_send(client->fd_ctrl, client->fd_out,
168 &data, sizeof(data));
169 }
170
171 if (ret <= 0) {
172 if (ret < 0) {
173 e_error(client->event, "fd_send(%s, %d) failed: %m",
174 socket_path, client->fd_ctrl);
175 } else {
176 e_error(client->event, "fd_send(%s, %d) failed to send byte",
177 socket_path, client->fd_ctrl);
178 }
179 client_worker_disconnect(client);
180 return -1;
181 }
182
183 client->ctrl_output = o_stream_create_fd(client->fd_ctrl, SIZE_MAX);
184
185 /* send protocol version handshake */
186 if (o_stream_send_str(client->ctrl_output, handshake) < 0) {
187 e_error(client->event,
188 "Error sending handshake to imap-urlauth worker: %m");
189 client_worker_disconnect(client);
190 return -1;
191 }
192
193 client->ctrl_input =
194 i_stream_create_fd(client->fd_ctrl, MAX_INBUF_SIZE);
195 client->ctrl_io =
196 io_add(client->fd_ctrl, IO_READ, client_worker_input, client);
197 return 0;
198 }
199
client_worker_disconnect(struct client * client)200 void client_worker_disconnect(struct client *client)
201 {
202 client->worker_state = IMAP_URLAUTH_WORKER_STATE_INACTIVE;
203
204 io_remove(&client->ctrl_io);
205 o_stream_destroy(&client->ctrl_output);
206 i_stream_destroy(&client->ctrl_input);
207 if (client->fd_ctrl >= 0) {
208 net_disconnect(client->fd_ctrl);
209 client->fd_ctrl = -1;
210 }
211 }
212
213 static int
client_worker_input_line(struct client * client,const char * response)214 client_worker_input_line(struct client *client, const char *response)
215 {
216 const char *const *apps;
217 unsigned int count, i;
218 bool restart;
219 string_t *str;
220 int ret;
221
222 switch (client->worker_state) {
223 case IMAP_URLAUTH_WORKER_STATE_INACTIVE:
224 if (strcasecmp(response, "OK") != 0) {
225 client_disconnect(client, "Worker handshake failed");
226 return -1;
227 }
228 client->worker_state = IMAP_URLAUTH_WORKER_STATE_CONNECTED;
229
230 str = t_str_new(256);
231 str_append(str, "ACCESS\t");
232 if (client->username != NULL)
233 str_append_tabescaped(str, client->username);
234 str_append(str, "\t");
235 str_append_tabescaped(str, client->service);
236 if (client->set->mail_debug)
237 str_append(str, "\tdebug");
238 if (array_count(&client->access_apps) > 0) {
239 str_append(str, "\tapps=");
240 apps = array_get(&client->access_apps, &count);
241 str_append(str, apps[0]);
242 for (i = 1; i < count; i++) {
243 str_append_c(str, ',');
244 str_append_tabescaped(str, apps[i]);
245 }
246 }
247 str_append(str, "\n");
248
249 ret = o_stream_send(client->ctrl_output,
250 str_data(str), str_len(str));
251 i_assert(ret < 0 || (size_t)ret == str_len(str));
252 if (ret < 0) {
253 client_disconnect(client,
254 "Failed to send ACCESS control command to worker");
255 return -1;
256 }
257 break;
258
259 case IMAP_URLAUTH_WORKER_STATE_CONNECTED:
260 if (strcasecmp(response, "OK") != 0) {
261 client_disconnect(client,
262 "Failed to negotiate access parameters");
263 return -1;
264 }
265 client->worker_state = IMAP_URLAUTH_WORKER_STATE_ACTIVE;
266 break;
267
268 case IMAP_URLAUTH_WORKER_STATE_ACTIVE:
269 restart = TRUE;
270 if (strcasecmp(response, "DISCONNECTED") == 0) {
271 /* worker detected client disconnect */
272 restart = FALSE;
273 } else if (strcasecmp(response, "FINISHED") != 0) {
274 /* unknown response */
275 client_disconnect(client,
276 "Worker finished with unknown response");
277 return -1;
278 }
279
280 e_debug(client->event, "Worker finished successfully");
281
282 if (restart) {
283 /* connect to new worker for accessing different user */
284 client_worker_disconnect(client);
285 if (client_worker_connect(client) < 0) {
286 client_disconnect(client,
287 "Failed to connect to new worker");
288 return -1;
289 }
290
291 /* indicate success of "END" command */
292 client_send_line(client, "OK");
293 } else {
294 client_disconnect(client, "Client disconnected");
295 }
296 return -1;
297 default:
298 i_unreached();
299 }
300 return 0;
301 }
302
client_worker_input(struct client * client)303 void client_worker_input(struct client *client)
304 {
305 struct istream *input = client->ctrl_input;
306 const char *line;
307
308 if (input->closed) {
309 /* disconnected */
310 client_disconnect(client, "Worker disconnected unexpectedly");
311 return;
312 }
313
314 switch (i_stream_read(input)) {
315 case -1:
316 /* disconnected */
317 client_disconnect(client, "Worker disconnected unexpectedly");
318 return;
319 case -2:
320 /* input buffer full */
321 client_disconnect(client, "Worker sent too large input");
322 return;
323 }
324
325 while ((line = i_stream_next_line(input)) != NULL) {
326 if (client_worker_input_line(client, line) < 0)
327 return;
328 }
329 }
330
client_destroy(struct client * client,const char * reason)331 void client_destroy(struct client *client, const char *reason)
332 {
333 i_assert(reason != NULL || client->disconnected);
334
335 if (!client->disconnected)
336 e_info(client->event, "Disconnected: %s", reason);
337
338 imap_urlauth_client_count--;
339 DLLIST_REMOVE(&imap_urlauth_clients, client);
340
341 timeout_remove(&client->to_idle);
342
343 client_worker_disconnect(client);
344
345 o_stream_destroy(&client->output);
346
347 fd_close_maybe_stdio(&client->fd_in, &client->fd_out);
348
349 event_unref(&client->event);
350
351 i_free(client->username);
352 i_free(client->service);
353 array_free(&client->access_apps);
354 i_free(client);
355
356 master_service_client_connection_destroyed(master_service);
357 imap_urlauth_refresh_proctitle();
358 }
359
client_destroy_timeout(struct client * client)360 static void client_destroy_timeout(struct client *client)
361 {
362 client_destroy(client, NULL);
363 }
364
client_disconnect(struct client * client,const char * reason)365 void client_disconnect(struct client *client, const char *reason)
366 {
367 if (client->disconnected)
368 return;
369
370 client->disconnected = TRUE;
371 e_info(client->event, "Disconnected: %s", reason);
372
373 client->to_idle = timeout_add(0, client_destroy_timeout, client);
374 }
375
clients_destroy_all(void)376 void clients_destroy_all(void)
377 {
378 while (imap_urlauth_clients != NULL)
379 client_destroy(imap_urlauth_clients, "Server shutting down.");
380 }
381