fba85ff3f42c16a476fb88832aab7903fd237b71
[src/app-framework-binder.git] / src / afb-ws-client.c
1 /*
2  * Copyright (C) 2016-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 <stdio.h>
22 #include <unistd.h>
23 #include <assert.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <netdb.h>
29 #include <fcntl.h>
30
31 #include "afb-wsj1.h"
32 #include "fdev-systemd.h"
33
34 /**************** WebSocket handshake ****************************/
35
36 static const char *compkeys[32] = {
37         "lYKr2sn9+ILcLpkqdrE2VQ==", "G5J7ncQnmS/MubIYcqKWM+E6k8I=",
38         "gjN6eOU/6Yy7dBTJ+EaQSw==", "P5QzN7mRt4DeRWxKdG7s4/NCEwk=",
39         "ziLin6OQ0/a1+cGaI9Mupg==", "yvpxcFJAGam6huL77vz34CdShyU=",
40         "KMfd2bHKah0U5mk2Kg/LIg==", "lyYxfDP5YunhkBF+nAWb/w6K4yg=",
41         "fQ/ISF1mNCPRMyAj3ucqNg==", "91YY1EUelb4eMU24Z8WHhJ9cHmc=",
42         "RHlfiVVE1lM1AJnErI8dFg==", "UdZQc0JaihQJV5ETCZ84Av88pxQ=",
43         "NVy3L2ujXN7v3KEJwK92ww==", "+dE7iITxhExjBtf06VYNWChHqx8=",
44         "cCNAgttlgELfbDDIfhujww==", "W2JiswqbTAXx5u84EtjbtqAW2Bg=",
45         "K+oQvEDWJP+WXzRS5BJDFw==", "szgW10a9AuD+HtfS4ylaqWfzWAs=",
46         "nmg43S4DpVaxye+oQv9KTw==", "8XK74jB9xFfTzzl0wTqW04k3tPE=",
47         "LIqZ23sEppbF4YJR9LQ4/w==", "f8lJBQEbR8QmmvPHZpA0smlIeeA=",
48         "WY1vvvY2j/3V9DAGW3ZZcA==", "lROlE4vL4cjU1Vnk6rISc9gVKN0=",
49         "Ia+dgHnA9QaBrbxuqh4wgQ==", "GiGjxFdSaF0EGTl2cjvFsVmJnfM=",
50         "MfpIVG082jFTV7SxTNNijQ==", "f5I2h53hBsT5ES3EHhnxAJ2nqsw=",
51         "kFumnAw5d/WctG0yAUHPiQ==", "aQQmOjoABl7mrbliTPS1bOkndOs=",
52         "MHiEc+Qc8w/SJ3zMHEM8pA==", "FVCxLBmoil3gY0jSX3aNJ6kR/t4="
53 };
54
55 /* get randomly a pair of key/accept value */
56 static void getkeypair(const char **key, const char **ack)
57 {
58         int r;
59         r = rand();
60         while (r > 15)
61                 r = (r & 15) + (r >> 4);
62         r = (r & 15) << 1;
63         *key = compkeys[r];
64         *ack = compkeys[r+1];
65 }
66
67 /* joins the strings using the separator */
68 static char *strjoin(int count, const char **strings, const char *separ)
69 {
70         char *result, *iter;
71         size_t length;
72         int idx;
73
74         /* creates the count if needed */
75         if (count < 0)
76                 for(count = 0 ; strings[count] != NULL ; count++);
77
78         /* compute the length of the result */
79         if (count == 0)
80                 length = 0;
81         else {
82                 length = (unsigned)(count - 1) * strlen(separ);
83                 for (idx = 0 ; idx < count ; idx ++)
84                         length += strlen(strings[idx]);
85         }
86
87         /* allocates the result */
88         result = malloc(length + 1);
89         if (result == NULL)
90                 errno = ENOMEM;
91         else {
92                 /* create the result */
93                 if (count != 0) {
94                         iter = stpcpy(result, strings[idx = 0]);
95                         while (++idx < count)
96                                 iter = stpcpy(stpcpy(iter, separ), strings[idx]);
97                         // assert(iter - result == length);
98                 }
99                 result[length] = 0;
100         }
101         return result;
102 }
103
104 /* creates the http message for the request */
105 static int make_request(char **request, const char *path, const char *host, const char *key, const char *protocols)
106 {
107         int rc = asprintf(request,
108                         "GET %s HTTP/1.1\r\n"
109                         "Host: %s\r\n"
110                         "Upgrade: websocket\r\n"
111                         "Connection: Upgrade\r\n"
112                         "Sec-WebSocket-Version: 13\r\n"
113                         "Sec-WebSocket-Key: %s\r\n"
114                         "Sec-WebSocket-Protocol: %s\r\n"
115                         "Content-Length: 0\r\n"
116                         "\r\n"
117                         , path
118                         , host
119                         , key
120                         , protocols
121                 );
122         if (rc < 0) {
123                 errno = ENOMEM;
124                 *request = NULL;
125                 return -1;
126         }
127         return rc;
128 }
129
130 /* create the request and send it to fd, returns the expected accept string */
131 static const char *send_request(int fd, const char **protocols, const char *path, const char *host)
132 {
133         const char *key, *ack;
134         char *protolist, *request;
135         int length, rc;
136
137         /* make the list of accepted protocols */
138         protolist = strjoin(-1, protocols, ", ");
139         if (protolist == NULL)
140                 return NULL;
141
142         /* create the request */
143         getkeypair(&key, &ack);
144         length = make_request(&request, path, host, key, protolist);
145         free(protolist);
146         if (length < 0)
147                 return NULL;
148
149         /* send the request */
150         do { rc = (int)write(fd, request, length); } while(rc < 0 && errno == EINTR);
151         free(request);
152         return rc < 0 ? NULL : ack;
153 }
154
155 /* read a line not efficiently but without buffering */
156 static int receive_line(int fd, char *line, int size)
157 {
158         int rc, length = 0, cr = 0;
159         for(;;) {
160                 if (length >= size) {
161                         errno = EFBIG;
162                         return -1;
163                 }
164                 do { rc = (int)read(fd, line + length, 1); } while (rc < 0 && errno == EINTR);
165                 if (rc < 0)
166                         return -1;
167                 if (line[length] == '\r')
168                         cr = 1;
169                 else if (cr != 0 && line[length] == '\n') {
170                         line[--length] = 0;
171                         return length;
172                 } else
173                         cr = 0;
174                 length++;
175         }
176 }
177
178 /* check a header */
179 static inline int isheader(const char *head, size_t klen, const char *key)
180 {
181         return strncasecmp(head, key, klen) == 0 && key[klen] == 0;
182 }
183
184 /* receives and scan the response */
185 static int receive_response(int fd, const char **protocols, const char *ack)
186 {
187         char line[4096], *it;
188         int rc, haserr, result = -1;
189         size_t len, clen;
190
191         /* check the header line to be something like: "HTTP/1.1 101 Switching Protocols" */
192         rc = receive_line(fd, line, (int)sizeof(line));
193         if (rc < 0)
194                 goto error;
195         len = strcspn(line, " ");
196         if (len != 8 || 0 != strncmp(line, "HTTP/1.1", 8))
197                 goto abort;
198         it = line + len;
199         len = strspn(it, " ");
200         if (len == 0)
201                 goto abort;
202         it += len;
203         len = strcspn(it, " ");
204         if (len != 3 || 0 != strncmp(it, "101", 3))
205                 goto abort;
206
207         /* reads the rest of the response until empty line */
208         clen = 0;
209         haserr = 0;
210         for(;;) {
211                 rc = receive_line(fd, line, (int)sizeof(line));
212                 if (rc < 0)
213                         goto error;
214                 if (rc == 0)
215                         break;
216                 len = strcspn(line, ": ");
217                 if (len != 0 && line[len] == ':') {
218                         /* checks the headers values */
219                         it = line + len + 1;
220                         it += strspn(it, " ,");
221                         it[strcspn(it, " ,")] = 0;
222                         if (isheader(line, len, "Sec-WebSocket-Accept")) {
223                                 if (strcmp(it, ack) != 0)
224                                         haserr = 1;
225                         } else if (isheader(line, len, "Sec-WebSocket-Protocol")) {
226                                 result = 0;
227                                 while(protocols[result] != NULL && strcmp(it, protocols[result]) != 0)
228                                         result++;
229                         } else if (isheader(line, len, "Upgrade")) {
230                                 if (strcmp(it, "websocket") != 0)
231                                         haserr = 1;
232                         } else if (isheader(line, len, "Content-Length")) {
233                                 clen = atol(it);
234                         }
235                 }
236         }
237
238         /* skips the remaining of the message */
239         while (clen >= sizeof line) {
240                 while (read(fd, line, sizeof line) < 0 && errno == EINTR);
241                 clen -= sizeof line;
242         }
243         if (clen > 0) {
244                 while (read(fd, line, len) < 0 && errno == EINTR);
245         }
246         if (haserr != 0 || result < 0)
247                 goto abort;
248         return result;
249 abort:
250         errno = ECONNABORTED;
251 error:
252         return -1;
253 }
254
255 static int negociate(int fd, const char **protocols, const char *path, const char *host)
256 {
257         const char *ack = send_request(fd, protocols, path, host);
258         return ack == NULL ? -1 : receive_response(fd, protocols, ack);
259 }
260
261 /* tiny parse a "standard" websock uri ws://host:port/path... */
262 static int parse_uri(const char *uri, char **host, char **service, const char **path)
263 {
264         const char *h, *p;
265         size_t hlen, plen;
266
267         /* the scheme */
268         if (strncmp(uri, "ws://", 5) == 0)
269                 uri += 5;
270
271         /* the host */
272         h = uri;
273         hlen = strcspn(h, ":/");
274         if (hlen == 0)
275                 goto invalid;
276         uri += hlen;
277
278         /* the port (optional) */
279         if (*uri == ':') {
280                 p = ++uri;
281                 plen = strcspn(p, "/");
282                 if (plen == 0)
283                         goto invalid;
284                 uri += plen;
285         } else {
286                 p = NULL;
287                 plen = 0;
288         }
289
290         /* the path */
291         if (*uri != '/')
292                 goto invalid;
293
294         /* make the result */
295         *host = strndup(h, hlen);
296         if (*host != NULL) {
297                 *service = plen ? strndup(p, plen) : strdup("http");
298                 if (*service != NULL) {
299                         *path = uri;
300                         return 0;
301                 }
302                 free(*host);
303         }
304         errno = ENOMEM;
305         goto error;
306 invalid:
307         errno = EINVAL;
308 error:
309         return -1;
310 }
311
312
313
314
315 static const char *proto_json1[2] = { "x-afb-ws-json1", NULL };
316
317 struct afb_wsj1 *afb_ws_client_connect_wsj1(struct sd_event *eloop, const char *uri, struct afb_wsj1_itf *itf, void *closure)
318 {
319         int rc, fd;
320         char *host, *service, xhost[32];
321         const char *path;
322         struct addrinfo hint, *rai, *iai;
323         struct afb_wsj1 *result;
324         struct fdev *fdev;
325
326         /* scan the uri */
327         rc = parse_uri(uri, &host, &service, &path);
328         if (rc < 0)
329                 return NULL;
330
331         /* get addr */
332         memset(&hint, 0, sizeof hint);
333         hint.ai_family = AF_INET;
334         hint.ai_socktype = SOCK_STREAM;
335         rc = getaddrinfo(host, service, &hint, &rai);
336         free(host);
337         free(service);
338         if (rc != 0) {
339                 errno = EINVAL;
340                 return NULL;
341         }
342
343         /* get the socket */
344         result = NULL;
345         iai = rai;
346         while (iai != NULL) {
347                 struct sockaddr_in *a = (struct sockaddr_in*)(iai->ai_addr);
348                 unsigned char *ipv4 = (unsigned char*)&(a->sin_addr.s_addr);
349                 unsigned char *port = (unsigned char*)&(a->sin_port);
350                 sprintf(xhost, "%d.%d.%d.%d:%d",
351                         (int)ipv4[0], (int)ipv4[1], (int)ipv4[2], (int)ipv4[3],
352                         (((int)port[0]) << 8)|(int)port[1]);
353                 fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol);
354                 if (fd >= 0) {
355                         rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
356                         if (rc == 0) {
357                                 rc = negociate(fd, proto_json1, path, xhost);
358                                 if (rc == 0) {
359                                         fdev = fdev_systemd_create(eloop, fd);
360                                         if (fdev) {
361                                                 result = afb_wsj1_create(fdev, itf, closure);
362                                                 if (result != NULL) {
363                                                         fcntl(fd, F_SETFL, O_NONBLOCK);
364                                                         break;
365                                                 }
366                                         }
367                                 }
368                         }
369                         close(fd);
370                 }
371                 iai = iai->ai_next;
372         }
373         freeaddrinfo(rai);
374         return result;
375 }
376
377 #if 0
378 /* compute the queried path */
379 static char *makequery(const char *path, const char *uuid, const char *token)
380 {
381         char *result;
382         int rc;
383
384         while(*path == '/')
385                 path++;
386         if (uuid == NULL) {
387                 if (token == NULL)
388                         rc = asprintf(&result, "/%s", path);
389                 else
390                         rc = asprintf(&result, "/%s?x-afb-token=%s", path, token);
391         } else {
392                 if (token == NULL)
393                         rc = asprintf(&result, "/%s?x-afb-uuid=%s", path, uuid);
394                 else
395                         rc = asprintf(&result, "/%s?x-afb-uuid=%s&x-afb-token=%s", path, uuid, token);
396         }
397         if (rc < 0) {
398                 errno = ENOMEM;
399                 return NULL;
400         }
401         return result;
402 }
403 #endif
404
405 /*****************************************************************************************************************************/
406
407 #include <sys/un.h>
408 #include "afb-proto-ws.h"
409
410 static int get_socket_unix(const char *uri)
411 {
412         int fd, rc;
413         struct sockaddr_un addr;
414         size_t length;
415
416         length = strlen(uri);
417         if (length >= 108) {
418                 errno = ENAMETOOLONG;
419                 return -1;
420         }
421
422         fd = socket(AF_UNIX, SOCK_STREAM, 0);
423         if (fd < 0)
424                 return fd;
425
426         memset(&addr, 0, sizeof addr);
427         addr.sun_family = AF_UNIX;
428         strcpy(addr.sun_path, uri);
429         if (addr.sun_path[0] == '@')
430                 addr.sun_path[0] = 0; /* implement abstract sockets */
431         rc = connect(fd, (struct sockaddr *) &addr, (socklen_t)(sizeof addr));
432         if (rc < 0) {
433                 close(fd);
434                 return rc;
435         }
436         return fd;
437 }
438
439 static int get_socket_inet(const char *uri)
440 {
441         int rc, fd;
442         const char *service, *host, *api;
443         struct addrinfo hint, *rai, *iai;
444
445         /* scan the uri */
446         service = strrchr(uri, ':');
447         if (service == NULL) {
448                 errno = EINVAL;
449                 return -1;
450         }
451         api = strchrnul(service, '/');
452         host = strndupa(uri, service++ - uri);
453         service = strndupa(service, api - service);
454
455         /* get addr */
456         memset(&hint, 0, sizeof hint);
457         hint.ai_family = AF_INET;
458         hint.ai_socktype = SOCK_STREAM;
459         rc = getaddrinfo(host, service, &hint, &rai);
460         if (rc != 0) {
461                 errno = EINVAL;
462                 return -1;
463         }
464
465         /* get the socket */
466         iai = rai;
467         while (iai != NULL) {
468                 fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol);
469                 if (fd >= 0) {
470                         rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
471                         if (rc == 0) {
472                                 freeaddrinfo(rai);
473                                 return fd;
474                         }
475                         close(fd);
476                 }
477                 iai = iai->ai_next;
478         }
479         freeaddrinfo(rai);
480         return -1;
481 }
482
483 static int get_socket(const char *uri)
484 {
485         int fd;
486
487         /* check for unix socket */
488         if (0 == strncmp(uri, "unix:", 5))
489                 /* unix socket */
490                 fd = get_socket_unix(uri + 5);
491         else if (0 == strncmp(uri, "tcp:", 4))
492                 /* unix socket */
493                 fd = get_socket_inet(uri + 4);
494         else
495                 /* inet socket */
496                 fd = get_socket_inet(uri);
497
498         /* configure the socket */
499         if (fd >= 0) {
500                 fcntl(fd, F_SETFD, FD_CLOEXEC);
501                 fcntl(fd, F_SETFL, O_NONBLOCK);
502         }
503         return fd;
504 }
505
506 /*
507  * Establish a websocket-like client connection to the API of 'uri' and if successful
508  * instantiate a client afb_proto_ws websocket for this API using 'itf' and 'closure'.
509  * (see afb_proto_ws_create_client).
510  * The systemd event loop 'eloop' is used to handle the websocket.
511  * Returns NULL in case of failure with errno set appropriately.
512  */
513 struct afb_proto_ws *afb_ws_client_connect_api(struct sd_event *eloop, const char *uri, struct afb_proto_ws_client_itf *itf, void *closure)
514 {
515         int fd;
516         struct afb_proto_ws *pws;
517         struct fdev *fdev;
518
519         fd = get_socket(uri);
520         if (fd >= 0) {
521                 fdev = fdev_systemd_create(eloop, fd);
522                 if (fdev) {
523                         pws = afb_proto_ws_create_client(fdev, itf, closure);
524                         if (pws)
525                                 return pws;
526                 }
527                 close(fd);
528         }
529         return NULL;
530 }
531