1 /*
2 * Copyright (C) 2012-2013 Crocodile RCS Ltd
3 *
4 * This file is part of Kamailio, a free SIP server.
5 *
6 * Kamailio is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version
10 *
11 * Kamailio is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 *
20 * Exception: permission to copy, modify, propagate, and distribute a work
21 * formed by combining OpenSSL toolkit software and the code in this file,
22 * such as linking with software components and libraries released under
23 * OpenSSL project license.
24 *
25 */
26
27 #include <openssl/sha.h>
28
29 #include "../../core/basex.h"
30 #include "../../core/data_lump_rpl.h"
31 #include "../../core/dprint.h"
32 #include "../../core/locking.h"
33 #include "../../core/str.h"
34 #include "../../core/tcp_conn.h"
35 #include "../../core/counters.h"
36 #include "../../core/strutils.h"
37 #include "../../core/mem/mem.h"
38 #include "../../core/parser/msg_parser.h"
39 #include "../sl/sl.h"
40 #include "../tls/tls_cfg.h"
41 #include "ws_conn.h"
42 #include "ws_handshake.h"
43 #include "websocket.h"
44 #include "config.h"
45
46 #define WS_VERSION (13)
47
48 int ws_sub_protocols = DEFAULT_SUB_PROTOCOLS;
49 int ws_cors_mode = CORS_MODE_NONE;
50
51 stat_var *ws_failed_handshakes;
52 stat_var *ws_successful_handshakes;
53 stat_var *ws_sip_successful_handshakes;
54 stat_var *ws_msrp_successful_handshakes;
55
56 static str str_sip = str_init("sip");
57 static str str_msrp = str_init("msrp");
58 static str str_upgrade = str_init("upgrade");
59 static str str_websocket = str_init("websocket");
60 static str str_ws_guid = str_init("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
61
62 /* HTTP headers */
63 static str str_hdr_connection = str_init("Connection");
64 static str str_hdr_upgrade = str_init("Upgrade");
65 static str str_hdr_sec_websocket_accept = str_init("Sec-WebSocket-Accept");
66 static str str_hdr_sec_websocket_key = str_init("Sec-WebSocket-Key");
67 static str str_hdr_sec_websocket_protocol = str_init("Sec-WebSocket-Protocol");
68 static str str_hdr_sec_websocket_version = str_init("Sec-WebSocket-Version");
69 static str str_hdr_origin = str_init("Origin");
70 static str str_hdr_access_control_allow_origin =
71 str_init("Access-Control-Allow-Origin");
72 #define CONNECTION (1 << 0)
73 #define UPGRADE (1 << 1)
74 #define SEC_WEBSOCKET_ACCEPT (1 << 2)
75 #define SEC_WEBSOCKET_KEY (1 << 3)
76 #define SEC_WEBSOCKET_PROTOCOL (1 << 4)
77 #define SEC_WEBSOCKET_VERSION (1 << 5)
78 #define ORIGIN (1 << 6)
79
80 #define REQUIRED_HEADERS \
81 (CONNECTION | UPGRADE | SEC_WEBSOCKET_KEY | SEC_WEBSOCKET_PROTOCOL \
82 | SEC_WEBSOCKET_VERSION)
83
84 /* HTTP status text */
85 static str str_status_switching_protocols = str_init("Switching Protocols");
86 static str str_status_bad_request = str_init("Bad Request");
87 static str str_status_upgrade_required = str_init("Upgrade Required");
88 static str str_status_internal_server_error = str_init("Internal Server Error");
89 static str str_status_service_unavailable = str_init("Service Unavailable");
90
91 #define HDR_BUF_LEN (512)
92 static char headers_buf[HDR_BUF_LEN];
93
94 static char key_buf[base64_enc_len(SHA_DIGEST_LENGTH)];
95
ws_send_reply(sip_msg_t * msg,int code,str * reason,str * hdrs)96 static int ws_send_reply(sip_msg_t *msg, int code, str *reason, str *hdrs)
97 {
98 if(hdrs && hdrs->len > 0) {
99 if(add_lump_rpl(msg, hdrs->s, hdrs->len, LUMP_RPL_HDR) == 0) {
100 LM_ERR("inserting extra-headers lump\n");
101 update_stat(ws_failed_handshakes, 1);
102 return -1;
103 }
104 }
105
106 if(ws_slb.freply(msg, code, reason) < 0) {
107 LM_ERR("sending reply\n");
108 update_stat(ws_failed_handshakes, 1);
109 return -1;
110 }
111
112 update_stat(
113 code == 101 ? ws_successful_handshakes : ws_failed_handshakes, 1);
114
115 return 0;
116 }
117
ws_handle_handshake(struct sip_msg * msg)118 int ws_handle_handshake(struct sip_msg *msg)
119 {
120 str key = {0, 0}, headers = {0, 0}, reply_key = {0, 0}, origin = {0, 0};
121 unsigned char sha1[SHA_DIGEST_LENGTH];
122 unsigned int hdr_flags = 0, sub_protocol = 0;
123 int version = 0;
124 struct hdr_field *hdr = msg->headers;
125 struct tcp_connection *con;
126 ws_connection_t *wsc;
127
128 /* Make sure that the connection is closed after the response _and_
129 the existing connection (from the request) is reused for the
130 response. The close flag will be unset later if the handshake is
131 successful. */
132 msg->rpl_send_flags.f |= SND_F_CON_CLOSE;
133 msg->rpl_send_flags.f |= SND_F_FORCE_CON_REUSE;
134
135 if(cfg_get(websocket, ws_cfg, enabled) == 0) {
136 LM_INFO("disabled: bouncing handshake\n");
137 ws_send_reply(msg, 503, &str_status_service_unavailable, NULL);
138 return 0;
139 }
140
141 /* Retrieve TCP/TLS connection */
142 if((con = tcpconn_get(msg->rcv.proto_reserved1, 0, 0, 0, 0)) == NULL) {
143 LM_ERR("retrieving connection\n");
144 ws_send_reply(msg, 500, &str_status_internal_server_error, NULL);
145 return 0;
146 }
147
148 if(con->type != PROTO_TCP && con->type != PROTO_TLS) {
149 LM_ERR("unsupported transport: %d", con->type);
150 goto end;
151 }
152
153 if(parse_headers(msg, HDR_EOH_F, 0) < 0) {
154 LM_ERR("error parsing headers\n");
155 ws_send_reply(msg, 500, &str_status_internal_server_error, NULL);
156 goto end;
157 }
158
159 /* Process HTTP headers */
160 while(hdr != NULL) {
161 /* Decode and validate Connection */
162 if(cmp_hdrname_strzn(
163 &hdr->name, str_hdr_connection.s, str_hdr_connection.len)
164 == 0) {
165 strlower(&hdr->body);
166 if(str_search(&hdr->body, &str_upgrade) != NULL) {
167 LM_DBG("found %.*s: %.*s\n",
168
169 hdr->name.len, hdr->name.s, hdr->body.len, hdr->body.s);
170 hdr_flags |= CONNECTION;
171 }
172 }
173 /* Decode and validate Upgrade */
174 else if(cmp_hdrname_strzn(
175 &hdr->name, str_hdr_upgrade.s, str_hdr_upgrade.len)
176 == 0) {
177 strlower(&hdr->body);
178 if(str_search(&hdr->body, &str_websocket) != NULL) {
179 LM_DBG("found %.*s: %.*s\n", hdr->name.len, hdr->name.s,
180 hdr->body.len, hdr->body.s);
181 hdr_flags |= UPGRADE;
182 }
183 }
184 /* Decode and validate Sec-WebSocket-Key */
185 else if(cmp_hdrname_strzn(&hdr->name, str_hdr_sec_websocket_key.s,
186 str_hdr_sec_websocket_key.len)
187 == 0) {
188 if(hdr_flags & SEC_WEBSOCKET_KEY) {
189 LM_WARN("%.*s found multiple times\n", hdr->name.len,
190 hdr->name.s);
191 ws_send_reply(msg, 400, &str_status_bad_request, NULL);
192 goto end;
193 }
194
195 LM_DBG("found %.*s: %.*s\n", hdr->name.len, hdr->name.s,
196 hdr->body.len, hdr->body.s);
197 key = hdr->body;
198 hdr_flags |= SEC_WEBSOCKET_KEY;
199 }
200 /* Decode and validate Sec-WebSocket-Protocol */
201 else if(cmp_hdrname_strzn(&hdr->name, str_hdr_sec_websocket_protocol.s,
202 str_hdr_sec_websocket_protocol.len)
203 == 0) {
204 strlower(&hdr->body);
205 if(str_search(&hdr->body, &str_sip) != NULL) {
206 LM_DBG("found %.*s: %.*s\n", hdr->name.len, hdr->name.s,
207 hdr->body.len, hdr->body.s);
208 hdr_flags |= SEC_WEBSOCKET_PROTOCOL;
209 sub_protocol |= SUB_PROTOCOL_SIP;
210 }
211 if(str_search(&hdr->body, &str_msrp) != NULL) {
212 LM_DBG("found %.*s: %.*s\n", hdr->name.len, hdr->name.s,
213 hdr->body.len, hdr->body.s);
214 hdr_flags |= SEC_WEBSOCKET_PROTOCOL;
215 sub_protocol |= SUB_PROTOCOL_MSRP;
216 }
217 }
218 /* Decode and validate Sec-WebSocket-Version */
219 else if(cmp_hdrname_strzn(&hdr->name, str_hdr_sec_websocket_version.s,
220 str_hdr_sec_websocket_version.len)
221 == 0) {
222 if(hdr_flags & SEC_WEBSOCKET_VERSION) {
223 LM_WARN("%.*s found multiple times\n", hdr->name.len,
224 hdr->name.s);
225 ws_send_reply(msg, 400, &str_status_bad_request, NULL);
226 goto end;
227 }
228
229 str2sint(&hdr->body, &version);
230
231 if(version != WS_VERSION) {
232 LM_WARN("Unsupported protocol version %.*s\n", hdr->body.len,
233 hdr->body.s);
234 headers.s = headers_buf;
235 headers.len = snprintf(headers.s, HDR_BUF_LEN, "%.*s: %d\r\n",
236 str_hdr_sec_websocket_version.len,
237 str_hdr_sec_websocket_version.s, WS_VERSION);
238 ws_send_reply(msg, 426, &str_status_upgrade_required, &headers);
239 goto end;
240 }
241
242 LM_DBG("found %.*s: %.*s\n", hdr->name.len, hdr->name.s,
243 hdr->body.len, hdr->body.s);
244 hdr_flags |= SEC_WEBSOCKET_VERSION;
245 }
246 /* Decode Origin */
247 else if(cmp_hdrname_strzn(
248 &hdr->name, str_hdr_origin.s, str_hdr_origin.len)
249 == 0) {
250 if(hdr_flags & ORIGIN) {
251 LM_WARN("%.*s found multiple times\n", hdr->name.len,
252 hdr->name.s);
253 ws_send_reply(msg, 400, &str_status_bad_request, NULL);
254 goto end;
255 }
256
257 LM_DBG("found %.*s: %.*s\n", hdr->name.len, hdr->name.s,
258 hdr->body.len, hdr->body.s);
259 origin = hdr->body;
260 hdr_flags |= ORIGIN;
261 }
262
263 hdr = hdr->next;
264 }
265
266 /* Final check that all required headers/values were found */
267 sub_protocol &= ws_sub_protocols;
268 if((hdr_flags & REQUIRED_HEADERS) != REQUIRED_HEADERS
269 || sub_protocol == 0) {
270
271 LM_WARN("required headers not present\n");
272 headers.s = headers_buf;
273 headers.len = 0;
274
275 if(ws_sub_protocols & SUB_PROTOCOL_SIP)
276 headers.len += snprintf(headers.s + headers.len,
277 HDR_BUF_LEN - headers.len, "%.*s: %.*s\r\n",
278 str_hdr_sec_websocket_protocol.len,
279 str_hdr_sec_websocket_protocol.s, str_sip.len, str_sip.s);
280
281 if(ws_sub_protocols & SUB_PROTOCOL_MSRP)
282 headers.len += snprintf(headers.s + headers.len,
283 HDR_BUF_LEN - headers.len, "%.*s: %.*s\r\n",
284 str_hdr_sec_websocket_protocol.len,
285 str_hdr_sec_websocket_protocol.s, str_msrp.len, str_msrp.s);
286
287 headers.len +=
288 snprintf(headers.s + headers.len, HDR_BUF_LEN - headers.len,
289 "%.*s: %d\r\n", str_hdr_sec_websocket_version.len,
290 str_hdr_sec_websocket_version.s, WS_VERSION);
291 ws_send_reply(msg, 400, &str_status_bad_request, &headers);
292 goto end;
293 }
294
295 /* Construct reply_key */
296 reply_key.s =
297 (char *)pkg_malloc((key.len + str_ws_guid.len) * sizeof(char));
298 if(reply_key.s == NULL) {
299 LM_ERR("allocating pkg memory\n");
300 ws_send_reply(msg, 500, &str_status_internal_server_error, NULL);
301 goto end;
302 }
303 memcpy(reply_key.s, key.s, key.len);
304 memcpy(reply_key.s + key.len, str_ws_guid.s, str_ws_guid.len);
305 reply_key.len = key.len + str_ws_guid.len;
306 SHA1((const unsigned char *)reply_key.s, reply_key.len, sha1);
307 pkg_free(reply_key.s);
308 reply_key.s = key_buf;
309 reply_key.len = base64_enc(sha1, SHA_DIGEST_LENGTH,
310 (unsigned char *)reply_key.s, base64_enc_len(SHA_DIGEST_LENGTH));
311
312 /* Add the connection to the WebSocket connection table */
313 wsconn_add(&msg->rcv, sub_protocol);
314
315 /* Make sure Kamailio core sends future messages on this connection
316 directly to this module */
317 if(con->type == PROTO_TLS)
318 con->type = con->rcv.proto = PROTO_WSS;
319 else
320 con->type = con->rcv.proto = PROTO_WS;
321
322 /* Now Kamailio is ready to receive WebSocket frames build and send a
323 101 reply */
324 headers.s = headers_buf;
325 headers.len = 0;
326
327 if(ws_cors_mode == CORS_MODE_ANY)
328 headers.len +=
329 snprintf(headers.s + headers.len, HDR_BUF_LEN - headers.len,
330 "%.*s: *\r\n", str_hdr_access_control_allow_origin.len,
331 str_hdr_access_control_allow_origin.s);
332 else if(ws_cors_mode == CORS_MODE_ORIGIN && origin.len > 0)
333 headers.len += snprintf(headers.s + headers.len,
334 HDR_BUF_LEN - headers.len, "%.*s: %.*s\r\n",
335 str_hdr_access_control_allow_origin.len,
336 str_hdr_access_control_allow_origin.s, origin.len, origin.s);
337
338 if(sub_protocol & SUB_PROTOCOL_SIP)
339 headers.len += snprintf(headers.s + headers.len,
340 HDR_BUF_LEN - headers.len, "%.*s: %.*s\r\n",
341 str_hdr_sec_websocket_protocol.len,
342 str_hdr_sec_websocket_protocol.s, str_sip.len, str_sip.s);
343 else if(sub_protocol & SUB_PROTOCOL_MSRP)
344 headers.len += snprintf(headers.s + headers.len,
345 HDR_BUF_LEN - headers.len, "%.*s: %.*s\r\n",
346 str_hdr_sec_websocket_protocol.len,
347 str_hdr_sec_websocket_protocol.s, str_msrp.len, str_msrp.s);
348
349 headers.len += snprintf(headers.s + headers.len, HDR_BUF_LEN - headers.len,
350 "%.*s: %.*s\r\n"
351 "%.*s: %.*s\r\n"
352 "%.*s: %.*s\r\n",
353 str_hdr_upgrade.len, str_hdr_upgrade.s, str_websocket.len,
354 str_websocket.s, str_hdr_connection.len, str_hdr_connection.s,
355 str_upgrade.len, str_upgrade.s, str_hdr_sec_websocket_accept.len,
356 str_hdr_sec_websocket_accept.s, reply_key.len, reply_key.s);
357 msg->rpl_send_flags.f &= ~SND_F_CON_CLOSE;
358 if(ws_send_reply(msg, 101, &str_status_switching_protocols, &headers) < 0) {
359 if((wsc = wsconn_get(msg->rcv.proto_reserved1)) != NULL) {
360 wsconn_rm(wsc, WSCONN_EVENTROUTE_NO);
361 wsconn_put(wsc);
362 }
363 goto end;
364 } else {
365 if(sub_protocol & SUB_PROTOCOL_SIP)
366 update_stat(ws_sip_successful_handshakes, 1);
367 else if(sub_protocol & SUB_PROTOCOL_MSRP)
368 update_stat(ws_msrp_successful_handshakes, 1);
369 }
370
371 tcpconn_put(con);
372 return 1;
373 end:
374 if(con)
375 tcpconn_put(con);
376 return 0;
377 }
378
w_ws_handle_handshake(sip_msg_t * msg,char * p1,char * p2)379 int w_ws_handle_handshake(sip_msg_t *msg, char *p1, char *p2)
380 {
381 return ws_handle_handshake(msg);
382 }
383
ws_rpc_disable(rpc_t * rpc,void * ctx)384 void ws_rpc_disable(rpc_t *rpc, void *ctx)
385 {
386 cfg_get(websocket, ws_cfg, enabled) = 0;
387 LM_WARN("disabling websockets - new connections will be dropped\n");
388 return;
389 }
390
ws_rpc_enable(rpc_t * rpc,void * ctx)391 void ws_rpc_enable(rpc_t *rpc, void *ctx)
392 {
393 cfg_get(websocket, ws_cfg, enabled) = 1;
394 LM_WARN("enabling websockets\n");
395 return;
396 }
397