2 * Copyright (C) 2016-2019 "IoT.bzh"
3 * Author: José Bollo <jose.bollo@iot.bzh>
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
26 #include <sys/types.h>
27 #include <sys/socket.h>
32 #include "fdev-systemd.h"
34 /**************** WebSocket handshake ****************************/
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="
55 /* get randomly a pair of key/accept value */
56 static void getkeypair(const char **key, const char **ack)
61 r = (r & 15) + (r >> 4);
67 /* joins the strings using the separator */
68 static char *strjoin(int count, const char **strings, const char *separ)
74 /* creates the count if needed */
76 for(count = 0 ; strings[count] != NULL ; count++);
78 /* compute the length of the result */
82 length = (unsigned)(count - 1) * strlen(separ);
83 for (idx = 0 ; idx < count ; idx ++)
84 length += strlen(strings[idx]);
87 /* allocates the result */
88 result = malloc(length + 1);
92 /* create the result */
94 iter = stpcpy(result, strings[idx = 0]);
96 iter = stpcpy(stpcpy(iter, separ), strings[idx]);
97 // assert(iter - result == length);
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)
107 int rc = asprintf(request,
108 "GET %s HTTP/1.1\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"
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)
133 const char *key, *ack;
134 char *protolist, *request;
137 /* make the list of accepted protocols */
138 protolist = strjoin(-1, protocols, ", ");
139 if (protolist == NULL)
142 /* create the request */
143 getkeypair(&key, &ack);
144 length = make_request(&request, path, host, key, protolist);
149 /* send the request */
150 do { rc = (int)write(fd, request, length); } while(rc < 0 && errno == EINTR);
152 return rc < 0 ? NULL : ack;
155 /* read a line not efficiently but without buffering */
156 static int receive_line(int fd, char *line, int size)
158 int rc, length = 0, cr = 0;
160 if (length >= size) {
164 do { rc = (int)read(fd, line + length, 1); } while (rc < 0 && errno == EINTR);
167 if (line[length] == '\r')
169 else if (cr != 0 && line[length] == '\n') {
179 static inline int isheader(const char *head, size_t klen, const char *key)
181 return strncasecmp(head, key, klen) == 0 && key[klen] == 0;
184 /* receives and scan the response */
185 static int receive_response(int fd, const char **protocols, const char *ack)
187 char line[4096], *it;
188 int rc, haserr, result = -1;
191 /* check the header line to be something like: "HTTP/1.1 101 Switching Protocols" */
192 rc = receive_line(fd, line, (int)sizeof(line));
195 len = strcspn(line, " ");
196 if (len != 8 || 0 != strncmp(line, "HTTP/1.1", 8))
199 len = strspn(it, " ");
203 len = strcspn(it, " ");
204 if (len != 3 || 0 != strncmp(it, "101", 3))
207 /* reads the rest of the response until empty line */
211 rc = receive_line(fd, line, (int)sizeof(line));
216 len = strcspn(line, ": ");
217 if (len != 0 && line[len] == ':') {
218 /* checks the headers values */
220 it += strspn(it, " ,");
221 it[strcspn(it, " ,")] = 0;
222 if (isheader(line, len, "Sec-WebSocket-Accept")) {
223 if (strcmp(it, ack) != 0)
225 } else if (isheader(line, len, "Sec-WebSocket-Protocol")) {
227 while(protocols[result] != NULL && strcmp(it, protocols[result]) != 0)
229 } else if (isheader(line, len, "Upgrade")) {
230 if (strcmp(it, "websocket") != 0)
232 } else if (isheader(line, len, "Content-Length")) {
238 /* skips the remaining of the message */
239 while (clen >= sizeof line) {
240 while (read(fd, line, sizeof line) < 0 && errno == EINTR);
244 while (read(fd, line, len) < 0 && errno == EINTR);
246 if (haserr != 0 || result < 0)
250 errno = ECONNABORTED;
255 static int negociate(int fd, const char **protocols, const char *path, const char *host)
257 const char *ack = send_request(fd, protocols, path, host);
258 return ack == NULL ? -1 : receive_response(fd, protocols, ack);
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)
268 if (strncmp(uri, "ws://", 5) == 0)
273 hlen = strcspn(h, ":/");
278 /* the port (optional) */
281 plen = strcspn(p, "/");
294 /* make the result */
295 *host = strndup(h, hlen);
297 *service = plen ? strndup(p, plen) : strdup("http");
298 if (*service != NULL) {
315 static const char *proto_json1[2] = { "x-afb-ws-json1", NULL };
317 struct afb_wsj1 *afb_ws_client_connect_wsj1(struct sd_event *eloop, const char *uri, struct afb_wsj1_itf *itf, void *closure)
320 char *host, *service, xhost[32];
322 struct addrinfo hint, *rai, *iai;
323 struct afb_wsj1 *result;
327 rc = parse_uri(uri, &host, &service, &path);
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);
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);
355 rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
357 rc = negociate(fd, proto_json1, path, xhost);
359 fdev = fdev_systemd_create(eloop, fd);
361 result = afb_wsj1_create(fdev, itf, closure);
362 if (result != NULL) {
363 fcntl(fd, F_SETFL, O_NONBLOCK);
378 /* compute the queried path */
379 static char *makequery(const char *path, const char *uuid, const char *token)
388 rc = asprintf(&result, "/%s", path);
390 rc = asprintf(&result, "/%s?x-afb-token=%s", path, token);
393 rc = asprintf(&result, "/%s?x-afb-uuid=%s", path, uuid);
395 rc = asprintf(&result, "/%s?x-afb-uuid=%s&x-afb-token=%s", path, uuid, token);
405 /*****************************************************************************************************************************/
408 #include "afb-proto-ws.h"
410 static int get_socket_unix(const char *uri)
413 struct sockaddr_un addr;
416 length = strlen(uri);
418 errno = ENAMETOOLONG;
422 fd = socket(AF_UNIX, SOCK_STREAM, 0);
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));
439 static int get_socket_inet(const char *uri)
442 const char *service, *host, *api;
443 struct addrinfo hint, *rai, *iai;
446 api = strrchr(uri, '/');
447 service = strrchr(uri, ':');
448 if (api == NULL || service == NULL || api < service) {
452 host = strndupa(uri, service++ - uri);
453 service = strndupa(service, api - service);
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);
467 while (iai != NULL) {
468 fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol);
470 rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
483 static int get_socket(const char *uri)
487 /* check for unix socket */
488 if (0 == strncmp(uri, "unix:", 5))
490 fd = get_socket_unix(uri + 5);
493 fd = get_socket_inet(uri);
495 /* configure the socket */
497 fcntl(fd, F_SETFD, FD_CLOEXEC);
498 fcntl(fd, F_SETFL, O_NONBLOCK);
504 * Establish a websocket-like client connection to the API of 'uri' and if successful
505 * instantiate a client afb_proto_ws websocket for this API using 'itf' and 'closure'.
506 * (see afb_proto_ws_create_client).
507 * The systemd event loop 'eloop' is used to handle the websocket.
508 * Returns NULL in case of failure with errno set appropriately.
510 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)
513 struct afb_proto_ws *pws;
516 fd = get_socket(uri);
518 fdev = fdev_systemd_create(eloop, fd);
520 pws = afb_proto_ws_create_client(fdev, itf, closure);