e66d3c515c784a3fe0a62f31e104bb373e45a92f
[src/app-framework-binder.git] / src / afb-socket.c
1 /*
2  * Copyright (C) 2015-2019 "IoT.bzh"
3  * Author José Bollo <jose.bollo@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #define _GNU_SOURCE
19
20 #include <stdlib.h>
21 #include <string.h>
22 #include <assert.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <errno.h>
26 #include <endian.h>
27 #include <netdb.h>
28 #include <sys/types.h>
29 #include <sys/socket.h>
30 #include <sys/un.h>
31
32 #include "afb-fdev.h"
33 #include "afb-socket.h"
34
35 #include "systemd.h"
36 #include "fdev.h"
37 #include "verbose.h"
38
39 #define BACKLOG  5
40
41 /******************************************************************************/
42
43 /**
44  * known types
45  */
46 enum type {
47         /** type internet */
48         Type_Inet,
49
50         /** type systemd */
51         Type_Systemd,
52
53         /** type Unix */
54         Type_Unix
55 };
56
57 /**
58  * Structure for known entries
59  */
60 struct entry
61 {
62         /** the known prefix */
63         const char *prefix;
64
65         /** the type of the entry */
66         unsigned type: 2;
67
68         /** should not set SO_REUSEADDR for servers */
69         unsigned noreuseaddr: 1;
70
71         /** should not call listen for servers */
72         unsigned nolisten: 1;
73 };
74
75 /**
76  * The known entries with the default one at the first place
77  */
78 static struct entry entries[] = {
79         {
80                 .prefix = "tcp:",
81                 .type = Type_Inet
82         },
83         {
84                 .prefix = "sd:",
85                 .type = Type_Systemd,
86                 .noreuseaddr = 1,
87                 .nolisten = 1
88         },
89         {
90                 .prefix = "unix:",
91                 .type = Type_Unix
92         }
93 };
94
95 /**
96  * It is possible to set explicit api name instead of using the
97  * default one.
98  */
99 static const char as_api[] = "?as-api=";
100
101 /******************************************************************************/
102
103 /**
104  * open a unix domain socket for client or server
105  *
106  * @param spec the specification of the path (prefix with @ for abstract)
107  * @param server 0 for client, server otherwise
108  *
109  * @return the file descriptor number of the socket or -1 in case of error
110  */
111 static int open_unix(const char *spec, int server)
112 {
113         int fd, rc, abstract;
114         struct sockaddr_un addr;
115         size_t length;
116
117         abstract = spec[0] == '@';
118
119         /* check the length */
120         length = strlen(spec);
121         if (length >= 108) {
122                 errno = ENAMETOOLONG;
123                 return -1;
124         }
125
126         /* remove the file on need */
127         if (server && !abstract)
128                 unlink(spec);
129
130         /* create a  socket */
131         fd = socket(AF_UNIX, SOCK_STREAM, 0);
132         if (fd < 0)
133                 return fd;
134
135         /* prepare address  */
136         memset(&addr, 0, sizeof addr);
137         addr.sun_family = AF_UNIX;
138         strcpy(addr.sun_path, spec);
139         if (abstract)
140                 addr.sun_path[0] = 0; /* implement abstract sockets */
141
142         if (server) {
143                 rc = bind(fd, (struct sockaddr *) &addr, (socklen_t)(sizeof addr));
144         } else {
145                 rc = connect(fd, (struct sockaddr *) &addr, (socklen_t)(sizeof addr));
146         }
147         if (rc < 0) {
148                 close(fd);
149                 return rc;
150         }
151         return fd;
152 }
153
154 /**
155  * open a tcp socket for client or server
156  *
157  * @param spec the specification of the host:port/...
158  * @param server 0 for client, server otherwise
159  *
160  * @return the file descriptor number of the socket or -1 in case of error
161  */
162 static int open_tcp(const char *spec, int server, int reuseaddr)
163 {
164         int rc, fd;
165         const char *service, *host, *tail;
166         struct addrinfo hint, *rai, *iai;
167
168         /* scan the uri */
169         tail = strchrnul(spec, '/');
170         service = strchr(spec, ':');
171         if (tail == NULL || service == NULL || tail < service) {
172                 errno = EINVAL;
173                 return -1;
174         }
175         host = strndupa(spec, service++ - spec);
176         service = strndupa(service, tail - service);
177
178         /* get addr */
179         memset(&hint, 0, sizeof hint);
180         hint.ai_family = AF_INET;
181         hint.ai_socktype = SOCK_STREAM;
182         if (server) {
183                 hint.ai_flags = AI_PASSIVE;
184                 if (host[0] == 0 || (host[0] == '*' && host[1] == 0))
185                         host = NULL;
186         }
187         rc = getaddrinfo(host, service, &hint, &rai);
188         if (rc != 0) {
189                 errno = EINVAL;
190                 return -1;
191         }
192
193         /* get the socket */
194         iai = rai;
195         while (iai != NULL) {
196                 fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol);
197                 if (fd >= 0) {
198                         if (server) {
199                                 if (reuseaddr) {
200                                         rc = 1;
201                                         setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &rc, sizeof rc);
202                                 }
203                                 rc = bind(fd, iai->ai_addr, iai->ai_addrlen);
204                         } else {
205                                 rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
206                         }
207                         if (rc == 0) {
208                                 freeaddrinfo(rai);
209                                 return fd;
210                         }
211                         close(fd);
212                 }
213                 iai = iai->ai_next;
214         }
215         freeaddrinfo(rai);
216         return -1;
217 }
218
219 /**
220  * open a systemd socket for server
221  *
222  * @param spec the specification of the systemd name
223  *
224  * @return the file descriptor number of the socket or -1 in case of error
225  */
226 static int open_systemd(const char *spec)
227 {
228 #if defined(NO_SYSTEMD_ACTIVATION)
229         errno = EAFNOSUPPORT;
230         return -1;
231 #else
232         return systemd_fds_for(spec);
233 #endif
234 }
235
236 /******************************************************************************/
237
238 /**
239  * Get the entry of the uri by searching to its prefix
240  *
241  * @param uri the searched uri
242  * @param offset where to store the prefix length
243  * @param scheme the default scheme to use if none is set in uri (can be NULL)
244  *
245  * @return the found entry or the default one
246  */
247 static struct entry *get_entry(const char *uri, int *offset, const char *scheme)
248 {
249         int len, i, deflen;
250
251         /* search as prefix of URI */
252         i = (int)(sizeof entries / sizeof * entries);
253         while (i) {
254                 i--;
255                 len = (int)strlen(entries[i].prefix);
256                 if (!strncmp(uri, entries[i].prefix, len))
257                         goto end; /* found */
258         }
259
260         /* not a prefix of uri */
261         len = 0;
262
263         /* search default scheme if given and valid */
264         if (scheme && *scheme) {
265                 deflen = (int)strlen(scheme);
266                 deflen += (scheme[deflen - 1] != ':'); /* add virtual trailing colon */
267                 i = (int)(sizeof entries / sizeof * entries);
268                 while (i) {
269                         i--;
270                         if (deflen == (int)strlen(entries[i].prefix)
271                          && !strncmp(scheme, entries[i].prefix, deflen - 1))
272                                 goto end; /* found */
273                 }
274         }
275
276 end:
277         *offset = len;
278         return &entries[i];
279 }
280
281 /**
282  * open socket for client or server
283  *
284  * @param uri the specification of the socket
285  * @param server 0 for client, server otherwise
286  * @param scheme the default scheme to use if none is set in uri (can be NULL)
287  *
288  * @return the file descriptor number of the socket or -1 in case of error
289  */
290 static int open_uri(const char *uri, int server, const char *scheme)
291 {
292         int fd, offset;
293         struct entry *e;
294         const char *api;
295
296         /* search for the entry */
297         e = get_entry(uri, &offset, scheme);
298
299         /* get the names */
300         uri += offset;
301         api = strstr(uri, as_api);
302         if (api)
303                 uri = strndupa(uri, api - uri);
304
305         /* open the socket */
306         switch (e->type) {
307         case Type_Unix:
308                 fd = open_unix(uri, server);
309                 break;
310         case Type_Inet:
311                 fd = open_tcp(uri, server, !e->noreuseaddr);
312                 break;
313         case Type_Systemd:
314                 if (server)
315                         fd = open_systemd(uri);
316                 else {
317                         errno = EINVAL;
318                         fd = -1;
319                 }
320                 break;
321         default:
322                 errno = EAFNOSUPPORT;
323                 fd = -1;
324                 break;
325         }
326         if (fd < 0)
327                 return -1;
328
329         /* set it up */
330         fcntl(fd, F_SETFD, FD_CLOEXEC);
331         fcntl(fd, F_SETFL, O_NONBLOCK);
332         if (server) {
333                 if (!e->nolisten)
334                         listen(fd, BACKLOG);
335         }
336         return fd;
337 }
338
339 /**
340  * open socket for client or server
341  *
342  * @param uri the specification of the socket
343  * @param server 0 for client, server otherwise
344  * @param scheme the default scheme to use if none is set in uri (can be NULL)
345  *
346  * @return the file descriptor number of the socket or -1 in case of error
347  */
348 int afb_socket_open_scheme(const char *uri, int server, const char *scheme)
349 {
350         int fd = open_uri(uri, server, scheme);
351         if (fd < 0)
352                 ERROR("can't open %s socket for %s", server ? "server" : "client", uri);
353         return fd;
354 }
355
356 /**
357  * open socket for client or server
358  *
359  * @param uri the specification of the socket
360  * @param server 0 for client, server otherwise
361  * @param scheme the default scheme to use if none is set in uri (can be NULL)
362  *
363  * @return the fdev of the socket or NULL in case of error
364  */
365 struct fdev *afb_socket_open_fdev_scheme(const char *uri, int server, const char *scheme)
366 {
367         struct fdev *fdev;
368         int fd;
369
370         fd = afb_socket_open_scheme(uri, server, scheme);
371         if (fd < 0)
372                 fdev = NULL;
373         else {
374                 fdev = afb_fdev_create(fd);
375                 if (!fdev) {
376                         close(fd);
377                         ERROR("can't make %s socket for %s", server ? "server" : "client", uri);
378                 }
379         }
380         return fdev;
381 }
382
383 /**
384  * Get the api name of the uri
385  *
386  * @param uri the specification of the socket
387  *
388  * @return the api name or NULL if none can be deduced
389  */
390 const char *afb_socket_api(const char *uri)
391 {
392         int offset;
393         const char *api;
394         struct entry *entry;
395
396         entry = get_entry(uri, &offset, NULL);
397         uri += offset;
398         uri += (entry->type == Type_Unix && *uri == '@');
399         api = strstr(uri, as_api);
400         if (api)
401                 api += sizeof as_api - 1;
402         else {
403                 api = strrchr(uri, '/');
404                 if (api)
405                         api++;
406                 else
407                         api = uri;
408                 if (strchr(api, ':'))
409                         api = NULL;
410         }
411         return api;
412 }