Skip to content
Snippets Groups Projects
client-handshake.c 12.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • #include "private-libwebsockets.h"
    #include <netdb.h>
    
    
    /*
     * In-place str to lower case
     */
    
    void
    strtolower(char *s)
    {
    
    	while (*s) {
    		*s = tolower(*s);
    		s++;
    	}
    
    
    /**
     * libwebsocket_client_connect() - Connect to another websocket server
     * @this:	Websocket context
     * @address:	Remote server address, eg, "myserver.com"
     * @port:	Port to connect to on the remote server, eg, 80
     * @ssl_connection:	0 = ws://, 1 = wss:// encrypted, 2 = wss:// allow self
     *			signed certs
     * @path:	Websocket path on server
     * @host:	Hostname on server
     * @origin:	Socket origin name
     * @protocol:	Comma-separated list of protocols being asked for from
     *		the server, or just one.  The server will pick the one it
     *		likes best.
    
     * @ietf_version_or_minus_one: -1 to ask to connect using the default, latest
     * 		protocol supported, or the specific protocol ordinal
    
     *
     *	This function creates a connection to a remote server
     */
    
    
    struct libwebsocket *
    
    libwebsocket_client_connect(struct libwebsocket_context *this,
    
    			      const char *address,
    			      int port,
    
    			      int ssl_connection,
    
    			      const char *path,
    			      const char *host,
    			      const char *origin,
    
    			      const char *protocol,
    			      int ietf_version_or_minus_one)
    
    {
    	struct hostent *server_hostent;
    	struct sockaddr_in server_addr;
    	char buf[150];
    	char key_b64[150];
    	char hash[20];
    	struct pollfd pfd;
    	static const char magic_websocket_guid[] =
    					 "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    	static const char magic_websocket_04_masking_guid[] =
    					 "61AC5F19-FBBA-4540-B96F-6561F1AB40A8";
    	char pkt[1024];
    	char *p = &pkt[0];
    
    Andy Green's avatar
    Andy Green committed
    	const char *pc;
    
    	int len;
    	int okay = 0;
    	struct libwebsocket *wsi;
    	int n;
    
    	int plen = 0;
    
    #ifdef LWS_OPENSSL_SUPPORT
    	char ssl_err_buf[512];
    #else
    	if (ssl_connection) {
    		fprintf(stderr, "libwebsockets not configured for ssl\n");
    		return NULL;
    	}
    #endif
    
    Andy Green's avatar
    Andy Green committed
    	wsi = malloc(sizeof(struct libwebsocket));
    
    		fprintf(stderr, "Out of memory allocing new connection\n");
    
    		return NULL;
    
    	memset(wsi, 0, sizeof *wsi);
    
    
    	/* -1 means just use latest supported */
    
    	if (ietf_version_or_minus_one == -1)
    		ietf_version_or_minus_one = 5;
    
    	wsi->ietf_spec_revision = ietf_version_or_minus_one;
    
    	wsi->name_buffer_pos = 0;
    	wsi->user_space = NULL;
    	wsi->state = WSI_STATE_CLIENT_UNCONNECTED;
    	wsi->pings_vs_pongs = 0;
    
    	wsi->protocol = NULL;
    
    Andy Green's avatar
    Andy Green committed
    	wsi->pending_timeout = NO_PENDING_TIMEOUT;
    
    	/* set up appropriate masking */
    
    	wsi->xor_mask = xor_no_mask;
    
    	switch (wsi->ietf_spec_revision) {
    	case 4:
    		wsi->xor_mask = xor_mask_04;
    		break;
    	case 5:
    		wsi->xor_mask = xor_mask_05;
    		break;
    	default:
    		fprintf(stderr,
    			"Client ietf version %d not supported\n",
    						       wsi->ietf_spec_revision);
    		return NULL;
    	}
    
    	/* force no mask if he asks for that though */
    
    	if (this->options & LWS_SERVER_OPTION_DEFEAT_CLIENT_MASK)
    		wsi->xor_mask = xor_no_mask;
    
    
    	for (n = 0; n < WSI_TOKEN_COUNT; n++) {
    		wsi->utf8_token[n].token = NULL;
    		wsi->utf8_token[n].token_len = 0;
    	}
    
    	/*
    
    	 * proxy?
    	 */
    
    	if (this->http_proxy_port) {
    		plen = sprintf(pkt, "CONNECT %s:%u HTTP/1.0\x0d\x0a"
    			"User-agent: libwebsockets\x0d\x0a"
    /*Proxy-authorization: basic aGVsbG86d29ybGQ= */
    			"\x0d\x0a", address, port);
    
    		/* OK from now on we talk via the proxy */
    
    		address = this->http_proxy_address;
    		port = this->http_proxy_port;
    	}
    
    	/*
    	 * prepare the actual connection (to the proxy, if any)
    
    	 */
    
    	server_hostent = gethostbyname(address);
    	if (server_hostent == NULL) {
    		fprintf(stderr, "Unable to get host name from %s\n", address);
    		goto bail1;
    	}
    
    	wsi->sock = socket(AF_INET, SOCK_STREAM, 0);
    
    	if (wsi->sock < 0) {
    		fprintf(stderr, "Unable to open socket\n");
    		goto bail1;
    	}
    
    
    	insert_wsi(this, wsi);
    
    
    	server_addr.sin_family = AF_INET;
    	server_addr.sin_port = htons(port);
    	server_addr.sin_addr = *((struct in_addr *)server_hostent->h_addr);
    	bzero(&server_addr.sin_zero, 8);
    
    	if (connect(wsi->sock, (struct sockaddr *)&server_addr,
    					      sizeof(struct sockaddr)) == -1)  {
    
    		fprintf(stderr, "Connect failed\n");
    
    		goto bail1;
    
    Andy Green's avatar
    Andy Green committed
    	}
    
    	/* we are connected to server, or proxy */
    
    	if (this->http_proxy_port) {
    
    		n = send(wsi->sock, pkt, plen, 0);
    		if (n < 0) {
    
    			close(wsi->sock);
    			fprintf(stderr, "ERROR writing to proxy socket\n");
    			goto bail1;
    		}
    
    		pfd.fd = wsi->sock;
    		pfd.events = POLLIN;
    		pfd.revents = 0;
    
    		n = poll(&pfd, 1, 5000);
    		if (n <= 0) {
    			close(wsi->sock);
    			fprintf(stderr, "libwebsocket_client_handshake "
    					"timeout on proxy response");
    
    		}
    
    		n = recv(wsi->sock, pkt, sizeof pkt, 0);
    		if (n < 0) {
    
    			close(wsi->sock);
    
    			fprintf(stderr, "ERROR reading from proxy socket\n");
    
    		}
    
    		pkt[13] = '\0';
    		if (strcmp(pkt, "HTTP/1.0 200 ") != 0) {
    
    			close(wsi->sock);
    
    			fprintf(stderr, "ERROR from proxy: %s\n", pkt);
    
    		}
    
    		/* we can just start sending to proxy */
    	}
    
    
    #ifdef LWS_OPENSSL_SUPPORT
    	if (ssl_connection) {
    
    		wsi->ssl = SSL_new(this->ssl_client_ctx);
    		wsi->client_bio = BIO_new_socket(wsi->sock, BIO_NOCLOSE);
    		SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio);
    
    		if (SSL_connect(wsi->ssl) <= 0) {
    			fprintf(stderr, "SSL connect error %s\n",
    				ERR_error_string(ERR_get_error(), ssl_err_buf));
    
    		}
    
    		n = SSL_get_verify_result(wsi->ssl);
    		if (n != X509_V_OK) {
    
    			if (n != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
    							  ssl_connection != 2) {
    
    				fprintf(stderr, "server's cert didn't "
    							   "look good %d\n", n);
    				goto bail2;
    			}
    
    
    
    #ifdef LWS_OPENSSL_SUPPORT
    	}
    #endif
    
    
    Andy Green's avatar
    Andy Green committed
    	/*
    	 * create the random key
    	 */
    
    	n = read(this->fd_random, hash, 16);
    
    	if (n != 16) {
    		fprintf(stderr, "Unable to read from random device %s\n",
    							SYSTEM_RANDOM_FILEPATH);
    		goto bail2;
    	}
    
    
    Andy Green's avatar
    Andy Green committed
    	lws_b64_encode_string(hash, 16, key_b64, sizeof key_b64);
    
    
    	/*
    	 * 04 example client handshake
    	 *
    	 * GET /chat HTTP/1.1
    	 * Host: server.example.com
    	 * Upgrade: websocket
    	 * Connection: Upgrade
    	 * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    	 * Sec-WebSocket-Origin: http://example.com
    	 * Sec-WebSocket-Protocol: chat, superchat
    	 * Sec-WebSocket-Version: 4
    	 */
    
    	 p += sprintf(p, "GET %s HTTP/1.1\x0d\x0a", path);
    	 p += sprintf(p, "Host: %s\x0d\x0a", host);
    	 p += sprintf(p, "Upgrade: websocket\x0d\x0a");
    	 p += sprintf(p, "Connection: Upgrade\x0d\x0aSec-WebSocket-Key: ");
    	 strcpy(p, key_b64);
    	 p += strlen(key_b64);
    	 p += sprintf(p, "\x0d\x0aSec-WebSocket-Origin: %s\x0d\x0a", origin);
    	 if (protocol != NULL)
    		p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", protocol);
    
    	 p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a\x0d\x0a",
    						       wsi->ietf_spec_revision);
    
    
    
    	/* prepare the expected server accept response */
    
    	strcpy(buf, key_b64);
    	strcpy(&buf[strlen(buf)], magic_websocket_guid);
    
    	SHA1((unsigned char *)buf, strlen(buf), (unsigned char *)hash);
    
    	lws_b64_encode_string(hash, 20, wsi->initial_handshake_hash_base64,
    				  sizeof wsi->initial_handshake_hash_base64);
    
    	/* send our request to the server */
    
    
    #ifdef LWS_OPENSSL_SUPPORT
    	if (ssl_connection)
    		n = SSL_write(wsi->ssl, pkt, p - pkt);
    	else
    #endif
    		n = send(wsi->sock, pkt, p - pkt, 0);
    
    	if (n < 0) {
    		fprintf(stderr, "ERROR writing to client socket\n");
    		goto bail2;
    	}
    
    
    	wsi->parser_state = WSI_TOKEN_NAME_PART;
    
    	pfd.fd = wsi->sock;
    	pfd.events = POLLIN;
    	pfd.revents = 0;
    
    	n = poll(&pfd, 1, 5000);
    	if (n < 0) {
    		fprintf(stderr, "libwebsocket_client_handshake socket error "
    				"while waiting for handshake response");
    		goto bail2;
    	}
    	if (n == 0) {
    		fprintf(stderr, "libwebsocket_client_handshake timeout "
    				"while waiting for handshake response");
    		goto bail2;
    	}
    
    	/* interpret the server response */
    
    	/*
    	 *  HTTP/1.1 101 Switching Protocols
    	 *  Upgrade: websocket
    	 *  Connection: Upgrade
    	 *  Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo=
    	 *  Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC==
    	 *  Sec-WebSocket-Protocol: chat
    	 */
    
    
    #ifdef LWS_OPENSSL_SUPPORT
    	if (ssl_connection)
    		len = SSL_read(wsi->ssl, pkt, sizeof pkt);
    	else
    #endif
    		len = recv(wsi->sock, pkt, sizeof pkt, 0);
    
    
    	if (len < 0) {
    		fprintf(stderr, "libwebsocket_client_handshake read error\n");
    		goto bail2;
    	}
    
    	p = pkt;
    	for (n = 0; n < len; n++)
    		libwebsocket_parse(wsi, *p++);
    
    	if (wsi->parser_state != WSI_PARSING_COMPLETE) {
    		fprintf(stderr, "libwebsocket_client_handshake server response"
    				" failed parsing\n");
    		goto bail2;
    	}
    
    	/*
    	 * well, what the server sent looked reasonable for syntax.
    	 * Now let's confirm it sent all the necessary headers
    	 */
    
    	 if (!wsi->utf8_token[WSI_TOKEN_HTTP].token_len ||
    			!wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len ||
    			!wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len ||
    			!wsi->utf8_token[WSI_TOKEN_ACCEPT].token_len ||
    			!wsi->utf8_token[WSI_TOKEN_NONCE].token_len ||
    			(!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len &&
    							    protocol != NULL)) {
    		fprintf(stderr, "libwebsocket_client_handshake "
    
    						"missing required header(s)\n");
    
    		fprintf(stderr, "%s", pkt);
    
    		goto bail2;
    	}
    
    	/*
    	 * Everything seems to be there, now take a closer look at what is in
    	 * each header
    	 */
    
    	strtolower(wsi->utf8_token[WSI_TOKEN_HTTP].token);
    	if (strcmp(wsi->utf8_token[WSI_TOKEN_HTTP].token,
    						   "101 switching protocols")) {
    		fprintf(stderr, "libwebsocket_client_handshake server sent bad"
    				" HTTP response '%s'\n",
    					 wsi->utf8_token[WSI_TOKEN_HTTP].token);
    		goto bail2;
    	}
    
    	strtolower(wsi->utf8_token[WSI_TOKEN_UPGRADE].token);
    	if (strcmp(wsi->utf8_token[WSI_TOKEN_UPGRADE].token, "websocket")) {
    		fprintf(stderr, "libwebsocket_client_handshake server sent bad"
    				" Upgrade header '%s'\n",
    				      wsi->utf8_token[WSI_TOKEN_UPGRADE].token);
    		goto bail2;
    
    Andy Green's avatar
    Andy Green committed
    	}
    
    
    	strtolower(wsi->utf8_token[WSI_TOKEN_CONNECTION].token);
    	if (strcmp(wsi->utf8_token[WSI_TOKEN_CONNECTION].token, "upgrade")) {
    		fprintf(stderr, "libwebsocket_client_handshake server sent bad"
    				" Connection hdr '%s'\n",
    				   wsi->utf8_token[WSI_TOKEN_CONNECTION].token);
    		goto bail2;
    	}
    	/*
    	 * confirm the protocol the server wants to talk was in the list of
    	 * protocols we offered
    	 */
    
    	if (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len) {
    
    		/* no protocol name to work from, default to first protocol */
    
    		wsi->protocol = &this->protocols[0];
    
    
    		goto check_accept;
    	}
    
    	pc = protocol;
    	while (*pc && !okay) {
    		if ((!strncmp(pc, wsi->utf8_token[WSI_TOKEN_PROTOCOL].token,
    			wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len)) &&
    		(pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == ',' ||
    		   pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == '\0')) {
    			okay = 1;
    			continue;
    		}
    		while (*pc && *pc != ',')
    			pc++;
    		while (*pc && *pc != ' ')
    			pc++;
    	}
    	if (!okay) {
    		fprintf(stderr, "libwebsocket_client_handshake server "
    					"sent bad protocol '%s'\n",
    			     wsi->utf8_token[WSI_TOKEN_PROTOCOL].token);
    		goto bail2;
    	}
    
    	/*
    	 * identify the selected protocol struct and set it
    	 */
    	n = 0;
    	wsi->protocol = NULL;
    
    	while (this->protocols[n].callback) {
    
    		if (strcmp(wsi->utf8_token[WSI_TOKEN_PROTOCOL].token,
    
    				       this->protocols[n].name) == 0)
    			wsi->protocol = &this->protocols[n];
    
    		n++;
    	}
    
    	if (wsi->protocol == NULL) {
    		fprintf(stderr, "libwebsocket_client_handshake server "
    				"requested protocol '%s', which we "
    				"said we supported but we don't!\n",
    			     wsi->utf8_token[WSI_TOKEN_PROTOCOL].token);
    		goto bail2;
    	}
    
    check_accept:
    	/*
    	 * Confirm his accept token is the same as the one we precomputed
    	 */
    
    	if (strcmp(wsi->utf8_token[WSI_TOKEN_ACCEPT].token,
    					  wsi->initial_handshake_hash_base64)) {
    		fprintf(stderr, "libwebsocket_client_handshake server sent "
    				"bad ACCEPT '%s' vs computed '%s'\n",
    				wsi->utf8_token[WSI_TOKEN_ACCEPT].token,
    					    wsi->initial_handshake_hash_base64);
    		goto bail2;
    	}
    
    	/*
    	 * Calculate the masking key to use when sending data to server
    	 */
    
    	strcpy(buf, key_b64);
    	p = buf + strlen(key_b64);
    	strcpy(p, wsi->utf8_token[WSI_TOKEN_NONCE].token);
    	p += wsi->utf8_token[WSI_TOKEN_NONCE].token_len;
    	strcpy(p, magic_websocket_04_masking_guid);
    	SHA1((unsigned char *)buf, strlen(buf), wsi->masking_key_04);
    
    
    	/* allocate the per-connection user memory (if any) */
    
    	if (wsi->protocol->per_session_data_size) {
    		wsi->user_space = malloc(
    				  wsi->protocol->per_session_data_size);
    		if (wsi->user_space  == NULL) {
    			fprintf(stderr, "Out of memory for "
    						   "conn user space\n");
    			goto bail2;
    		}
    	} else
    		wsi->user_space = NULL;
    
    
    	/* okay he is good to go */
    
    
    	this->fds[this->fds_count].fd = wsi->sock;
    	this->fds[this->fds_count].revents = 0;
    	this->fds[this->fds_count++].events = POLLIN;
    
    	/* external POLL support via protocol 0 */
    
    	this->protocols[0].callback(this, wsi,
    
    		LWS_CALLBACK_ADD_POLL_FD,
    		(void *)(long)wsi->sock, NULL, POLLIN);
    
    
    
    	wsi->state = WSI_STATE_ESTABLISHED;
    
    	wsi->mode = LWS_CONNMODE_WS_CLIENT;
    
    
    	fprintf(stderr, "handshake OK for protocol %s\n", wsi->protocol->name);
    
    
    	/* call him back to inform him he is up */
    
    
    	wsi->protocol->callback(this, wsi,
    
    			 LWS_CALLBACK_CLIENT_ESTABLISHED,
    			 wsi->user_space,
    			 NULL, 0);
    
    	return wsi;
    
    
    bail2:
    
    	libwebsocket_close_and_free_session(this, wsi);
    
    bail1:
    	free(wsi);
    
    	return NULL;
    }