| 1 | /* |
| 2 | Copyright (c) 2007-2019 Contributors as noted in the AUTHORS file |
| 3 | |
| 4 | This file is part of libzmq, the ZeroMQ core engine in C++. |
| 5 | |
| 6 | libzmq is free software; you can redistribute it and/or modify it under |
| 7 | the terms of the GNU Lesser General Public License (LGPL) as published |
| 8 | by the Free Software Foundation; either version 3 of the License, or |
| 9 | (at your option) any later version. |
| 10 | |
| 11 | As a special exception, the Contributors give you permission to link |
| 12 | this library with independent modules to produce an executable, |
| 13 | regardless of the license terms of these independent modules, and to |
| 14 | copy and distribute the resulting executable under terms of your choice, |
| 15 | provided that you also meet, for each linked independent module, the |
| 16 | terms and conditions of the license of that module. An independent |
| 17 | module is a module which is not derived from or based on this library. |
| 18 | If you modify this library, you must extend this exception to your |
| 19 | version of the library. |
| 20 | |
| 21 | libzmq is distributed in the hope that it will be useful, but WITHOUT |
| 22 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 23 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
| 24 | License for more details. |
| 25 | |
| 26 | You should have received a copy of the GNU Lesser General Public License |
| 27 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 28 | */ |
| 29 | |
| 30 | #include "precompiled.hpp" |
| 31 | |
| 32 | #ifdef ZMQ_USE_NSS |
| 33 | #include <secoid.h> |
| 34 | #include <sechash.h> |
| 35 | #define SHA_DIGEST_LENGTH 20 |
| 36 | #elif defined ZMQ_USE_BUILTIN_SHA1 |
| 37 | #include "../external/sha1/sha1.h" |
| 38 | #elif defined ZMQ_USE_GNUTLS |
| 39 | #define SHA_DIGEST_LENGTH 20 |
| 40 | #include <gnutls/gnutls.h> |
| 41 | #include <gnutls/crypto.h> |
| 42 | #endif |
| 43 | |
| 44 | #if !defined ZMQ_HAVE_WINDOWS |
| 45 | #include <sys/types.h> |
| 46 | #include <unistd.h> |
| 47 | #include <sys/socket.h> |
| 48 | #include <netinet/in.h> |
| 49 | #include <arpa/inet.h> |
| 50 | #ifdef ZMQ_HAVE_VXWORKS |
| 51 | #include <sockLib.h> |
| 52 | #endif |
| 53 | #endif |
| 54 | |
| 55 | #include "tcp.hpp" |
| 56 | #include "ws_engine.hpp" |
| 57 | #include "session_base.hpp" |
| 58 | #include "err.hpp" |
| 59 | #include "ip.hpp" |
| 60 | #include "random.hpp" |
| 61 | #include "ws_decoder.hpp" |
| 62 | #include "ws_encoder.hpp" |
| 63 | #include "null_mechanism.hpp" |
| 64 | #include "plain_server.hpp" |
| 65 | #include "plain_client.hpp" |
| 66 | |
| 67 | #ifdef ZMQ_HAVE_CURVE |
| 68 | #include "curve_client.hpp" |
| 69 | #include "curve_server.hpp" |
| 70 | #endif |
| 71 | |
| 72 | #ifdef ZMQ_HAVE_WINDOWS |
| 73 | #define strcasecmp _stricmp |
| 74 | #endif |
| 75 | |
| 76 | // OSX uses a different name for this socket option |
| 77 | #ifndef IPV6_ADD_MEMBERSHIP |
| 78 | #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP |
| 79 | #endif |
| 80 | |
| 81 | #ifdef __APPLE__ |
| 82 | #include <TargetConditionals.h> |
| 83 | #endif |
| 84 | |
| 85 | static int |
| 86 | encode_base64 (const unsigned char *in_, int in_len_, char *out_, int out_len_); |
| 87 | |
| 88 | static void compute_accept_key (char *key_, |
| 89 | unsigned char hash_[SHA_DIGEST_LENGTH]); |
| 90 | |
| 91 | zmq::ws_engine_t::ws_engine_t (fd_t fd_, |
| 92 | const options_t &options_, |
| 93 | const endpoint_uri_pair_t &endpoint_uri_pair_, |
| 94 | ws_address_t &address_, |
| 95 | bool client_) : |
| 96 | stream_engine_base_t (fd_, options_, endpoint_uri_pair_), |
| 97 | _client (client_), |
| 98 | _address (address_), |
| 99 | _client_handshake_state (client_handshake_initial), |
| 100 | _server_handshake_state (handshake_initial), |
| 101 | _header_name_position (0), |
| 102 | _header_value_position (0), |
| 103 | _header_upgrade_websocket (false), |
| 104 | _header_connection_upgrade (false) |
| 105 | { |
| 106 | memset (_websocket_key, 0, MAX_HEADER_VALUE_LENGTH + 1); |
| 107 | memset (_websocket_accept, 0, MAX_HEADER_VALUE_LENGTH + 1); |
| 108 | memset (_websocket_protocol, 0, MAX_HEADER_VALUE_LENGTH + 1); |
| 109 | |
| 110 | _next_msg = &ws_engine_t::next_handshake_command; |
| 111 | _process_msg = &ws_engine_t::process_handshake_command; |
| 112 | } |
| 113 | |
| 114 | zmq::ws_engine_t::~ws_engine_t () |
| 115 | { |
| 116 | } |
| 117 | |
| 118 | void zmq::ws_engine_t::start_ws_handshake () |
| 119 | { |
| 120 | if (_client) { |
| 121 | char protocol[21]; |
| 122 | if (_options.mechanism == ZMQ_NULL) |
| 123 | strcpy (protocol, "ZWS2.0/NULL,ZWS2.0" ); |
| 124 | else if (_options.mechanism == ZMQ_PLAIN) |
| 125 | strcpy (protocol, "ZWS2.0/PLAIN" ); |
| 126 | #ifdef ZMQ_HAVE_CURVE |
| 127 | else if (_options.mechanism == ZMQ_CURVE) |
| 128 | strcpy (protocol, "ZWS2.0/CURVE" ); |
| 129 | #endif |
| 130 | else |
| 131 | assert (false); |
| 132 | |
| 133 | unsigned char nonce[16]; |
| 134 | int *p = reinterpret_cast<int *> (nonce); |
| 135 | |
| 136 | // The nonce doesn't have to be secure one, it is just use to avoid proxy cache |
| 137 | *p = zmq::generate_random (); |
| 138 | *(p + 1) = zmq::generate_random (); |
| 139 | *(p + 2) = zmq::generate_random (); |
| 140 | *(p + 3) = zmq::generate_random (); |
| 141 | |
| 142 | int size = |
| 143 | encode_base64 (nonce, 16, _websocket_key, MAX_HEADER_VALUE_LENGTH); |
| 144 | assert (size > 0); |
| 145 | |
| 146 | size = snprintf ( |
| 147 | reinterpret_cast<char *> (_write_buffer), WS_BUFFER_SIZE, |
| 148 | "GET %s HTTP/1.1\r\n" |
| 149 | "Host: %s\r\n" |
| 150 | "Upgrade: websocket\r\n" |
| 151 | "Connection: Upgrade\r\n" |
| 152 | "Sec-WebSocket-Key: %s\r\n" |
| 153 | "Sec-WebSocket-Protocol: %s\r\n" |
| 154 | "Sec-WebSocket-Version: 13\r\n\r\n" , |
| 155 | _address.path (), _address.host (), _websocket_key, protocol); |
| 156 | assert (size > 0 && size < WS_BUFFER_SIZE); |
| 157 | _outpos = _write_buffer; |
| 158 | _outsize = size; |
| 159 | set_pollout (); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | void zmq::ws_engine_t::plug_internal () |
| 164 | { |
| 165 | start_ws_handshake (); |
| 166 | set_pollin (); |
| 167 | in_event (); |
| 168 | } |
| 169 | |
| 170 | int zmq::ws_engine_t::routing_id_msg (msg_t *msg_) |
| 171 | { |
| 172 | int rc = msg_->init_size (_options.routing_id_size); |
| 173 | errno_assert (rc == 0); |
| 174 | if (_options.routing_id_size > 0) |
| 175 | memcpy (msg_->data (), _options.routing_id, _options.routing_id_size); |
| 176 | _next_msg = &ws_engine_t::pull_msg_from_session; |
| 177 | |
| 178 | return 0; |
| 179 | } |
| 180 | |
| 181 | int zmq::ws_engine_t::process_routing_id_msg (msg_t *msg_) |
| 182 | { |
| 183 | if (_options.recv_routing_id) { |
| 184 | msg_->set_flags (msg_t::routing_id); |
| 185 | int rc = session ()->push_msg (msg_); |
| 186 | errno_assert (rc == 0); |
| 187 | } else { |
| 188 | int rc = msg_->close (); |
| 189 | errno_assert (rc == 0); |
| 190 | rc = msg_->init (); |
| 191 | errno_assert (rc == 0); |
| 192 | } |
| 193 | |
| 194 | _process_msg = &ws_engine_t::push_msg_to_session; |
| 195 | |
| 196 | return 0; |
| 197 | } |
| 198 | |
| 199 | bool zmq::ws_engine_t::select_protocol (char *protocol_) |
| 200 | { |
| 201 | if (_options.mechanism == ZMQ_NULL && (strcmp ("ZWS2.0" , protocol_) == 0)) { |
| 202 | _next_msg = static_cast<int (stream_engine_base_t::*) (msg_t *)> ( |
| 203 | &ws_engine_t::routing_id_msg); |
| 204 | _process_msg = static_cast<int (stream_engine_base_t::*) (msg_t *)> ( |
| 205 | &ws_engine_t::process_routing_id_msg); |
| 206 | return true; |
| 207 | } |
| 208 | if (_options.mechanism == ZMQ_NULL |
| 209 | && strcmp ("ZWS2.0/NULL" , protocol_) == 0) { |
| 210 | _mechanism = new (std::nothrow) |
| 211 | null_mechanism_t (session (), _peer_address, _options); |
| 212 | alloc_assert (_mechanism); |
| 213 | return true; |
| 214 | } else if (_options.mechanism == ZMQ_PLAIN |
| 215 | && strcmp ("ZWS2.0/PLAIN" , protocol_) == 0) { |
| 216 | if (_options.as_server) |
| 217 | _mechanism = new (std::nothrow) |
| 218 | plain_server_t (session (), _peer_address, _options); |
| 219 | else |
| 220 | _mechanism = |
| 221 | new (std::nothrow) plain_client_t (session (), _options); |
| 222 | alloc_assert (_mechanism); |
| 223 | return true; |
| 224 | } |
| 225 | #ifdef ZMQ_HAVE_CURVE |
| 226 | else if (_options.mechanism == ZMQ_CURVE |
| 227 | && strcmp ("ZWS2.0/CURVE" , protocol_) == 0) { |
| 228 | if (_options.as_server) |
| 229 | _mechanism = new (std::nothrow) |
| 230 | curve_server_t (session (), _peer_address, _options); |
| 231 | else |
| 232 | _mechanism = |
| 233 | new (std::nothrow) curve_client_t (session (), _options); |
| 234 | alloc_assert (_mechanism); |
| 235 | return true; |
| 236 | } |
| 237 | #endif |
| 238 | |
| 239 | return false; |
| 240 | } |
| 241 | |
| 242 | bool zmq::ws_engine_t::handshake () |
| 243 | { |
| 244 | bool complete; |
| 245 | |
| 246 | if (_client) |
| 247 | complete = client_handshake (); |
| 248 | else |
| 249 | complete = server_handshake (); |
| 250 | |
| 251 | if (complete) { |
| 252 | _encoder = |
| 253 | new (std::nothrow) ws_encoder_t (_options.out_batch_size, _client); |
| 254 | alloc_assert (_encoder); |
| 255 | |
| 256 | _decoder = new (std::nothrow) |
| 257 | ws_decoder_t (_options.in_batch_size, _options.maxmsgsize, |
| 258 | _options.zero_copy, !_client); |
| 259 | alloc_assert (_decoder); |
| 260 | |
| 261 | socket ()->event_handshake_succeeded (_endpoint_uri_pair, 0); |
| 262 | |
| 263 | set_pollout (); |
| 264 | } |
| 265 | |
| 266 | return complete; |
| 267 | } |
| 268 | |
| 269 | bool zmq::ws_engine_t::server_handshake () |
| 270 | { |
| 271 | int nbytes = read (_read_buffer, WS_BUFFER_SIZE); |
| 272 | if (nbytes == -1) { |
| 273 | if (errno != EAGAIN) |
| 274 | error (zmq::i_engine::connection_error); |
| 275 | return false; |
| 276 | } |
| 277 | |
| 278 | _inpos = _read_buffer; |
| 279 | _insize = nbytes; |
| 280 | |
| 281 | while (_insize > 0) { |
| 282 | char c = static_cast<char> (*_inpos); |
| 283 | |
| 284 | switch (_server_handshake_state) { |
| 285 | case handshake_initial: |
| 286 | if (c == 'G') |
| 287 | _server_handshake_state = request_line_G; |
| 288 | else |
| 289 | _server_handshake_state = handshake_error; |
| 290 | break; |
| 291 | case request_line_G: |
| 292 | if (c == 'E') |
| 293 | _server_handshake_state = request_line_GE; |
| 294 | else |
| 295 | _server_handshake_state = handshake_error; |
| 296 | break; |
| 297 | case request_line_GE: |
| 298 | if (c == 'T') |
| 299 | _server_handshake_state = request_line_GET; |
| 300 | else |
| 301 | _server_handshake_state = handshake_error; |
| 302 | break; |
| 303 | case request_line_GET: |
| 304 | if (c == ' ') |
| 305 | _server_handshake_state = request_line_GET_space; |
| 306 | else |
| 307 | _server_handshake_state = handshake_error; |
| 308 | break; |
| 309 | case request_line_GET_space: |
| 310 | if (c == '\r' || c == '\n') |
| 311 | _server_handshake_state = handshake_error; |
| 312 | // TODO: instead of check what is not allowed check what is allowed |
| 313 | if (c != ' ') |
| 314 | _server_handshake_state = request_line_resource; |
| 315 | else |
| 316 | _server_handshake_state = request_line_GET_space; |
| 317 | break; |
| 318 | case request_line_resource: |
| 319 | if (c == '\r' || c == '\n') |
| 320 | _server_handshake_state = handshake_error; |
| 321 | else if (c == ' ') |
| 322 | _server_handshake_state = request_line_resource_space; |
| 323 | else |
| 324 | _server_handshake_state = request_line_resource; |
| 325 | break; |
| 326 | case request_line_resource_space: |
| 327 | if (c == 'H') |
| 328 | _server_handshake_state = request_line_H; |
| 329 | else |
| 330 | _server_handshake_state = handshake_error; |
| 331 | break; |
| 332 | case request_line_H: |
| 333 | if (c == 'T') |
| 334 | _server_handshake_state = request_line_HT; |
| 335 | else |
| 336 | _server_handshake_state = handshake_error; |
| 337 | break; |
| 338 | case request_line_HT: |
| 339 | if (c == 'T') |
| 340 | _server_handshake_state = request_line_HTT; |
| 341 | else |
| 342 | _server_handshake_state = handshake_error; |
| 343 | break; |
| 344 | case request_line_HTT: |
| 345 | if (c == 'P') |
| 346 | _server_handshake_state = request_line_HTTP; |
| 347 | else |
| 348 | _server_handshake_state = handshake_error; |
| 349 | break; |
| 350 | case request_line_HTTP: |
| 351 | if (c == '/') |
| 352 | _server_handshake_state = request_line_HTTP_slash; |
| 353 | else |
| 354 | _server_handshake_state = handshake_error; |
| 355 | break; |
| 356 | case request_line_HTTP_slash: |
| 357 | if (c == '1') |
| 358 | _server_handshake_state = request_line_HTTP_slash_1; |
| 359 | else |
| 360 | _server_handshake_state = handshake_error; |
| 361 | break; |
| 362 | case request_line_HTTP_slash_1: |
| 363 | if (c == '.') |
| 364 | _server_handshake_state = request_line_HTTP_slash_1_dot; |
| 365 | else |
| 366 | _server_handshake_state = handshake_error; |
| 367 | break; |
| 368 | case request_line_HTTP_slash_1_dot: |
| 369 | if (c == '1') |
| 370 | _server_handshake_state = request_line_HTTP_slash_1_dot_1; |
| 371 | else |
| 372 | _server_handshake_state = handshake_error; |
| 373 | break; |
| 374 | case request_line_HTTP_slash_1_dot_1: |
| 375 | if (c == '\r') |
| 376 | _server_handshake_state = request_line_cr; |
| 377 | else |
| 378 | _server_handshake_state = handshake_error; |
| 379 | break; |
| 380 | case request_line_cr: |
| 381 | if (c == '\n') |
| 382 | _server_handshake_state = header_field_begin_name; |
| 383 | else |
| 384 | _server_handshake_state = handshake_error; |
| 385 | break; |
| 386 | case header_field_begin_name: |
| 387 | switch (c) { |
| 388 | case '\r': |
| 389 | _server_handshake_state = handshake_end_line_cr; |
| 390 | break; |
| 391 | case '\n': |
| 392 | _server_handshake_state = handshake_error; |
| 393 | break; |
| 394 | default: |
| 395 | _header_name[0] = c; |
| 396 | _header_name_position = 1; |
| 397 | _server_handshake_state = header_field_name; |
| 398 | break; |
| 399 | } |
| 400 | break; |
| 401 | case header_field_name: |
| 402 | if (c == '\r' || c == '\n') |
| 403 | _server_handshake_state = handshake_error; |
| 404 | else if (c == ':') { |
| 405 | _header_name[_header_name_position] = '\0'; |
| 406 | _server_handshake_state = header_field_colon; |
| 407 | } else if (_header_name_position + 1 > MAX_HEADER_NAME_LENGTH) |
| 408 | _server_handshake_state = handshake_error; |
| 409 | else { |
| 410 | _header_name[_header_name_position] = c; |
| 411 | _header_name_position++; |
| 412 | _server_handshake_state = header_field_name; |
| 413 | } |
| 414 | break; |
| 415 | case header_field_colon: |
| 416 | case header_field_value_trailing_space: |
| 417 | if (c == '\n') |
| 418 | _server_handshake_state = handshake_error; |
| 419 | else if (c == '\r') |
| 420 | _server_handshake_state = header_field_cr; |
| 421 | else if (c == ' ') |
| 422 | _server_handshake_state = header_field_value_trailing_space; |
| 423 | else { |
| 424 | _header_value[0] = c; |
| 425 | _header_value_position = 1; |
| 426 | _server_handshake_state = header_field_value; |
| 427 | } |
| 428 | break; |
| 429 | case header_field_value: |
| 430 | if (c == '\n') |
| 431 | _server_handshake_state = handshake_error; |
| 432 | else if (c == '\r') { |
| 433 | _header_value[_header_value_position] = '\0'; |
| 434 | |
| 435 | if (strcasecmp ("upgrade" , _header_name) == 0) |
| 436 | _header_upgrade_websocket = |
| 437 | strcasecmp ("websocket" , _header_value) == 0; |
| 438 | else if (strcasecmp ("connection" , _header_name) == 0) |
| 439 | _header_connection_upgrade = |
| 440 | strcasecmp ("upgrade" , _header_value) == 0; |
| 441 | else if (strcasecmp ("Sec-WebSocket-Key" , _header_name) |
| 442 | == 0) |
| 443 | strcpy (_websocket_key, _header_value); |
| 444 | else if (strcasecmp ("Sec-WebSocket-Protocol" , _header_name) |
| 445 | == 0) { |
| 446 | // Currently only the ZWS2.0 is supported |
| 447 | // Sec-WebSocket-Protocol can appear multiple times or be a comma separated list |
| 448 | // if _websocket_protocol is already set we skip the check |
| 449 | if (_websocket_protocol[0] == '\0') { |
| 450 | char *p = strtok (_header_value, "," ); |
| 451 | while (p != NULL) { |
| 452 | if (*p == ' ') |
| 453 | p++; |
| 454 | |
| 455 | if (select_protocol (p)) { |
| 456 | strcpy (_websocket_protocol, p); |
| 457 | break; |
| 458 | } |
| 459 | |
| 460 | p = strtok (NULL, "," ); |
| 461 | } |
| 462 | } |
| 463 | } |
| 464 | |
| 465 | _server_handshake_state = header_field_cr; |
| 466 | } else if (_header_value_position + 1 > MAX_HEADER_VALUE_LENGTH) |
| 467 | _server_handshake_state = handshake_error; |
| 468 | else { |
| 469 | _header_value[_header_value_position] = c; |
| 470 | _header_value_position++; |
| 471 | _server_handshake_state = header_field_value; |
| 472 | } |
| 473 | break; |
| 474 | case header_field_cr: |
| 475 | if (c == '\n') |
| 476 | _server_handshake_state = header_field_begin_name; |
| 477 | else |
| 478 | _server_handshake_state = handshake_error; |
| 479 | break; |
| 480 | case handshake_end_line_cr: |
| 481 | if (c == '\n') { |
| 482 | if (_header_connection_upgrade && _header_upgrade_websocket |
| 483 | && _websocket_protocol[0] != '\0' |
| 484 | && _websocket_key[0] != '\0') { |
| 485 | _server_handshake_state = handshake_complete; |
| 486 | |
| 487 | unsigned char hash[SHA_DIGEST_LENGTH]; |
| 488 | compute_accept_key (_websocket_key, hash); |
| 489 | |
| 490 | int accept_key_len = encode_base64 ( |
| 491 | hash, SHA_DIGEST_LENGTH, _websocket_accept, |
| 492 | MAX_HEADER_VALUE_LENGTH); |
| 493 | assert (accept_key_len > 0); |
| 494 | _websocket_accept[accept_key_len] = '\0'; |
| 495 | |
| 496 | int written = |
| 497 | snprintf (reinterpret_cast<char *> (_write_buffer), |
| 498 | WS_BUFFER_SIZE, |
| 499 | "HTTP/1.1 101 Switching Protocols\r\n" |
| 500 | "Upgrade: websocket\r\n" |
| 501 | "Connection: Upgrade\r\n" |
| 502 | "Sec-WebSocket-Accept: %s\r\n" |
| 503 | "Sec-WebSocket-Protocol: %s\r\n" |
| 504 | "\r\n" , |
| 505 | _websocket_accept, _websocket_protocol); |
| 506 | assert (written >= 0 && written < WS_BUFFER_SIZE); |
| 507 | _outpos = _write_buffer; |
| 508 | _outsize = written; |
| 509 | |
| 510 | _inpos++; |
| 511 | _insize--; |
| 512 | |
| 513 | return true; |
| 514 | } |
| 515 | _server_handshake_state = handshake_error; |
| 516 | } else |
| 517 | _server_handshake_state = handshake_error; |
| 518 | break; |
| 519 | default: |
| 520 | assert (false); |
| 521 | } |
| 522 | |
| 523 | _inpos++; |
| 524 | _insize--; |
| 525 | |
| 526 | if (_server_handshake_state == handshake_error) { |
| 527 | // TODO: send bad request |
| 528 | |
| 529 | socket ()->event_handshake_failed_protocol ( |
| 530 | _endpoint_uri_pair, ZMQ_PROTOCOL_ERROR_WS_UNSPECIFIED); |
| 531 | |
| 532 | error (zmq::i_engine::protocol_error); |
| 533 | return false; |
| 534 | } |
| 535 | } |
| 536 | return false; |
| 537 | } |
| 538 | |
| 539 | bool zmq::ws_engine_t::client_handshake () |
| 540 | { |
| 541 | int nbytes = read (_read_buffer, WS_BUFFER_SIZE); |
| 542 | if (nbytes == -1) { |
| 543 | if (errno != EAGAIN) |
| 544 | error (zmq::i_engine::connection_error); |
| 545 | return false; |
| 546 | } |
| 547 | |
| 548 | _inpos = _read_buffer; |
| 549 | _insize = nbytes; |
| 550 | |
| 551 | while (_insize > 0) { |
| 552 | char c = static_cast<char> (*_inpos); |
| 553 | |
| 554 | switch (_client_handshake_state) { |
| 555 | case client_handshake_initial: |
| 556 | if (c == 'H') |
| 557 | _client_handshake_state = response_line_H; |
| 558 | else |
| 559 | _client_handshake_state = client_handshake_error; |
| 560 | break; |
| 561 | case response_line_H: |
| 562 | if (c == 'T') |
| 563 | _client_handshake_state = response_line_HT; |
| 564 | else |
| 565 | _client_handshake_state = client_handshake_error; |
| 566 | break; |
| 567 | case response_line_HT: |
| 568 | if (c == 'T') |
| 569 | _client_handshake_state = response_line_HTT; |
| 570 | else |
| 571 | _client_handshake_state = client_handshake_error; |
| 572 | break; |
| 573 | case response_line_HTT: |
| 574 | if (c == 'P') |
| 575 | _client_handshake_state = response_line_HTTP; |
| 576 | else |
| 577 | _client_handshake_state = client_handshake_error; |
| 578 | break; |
| 579 | case response_line_HTTP: |
| 580 | if (c == '/') |
| 581 | _client_handshake_state = response_line_HTTP_slash; |
| 582 | else |
| 583 | _client_handshake_state = client_handshake_error; |
| 584 | break; |
| 585 | case response_line_HTTP_slash: |
| 586 | if (c == '1') |
| 587 | _client_handshake_state = response_line_HTTP_slash_1; |
| 588 | else |
| 589 | _client_handshake_state = client_handshake_error; |
| 590 | break; |
| 591 | case response_line_HTTP_slash_1: |
| 592 | if (c == '.') |
| 593 | _client_handshake_state = response_line_HTTP_slash_1_dot; |
| 594 | else |
| 595 | _client_handshake_state = client_handshake_error; |
| 596 | break; |
| 597 | case response_line_HTTP_slash_1_dot: |
| 598 | if (c == '1') |
| 599 | _client_handshake_state = response_line_HTTP_slash_1_dot_1; |
| 600 | else |
| 601 | _client_handshake_state = client_handshake_error; |
| 602 | break; |
| 603 | case response_line_HTTP_slash_1_dot_1: |
| 604 | if (c == ' ') |
| 605 | _client_handshake_state = |
| 606 | response_line_HTTP_slash_1_dot_1_space; |
| 607 | else |
| 608 | _client_handshake_state = client_handshake_error; |
| 609 | break; |
| 610 | case response_line_HTTP_slash_1_dot_1_space: |
| 611 | if (c == ' ') |
| 612 | _client_handshake_state = |
| 613 | response_line_HTTP_slash_1_dot_1_space; |
| 614 | else if (c == '1') |
| 615 | _client_handshake_state = response_line_status_1; |
| 616 | else |
| 617 | _client_handshake_state = client_handshake_error; |
| 618 | break; |
| 619 | case response_line_status_1: |
| 620 | if (c == '0') |
| 621 | _client_handshake_state = response_line_status_10; |
| 622 | else |
| 623 | _client_handshake_state = client_handshake_error; |
| 624 | break; |
| 625 | case response_line_status_10: |
| 626 | if (c == '1') |
| 627 | _client_handshake_state = response_line_status_101; |
| 628 | else |
| 629 | _client_handshake_state = client_handshake_error; |
| 630 | break; |
| 631 | case response_line_status_101: |
| 632 | if (c == ' ') |
| 633 | _client_handshake_state = response_line_status_101_space; |
| 634 | else |
| 635 | _client_handshake_state = client_handshake_error; |
| 636 | break; |
| 637 | case response_line_status_101_space: |
| 638 | if (c == ' ') |
| 639 | _client_handshake_state = response_line_status_101_space; |
| 640 | else if (c == 'S') |
| 641 | _client_handshake_state = response_line_s; |
| 642 | else |
| 643 | _client_handshake_state = client_handshake_error; |
| 644 | break; |
| 645 | case response_line_s: |
| 646 | if (c == 'w') |
| 647 | _client_handshake_state = response_line_sw; |
| 648 | else |
| 649 | _client_handshake_state = client_handshake_error; |
| 650 | break; |
| 651 | case response_line_sw: |
| 652 | if (c == 'i') |
| 653 | _client_handshake_state = response_line_swi; |
| 654 | else |
| 655 | _client_handshake_state = client_handshake_error; |
| 656 | break; |
| 657 | case response_line_swi: |
| 658 | if (c == 't') |
| 659 | _client_handshake_state = response_line_swit; |
| 660 | else |
| 661 | _client_handshake_state = client_handshake_error; |
| 662 | break; |
| 663 | case response_line_swit: |
| 664 | if (c == 'c') |
| 665 | _client_handshake_state = response_line_switc; |
| 666 | else |
| 667 | _client_handshake_state = client_handshake_error; |
| 668 | break; |
| 669 | case response_line_switc: |
| 670 | if (c == 'h') |
| 671 | _client_handshake_state = response_line_switch; |
| 672 | else |
| 673 | _client_handshake_state = client_handshake_error; |
| 674 | break; |
| 675 | case response_line_switch: |
| 676 | if (c == 'i') |
| 677 | _client_handshake_state = response_line_switchi; |
| 678 | else |
| 679 | _client_handshake_state = client_handshake_error; |
| 680 | break; |
| 681 | case response_line_switchi: |
| 682 | if (c == 'n') |
| 683 | _client_handshake_state = response_line_switchin; |
| 684 | else |
| 685 | _client_handshake_state = client_handshake_error; |
| 686 | break; |
| 687 | case response_line_switchin: |
| 688 | if (c == 'g') |
| 689 | _client_handshake_state = response_line_switching; |
| 690 | else |
| 691 | _client_handshake_state = client_handshake_error; |
| 692 | break; |
| 693 | case response_line_switching: |
| 694 | if (c == ' ') |
| 695 | _client_handshake_state = response_line_switching_space; |
| 696 | else |
| 697 | _client_handshake_state = client_handshake_error; |
| 698 | break; |
| 699 | case response_line_switching_space: |
| 700 | if (c == 'P') |
| 701 | _client_handshake_state = response_line_p; |
| 702 | else |
| 703 | _client_handshake_state = client_handshake_error; |
| 704 | break; |
| 705 | case response_line_p: |
| 706 | if (c == 'r') |
| 707 | _client_handshake_state = response_line_pr; |
| 708 | else |
| 709 | _client_handshake_state = client_handshake_error; |
| 710 | break; |
| 711 | case response_line_pr: |
| 712 | if (c == 'o') |
| 713 | _client_handshake_state = response_line_pro; |
| 714 | else |
| 715 | _client_handshake_state = client_handshake_error; |
| 716 | break; |
| 717 | case response_line_pro: |
| 718 | if (c == 't') |
| 719 | _client_handshake_state = response_line_prot; |
| 720 | else |
| 721 | _client_handshake_state = client_handshake_error; |
| 722 | break; |
| 723 | case response_line_prot: |
| 724 | if (c == 'o') |
| 725 | _client_handshake_state = response_line_proto; |
| 726 | else |
| 727 | _client_handshake_state = client_handshake_error; |
| 728 | break; |
| 729 | case response_line_proto: |
| 730 | if (c == 'c') |
| 731 | _client_handshake_state = response_line_protoc; |
| 732 | else |
| 733 | _client_handshake_state = client_handshake_error; |
| 734 | break; |
| 735 | case response_line_protoc: |
| 736 | if (c == 'o') |
| 737 | _client_handshake_state = response_line_protoco; |
| 738 | else |
| 739 | _client_handshake_state = client_handshake_error; |
| 740 | break; |
| 741 | case response_line_protoco: |
| 742 | if (c == 'l') |
| 743 | _client_handshake_state = response_line_protocol; |
| 744 | else |
| 745 | _client_handshake_state = client_handshake_error; |
| 746 | break; |
| 747 | case response_line_protocol: |
| 748 | if (c == 's') |
| 749 | _client_handshake_state = response_line_protocols; |
| 750 | else |
| 751 | _client_handshake_state = client_handshake_error; |
| 752 | break; |
| 753 | case response_line_protocols: |
| 754 | if (c == '\r') |
| 755 | _client_handshake_state = response_line_cr; |
| 756 | else |
| 757 | _client_handshake_state = client_handshake_error; |
| 758 | break; |
| 759 | case response_line_cr: |
| 760 | if (c == '\n') |
| 761 | _client_handshake_state = client_header_field_begin_name; |
| 762 | else |
| 763 | _client_handshake_state = client_handshake_error; |
| 764 | break; |
| 765 | case client_header_field_begin_name: |
| 766 | switch (c) { |
| 767 | case '\r': |
| 768 | _client_handshake_state = client_handshake_end_line_cr; |
| 769 | break; |
| 770 | case '\n': |
| 771 | _client_handshake_state = client_handshake_error; |
| 772 | break; |
| 773 | default: |
| 774 | _header_name[0] = c; |
| 775 | _header_name_position = 1; |
| 776 | _client_handshake_state = client_header_field_name; |
| 777 | break; |
| 778 | } |
| 779 | break; |
| 780 | case client_header_field_name: |
| 781 | if (c == '\r' || c == '\n') |
| 782 | _client_handshake_state = client_handshake_error; |
| 783 | else if (c == ':') { |
| 784 | _header_name[_header_name_position] = '\0'; |
| 785 | _client_handshake_state = client_header_field_colon; |
| 786 | } else if (_header_name_position + 1 > MAX_HEADER_NAME_LENGTH) |
| 787 | _client_handshake_state = client_handshake_error; |
| 788 | else { |
| 789 | _header_name[_header_name_position] = c; |
| 790 | _header_name_position++; |
| 791 | _client_handshake_state = client_header_field_name; |
| 792 | } |
| 793 | break; |
| 794 | case client_header_field_colon: |
| 795 | case client_header_field_value_trailing_space: |
| 796 | if (c == '\n') |
| 797 | _client_handshake_state = client_handshake_error; |
| 798 | else if (c == '\r') |
| 799 | _client_handshake_state = client_header_field_cr; |
| 800 | else if (c == ' ') |
| 801 | _client_handshake_state = |
| 802 | client_header_field_value_trailing_space; |
| 803 | else { |
| 804 | _header_value[0] = c; |
| 805 | _header_value_position = 1; |
| 806 | _client_handshake_state = client_header_field_value; |
| 807 | } |
| 808 | break; |
| 809 | case client_header_field_value: |
| 810 | if (c == '\n') |
| 811 | _client_handshake_state = client_handshake_error; |
| 812 | else if (c == '\r') { |
| 813 | _header_value[_header_value_position] = '\0'; |
| 814 | |
| 815 | if (strcasecmp ("upgrade" , _header_name) == 0) |
| 816 | _header_upgrade_websocket = |
| 817 | strcasecmp ("websocket" , _header_value) == 0; |
| 818 | else if (strcasecmp ("connection" , _header_name) == 0) |
| 819 | _header_connection_upgrade = |
| 820 | strcasecmp ("upgrade" , _header_value) == 0; |
| 821 | else if (strcasecmp ("Sec-WebSocket-Accept" , _header_name) |
| 822 | == 0) |
| 823 | strcpy (_websocket_accept, _header_value); |
| 824 | else if (strcasecmp ("Sec-WebSocket-Protocol" , _header_name) |
| 825 | == 0) { |
| 826 | if (select_protocol (_header_value)) |
| 827 | strcpy (_websocket_protocol, _header_value); |
| 828 | } |
| 829 | _client_handshake_state = client_header_field_cr; |
| 830 | } else if (_header_value_position + 1 > MAX_HEADER_VALUE_LENGTH) |
| 831 | _client_handshake_state = client_handshake_error; |
| 832 | else { |
| 833 | _header_value[_header_value_position] = c; |
| 834 | _header_value_position++; |
| 835 | _client_handshake_state = client_header_field_value; |
| 836 | } |
| 837 | break; |
| 838 | case client_header_field_cr: |
| 839 | if (c == '\n') |
| 840 | _client_handshake_state = client_header_field_begin_name; |
| 841 | else |
| 842 | _client_handshake_state = client_handshake_error; |
| 843 | break; |
| 844 | case client_handshake_end_line_cr: |
| 845 | if (c == '\n') { |
| 846 | if (_header_connection_upgrade && _header_upgrade_websocket |
| 847 | && _websocket_protocol[0] != '\0' |
| 848 | && _websocket_accept[0] != '\0') { |
| 849 | _client_handshake_state = client_handshake_complete; |
| 850 | |
| 851 | // TODO: validate accept key |
| 852 | |
| 853 | _inpos++; |
| 854 | _insize--; |
| 855 | |
| 856 | return true; |
| 857 | } |
| 858 | _client_handshake_state = client_handshake_error; |
| 859 | } else |
| 860 | _client_handshake_state = client_handshake_error; |
| 861 | break; |
| 862 | default: |
| 863 | assert (false); |
| 864 | } |
| 865 | |
| 866 | _inpos++; |
| 867 | _insize--; |
| 868 | |
| 869 | if (_client_handshake_state == client_handshake_error) { |
| 870 | socket ()->event_handshake_failed_protocol ( |
| 871 | _endpoint_uri_pair, ZMQ_PROTOCOL_ERROR_WS_UNSPECIFIED); |
| 872 | |
| 873 | error (zmq::i_engine::protocol_error); |
| 874 | return false; |
| 875 | } |
| 876 | } |
| 877 | |
| 878 | return false; |
| 879 | } |
| 880 | |
| 881 | static int |
| 882 | encode_base64 (const unsigned char *in_, int in_len_, char *out_, int out_len_) |
| 883 | { |
| 884 | static const unsigned char base64enc_tab[65] = |
| 885 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ; |
| 886 | |
| 887 | int ii, io; |
| 888 | uint32_t v; |
| 889 | int rem; |
| 890 | |
| 891 | for (io = 0, ii = 0, v = 0, rem = 0; ii < in_len_; ii++) { |
| 892 | unsigned char ch; |
| 893 | ch = in_[ii]; |
| 894 | v = (v << 8) | ch; |
| 895 | rem += 8; |
| 896 | while (rem >= 6) { |
| 897 | rem -= 6; |
| 898 | if (io >= out_len_) |
| 899 | return -1; /* truncation is failure */ |
| 900 | out_[io++] = base64enc_tab[(v >> rem) & 63]; |
| 901 | } |
| 902 | } |
| 903 | if (rem) { |
| 904 | v <<= (6 - rem); |
| 905 | if (io >= out_len_) |
| 906 | return -1; /* truncation is failure */ |
| 907 | out_[io++] = base64enc_tab[v & 63]; |
| 908 | } |
| 909 | while (io & 3) { |
| 910 | if (io >= out_len_) |
| 911 | return -1; /* truncation is failure */ |
| 912 | out_[io++] = '='; |
| 913 | } |
| 914 | if (io >= out_len_) |
| 915 | return -1; /* no room for null terminator */ |
| 916 | out_[io] = 0; |
| 917 | return io; |
| 918 | } |
| 919 | |
| 920 | static void compute_accept_key (char *key_, unsigned char *hash_) |
| 921 | { |
| 922 | const char *magic_string = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ; |
| 923 | #ifdef ZMQ_USE_NSS |
| 924 | unsigned int len; |
| 925 | HASH_HashType type = HASH_GetHashTypeByOidTag (SEC_OID_SHA1); |
| 926 | HASHContext *ctx = HASH_Create (type); |
| 927 | assert (ctx); |
| 928 | |
| 929 | HASH_Begin (ctx); |
| 930 | HASH_Update (ctx, (unsigned char *) key_, (unsigned int) strlen (key_)); |
| 931 | HASH_Update (ctx, (unsigned char *) magic_string, |
| 932 | (unsigned int) strlen (magic_string)); |
| 933 | HASH_End (ctx, hash_, &len, SHA_DIGEST_LENGTH); |
| 934 | HASH_Destroy (ctx); |
| 935 | #elif defined ZMQ_USE_BUILTIN_SHA1 |
| 936 | sha1_ctxt ctx; |
| 937 | SHA1_Init (&ctx); |
| 938 | SHA1_Update (&ctx, (unsigned char *) key_, strlen (key_)); |
| 939 | SHA1_Update (&ctx, (unsigned char *) magic_string, strlen (magic_string)); |
| 940 | |
| 941 | SHA1_Final (hash_, &ctx); |
| 942 | #elif defined ZMQ_USE_GNUTLS |
| 943 | gnutls_hash_hd_t hd; |
| 944 | gnutls_hash_init (&hd, GNUTLS_DIG_SHA1); |
| 945 | gnutls_hash (hd, key_, strlen (key_)); |
| 946 | gnutls_hash (hd, magic_string, strlen (magic_string)); |
| 947 | gnutls_hash_deinit (hd, hash_); |
| 948 | #else |
| 949 | #error "No sha1 implementation set" |
| 950 | #endif |
| 951 | } |
| 952 | |