blob: e5d53510350536f5cd84a56464e92a624c174802 [file] [log] [blame]
Andy Green4739e5c2011-01-22 12:51:57 +00001#include "private-libwebsockets.h"
2#include <netdb.h>
3
4
5/*
6 * In-place str to lower case
7 */
8
9void
10strtolower(char *s)
11{
Andy Green90c7cbc2011-01-27 06:26:52 +000012 while (*s) {
13 *s = tolower(*s);
14 s++;
15 }
Andy Green4739e5c2011-01-22 12:51:57 +000016}
17
18void
19libwebsocket_client_close(struct libwebsocket *wsi)
20{
21 int n = wsi->state;
22 struct libwebsocket_context *clients;
23
24 /* mark the WSI as dead and let the callback know */
25
26 wsi->state = WSI_STATE_DEAD_SOCKET;
27
28 if (wsi->protocol) {
29 if (wsi->protocol->callback && n == WSI_STATE_ESTABLISHED)
30 wsi->protocol->callback(wsi, LWS_CALLBACK_CLOSED,
31 wsi->user_space, NULL, 0);
32
33 /* remove it from the client polling list */
34 clients = wsi->protocol->owning_server;
35 if (clients)
36 for (n = 0; n < clients->fds_count; n++) {
37 if (clients->wsi[n] != wsi)
38 continue;
39 while (n < clients->fds_count - 1) {
40 clients->fds[n] = clients->fds[n + 1];
41 clients->wsi[n] = clients->wsi[n + 1];
42 }
43 /* we only have to deal with one */
44 n = clients->fds_count;
45 }
46
47 }
48
49 /* clean out any parsing allocations */
50
51 for (n = 0; n < WSI_TOKEN_COUNT; n++)
52 if (wsi->utf8_token[n].token)
53 free(wsi->utf8_token[n].token);
54
55 /* shut down reasonably cleanly */
56
57#ifdef LWS_OPENSSL_SUPPORT
Andy Green90c7cbc2011-01-27 06:26:52 +000058 if (wsi->ssl) {
Andy Green4739e5c2011-01-22 12:51:57 +000059 n = SSL_get_fd(wsi->ssl);
60 SSL_shutdown(wsi->ssl);
61 close(n);
62 SSL_free(wsi->ssl);
63 } else {
64#endif
65 shutdown(wsi->sock, SHUT_RDWR);
66 close(wsi->sock);
67#ifdef LWS_OPENSSL_SUPPORT
68 }
69#endif
70}
71
Andy Green90c7cbc2011-01-27 06:26:52 +000072
73/**
74 * libwebsocket_client_connect() - Connect to another websocket server
75 * @this: Websocket context
76 * @address: Remote server address, eg, "myserver.com"
77 * @port: Port to connect to on the remote server, eg, 80
78 * @ssl_connection: 0 = ws://, 1 = wss:// encrypted, 2 = wss:// allow self
79 * signed certs
80 * @path: Websocket path on server
81 * @host: Hostname on server
82 * @origin: Socket origin name
83 * @protocol: Comma-separated list of protocols being asked for from
84 * the server, or just one. The server will pick the one it
85 * likes best.
86 *
87 * This function creates a connection to a remote server
88 */
89
Andy Green4739e5c2011-01-22 12:51:57 +000090struct libwebsocket *
Andy Green90c7cbc2011-01-27 06:26:52 +000091libwebsocket_client_connect(struct libwebsocket_context *this,
Andy Green4739e5c2011-01-22 12:51:57 +000092 const char *address,
93 int port,
Andy Green90c7cbc2011-01-27 06:26:52 +000094 int ssl_connection,
Andy Green4739e5c2011-01-22 12:51:57 +000095 const char *path,
96 const char *host,
97 const char *origin,
98 const char *protocol)
99{
100 struct hostent *server_hostent;
101 struct sockaddr_in server_addr;
102 char buf[150];
103 char key_b64[150];
104 char hash[20];
105 int fd;
106 struct pollfd pfd;
107 static const char magic_websocket_guid[] =
108 "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
109 static const char magic_websocket_04_masking_guid[] =
110 "61AC5F19-FBBA-4540-B96F-6561F1AB40A8";
111 char pkt[1024];
112 char *p = &pkt[0];
Andy Green6964bb52011-01-23 16:50:33 +0000113 const char *pc;
Andy Green4739e5c2011-01-22 12:51:57 +0000114 int len;
115 int okay = 0;
116 struct libwebsocket *wsi;
117 int n;
Andy Green9659f372011-01-27 22:01:43 +0000118 int plen = 0;
Andy Green90c7cbc2011-01-27 06:26:52 +0000119#ifdef LWS_OPENSSL_SUPPORT
120 char ssl_err_buf[512];
121#else
122 if (ssl_connection) {
123 fprintf(stderr, "libwebsockets not configured for ssl\n");
124 return NULL;
125 }
126#endif
Andy Green4739e5c2011-01-22 12:51:57 +0000127
Andy Green6964bb52011-01-23 16:50:33 +0000128 wsi = malloc(sizeof(struct libwebsocket));
Andy Green90c7cbc2011-01-27 06:26:52 +0000129 if (wsi == NULL) {
Andy Green9659f372011-01-27 22:01:43 +0000130 fprintf(stderr, "Out of memory allocing new connection\n");
Andy Green4739e5c2011-01-22 12:51:57 +0000131 return NULL;
Andy Green90c7cbc2011-01-27 06:26:52 +0000132 }
Andy Green4739e5c2011-01-22 12:51:57 +0000133
Andy Green90c7cbc2011-01-27 06:26:52 +0000134 this->wsi[this->fds_count] = wsi;
Andy Green4739e5c2011-01-22 12:51:57 +0000135
136 wsi->ietf_spec_revision = 4;
137 wsi->name_buffer_pos = 0;
138 wsi->user_space = NULL;
139 wsi->state = WSI_STATE_CLIENT_UNCONNECTED;
140 wsi->pings_vs_pongs = 0;
141
142 for (n = 0; n < WSI_TOKEN_COUNT; n++) {
143 wsi->utf8_token[n].token = NULL;
144 wsi->utf8_token[n].token_len = 0;
145 }
146
147 /*
Andy Green9659f372011-01-27 22:01:43 +0000148 * proxy?
149 */
150
151 if (this->http_proxy_port) {
152 plen = sprintf(pkt, "CONNECT %s:%u HTTP/1.0\x0d\x0a"
153 "User-agent: libwebsockets\x0d\x0a"
154/*Proxy-authorization: basic aGVsbG86d29ybGQ= */
155 "\x0d\x0a", address, port);
156
157 /* OK from now on we talk via the proxy */
158
159 address = this->http_proxy_address;
160 port = this->http_proxy_port;
161 }
162
163 /*
164 * prepare the actual connection (to the proxy, if any)
Andy Green4739e5c2011-01-22 12:51:57 +0000165 */
166
167 server_hostent = gethostbyname(address);
168 if (server_hostent == NULL) {
169 fprintf(stderr, "Unable to get host name from %s\n", address);
170 goto bail1;
171 }
172
173 wsi->sock = socket(AF_INET, SOCK_STREAM, 0);
Andy Green6964bb52011-01-23 16:50:33 +0000174
Andy Green4739e5c2011-01-22 12:51:57 +0000175 if (wsi->sock < 0) {
176 fprintf(stderr, "Unable to open socket\n");
177 goto bail1;
178 }
179
180
181 server_addr.sin_family = AF_INET;
182 server_addr.sin_port = htons(port);
183 server_addr.sin_addr = *((struct in_addr *)server_hostent->h_addr);
184 bzero(&server_addr.sin_zero, 8);
185
186 if (connect(wsi->sock, (struct sockaddr *)&server_addr,
187 sizeof(struct sockaddr)) == -1) {
Andy Green90c7cbc2011-01-27 06:26:52 +0000188 fprintf(stderr, "Connect failed\n");
Andy Green4739e5c2011-01-22 12:51:57 +0000189 goto bail1;
Andy Green6964bb52011-01-23 16:50:33 +0000190 }
Andy Green4739e5c2011-01-22 12:51:57 +0000191
Andy Green9659f372011-01-27 22:01:43 +0000192 /* we are connected to server, or proxy */
193
Andy Green9659f372011-01-27 22:01:43 +0000194 if (this->http_proxy_port) {
195
196 n = send(wsi->sock, pkt, plen, 0);
197 if (n < 0) {
Andy Green5b9a4c02011-01-28 09:39:29 +0000198 close(wsi->sock);
199 fprintf(stderr, "ERROR writing to proxy socket\n");
200 goto bail1;
201 }
202
203 pfd.fd = wsi->sock;
204 pfd.events = POLLIN;
205 pfd.revents = 0;
206
207 n = poll(&pfd, 1, 5000);
208 if (n <= 0) {
209 close(wsi->sock);
210 fprintf(stderr, "libwebsocket_client_handshake "
211 "timeout on proxy response");
Andy Green02244bb2011-01-28 09:01:19 +0000212 goto bail1;
Andy Green9659f372011-01-27 22:01:43 +0000213 }
214
215 n = recv(wsi->sock, pkt, sizeof pkt, 0);
216 if (n < 0) {
Andy Green02244bb2011-01-28 09:01:19 +0000217 close(wsi->sock);
Andy Green5b9a4c02011-01-28 09:39:29 +0000218 fprintf(stderr, "ERROR reading from proxy socket\n");
Andy Green02244bb2011-01-28 09:01:19 +0000219 goto bail1;
Andy Green9659f372011-01-27 22:01:43 +0000220 }
221
222 pkt[13] = '\0';
223 if (strcmp(pkt, "HTTP/1.0 200 ") != 0) {
Andy Green02244bb2011-01-28 09:01:19 +0000224 close(wsi->sock);
Andy Green9659f372011-01-27 22:01:43 +0000225 fprintf(stderr, "ERROR from proxy: %s\n", pkt);
Andy Green02244bb2011-01-28 09:01:19 +0000226 goto bail1;
Andy Green9659f372011-01-27 22:01:43 +0000227 }
228
229 /* we can just start sending to proxy */
230 }
231
Andy Green90c7cbc2011-01-27 06:26:52 +0000232#ifdef LWS_OPENSSL_SUPPORT
233 if (ssl_connection) {
234
235 wsi->ssl = SSL_new(this->ssl_client_ctx);
236 wsi->client_bio = BIO_new_socket(wsi->sock, BIO_NOCLOSE);
237 SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio);
238
239 if (SSL_connect(wsi->ssl) <= 0) {
240 fprintf(stderr, "SSL connect error %s\n",
241 ERR_error_string(ERR_get_error(), ssl_err_buf));
Andy Green9659f372011-01-27 22:01:43 +0000242 goto bail1;
Andy Green90c7cbc2011-01-27 06:26:52 +0000243 }
244
245 n = SSL_get_verify_result(wsi->ssl);
246 if (n != X509_V_OK) {
Andy Green9659f372011-01-27 22:01:43 +0000247 if (n != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
248 ssl_connection != 2) {
Andy Green90c7cbc2011-01-27 06:26:52 +0000249
Andy Green9659f372011-01-27 22:01:43 +0000250 fprintf(stderr, "server's cert didn't "
251 "look good %d\n", n);
252 goto bail2;
253 }
Andy Green90c7cbc2011-01-27 06:26:52 +0000254 }
Andy Green9659f372011-01-27 22:01:43 +0000255 } else {
Andy Green90c7cbc2011-01-27 06:26:52 +0000256 wsi->ssl = NULL;
Andy Green90c7cbc2011-01-27 06:26:52 +0000257#endif
Andy Green9659f372011-01-27 22:01:43 +0000258
259
260#ifdef LWS_OPENSSL_SUPPORT
261 }
262#endif
263
Andy Green6964bb52011-01-23 16:50:33 +0000264 /*
265 * create the random key
266 */
Andy Green4739e5c2011-01-22 12:51:57 +0000267
268 fd = open(SYSTEM_RANDOM_FILEPATH, O_RDONLY);
269 if (fd < 1) {
270 fprintf(stderr, "Unable to open random device %s\n",
271 SYSTEM_RANDOM_FILEPATH);
272 goto bail2;
273 }
274 n = read(fd, hash, 16);
275 if (n != 16) {
276 fprintf(stderr, "Unable to read from random device %s\n",
277 SYSTEM_RANDOM_FILEPATH);
278 close(fd);
279 goto bail2;
280 }
281 close(fd);
282
Andy Green6964bb52011-01-23 16:50:33 +0000283 lws_b64_encode_string(hash, 16, key_b64, sizeof key_b64);
Andy Green4739e5c2011-01-22 12:51:57 +0000284
285 /*
286 * 04 example client handshake
287 *
288 * GET /chat HTTP/1.1
289 * Host: server.example.com
290 * Upgrade: websocket
291 * Connection: Upgrade
292 * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
293 * Sec-WebSocket-Origin: http://example.com
294 * Sec-WebSocket-Protocol: chat, superchat
295 * Sec-WebSocket-Version: 4
296 */
297
298 p += sprintf(p, "GET %s HTTP/1.1\x0d\x0a", path);
299 p += sprintf(p, "Host: %s\x0d\x0a", host);
300 p += sprintf(p, "Upgrade: websocket\x0d\x0a");
301 p += sprintf(p, "Connection: Upgrade\x0d\x0aSec-WebSocket-Key: ");
302 strcpy(p, key_b64);
303 p += strlen(key_b64);
304 p += sprintf(p, "\x0d\x0aSec-WebSocket-Origin: %s\x0d\x0a", origin);
305 if (protocol != NULL)
306 p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", protocol);
307 p += sprintf(p, "Sec-WebSocket-Version: 4\x0d\x0a\x0d\x0a");
308
309
310 /* prepare the expected server accept response */
311
312 strcpy(buf, key_b64);
313 strcpy(&buf[strlen(buf)], magic_websocket_guid);
314
315 SHA1((unsigned char *)buf, strlen(buf), (unsigned char *)hash);
316
317 lws_b64_encode_string(hash, 20, wsi->initial_handshake_hash_base64,
318 sizeof wsi->initial_handshake_hash_base64);
319
320 /* send our request to the server */
321
Andy Green90c7cbc2011-01-27 06:26:52 +0000322#ifdef LWS_OPENSSL_SUPPORT
323 if (ssl_connection)
324 n = SSL_write(wsi->ssl, pkt, p - pkt);
325 else
326#endif
327 n = send(wsi->sock, pkt, p - pkt, 0);
328
329 if (n < 0) {
330 fprintf(stderr, "ERROR writing to client socket\n");
331 goto bail2;
332 }
Andy Green4739e5c2011-01-22 12:51:57 +0000333
334 wsi->parser_state = WSI_TOKEN_NAME_PART;
335
336 pfd.fd = wsi->sock;
337 pfd.events = POLLIN;
338 pfd.revents = 0;
339
340 n = poll(&pfd, 1, 5000);
341 if (n < 0) {
342 fprintf(stderr, "libwebsocket_client_handshake socket error "
343 "while waiting for handshake response");
344 goto bail2;
345 }
346 if (n == 0) {
347 fprintf(stderr, "libwebsocket_client_handshake timeout "
348 "while waiting for handshake response");
349 goto bail2;
350 }
351
352 /* interpret the server response */
353
354 /*
355 * HTTP/1.1 101 Switching Protocols
356 * Upgrade: websocket
357 * Connection: Upgrade
358 * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo=
359 * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC==
360 * Sec-WebSocket-Protocol: chat
361 */
362
Andy Green90c7cbc2011-01-27 06:26:52 +0000363#ifdef LWS_OPENSSL_SUPPORT
364 if (ssl_connection)
365 len = SSL_read(wsi->ssl, pkt, sizeof pkt);
366 else
367#endif
368 len = recv(wsi->sock, pkt, sizeof pkt, 0);
369
Andy Green4739e5c2011-01-22 12:51:57 +0000370 if (len < 0) {
371 fprintf(stderr, "libwebsocket_client_handshake read error\n");
372 goto bail2;
373 }
374
375 p = pkt;
376 for (n = 0; n < len; n++)
377 libwebsocket_parse(wsi, *p++);
378
379 if (wsi->parser_state != WSI_PARSING_COMPLETE) {
380 fprintf(stderr, "libwebsocket_client_handshake server response"
381 " failed parsing\n");
382 goto bail2;
383 }
384
385 /*
386 * well, what the server sent looked reasonable for syntax.
387 * Now let's confirm it sent all the necessary headers
388 */
389
390 if (!wsi->utf8_token[WSI_TOKEN_HTTP].token_len ||
391 !wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len ||
392 !wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len ||
393 !wsi->utf8_token[WSI_TOKEN_ACCEPT].token_len ||
394 !wsi->utf8_token[WSI_TOKEN_NONCE].token_len ||
395 (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len &&
396 protocol != NULL)) {
397 fprintf(stderr, "libwebsocket_client_handshake "
398 "missing required header\n");
399 goto bail2;
400 }
401
402 /*
403 * Everything seems to be there, now take a closer look at what is in
404 * each header
405 */
406
407 strtolower(wsi->utf8_token[WSI_TOKEN_HTTP].token);
408 if (strcmp(wsi->utf8_token[WSI_TOKEN_HTTP].token,
409 "101 switching protocols")) {
410 fprintf(stderr, "libwebsocket_client_handshake server sent bad"
411 " HTTP response '%s'\n",
412 wsi->utf8_token[WSI_TOKEN_HTTP].token);
413 goto bail2;
414 }
415
416 strtolower(wsi->utf8_token[WSI_TOKEN_UPGRADE].token);
417 if (strcmp(wsi->utf8_token[WSI_TOKEN_UPGRADE].token, "websocket")) {
418 fprintf(stderr, "libwebsocket_client_handshake server sent bad"
419 " Upgrade header '%s'\n",
420 wsi->utf8_token[WSI_TOKEN_UPGRADE].token);
421 goto bail2;
Andy Green6964bb52011-01-23 16:50:33 +0000422 }
Andy Green4739e5c2011-01-22 12:51:57 +0000423
424 strtolower(wsi->utf8_token[WSI_TOKEN_CONNECTION].token);
425 if (strcmp(wsi->utf8_token[WSI_TOKEN_CONNECTION].token, "upgrade")) {
426 fprintf(stderr, "libwebsocket_client_handshake server sent bad"
427 " Connection hdr '%s'\n",
428 wsi->utf8_token[WSI_TOKEN_CONNECTION].token);
429 goto bail2;
430 }
431 /*
432 * confirm the protocol the server wants to talk was in the list of
433 * protocols we offered
434 */
435
436 if (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len) {
437
438 /* no protocol name to work from, default to first protocol */
Andy Green90c7cbc2011-01-27 06:26:52 +0000439 wsi->protocol = &this->protocols[0];
Andy Green4739e5c2011-01-22 12:51:57 +0000440
441 goto check_accept;
442 }
443
444 pc = protocol;
445 while (*pc && !okay) {
446 if ((!strncmp(pc, wsi->utf8_token[WSI_TOKEN_PROTOCOL].token,
447 wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len)) &&
448 (pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == ',' ||
449 pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == '\0')) {
450 okay = 1;
451 continue;
452 }
453 while (*pc && *pc != ',')
454 pc++;
455 while (*pc && *pc != ' ')
456 pc++;
457 }
458 if (!okay) {
459 fprintf(stderr, "libwebsocket_client_handshake server "
460 "sent bad protocol '%s'\n",
461 wsi->utf8_token[WSI_TOKEN_PROTOCOL].token);
462 goto bail2;
463 }
464
465 /*
466 * identify the selected protocol struct and set it
467 */
468 n = 0;
469 wsi->protocol = NULL;
Andy Green90c7cbc2011-01-27 06:26:52 +0000470 while (this->protocols[n].callback) {
Andy Green4739e5c2011-01-22 12:51:57 +0000471 if (strcmp(wsi->utf8_token[WSI_TOKEN_PROTOCOL].token,
Andy Green90c7cbc2011-01-27 06:26:52 +0000472 this->protocols[n].name) == 0)
473 wsi->protocol = &this->protocols[n];
Andy Green4739e5c2011-01-22 12:51:57 +0000474 n++;
475 }
476
477 if (wsi->protocol == NULL) {
478 fprintf(stderr, "libwebsocket_client_handshake server "
479 "requested protocol '%s', which we "
480 "said we supported but we don't!\n",
481 wsi->utf8_token[WSI_TOKEN_PROTOCOL].token);
482 goto bail2;
483 }
484
485check_accept:
486 /*
487 * Confirm his accept token is the same as the one we precomputed
488 */
489
490 if (strcmp(wsi->utf8_token[WSI_TOKEN_ACCEPT].token,
491 wsi->initial_handshake_hash_base64)) {
492 fprintf(stderr, "libwebsocket_client_handshake server sent "
493 "bad ACCEPT '%s' vs computed '%s'\n",
494 wsi->utf8_token[WSI_TOKEN_ACCEPT].token,
495 wsi->initial_handshake_hash_base64);
496 goto bail2;
497 }
498
499 /*
500 * Calculate the masking key to use when sending data to server
501 */
502
503 strcpy(buf, key_b64);
504 p = buf + strlen(key_b64);
505 strcpy(p, wsi->utf8_token[WSI_TOKEN_NONCE].token);
506 p += wsi->utf8_token[WSI_TOKEN_NONCE].token_len;
507 strcpy(p, magic_websocket_04_masking_guid);
508 SHA1((unsigned char *)buf, strlen(buf), wsi->masking_key_04);
509
510 /* okay he is good to go */
511
Andy Green90c7cbc2011-01-27 06:26:52 +0000512 this->fds[this->fds_count].fd = wsi->sock;
513 this->fds[this->fds_count].revents = 0;
514 this->fds[this->fds_count++].events = POLLIN;
Andy Green4739e5c2011-01-22 12:51:57 +0000515
516 wsi->state = WSI_STATE_ESTABLISHED;
517 wsi->client_mode = 1;
518
519 fprintf(stderr, "handshake OK for protocol %s\n", wsi->protocol->name);
520
Andy Green90c7cbc2011-01-27 06:26:52 +0000521 /* call him back to inform him he is up */
522
523 wsi->protocol->callback(wsi,
524 LWS_CALLBACK_CLIENT_ESTABLISHED,
525 wsi->user_space,
526 NULL, 0);
Andy Green4739e5c2011-01-22 12:51:57 +0000527 return wsi;
528
529
530bail2:
531 libwebsocket_client_close(wsi);
532bail1:
533 free(wsi);
534
535 return NULL;
536}