Code

connection: Let sdb_connection_accept() handle all connection setup.
[sysdb.git] / src / frontend / sock.c
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 (*setup)(sdb_conn_t *, void *);
82 } listener_t;
84 typedef struct {
85         int type;
86         const char *prefix;
88         int (*open)(listener_t *);
89         void (*close)(listener_t *);
90 } fe_listener_impl_t;
92 struct sdb_fe_socket {
93         listener_t *listeners;
94         size_t listeners_num;
96         sdb_llist_t *open_connections;
98         /* channel used for communication between main
99          * and connection handler threads */
100         sdb_channel_t *chan;
101 };
103 /*
104  * connection management functions
105  */
107 static int
108 setup_unixsock(sdb_conn_t *conn, void __attribute__((unused)) *user_data)
110         uid_t uid;
112         struct passwd pw_entry;
113         struct passwd *result = NULL;
114         char buf[1024];
116 #ifdef SO_PEERCRED
117         struct ucred cred;
118         socklen_t len = sizeof(cred);
120         if (getsockopt(conn->fd, SOL_SOCKET, SO_PEERCRED, &cred, &len)
121                         || (len != sizeof(cred))) {
122                 char errbuf[1024];
123                 sdb_log(SDB_LOG_ERR, "frontend: Failed to determine peer for "
124                                 "connection conn#%i: %s", conn->fd,
125                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
126                 return -1;
127         }
128         uid = cred.uid;
129 #else /* SO_PEERCRED */
130         sdb_log(SDB_LOG_ERR, "frontend: Failed to determine peer for "
131                         "connection conn#%i: operation not supported", conn->fd);
132         return -1;
133 #endif
135         memset(&pw_entry, 0, sizeof(pw_entry));
136         if (getpwuid_r(uid, &pw_entry, buf, sizeof(buf), &result) || (! result)
137                         || (! (conn->username = strdup(result->pw_name)))) {
138                 char errbuf[1024];
139                 sdb_log(SDB_LOG_ERR, "frontend: Failed to determine peer for "
140                                 "connection conn#%i: %s", conn->fd,
141                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
142                 return -1;
143         }
144         return 0;
145 } /* setup_unixsock */
147 static int
148 open_unixsock(listener_t *listener)
150         char *addr_copy;
151         char *base_dir;
152         struct sockaddr_un sa;
153         int status;
155         listener->sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
156         if (listener->sock_fd < 0) {
157                 char buf[1024];
158                 sdb_log(SDB_LOG_ERR, "frontend: Failed to open UNIX socket %s: %s",
159                                 listener->address, sdb_strerror(errno, buf, sizeof(buf)));
160                 return -1;
161         }
163         memset(&sa, 0, sizeof(sa));
164         sa.sun_family = AF_UNIX;
165         strncpy(sa.sun_path, listener->address, sizeof(sa.sun_path));
167         addr_copy = strdup(listener->address);
168         if (! addr_copy) {
169                 char errbuf[1024];
170                 sdb_log(SDB_LOG_ERR, "frontend: strdup failed: %s",
171                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
172                 return -1;
173         }
174         base_dir = dirname(addr_copy);
176         /* ensure that the directory exists */
177         if (sdb_mkdir_all(base_dir, 0777)) {
178                 char errbuf[1024];
179                 sdb_log(SDB_LOG_ERR, "frontend: Failed to create directory '%s': %s",
180                                 base_dir, sdb_strerror(errno, errbuf, sizeof(errbuf)));
181                 free(addr_copy);
182                 return -1;
183         }
184         free(addr_copy);
186         if (unlink(listener->address) && (errno != ENOENT)) {
187                 char errbuf[1024];
188                 sdb_log(SDB_LOG_WARNING, "frontend: Failed to remove stale UNIX "
189                                 "socket %s: %s", listener->address,
190                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
191         }
193         status = bind(listener->sock_fd, (struct sockaddr *)&sa, sizeof(sa));
194         if (status) {
195                 char buf[1024];
196                 sdb_log(SDB_LOG_ERR, "frontend: Failed to bind to UNIX socket %s: %s",
197                                 listener->address, sdb_strerror(errno, buf, sizeof(buf)));
198                 return -1;
199         }
201         listener->setup = setup_unixsock;
202         return 0;
203 } /* open_unixsock */
205 static void
206 close_unixsock(listener_t *listener)
208         assert(listener);
210         if (! listener->address)
211                 return;
213         if (listener->sock_fd >= 0)
214                 close(listener->sock_fd);
215         listener->sock_fd = -1;
217         unlink(listener->address);
218 } /* close_unixsock */
220 static int
221 open_tcp(listener_t *listener)
223         struct addrinfo *ai, *ai_list = NULL;
224         int status;
226         assert(listener);
228         if ((status = sdb_resolve(SDB_NET_TCP, listener->address, &ai_list))) {
229                 sdb_log(SDB_LOG_ERR, "frontend: Failed to resolve '%s': %s",
230                                 listener->address, gai_strerror(status));
231                 return -1;
232         }
234         for (ai = ai_list; ai != NULL; ai = ai->ai_next) {
235                 char errbuf[1024];
236                 int reuse = 1;
238                 listener->sock_fd = socket(ai->ai_family,
239                                 ai->ai_socktype, ai->ai_protocol);
240                 if (listener->sock_fd < 0) {
241                         sdb_log(SDB_LOG_ERR, "frontend: Failed to open socket for %s: %s",
242                                         listener->address,
243                                         sdb_strerror(errno, errbuf, sizeof(errbuf)));
244                         continue;
245                 }
247                 if (setsockopt(listener->sock_fd, SOL_SOCKET, SO_REUSEADDR,
248                                         &reuse, sizeof(reuse)) < 0) {
249                         sdb_log(SDB_LOG_ERR, "frontend: Failed to set socket option: %s",
250                                         sdb_strerror(errno, errbuf, sizeof(errbuf)));
251                         close(listener->sock_fd);
252                         listener->sock_fd = -1;
253                         continue;
254                 }
256                 if (bind(listener->sock_fd, ai->ai_addr, ai->ai_addrlen) < 0) {
257                         char host[1024], port[32];
258                         getnameinfo(ai->ai_addr, ai->ai_addrlen, host, sizeof(host),
259                                         port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV);
260                         sdb_log(SDB_LOG_ERR, "frontend: Failed to bind to %s:%s: %s",
261                                         host, port, sdb_strerror(errno, errbuf, sizeof(errbuf)));
262                         close(listener->sock_fd);
263                         listener->sock_fd = -1;
264                         continue;
265                 }
266                 break;
267         }
268         freeaddrinfo(ai_list);
270         if (listener->sock_fd < 0)
271                 return -1;
272         return 0;
273 } /* open_tcp */
275 static void
276 close_tcp(listener_t *listener)
278         assert(listener);
280         if (listener->sock_fd >= 0)
281                 close(listener->sock_fd);
282         listener->sock_fd = -1;
283 } /* close_tcp */
285 /*
286  * private variables
287  */
289 /* the enum has to be sorted the same as the implementations array
290  * to ensure that the type may be used as index into the array */
291 enum {
292         LISTENER_TCP = 0, /* this is the default */
293         LISTENER_UNIXSOCK,
294 };
295 static fe_listener_impl_t listener_impls[] = {
296         { LISTENER_TCP,      "tcp",  open_tcp,      close_tcp },
297         { LISTENER_UNIXSOCK, "unix", open_unixsock, close_unixsock },
298 };
300 /*
301  * private helper functions
302  */
304 static int
305 listener_listen(listener_t *listener)
307         assert(listener);
309         /* try to reopen */
310         if (listener->sock_fd < 0)
311                 if (listener_impls[listener->type].open(listener))
312                         return -1;
313         assert(listener->sock_fd >= 0);
315         if (listen(listener->sock_fd, /* backlog = */ 32)) {
316                 char buf[1024];
317                 sdb_log(SDB_LOG_ERR, "frontend: Failed to listen on socket %s: %s",
318                                 listener->address, sdb_strerror(errno, buf, sizeof(buf)));
319                 return -1;
320         }
321         return 0;
322 } /* listener_listen */
324 static void
325 listener_close(listener_t *listener)
327         assert(listener);
329         if (listener_impls[listener->type].close)
330                 listener_impls[listener->type].close(listener);
332         if (listener->sock_fd >= 0)
333                 close(listener->sock_fd);
334         listener->sock_fd = -1;
335 } /* listener_close */
337 static int
338 get_type(const char *address)
340         char *sep;
341         size_t len;
342         size_t i;
344         if (*address == '/')
345                 return LISTENER_UNIXSOCK;
346         sep = strchr(address, (int)':');
347         if (! sep)
348                 return listener_impls[0].type;
350         assert(sep > address);
351         len = (size_t)(sep - address);
353         for (i = 0; i < SDB_STATIC_ARRAY_LEN(listener_impls); ++i) {
354                 fe_listener_impl_t *impl = listener_impls + i;
356                 if (!strncmp(address, impl->prefix, len)) {
357                         assert(impl->type == (int)i);
358                         return impl->type;
359                 }
360         }
361         return -1;
362 } /* get_type */
364 static void
365 listener_destroy(listener_t *listener)
367         if (! listener)
368                 return;
370         listener_close(listener);
372         if (listener->address)
373                 free(listener->address);
374         listener->address = NULL;
375 } /* listener_destroy */
377 static listener_t *
378 listener_create(sdb_fe_socket_t *sock, const char *address)
380         listener_t *listener;
381         size_t len;
382         int type;
384         type = get_type(address);
385         if (type < 0) {
386                 sdb_log(SDB_LOG_ERR, "frontend: Unsupported address type specified "
387                                 "in listen address '%s'", address);
388                 return NULL;
389         }
391         listener = realloc(sock->listeners,
392                         (sock->listeners_num + 1) * sizeof(*sock->listeners));
393         if (! listener) {
394                 char buf[1024];
395                 sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate memory: %s",
396                                 sdb_strerror(errno, buf, sizeof(buf)));
397                 return NULL;
398         }
400         sock->listeners = listener;
401         listener = sock->listeners + sock->listeners_num;
403         len = strlen(listener_impls[type].prefix);
404         if ((! strncmp(address, listener_impls[type].prefix, len))
405                         && (address[len] == ':'))
406                 address += strlen(listener_impls[type].prefix) + 1;
408         listener->sock_fd = -1;
409         listener->address = strdup(address);
410         if (! listener->address) {
411                 char buf[1024];
412                 sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate memory: %s",
413                                 sdb_strerror(errno, buf, sizeof(buf)));
414                 listener_destroy(listener);
415                 return NULL;
416         }
417         listener->type = type;
418         listener->setup = NULL;
420         if (listener_impls[type].open(listener)) {
421                 /* prints error */
422                 listener_destroy(listener);
423                 return NULL;
424         }
426         ++sock->listeners_num;
427         return listener;
428 } /* listener_create */
430 static void
431 socket_clear(sdb_fe_socket_t *sock)
433         size_t i;
435         assert(sock);
436         for (i = 0; i < sock->listeners_num; ++i)
437                 listener_destroy(sock->listeners + i);
438         if (sock->listeners)
439                 free(sock->listeners);
440         sock->listeners = NULL;
441         sock->listeners_num = 0;
442 } /* socket_clear */
444 static void
445 socket_close(sdb_fe_socket_t *sock)
447         size_t i;
449         assert(sock);
450         for (i = 0; i < sock->listeners_num; ++i)
451                 listener_close(sock->listeners + i);
452 } /* socket_close */
454 /*
455  * connection handler functions
456  */
458 static void *
459 connection_handler(void *data)
461         sdb_fe_socket_t *sock = data;
463         assert(sock);
465         while (42) {
466                 struct timespec timeout = { 0, 500000000 }; /* .5 seconds */
467                 sdb_conn_t *conn;
468                 int status;
470                 errno = 0;
471                 status = sdb_channel_select(sock->chan, /* read */ NULL, &conn,
472                                 /* write */ NULL, NULL, &timeout);
473                 if (status) {
474                         char buf[1024];
476                         if (errno == ETIMEDOUT)
477                                 continue;
478                         if (errno == EBADF) /* channel shut down */
479                                 break;
481                         sdb_log(SDB_LOG_ERR, "frontend: Failed to read from channel: %s",
482                                         sdb_strerror(errno, buf, sizeof(buf)));
483                         continue;
484                 }
486                 status = (int)sdb_connection_handle(conn);
487                 if (status <= 0) {
488                         /* error or EOF -> close connection */
489                         sdb_object_deref(SDB_OBJ(conn));
490                         continue;
491                 }
493                 /* return the connection to the main loop */
494                 if (sdb_llist_append(sock->open_connections, SDB_OBJ(conn))) {
495                         sdb_log(SDB_LOG_ERR, "frontend: Failed to re-append "
496                                         "connection %s to list of open connections",
497                                         SDB_OBJ(conn)->name);
498                 }
500                 /* pass ownership back to list; or destroy in case of an error */
501                 sdb_object_deref(SDB_OBJ(conn));
502         }
503         return NULL;
504 } /* connection_handler */
506 static int
507 connection_accept(sdb_fe_socket_t *sock, listener_t *listener)
509         sdb_object_t *obj;
510         int status;
512         obj = SDB_OBJ(sdb_connection_accept(listener->sock_fd,
513                                 listener->setup, NULL));
514         if (! obj)
515                 return -1;
517         status = sdb_llist_append(sock->open_connections, obj);
518         if (status)
519                 sdb_log(SDB_LOG_ERR, "frontend: Failed to append "
520                                 "connection %s to list of open connections",
521                                 obj->name);
523         /* hand ownership over to the list; or destroy in case of an error */
524         sdb_object_deref(obj);
525         return status;
526 } /* connection_accept */
528 static int
529 socket_handle_incoming(sdb_fe_socket_t *sock,
530                 fd_set *ready, fd_set *exceptions)
532         sdb_llist_iter_t *iter;
533         size_t i;
535         for (i = 0; i < sock->listeners_num; ++i) {
536                 listener_t *listener = sock->listeners + i;
537                 if (FD_ISSET(listener->sock_fd, ready))
538                         if (connection_accept(sock, listener))
539                                 continue;
540         }
542         iter = sdb_llist_get_iter(sock->open_connections);
543         if (! iter) {
544                 sdb_log(SDB_LOG_ERR, "frontend: Failed to acquire iterator "
545                                 "for open connections");
546                 return -1;
547         }
549         while (sdb_llist_iter_has_next(iter)) {
550                 sdb_object_t *obj = sdb_llist_iter_get_next(iter);
552                 if (FD_ISSET(CONN(obj)->fd, exceptions)) {
553                         sdb_log(SDB_LOG_INFO, "Exception on fd %d",
554                                         CONN(obj)->fd);
555                         /* close the connection */
556                         sdb_llist_iter_remove_current(iter);
557                         sdb_object_deref(obj);
558                         continue;
559                 }
561                 if (FD_ISSET(CONN(obj)->fd, ready)) {
562                         sdb_llist_iter_remove_current(iter);
563                         sdb_channel_write(sock->chan, &obj);
564                 }
565         }
566         sdb_llist_iter_destroy(iter);
567         return 0;
568 } /* socket_handle_incoming */
570 /*
571  * public API
572  */
574 sdb_fe_socket_t *
575 sdb_fe_sock_create(void)
577         sdb_fe_socket_t *sock;
579         sock = calloc(1, sizeof(*sock));
580         if (! sock)
581                 return NULL;
583         sock->open_connections = sdb_llist_create();
584         if (! sock->open_connections) {
585                 sdb_fe_sock_destroy(sock);
586                 return NULL;
587         }
588         return sock;
589 } /* sdb_fe_sock_create */
591 void
592 sdb_fe_sock_destroy(sdb_fe_socket_t *sock)
594         if (! sock)
595                 return;
597         socket_clear(sock);
599         sdb_llist_destroy(sock->open_connections);
600         sock->open_connections = NULL;
601         free(sock);
602 } /* sdb_fe_sock_destroy */
604 int
605 sdb_fe_sock_add_listener(sdb_fe_socket_t *sock, const char *address)
607         listener_t *listener;
609         if ((! sock) || (! address))
610                 return -1;
612         listener = listener_create(sock, address);
613         if (! listener)
614                 return -1;
615         return 0;
616 } /* sdb_fe_sock_add_listener */
618 void
619 sdb_fe_sock_clear_listeners(sdb_fe_socket_t *sock)
621         if (! sock)
622                 return;
624         socket_clear(sock);
625 } /* sdb_fe_sock_clear_listeners */
627 int
628 sdb_fe_sock_listen_and_serve(sdb_fe_socket_t *sock, sdb_fe_loop_t *loop)
630         fd_set sockets;
631         int max_listen_fd = 0;
632         size_t i;
634         pthread_t handler_threads[loop->num_threads];
635         size_t num_threads;
637         if ((! sock) || (! sock->listeners_num) || sock->chan
638                         || (! loop) || (loop->num_threads <= 0))
639                 return -1;
641         if (! loop->do_loop)
642                 return 0;
644         FD_ZERO(&sockets);
645         for (i = 0; i < sock->listeners_num; ++i) {
646                 listener_t *listener = sock->listeners + i;
648                 if (listener_listen(listener)) {
649                         socket_close(sock);
650                         return -1;
651                 }
653                 FD_SET(listener->sock_fd, &sockets);
654                 if (listener->sock_fd > max_listen_fd)
655                         max_listen_fd = listener->sock_fd;
656         }
658         sock->chan = sdb_channel_create(1024, sizeof(sdb_conn_t *));
659         if (! sock->chan) {
660                 socket_close(sock);
661                 return -1;
662         }
664         sdb_log(SDB_LOG_INFO, "frontend: Starting %zu connection "
665                         "handler thread%s managing %zu listener%s",
666                         loop->num_threads, loop->num_threads == 1 ? "" : "s",
667                         sock->listeners_num, sock->listeners_num == 1 ? "" : "s");
669         num_threads = loop->num_threads;
670         memset(&handler_threads, 0, sizeof(handler_threads));
671         for (i = 0; i < num_threads; ++i) {
672                 errno = 0;
673                 if (pthread_create(&handler_threads[i], /* attr = */ NULL,
674                                         connection_handler, /* arg = */ sock)) {
675                         char errbuf[1024];
676                         sdb_log(SDB_LOG_ERR, "frontend: Failed to create "
677                                         "connection handler thread: %s",
678                                         sdb_strerror(errno, errbuf, sizeof(errbuf)));
679                         num_threads = i;
680                         break;
681                 }
682         }
684         while (loop->do_loop && num_threads) {
685                 struct timeval timeout = { 1, 0 }; /* one second */
686                 sdb_llist_iter_t *iter;
688                 int max_fd = max_listen_fd;
689                 fd_set ready;
690                 fd_set exceptions;
691                 int n;
693                 FD_ZERO(&ready);
694                 FD_ZERO(&exceptions);
696                 ready = sockets;
698                 iter = sdb_llist_get_iter(sock->open_connections);
699                 if (! iter) {
700                         sdb_log(SDB_LOG_ERR, "frontend: Failed to acquire iterator "
701                                         "for open connections");
702                         break;
703                 }
705                 while (sdb_llist_iter_has_next(iter)) {
706                         sdb_object_t *obj = sdb_llist_iter_get_next(iter);
708                         if (CONN(obj)->fd < 0) {
709                                 sdb_llist_iter_remove_current(iter);
710                                 sdb_object_deref(obj);
711                                 continue;
712                         }
714                         FD_SET(CONN(obj)->fd, &ready);
715                         FD_SET(CONN(obj)->fd, &exceptions);
717                         if (CONN(obj)->fd > max_fd)
718                                 max_fd = CONN(obj)->fd;
719                 }
720                 sdb_llist_iter_destroy(iter);
722                 errno = 0;
723                 n = select(max_fd + 1, &ready, NULL, &exceptions, &timeout);
724                 if (n < 0) {
725                         char buf[1024];
727                         if (errno == EINTR)
728                                 continue;
730                         sdb_log(SDB_LOG_ERR, "frontend: Failed to monitor sockets: %s",
731                                         sdb_strerror(errno, buf, sizeof(buf)));
732                         break;
733                 }
734                 else if (! n)
735                         continue;
737                 /* handle new and open connections */
738                 if (socket_handle_incoming(sock, &ready, &exceptions))
739                         break;
740         }
742         socket_close(sock);
744         sdb_log(SDB_LOG_INFO, "frontend: Waiting for connection handler threads "
745                         "to terminate");
746         if (! sdb_channel_shutdown(sock->chan))
747                 for (i = 0; i < num_threads; ++i)
748                         pthread_join(handler_threads[i], NULL);
749         /* else: we tried our best; let the operating system clean up */
751         sdb_channel_destroy(sock->chan);
752         sock->chan = NULL;
754         if (! num_threads)
755                 return -1;
756         return 0;
757 } /* sdb_fe_sock_listen_and_server */
759 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */