1 /*
2 * SysDB - src/frontend/sock.c
3 * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
28 #if HAVE_CONFIG_H
29 # include "config.h"
30 #endif /* HAVE_CONFIG_H */
32 #include "sysdb.h"
33 #include "core/object.h"
34 #include "frontend/connection-private.h"
35 #include "frontend/sock.h"
37 #include "utils/channel.h"
38 #include "utils/error.h"
39 #include "utils/llist.h"
40 #include "utils/os.h"
41 #include "utils/strbuf.h"
43 #include <assert.h>
44 #include <errno.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
50 #include <unistd.h>
52 #include <sys/time.h>
53 #include <sys/types.h>
54 #include <sys/select.h>
55 #include <sys/socket.h>
56 #include <sys/param.h>
57 #include <sys/un.h>
59 #ifdef HAVE_UCRED_H
60 # include <ucred.h>
61 #endif
62 #ifdef HAVE_SYS_UCRED_H
63 # include <sys/ucred.h>
64 #endif
66 #include <pwd.h>
68 #include <netdb.h>
69 #include <libgen.h>
70 #include <pthread.h>
72 /*
73 * private data types
74 */
76 typedef struct {
77 char *address;
78 int type;
80 int sock_fd;
81 int (*accept)(sdb_conn_t *);
82 int (*peer)(sdb_conn_t *);
83 } listener_t;
85 typedef struct {
86 int type;
87 const char *prefix;
89 int (*open)(listener_t *);
90 void (*close)(listener_t *);
91 } fe_listener_impl_t;
93 struct sdb_fe_socket {
94 listener_t *listeners;
95 size_t listeners_num;
97 sdb_llist_t *open_connections;
99 /* channel used for communication between main
100 * and connection handler threads */
101 sdb_channel_t *chan;
102 };
104 /*
105 * connection management functions
106 */
108 static int
109 unixsock_peer(sdb_conn_t *conn)
110 {
111 uid_t uid;
113 struct passwd pw_entry;
114 struct passwd *result = NULL;
115 char buf[1024];
117 #ifdef SO_PEERCRED
118 struct ucred cred;
119 socklen_t len = sizeof(cred);
121 if (getsockopt(conn->fd, SOL_SOCKET, SO_PEERCRED, &cred, &len)
122 || (len != sizeof(cred))) {
123 char errbuf[1024];
124 sdb_log(SDB_LOG_ERR, "frontend: Failed to determine peer for "
125 "connection conn#%i: %s", conn->fd,
126 sdb_strerror(errno, errbuf, sizeof(errbuf)));
127 return -1;
128 }
129 uid = cred.uid;
130 #else /* SO_PEERCRED */
131 sdb_log(SDB_LOG_ERR, "frontend: Failed to determine peer for "
132 "connection conn#%i: operation not supported", conn->fd);
133 return -1;
134 #endif
136 memset(&pw_entry, 0, sizeof(pw_entry));
137 if (getpwuid_r(uid, &pw_entry, buf, sizeof(buf), &result) || (! result)
138 || (! (conn->username = strdup(result->pw_name)))) {
139 char errbuf[1024];
140 sdb_log(SDB_LOG_ERR, "frontend: Failed to determine peer for "
141 "connection conn#%i: %s", conn->fd,
142 sdb_strerror(errno, errbuf, sizeof(errbuf)));
143 return -1;
144 }
145 return 0;
146 } /* unixsock_peer */
148 static int
149 open_unixsock(listener_t *listener)
150 {
151 char *addr_copy;
152 char *base_dir;
153 struct sockaddr_un sa;
154 int status;
156 listener->sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
157 if (listener->sock_fd < 0) {
158 char buf[1024];
159 sdb_log(SDB_LOG_ERR, "frontend: Failed to open UNIX socket %s: %s",
160 listener->address, sdb_strerror(errno, buf, sizeof(buf)));
161 return -1;
162 }
164 memset(&sa, 0, sizeof(sa));
165 sa.sun_family = AF_UNIX;
166 strncpy(sa.sun_path, listener->address, sizeof(sa.sun_path));
168 addr_copy = strdup(listener->address);
169 if (! addr_copy) {
170 char errbuf[1024];
171 sdb_log(SDB_LOG_ERR, "frontend: strdup failed: %s",
172 sdb_strerror(errno, errbuf, sizeof(errbuf)));
173 return -1;
174 }
175 base_dir = dirname(addr_copy);
177 /* ensure that the directory exists */
178 if (sdb_mkdir_all(base_dir, 0777)) {
179 char errbuf[1024];
180 sdb_log(SDB_LOG_ERR, "frontend: Failed to create directory '%s': %s",
181 base_dir, sdb_strerror(errno, errbuf, sizeof(errbuf)));
182 free(addr_copy);
183 return -1;
184 }
185 free(addr_copy);
187 if (unlink(listener->address) && (errno != ENOENT)) {
188 char errbuf[1024];
189 sdb_log(SDB_LOG_WARNING, "frontend: Failed to remove stale UNIX "
190 "socket %s: %s", listener->address,
191 sdb_strerror(errno, errbuf, sizeof(errbuf)));
192 }
194 status = bind(listener->sock_fd, (struct sockaddr *)&sa, sizeof(sa));
195 if (status) {
196 char buf[1024];
197 sdb_log(SDB_LOG_ERR, "frontend: Failed to bind to UNIX socket %s: %s",
198 listener->address, sdb_strerror(errno, buf, sizeof(buf)));
199 return -1;
200 }
202 listener->peer = unixsock_peer;
203 return 0;
204 } /* open_unixsock */
206 static void
207 close_unixsock(listener_t *listener)
208 {
209 assert(listener);
211 if (! listener->address)
212 return;
214 if (listener->sock_fd >= 0)
215 close(listener->sock_fd);
216 listener->sock_fd = -1;
218 unlink(listener->address);
219 } /* close_unixsock */
221 static int
222 open_tcp(listener_t *listener)
223 {
224 struct addrinfo *ai, *ai_list = NULL;
225 int status;
227 assert(listener);
229 if ((status = sdb_resolve(SDB_NET_TCP, listener->address, &ai_list))) {
230 sdb_log(SDB_LOG_ERR, "frontend: Failed to resolve '%s': %s",
231 listener->address, gai_strerror(status));
232 return -1;
233 }
235 for (ai = ai_list; ai != NULL; ai = ai->ai_next) {
236 char errbuf[1024];
237 int reuse = 1;
239 listener->sock_fd = socket(ai->ai_family,
240 ai->ai_socktype, ai->ai_protocol);
241 if (listener->sock_fd < 0) {
242 sdb_log(SDB_LOG_ERR, "frontend: Failed to open socket for %s: %s",
243 listener->address,
244 sdb_strerror(errno, errbuf, sizeof(errbuf)));
245 continue;
246 }
248 if (setsockopt(listener->sock_fd, SOL_SOCKET, SO_REUSEADDR,
249 &reuse, sizeof(reuse)) < 0) {
250 sdb_log(SDB_LOG_ERR, "frontend: Failed to set socket option: %s",
251 sdb_strerror(errno, errbuf, sizeof(errbuf)));
252 close(listener->sock_fd);
253 listener->sock_fd = -1;
254 continue;
255 }
257 if (bind(listener->sock_fd, ai->ai_addr, ai->ai_addrlen) < 0) {
258 char host[1024], port[32];
259 getnameinfo(ai->ai_addr, ai->ai_addrlen, host, sizeof(host),
260 port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV);
261 sdb_log(SDB_LOG_ERR, "frontend: Failed to bind to %s:%s: %s",
262 host, port, sdb_strerror(errno, errbuf, sizeof(errbuf)));
263 close(listener->sock_fd);
264 listener->sock_fd = -1;
265 continue;
266 }
267 break;
268 }
269 freeaddrinfo(ai_list);
271 if (listener->sock_fd < 0)
272 return -1;
273 return 0;
274 } /* open_tcp */
276 static void
277 close_tcp(listener_t *listener)
278 {
279 assert(listener);
281 if (listener->sock_fd >= 0)
282 close(listener->sock_fd);
283 listener->sock_fd = -1;
284 } /* close_tcp */
286 /*
287 * private variables
288 */
290 /* the enum has to be sorted the same as the implementations array
291 * to ensure that the type may be used as index into the array */
292 enum {
293 LISTENER_TCP = 0, /* this is the default */
294 LISTENER_UNIXSOCK,
295 };
296 static fe_listener_impl_t listener_impls[] = {
297 { LISTENER_TCP, "tcp", open_tcp, close_tcp },
298 { LISTENER_UNIXSOCK, "unix", open_unixsock, close_unixsock },
299 };
301 /*
302 * private helper functions
303 */
305 static int
306 listener_listen(listener_t *listener)
307 {
308 assert(listener);
310 /* try to reopen */
311 if (listener->sock_fd < 0)
312 if (listener_impls[listener->type].open(listener))
313 return -1;
314 assert(listener->sock_fd >= 0);
316 if (listen(listener->sock_fd, /* backlog = */ 32)) {
317 char buf[1024];
318 sdb_log(SDB_LOG_ERR, "frontend: Failed to listen on socket %s: %s",
319 listener->address, sdb_strerror(errno, buf, sizeof(buf)));
320 return -1;
321 }
322 return 0;
323 } /* listener_listen */
325 static void
326 listener_close(listener_t *listener)
327 {
328 assert(listener);
330 if (listener_impls[listener->type].close)
331 listener_impls[listener->type].close(listener);
333 if (listener->sock_fd >= 0)
334 close(listener->sock_fd);
335 listener->sock_fd = -1;
336 } /* listener_close */
338 static int
339 get_type(const char *address)
340 {
341 char *sep;
342 size_t len;
343 size_t i;
345 if (*address == '/')
346 return LISTENER_UNIXSOCK;
347 sep = strchr(address, (int)':');
348 if (! sep)
349 return listener_impls[0].type;
351 assert(sep > address);
352 len = (size_t)(sep - address);
354 for (i = 0; i < SDB_STATIC_ARRAY_LEN(listener_impls); ++i) {
355 fe_listener_impl_t *impl = listener_impls + i;
357 if (!strncmp(address, impl->prefix, len)) {
358 assert(impl->type == (int)i);
359 return impl->type;
360 }
361 }
362 return -1;
363 } /* get_type */
365 static void
366 listener_destroy(listener_t *listener)
367 {
368 if (! listener)
369 return;
371 listener_close(listener);
373 if (listener->address)
374 free(listener->address);
375 listener->address = NULL;
376 } /* listener_destroy */
378 static listener_t *
379 listener_create(sdb_fe_socket_t *sock, const char *address)
380 {
381 listener_t *listener;
382 size_t len;
383 int type;
385 type = get_type(address);
386 if (type < 0) {
387 sdb_log(SDB_LOG_ERR, "frontend: Unsupported address type specified "
388 "in listen address '%s'", address);
389 return NULL;
390 }
392 listener = realloc(sock->listeners,
393 (sock->listeners_num + 1) * sizeof(*sock->listeners));
394 if (! listener) {
395 char buf[1024];
396 sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate memory: %s",
397 sdb_strerror(errno, buf, sizeof(buf)));
398 return NULL;
399 }
401 sock->listeners = listener;
402 listener = sock->listeners + sock->listeners_num;
404 len = strlen(listener_impls[type].prefix);
405 if ((! strncmp(address, listener_impls[type].prefix, len))
406 && (address[len] == ':'))
407 address += strlen(listener_impls[type].prefix) + 1;
409 listener->sock_fd = -1;
410 listener->address = strdup(address);
411 if (! listener->address) {
412 char buf[1024];
413 sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate memory: %s",
414 sdb_strerror(errno, buf, sizeof(buf)));
415 listener_destroy(listener);
416 return NULL;
417 }
418 listener->type = type;
419 listener->accept = NULL;
420 listener->peer = NULL;
422 if (listener_impls[type].open(listener)) {
423 /* prints error */
424 listener_destroy(listener);
425 return NULL;
426 }
428 ++sock->listeners_num;
429 return listener;
430 } /* listener_create */
432 static void
433 socket_clear(sdb_fe_socket_t *sock)
434 {
435 size_t i;
437 assert(sock);
438 for (i = 0; i < sock->listeners_num; ++i)
439 listener_destroy(sock->listeners + i);
440 if (sock->listeners)
441 free(sock->listeners);
442 sock->listeners = NULL;
443 sock->listeners_num = 0;
444 } /* socket_clear */
446 static void
447 socket_close(sdb_fe_socket_t *sock)
448 {
449 size_t i;
451 assert(sock);
452 for (i = 0; i < sock->listeners_num; ++i)
453 listener_close(sock->listeners + i);
454 } /* socket_close */
456 /*
457 * connection handler functions
458 */
460 static void *
461 connection_handler(void *data)
462 {
463 sdb_fe_socket_t *sock = data;
465 assert(sock);
467 while (42) {
468 struct timespec timeout = { 0, 500000000 }; /* .5 seconds */
469 sdb_conn_t *conn;
470 int status;
472 errno = 0;
473 status = sdb_channel_select(sock->chan, /* read */ NULL, &conn,
474 /* write */ NULL, NULL, &timeout);
475 if (status) {
476 char buf[1024];
478 if (errno == ETIMEDOUT)
479 continue;
480 if (errno == EBADF) /* channel shut down */
481 break;
483 sdb_log(SDB_LOG_ERR, "frontend: Failed to read from channel: %s",
484 sdb_strerror(errno, buf, sizeof(buf)));
485 continue;
486 }
488 status = (int)sdb_connection_handle(conn);
489 if (status <= 0) {
490 /* error or EOF -> close connection */
491 sdb_object_deref(SDB_OBJ(conn));
492 continue;
493 }
495 /* return the connection to the main loop */
496 if (sdb_llist_append(sock->open_connections, SDB_OBJ(conn))) {
497 sdb_log(SDB_LOG_ERR, "frontend: Failed to re-append "
498 "connection %s to list of open connections",
499 SDB_OBJ(conn)->name);
500 }
502 /* pass ownership back to list; or destroy in case of an error */
503 sdb_object_deref(SDB_OBJ(conn));
504 }
505 return NULL;
506 } /* connection_handler */
508 static int
509 connection_accept(sdb_fe_socket_t *sock, listener_t *listener)
510 {
511 sdb_object_t *obj;
512 int status;
514 obj = SDB_OBJ(sdb_connection_accept(listener->sock_fd));
515 if (! obj)
516 return -1;
518 if (listener->accept && listener->accept(CONN(obj))) {
519 /* accept() is expected to log an error */
520 sdb_object_deref(obj);
521 return -1;
522 }
523 if (listener->peer && listener->peer(CONN(obj))) {
524 /* peer() is expected to log an error */
525 sdb_object_deref(obj);
526 return -1;
527 }
529 status = sdb_llist_append(sock->open_connections, obj);
530 if (status)
531 sdb_log(SDB_LOG_ERR, "frontend: Failed to append "
532 "connection %s to list of open connections",
533 obj->name);
535 /* hand ownership over to the list; or destroy in case of an error */
536 sdb_object_deref(obj);
537 return status;
538 } /* connection_accept */
540 static int
541 socket_handle_incoming(sdb_fe_socket_t *sock,
542 fd_set *ready, fd_set *exceptions)
543 {
544 sdb_llist_iter_t *iter;
545 size_t i;
547 for (i = 0; i < sock->listeners_num; ++i) {
548 listener_t *listener = sock->listeners + i;
549 if (FD_ISSET(listener->sock_fd, ready))
550 if (connection_accept(sock, listener))
551 continue;
552 }
554 iter = sdb_llist_get_iter(sock->open_connections);
555 if (! iter) {
556 sdb_log(SDB_LOG_ERR, "frontend: Failed to acquire iterator "
557 "for open connections");
558 return -1;
559 }
561 while (sdb_llist_iter_has_next(iter)) {
562 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
564 if (FD_ISSET(CONN(obj)->fd, exceptions)) {
565 sdb_log(SDB_LOG_INFO, "Exception on fd %d",
566 CONN(obj)->fd);
567 /* close the connection */
568 sdb_llist_iter_remove_current(iter);
569 sdb_object_deref(obj);
570 continue;
571 }
573 if (FD_ISSET(CONN(obj)->fd, ready)) {
574 sdb_llist_iter_remove_current(iter);
575 sdb_channel_write(sock->chan, &obj);
576 }
577 }
578 sdb_llist_iter_destroy(iter);
579 return 0;
580 } /* socket_handle_incoming */
582 /*
583 * public API
584 */
586 sdb_fe_socket_t *
587 sdb_fe_sock_create(void)
588 {
589 sdb_fe_socket_t *sock;
591 sock = calloc(1, sizeof(*sock));
592 if (! sock)
593 return NULL;
595 sock->open_connections = sdb_llist_create();
596 if (! sock->open_connections) {
597 sdb_fe_sock_destroy(sock);
598 return NULL;
599 }
600 return sock;
601 } /* sdb_fe_sock_create */
603 void
604 sdb_fe_sock_destroy(sdb_fe_socket_t *sock)
605 {
606 if (! sock)
607 return;
609 socket_clear(sock);
611 sdb_llist_destroy(sock->open_connections);
612 sock->open_connections = NULL;
613 free(sock);
614 } /* sdb_fe_sock_destroy */
616 int
617 sdb_fe_sock_add_listener(sdb_fe_socket_t *sock, const char *address)
618 {
619 listener_t *listener;
621 if ((! sock) || (! address))
622 return -1;
624 listener = listener_create(sock, address);
625 if (! listener)
626 return -1;
627 return 0;
628 } /* sdb_fe_sock_add_listener */
630 void
631 sdb_fe_sock_clear_listeners(sdb_fe_socket_t *sock)
632 {
633 if (! sock)
634 return;
636 socket_clear(sock);
637 } /* sdb_fe_sock_clear_listeners */
639 int
640 sdb_fe_sock_listen_and_serve(sdb_fe_socket_t *sock, sdb_fe_loop_t *loop)
641 {
642 fd_set sockets;
643 int max_listen_fd = 0;
644 size_t i;
646 pthread_t handler_threads[loop->num_threads];
647 size_t num_threads;
649 if ((! sock) || (! sock->listeners_num) || sock->chan
650 || (! loop) || (loop->num_threads <= 0))
651 return -1;
653 if (! loop->do_loop)
654 return 0;
656 FD_ZERO(&sockets);
657 for (i = 0; i < sock->listeners_num; ++i) {
658 listener_t *listener = sock->listeners + i;
660 if (listener_listen(listener)) {
661 socket_close(sock);
662 return -1;
663 }
665 FD_SET(listener->sock_fd, &sockets);
666 if (listener->sock_fd > max_listen_fd)
667 max_listen_fd = listener->sock_fd;
668 }
670 sock->chan = sdb_channel_create(1024, sizeof(sdb_conn_t *));
671 if (! sock->chan) {
672 socket_close(sock);
673 return -1;
674 }
676 sdb_log(SDB_LOG_INFO, "frontend: Starting %zu connection "
677 "handler thread%s managing %zu listener%s",
678 loop->num_threads, loop->num_threads == 1 ? "" : "s",
679 sock->listeners_num, sock->listeners_num == 1 ? "" : "s");
681 num_threads = loop->num_threads;
682 memset(&handler_threads, 0, sizeof(handler_threads));
683 for (i = 0; i < num_threads; ++i) {
684 errno = 0;
685 if (pthread_create(&handler_threads[i], /* attr = */ NULL,
686 connection_handler, /* arg = */ sock)) {
687 char errbuf[1024];
688 sdb_log(SDB_LOG_ERR, "frontend: Failed to create "
689 "connection handler thread: %s",
690 sdb_strerror(errno, errbuf, sizeof(errbuf)));
691 num_threads = i;
692 break;
693 }
694 }
696 while (loop->do_loop && num_threads) {
697 struct timeval timeout = { 1, 0 }; /* one second */
698 sdb_llist_iter_t *iter;
700 int max_fd = max_listen_fd;
701 fd_set ready;
702 fd_set exceptions;
703 int n;
705 FD_ZERO(&ready);
706 FD_ZERO(&exceptions);
708 ready = sockets;
710 iter = sdb_llist_get_iter(sock->open_connections);
711 if (! iter) {
712 sdb_log(SDB_LOG_ERR, "frontend: Failed to acquire iterator "
713 "for open connections");
714 break;
715 }
717 while (sdb_llist_iter_has_next(iter)) {
718 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
720 if (CONN(obj)->fd < 0) {
721 sdb_llist_iter_remove_current(iter);
722 sdb_object_deref(obj);
723 continue;
724 }
726 FD_SET(CONN(obj)->fd, &ready);
727 FD_SET(CONN(obj)->fd, &exceptions);
729 if (CONN(obj)->fd > max_fd)
730 max_fd = CONN(obj)->fd;
731 }
732 sdb_llist_iter_destroy(iter);
734 errno = 0;
735 n = select(max_fd + 1, &ready, NULL, &exceptions, &timeout);
736 if (n < 0) {
737 char buf[1024];
739 if (errno == EINTR)
740 continue;
742 sdb_log(SDB_LOG_ERR, "frontend: Failed to monitor sockets: %s",
743 sdb_strerror(errno, buf, sizeof(buf)));
744 break;
745 }
746 else if (! n)
747 continue;
749 /* handle new and open connections */
750 if (socket_handle_incoming(sock, &ready, &exceptions))
751 break;
752 }
754 socket_close(sock);
756 sdb_log(SDB_LOG_INFO, "frontend: Waiting for connection handler threads "
757 "to terminate");
758 if (! sdb_channel_shutdown(sock->chan))
759 for (i = 0; i < num_threads; ++i)
760 pthread_join(handler_threads[i], NULL);
761 /* else: we tried our best; let the operating system clean up */
763 sdb_channel_destroy(sock->chan);
764 sock->chan = NULL;
766 if (! num_threads)
767 return -1;
768 return 0;
769 } /* sdb_fe_sock_listen_and_server */
771 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */