Review Board 1.7.16


WebSocket SIP Support

Review Request #2008 - Created July 2, 2012 and submitted

Joshua Colp
websocket-sip
Reviewers
asterisk-dev
Asterisk
These changes add WebSocket transport support to chan_sip and fix some minor issues uncovered in the stack when used with WebSocket as a transport.
Tested using sipml5 javascript SIP stack. Confirmed protocol traffic is correct, that connections are shutdown properly when they should be, that registration works, and that calling works.
/trunk/channels/chan_sip.c
Revision 369768 New Change
[20] 1163 lines
[+20] [+] static struct ast_subscription_mwi_list {
1164
static void temp_pvt_cleanup(void *);
1164
static void temp_pvt_cleanup(void *);
1165

    
   
1165

   
1166
/*! \brief A per-thread temporary pvt structure */
1166
/*! \brief A per-thread temporary pvt structure */
1167
AST_THREADSTORAGE_CUSTOM(ts_temp_pvt, temp_pvt_init, temp_pvt_cleanup);
1167
AST_THREADSTORAGE_CUSTOM(ts_temp_pvt, temp_pvt_init, temp_pvt_cleanup);
1168

    
   
1168

   

    
   
1169
/*! \brief A per-thread buffer for transport to string conversion */

    
   
1170
AST_THREADSTORAGE(sip_transport_str_buf);

    
   
1171

   

    
   
1172
/*! \brief Size of the SIP transport buffer */

    
   
1173
#define SIP_TRANSPORT_STR_BUFSIZE 128

    
   
1174

   
1169
/*! \brief Authentication container for realm authentication */
1175
/*! \brief Authentication container for realm authentication */
1170
static struct sip_auth_container *authl = NULL;
1176
static struct sip_auth_container *authl = NULL;
1171
/*! \brief Global authentication container protection while adjusting the references. */
1177
/*! \brief Global authentication container protection while adjusting the references. */
1172
AST_MUTEX_DEFINE_STATIC(authl_lock);
1178
AST_MUTEX_DEFINE_STATIC(authl_lock);
1173

    
   
1179

   
[+20] [20] 1347 lines
[+20] [+] static void *sip_tcp_worker_fn(void *data)
2521
	struct ast_tcptls_session_instance *tcptls_session = data;
2527
	struct ast_tcptls_session_instance *tcptls_session = data;
2522

    
   
2528

   
2523
	return _sip_tcp_helper_thread(tcptls_session);
2529
	return _sip_tcp_helper_thread(tcptls_session);
2524
}
2530
}
2525

    
   
2531

   

    
   
2532
/*! \brief SIP WebSocket connection handler */

    
   
2533
static void sip_websocket_callback(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers)

    
   
2534
{

    
   
2535
	int res;

    
   
2536

   

    
   
2537
	if (ast_websocket_set_nonblock(session)) {

    
   
2538
		goto end;

    
   
2539
	}

    
   
2540

   

    
   
2541
	while ((res = ast_wait_for_input(ast_websocket_fd(session), -1)) > 0) {

    
   
2542
		char *payload;

    
   
2543
		uint64_t payload_len;

    
   
2544
		enum ast_websocket_opcode opcode;

    
   
2545
		int fragmented;

    
   
2546

   

    
   
2547
		if (ast_websocket_read(session, &payload, &payload_len, &opcode, &fragmented)) {

    
   
2548
			/* We err on the side of caution and terminate the session if any error occurs */

    
   
2549
			break;

    
   
2550
		}

    
   
2551

   

    
   
2552
		if (opcode == AST_WEBSOCKET_OPCODE_TEXT || opcode == AST_WEBSOCKET_OPCODE_BINARY) {

    
   
2553
			struct sip_request req = { 0, };

    
   
2554

   

    
   
2555
			if (!(req.data = ast_str_create(payload_len))) {

    
   
2556
				goto end;

    
   
2557
			}

    
   
2558

   

    
   
2559
			if (ast_str_set(&req.data, -1, "%s", payload) == AST_DYNSTR_BUILD_FAILED) {

    
   
2560
				deinit_req(&req);

    
   
2561
				goto end;

    
   
2562
			}

    
   
2563

   

    
   
2564
			req.socket.fd = ast_websocket_fd(session);

    
   
2565
			set_socket_transport(&req.socket, ast_websocket_is_secure(session) ? SIP_TRANSPORT_WSS : SIP_TRANSPORT_WS);

    
   
2566
			req.socket.ws_session = session;

    
   
2567

   

    
   
2568
			handle_request_do(&req, ast_websocket_remote_address(session));

    
   
2569
			deinit_req(&req);

    
   
2570

   

    
   
2571
		} else if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {

    
   
2572
			break;

    
   
2573
		}

    
   
2574
	}

    
   
2575

   

    
   
2576
end:

    
   
2577
	ast_websocket_unref(session);

    
   
2578
}

    
   
2579

   
2526
/*! \brief Check if the authtimeout has expired.
2580
/*! \brief Check if the authtimeout has expired.
2527
 * \param start the time when the session started
2581
 * \param start the time when the session started
2528
 *
2582
 *
2529
 * \retval 0 the timeout has expired
2583
 * \retval 0 the timeout has expired
2530
 * \retval -1 error
2584
 * \retval -1 error
[+20] [20] 265 lines
[+20] [+] static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_session)
2796
			}
2850
			}
2797
			/*! \todo XXX If there's no Content-Length or if the content-length and what
2851
			/*! \todo XXX If there's no Content-Length or if the content-length and what
2798
					we receive is not the same - we should generate an error */
2852
					we receive is not the same - we should generate an error */
2799

    
   
2853

   
2800
			req.socket.tcptls_session = tcptls_session;
2854
			req.socket.tcptls_session = tcptls_session;

    
   
2855
			req.socket.ws_session = NULL;
2801
			handle_request_do(&req, &tcptls_session->remote_address);
2856
			handle_request_do(&req, &tcptls_session->remote_address);
2802
		}
2857
		}
2803

    
   
2858

   
2804
		if (fds[1].revents) { /* alert_pipe indicates there is data in the send queue to be sent */
2859
		if (fds[1].revents) { /* alert_pipe indicates there is data in the send queue to be sent */
2805
			enum sip_tcptls_alert alert;
2860
			enum sip_tcptls_alert alert;
[+20] [20] 496 lines
[+20] [+] static int get_transport_str2enum(const char *transport)
3302
		res |= SIP_TRANSPORT_TCP;
3357
		res |= SIP_TRANSPORT_TCP;
3303
	}
3358
	}
3304
	if (!strcasecmp(transport, "tls")) {
3359
	if (!strcasecmp(transport, "tls")) {
3305
		res |= SIP_TRANSPORT_TLS;
3360
		res |= SIP_TRANSPORT_TLS;
3306
	}
3361
	}

    
   
3362
	if (!strcasecmp(transport, "ws")) {

    
   
3363
		res |= SIP_TRANSPORT_WS;

    
   
3364
	}

    
   
3365
	if (!strcasecmp(transport, "wss")) {

    
   
3366
		res |= SIP_TRANSPORT_WSS;

    
   
3367
	}
3307

    
   
3368

   
3308
	return res;
3369
	return res;
3309
}
3370
}
3310

    
   
3371

   
3311
/*! \brief Return configuration of transports for a device */
3372
/*! \brief Return configuration of transports for a device */
3312
static inline const char *get_transport_list(unsigned int transports) {
3373
static inline const char *get_transport_list(unsigned int transports)
3313
	switch (transports) {
3374
{
3314
		case SIP_TRANSPORT_UDP:
3375
	char *buf;
3315
			return "UDP";
3376

   
3316
		case SIP_TRANSPORT_TCP:
3377
	if (!transports) {
3317
			return "TCP";
3378
		return "UNKNOWN";
3318
		case SIP_TRANSPORT_TLS:
3379
	}
3319
			return "TLS";
3380

   
3320
		case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TCP:
3381
	if (!(buf = ast_threadstorage_get(&sip_transport_str_buf, SIP_TRANSPORT_STR_BUFSIZE))) {
3321
			return "TCP,UDP";
3382
		return "";
3322
		case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TLS:
3383
	}
3323
			return "TLS,UDP";
3384

   
3324
		case SIP_TRANSPORT_TCP | SIP_TRANSPORT_TLS:
3385
	memset(buf, 0, SIP_TRANSPORT_STR_BUFSIZE);
3325
			return "TLS,TCP";
3386

   
3326
		default:
3387
	if (transports & SIP_TRANSPORT_UDP) {
3327
			return transports ?
3388
		strncat(buf, "UDP,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));
3328
				"TLS,TCP,UDP" : "UNKNOWN";	
3389
	}

    
   
3390
	if (transports & SIP_TRANSPORT_TCP) {

    
   
3391
		strncat(buf, "TCP,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));
3329
	}
3392
	}

    
   
3393
	if (transports & SIP_TRANSPORT_TLS) {

    
   
3394
		strncat(buf, "TLS,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));

    
   
3395
	}

    
   
3396
	if (transports & SIP_TRANSPORT_WS) {

    
   
3397
		strncat(buf, "WS,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));

    
   
3398
	}

    
   
3399
	if (transports & SIP_TRANSPORT_WSS) {

    
   
3400
		strncat(buf, "WSS,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf));

    
   
3401
	}

    
   
3402

   

    
   
3403
	/* Remove the trailing ',' if present */

    
   
3404
	if (strlen(buf)) {

    
   
3405
		buf[strlen(buf) - 1] = 0;

    
   
3406
	}

    
   
3407

   

    
   
3408
	return buf;
3330
}
3409
}
3331

    
   
3410

   
3332
/*! \brief Return transport as string */
3411
/*! \brief Return transport as string */
3333
const char *sip_get_transport(enum sip_transport t)
3412
const char *sip_get_transport(enum sip_transport t)
3334
{
3413
{
3335
	switch (t) {
3414
	switch (t) {
3336
	case SIP_TRANSPORT_UDP:
3415
	case SIP_TRANSPORT_UDP:
3337
		return "UDP";
3416
		return "UDP";
3338
	case SIP_TRANSPORT_TCP:
3417
	case SIP_TRANSPORT_TCP:
3339
		return "TCP";
3418
		return "TCP";
3340
	case SIP_TRANSPORT_TLS:
3419
	case SIP_TRANSPORT_TLS:
3341
		return "TLS";
3420
		return "TLS";

    
   
3421
	case SIP_TRANSPORT_WS:

    
   
3422
	case SIP_TRANSPORT_WSS:

    
   
3423
		return "WS";
3342
	}
3424
	}
3343

    
   
3425

   
3344
	return "UNKNOWN";
3426
	return "UNKNOWN";
3345
}
3427
}
3346

    
   
3428

   
3347
/*! \brief Return protocol string for srv dns query */
3429
/*! \brief Return protocol string for srv dns query */
3348
static inline const char *get_srv_protocol(enum sip_transport t)
3430
static inline const char *get_srv_protocol(enum sip_transport t)
3349
{
3431
{
3350
	switch (t) {
3432
	switch (t) {
3351
	case SIP_TRANSPORT_UDP:
3433
	case SIP_TRANSPORT_UDP:
3352
		return "udp";
3434
		return "udp";

    
   
3435
	case SIP_TRANSPORT_WS:

    
   
3436
		return "ws";
3353
	case SIP_TRANSPORT_TLS:
3437
	case SIP_TRANSPORT_TLS:
3354
	case SIP_TRANSPORT_TCP:
3438
	case SIP_TRANSPORT_TCP:
3355
		return "tcp";
3439
		return "tcp";

    
   
3440
	case SIP_TRANSPORT_WSS:

    
   
3441
		return "wss";
3356
	}
3442
	}
3357

    
   
3443

   
3358
	return "udp";
3444
	return "udp";
3359
}
3445
}
3360

    
   
3446

   
3361
/*! \brief Return service string for srv dns query */
3447
/*! \brief Return service string for srv dns query */
3362
static inline const char *get_srv_service(enum sip_transport t)
3448
static inline const char *get_srv_service(enum sip_transport t)
3363
{
3449
{
3364
	switch (t) {
3450
	switch (t) {
3365
	case SIP_TRANSPORT_TCP:
3451
	case SIP_TRANSPORT_TCP:
3366
	case SIP_TRANSPORT_UDP:
3452
	case SIP_TRANSPORT_UDP:

    
   
3453
	case SIP_TRANSPORT_WS:
3367
		return "sip";
3454
		return "sip";
3368
	case SIP_TRANSPORT_TLS:
3455
	case SIP_TRANSPORT_TLS:

    
   
3456
	case SIP_TRANSPORT_WSS:
3369
		return "sips";
3457
		return "sips";
3370
	}
3458
	}
3371
	return "sip";
3459
	return "sip";
3372
}
3460
}
3373

    
   
3461

   
[+20] [20] 36 lines
[+20] [+] static int __sip_xmit(struct sip_pvt *p, struct ast_str *data)
3410

    
   
3498

   
3411
	if (p->socket.type == SIP_TRANSPORT_UDP) {
3499
	if (p->socket.type == SIP_TRANSPORT_UDP) {
3412
		res = ast_sendto(p->socket.fd, data->str, ast_str_strlen(data), 0, dst);
3500
		res = ast_sendto(p->socket.fd, data->str, ast_str_strlen(data), 0, dst);
3413
	} else if (p->socket.tcptls_session) {
3501
	} else if (p->socket.tcptls_session) {
3414
		res = sip_tcptls_write(p->socket.tcptls_session, data->str, ast_str_strlen(data));
3502
		res = sip_tcptls_write(p->socket.tcptls_session, data->str, ast_str_strlen(data));

    
   
3503
	} else if (p->socket.ws_session) {

    
   
3504
		if (!(res = ast_websocket_write(p->socket.ws_session, AST_WEBSOCKET_OPCODE_TEXT, data->str, ast_str_strlen(data)))) {

    
   
3505
			/* The WebSocket API just returns 0 on success and -1 on failure, while this code expects the payload length to be returned */

    
   
3506
			res = ast_str_strlen(data);

    
   
3507
		}
3415
	} else {
3508
	} else {
3416
		ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\n");
3509
		ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\n");
3417
		return XMIT_ERROR;
3510
		return XMIT_ERROR;
3418
	}
3511
	}
3419

    
   
3512

   
[+20] [20] 1306 lines
[+20] [+] static void sip_destroy_peer(struct sip_peer *peer)
4726
	}
4819
	}
4727

    
   
4820

   
4728
	if (peer->socket.tcptls_session) {
4821
	if (peer->socket.tcptls_session) {
4729
		ao2_ref(peer->socket.tcptls_session, -1);
4822
		ao2_ref(peer->socket.tcptls_session, -1);
4730
		peer->socket.tcptls_session = NULL;
4823
		peer->socket.tcptls_session = NULL;

    
   
4824
	} else if (peer->socket.ws_session) {

    
   
4825
		ast_websocket_unref(peer->socket.ws_session);

    
   
4826
		peer->socket.ws_session = NULL;
4731
	}
4827
	}
4732

    
   
4828

   
4733
	ast_cc_config_params_destroy(peer->cc_params);
4829
	ast_cc_config_params_destroy(peer->cc_params);
4734

    
   
4830

   
4735
	ast_string_field_free_memory(peer);
4831
	ast_string_field_free_memory(peer);
[+20] [20] 558 lines
[+20] [+] static void set_t38_capabilities(struct sip_pvt *p)
5294
static void copy_socket_data(struct sip_socket *to_sock, const struct sip_socket *from_sock)
5390
static void copy_socket_data(struct sip_socket *to_sock, const struct sip_socket *from_sock)
5295
{
5391
{
5296
	if (to_sock->tcptls_session) {
5392
	if (to_sock->tcptls_session) {
5297
		ao2_ref(to_sock->tcptls_session, -1);
5393
		ao2_ref(to_sock->tcptls_session, -1);
5298
		to_sock->tcptls_session = NULL;
5394
		to_sock->tcptls_session = NULL;

    
   
5395
	} else if (to_sock->ws_session) {

    
   
5396
		ast_websocket_unref(to_sock->ws_session);

    
   
5397
		to_sock->ws_session = NULL;
5299
	}
5398
	}
5300

    
   
5399

   
5301
	if (from_sock->tcptls_session) {
5400
	if (from_sock->tcptls_session) {
5302
		ao2_ref(from_sock->tcptls_session, +1);
5401
		ao2_ref(from_sock->tcptls_session, +1);

    
   
5402
	} else if (from_sock->ws_session) {

    
   
5403
		ast_websocket_ref(from_sock->ws_session);
5303
	}
5404
	}
5304

    
   
5405

   
5305
	*to_sock = *from_sock;
5406
	*to_sock = *from_sock;
5306
}
5407
}
5307

    
   
5408

   
[+20] [20] 699 lines
[+20] [+] void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist)
6007
	}
6108
	}
6008

    
   
6109

   
6009
	if (p->socket.tcptls_session) {
6110
	if (p->socket.tcptls_session) {
6010
		ao2_ref(p->socket.tcptls_session, -1);
6111
		ao2_ref(p->socket.tcptls_session, -1);
6011
		p->socket.tcptls_session = NULL;
6112
		p->socket.tcptls_session = NULL;

    
   
6113
	} else if (p->socket.ws_session) {

    
   
6114
		ast_websocket_unref(p->socket.ws_session);

    
   
6115
		p->socket.ws_session = NULL;
6012
	}
6116
	}
6013

    
   
6117

   
6014
	if (p->peerauth) {
6118
	if (p->peerauth) {
6015
		ao2_t_ref(p->peerauth, -1, "Removing active peer authentication");
6119
		ao2_t_ref(p->peerauth, -1, "Removing active peer authentication");
6016
		p->peerauth = NULL;
6120
		p->peerauth = NULL;
[+20] [20] 3311 lines
[+20] [+] static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action)
9328
		int audio = FALSE;
9432
		int audio = FALSE;
9329
		int video = FALSE;
9433
		int video = FALSE;
9330
		int image = FALSE;
9434
		int image = FALSE;
9331
		int text = FALSE;
9435
		int text = FALSE;
9332
		int processed_crypto = FALSE;
9436
		int processed_crypto = FALSE;
9333
		char protocol[5] = {0,};
9437
		char protocol[6] = {0,};
9334
		int x;
9438
		int x;
9335

    
   
9439

   
9336
		numberofports = 0;
9440
		numberofports = 0;
9337
		len = -1;
9441
		len = -1;
9338
		start = next;
9442
		start = next;
[+20] [20] 9 lines
[+20] static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action)
9348
		AST_LIST_INSERT_TAIL(&p->offered_media, offer, next);
9452
		AST_LIST_INSERT_TAIL(&p->offered_media, offer, next);
9349
		offer->type = SDP_UNKNOWN;
9453
		offer->type = SDP_UNKNOWN;
9350

    
   
9454

   
9351
		/* Check for 'audio' media offer */
9455
		/* Check for 'audio' media offer */
9352
		if (strncmp(m, "audio ", 6) == 0) {
9456
		if (strncmp(m, "audio ", 6) == 0) {
9353
			if ((sscanf(m, "audio %30u/%30u RTP/%4s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) ||
9457
			if ((sscanf(m, "audio %30u/%30u RTP/%5s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) ||
9354
			    (sscanf(m, "audio %30u RTP/%4s %n", &x, protocol, &len) == 2 && len > 0)) {
9458
			    (sscanf(m, "audio %30u RTP/%5s %n", &x, protocol, &len) == 2 && len > 0)) {
9355
				codecs = m + len;
9459
				codecs = m + len;
9356
				/* produce zero-port m-line since it may be needed later
9460
				/* produce zero-port m-line since it may be needed later
9357
				 * length is "m=audio 0 RTP/" + protocol + " " + codecs + "\0" */
9461
				 * length is "m=audio 0 RTP/" + protocol + " " + codecs + "\0" */
9358
				if (!(offer->decline_m_line = ast_malloc(14 + strlen(protocol) + 1 + strlen(codecs) + 1))) {
9462
				if (!(offer->decline_m_line = ast_malloc(14 + strlen(protocol) + 1 + strlen(codecs) + 1))) {
9359
					ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n");
9463
					ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n");
[+20] [20] 11 lines
[+20] static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action)
9371
				/* Check number of ports offered for stream */
9475
				/* Check number of ports offered for stream */
9372
				if (numberofports > 1) {
9476
				if (numberofports > 1) {
9373
					ast_log(LOG_WARNING, "%d ports offered for audio media, not supported by Asterisk. Will try anyway...\n", numberofports);
9477
					ast_log(LOG_WARNING, "%d ports offered for audio media, not supported by Asterisk. Will try anyway...\n", numberofports);
9374
				}
9478
				}
9375

    
   
9479

   
9376
				if (!strcmp(protocol, "SAVP")) {
9480
				if (!strcmp(protocol, "SAVP") || !strcmp(protocol, "SAVPF")) {
9377
					secure_audio = 1;
9481
					secure_audio = 1;
9378
				} else if (strcmp(protocol, "AVP")) {
9482
				} else if (strcmp(protocol, "AVP") && strcmp(protocol, "AVPF")) {
9379
					ast_log(LOG_WARNING, "Unknown RTP profile in audio offer: %s\n", m);
9483
					ast_log(LOG_WARNING, "Unknown RTP profile in audio offer: %s\n", m);
9380
					continue;
9484
					continue;
9381
				}
9485
				}
9382

    
   
9486

   
9383
				if (has_media_stream(p, SDP_AUDIO)) {
9487
				if (has_media_stream(p, SDP_AUDIO)) {
[+20] [20] 24 lines
[+20] static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action)
9408
				goto process_sdp_cleanup;
9512
				goto process_sdp_cleanup;
9409
			}
9513
			}
9410
		}
9514
		}
9411
		/* Check for 'video' media offer */
9515
		/* Check for 'video' media offer */
9412
		else if (strncmp(m, "video ", 6) == 0) {
9516
		else if (strncmp(m, "video ", 6) == 0) {
9413
			if ((sscanf(m, "video %30u/%30u RTP/%4s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) ||
9517
			if ((sscanf(m, "video %30u/%30u RTP/%5s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) ||
9414
			    (sscanf(m, "video %30u RTP/%4s %n", &x, protocol, &len) == 2 && len > 0)) {
9518
			    (sscanf(m, "video %30u RTP/%5s %n", &x, protocol, &len) == 2 && len > 0)) {
9415
				codecs = m + len;
9519
				codecs = m + len;
9416
				/* produce zero-port m-line since it may be needed later
9520
				/* produce zero-port m-line since it may be needed later
9417
				 * length is "m=video 0 RTP/" + protocol + " " + codecs + "\0" */
9521
				 * length is "m=video 0 RTP/" + protocol + " " + codecs + "\0" */
9418
				if (!(offer->decline_m_line = ast_malloc(14 + strlen(protocol) + 1 + strlen(codecs) + 1))) {
9522
				if (!(offer->decline_m_line = ast_malloc(14 + strlen(protocol) + 1 + strlen(codecs) + 1))) {
9419
					ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n");
9523
					ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n");
[+20] [20] 11 lines
[+20] static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action)
9431
				/* Check number of ports offered for stream */
9535
				/* Check number of ports offered for stream */
9432
				if (numberofports > 1) {
9536
				if (numberofports > 1) {
9433
					ast_log(LOG_WARNING, "%d ports offered for video stream, not supported by Asterisk. Will try anyway...\n", numberofports);
9537
					ast_log(LOG_WARNING, "%d ports offered for video stream, not supported by Asterisk. Will try anyway...\n", numberofports);
9434
				}
9538
				}
9435

    
   
9539

   
9436
				if (!strcmp(protocol, "SAVP")) {
9540
				if (!strcmp(protocol, "SAVP") || !strcmp(protocol, "SAVPF")) {
9437
					secure_video = 1;
9541
					secure_video = 1;
9438
				} else if (strcmp(protocol, "AVP")) {
9542
				} else if (strcmp(protocol, "AVP") && strcmp(protocol, "AVPF")) {
9439
					ast_log(LOG_WARNING, "Unknown RTP profile in video offer: %s\n", m);
9543
					ast_log(LOG_WARNING, "Unknown RTP profile in video offer: %s\n", m);
9440
					continue;
9544
					continue;
9441
				}
9545
				}
9442

    
   
9546

   
9443
				if (has_media_stream(p, SDP_VIDEO)) {
9547
				if (has_media_stream(p, SDP_VIDEO)) {
[+20] [20] 1241 lines
[+20] [+] static void add_route(struct sip_request *req, struct sip_route *route)
10685
 *
10789
 *
10686
 * If there's a sips: uri scheme, TLS will be required.
10790
 * If there's a sips: uri scheme, TLS will be required.
10687
 */
10791
 */
10688
static void set_destination(struct sip_pvt *p, char *uri)
10792
static void set_destination(struct sip_pvt *p, char *uri)
10689
{
10793
{
10690
	char *h, *maddr, hostname[256];
10794
	char *trans, *h, *maddr, hostname[256];
10691
	int hn;
10795
	int hn;
10692
	int debug=sip_debug_test_pvt(p);
10796
	int debug=sip_debug_test_pvt(p);
10693
	int tls_on = FALSE;
10797
	int tls_on = FALSE;
10694

    
   
10798

   
10695
	if (debug)
10799
	if (debug)
10696
		ast_verbose("set_destination: Parsing <%s> for address/port to send to\n", uri);
10800
		ast_verbose("set_destination: Parsing <%s> for address/port to send to\n", uri);
10697

    
   
10801

   

    
   
10802
	if ((trans = strcasestr(uri, ";transport="))) {

    
   
10803
		trans += strlen(";transport=");

    
   
10804

   

    
   
10805
		if (!strncasecmp(trans, "ws", 2)) {

    
   
10806
			if (debug)

    
   
10807
				ast_verbose("set_destination: URI is for WebSocket, we can't set destination\n");

    
   
10808
			return;

    
   
10809
		}

    
   
10810
	}

    
   
10811

   
10698
	/* Find and parse hostname */
10812
	/* Find and parse hostname */
10699
	h = strchr(uri, '@');
10813
	h = strchr(uri, '@');
10700
	if (h)
10814
	if (h)
10701
		++h;
10815
		++h;
10702
	else {
10816
	else {
[+20] [20] 1330 lines
[+20] [+] static void get_crypto_attrib(struct sip_pvt *p, struct sip_srtp *srtp, const char **a_crypto)
12033
			ast_log(LOG_WARNING, "No SRTP key management enabled\n");
12147
			ast_log(LOG_WARNING, "No SRTP key management enabled\n");
12034
		}
12148
		}
12035
	}
12149
	}
12036
}
12150
}
12037

    
   
12151

   

    
   
12152
static char *get_sdp_rtp_profile(const struct sip_pvt *p, unsigned int secure)

    
   
12153
{

    
   
12154
	if (ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {

    
   
12155
		return secure ? "SAVPF" : "AVPF";

    
   
12156
	} else {

    
   
12157
		return secure ? "SAVP" : "AVP";

    
   
12158
	}

    
   
12159
}

    
   
12160

   
12038
/*! \brief Add Session Description Protocol message
12161
/*! \brief Add Session Description Protocol message
12039

    
   
12162

   
12040
    If oldsdp is TRUE, then the SDP version number is not incremented. This mechanism
12163
    If oldsdp is TRUE, then the SDP version number is not incremented. This mechanism
12041
    is used in Session-Timers where RE-INVITEs are used for refreshing SIP sessions
12164
    is used in Session-Timers where RE-INVITEs are used for refreshing SIP sessions
12042
    without modifying the media session in any way.
12165
    without modifying the media session in any way.
[+20] [20] 150 lines
[+20] [+] static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int oldsdp, int add_audio, int add_t38)
12193
		/* Ok, we need video. Let's add what we need for video and set codecs.
12316
		/* Ok, we need video. Let's add what we need for video and set codecs.
12194
		   Video is handled differently than audio since we can not transcode. */
12317
		   Video is handled differently than audio since we can not transcode. */
12195
		if (needvideo) {
12318
		if (needvideo) {
12196
			get_crypto_attrib(p, p->vsrtp, &v_a_crypto);
12319
			get_crypto_attrib(p, p->vsrtp, &v_a_crypto);
12197
			ast_str_append(&m_video, 0, "m=video %d RTP/%s", ast_sockaddr_port(&vdest),
12320
			ast_str_append(&m_video, 0, "m=video %d RTP/%s", ast_sockaddr_port(&vdest),
12198
				v_a_crypto ? "SAVP" : "AVP");
12321
				       get_sdp_rtp_profile(p, a_crypto ? 1 : 0));
12199

    
   
12322

   
12200
			/* Build max bitrate string */
12323
			/* Build max bitrate string */
12201
			if (p->maxcallbitrate)
12324
			if (p->maxcallbitrate)
12202
				snprintf(bandwidth, sizeof(bandwidth), "b=CT:%d\r\n", p->maxcallbitrate);
12325
				snprintf(bandwidth, sizeof(bandwidth), "b=CT:%d\r\n", p->maxcallbitrate);
12203
			if (debug) {
12326
			if (debug) {
[+20] [20] 10 lines
[+20] static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int oldsdp, int add_audio, int add_t38)
12214
		if (needtext) {
12337
		if (needtext) {
12215
			if (sipdebug_text)
12338
			if (sipdebug_text)
12216
				ast_verbose("Lets set up the text sdp\n");
12339
				ast_verbose("Lets set up the text sdp\n");
12217
			get_crypto_attrib(p, p->tsrtp, &t_a_crypto);
12340
			get_crypto_attrib(p, p->tsrtp, &t_a_crypto);
12218
			ast_str_append(&m_text, 0, "m=text %d RTP/%s", ast_sockaddr_port(&tdest),
12341
			ast_str_append(&m_text, 0, "m=text %d RTP/%s", ast_sockaddr_port(&tdest),
12219
				t_a_crypto ? "SAVP" : "AVP");
12342
				       get_sdp_rtp_profile(p, a_crypto ? 1 : 0));
12220
			if (debug) {  /* XXX should I use tdest below ? */
12343
			if (debug) {  /* XXX should I use tdest below ? */
12221
				ast_verbose("Text is at %s\n", ast_sockaddr_stringify(&taddr));
12344
				ast_verbose("Text is at %s\n", ast_sockaddr_stringify(&taddr));
12222
			}
12345
			}
12223

    
   
12346

   
12224
			if (!doing_directmedia) {
12347
			if (!doing_directmedia) {
[+20] [20] 6 lines
[+20] static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int oldsdp, int add_audio, int add_t38)
12231
		/* We break with the "recommendation" and send our IP, in order that our
12354
		/* We break with the "recommendation" and send our IP, in order that our
12232
		   peer doesn't have to ast_gethostbyname() us */
12355
		   peer doesn't have to ast_gethostbyname() us */
12233

    
   
12356

   
12234
		get_crypto_attrib(p, p->srtp, &a_crypto);
12357
		get_crypto_attrib(p, p->srtp, &a_crypto);
12235
		ast_str_append(&m_audio, 0, "m=audio %d RTP/%s", ast_sockaddr_port(&dest),
12358
		ast_str_append(&m_audio, 0, "m=audio %d RTP/%s", ast_sockaddr_port(&dest),
12236
			a_crypto ? "SAVP" : "AVP");
12359
			       get_sdp_rtp_profile(p, a_crypto ? 1 : 0));
12237

    
   
12360

   
12238
		/* Now, start adding audio codecs. These are added in this order:
12361
		/* Now, start adding audio codecs. These are added in this order:
12239
		   - First what was requested by the calling channel
12362
		   - First what was requested by the calling channel
12240
		   - Then preferences in order from sip.conf device config for this peer/user
12363
		   - Then preferences in order from sip.conf device config for this peer/user
12241
		   - Then other codecs in capabilities, including video
12364
		   - Then other codecs in capabilities, including video
[+20] [20] 2402 lines
[+20] [+] static void set_socket_transport(struct sip_socket *socket, int transport)
14644
		socket->fd = -1;
14767
		socket->fd = -1;
14645
		socket->type = transport;
14768
		socket->type = transport;
14646
		if (socket->tcptls_session) {
14769
		if (socket->tcptls_session) {
14647
			ao2_ref(socket->tcptls_session, -1);
14770
			ao2_ref(socket->tcptls_session, -1);
14648
			socket->tcptls_session = NULL;
14771
			socket->tcptls_session = NULL;

    
   
14772
		} else if (socket->ws_session) {

    
   
14773
			ast_websocket_unref(socket->ws_session);

    
   
14774
			socket->ws_session = NULL;
14649
		}
14775
		}
14650
	}
14776
	}
14651
}
14777
}
14652

    
   
14778

   
14653
/*! \brief Expire registration of SIP peer */
14779
/*! \brief Expire registration of SIP peer */
[+20] [20] 12 lines
[+20] [+] static int expire_register(const void *data)
14666
	set_socket_transport(&peer->socket, peer->default_outbound_transport);
14792
	set_socket_transport(&peer->socket, peer->default_outbound_transport);
14667

    
   
14793

   
14668
	if (peer->socket.tcptls_session) {
14794
	if (peer->socket.tcptls_session) {
14669
		ao2_ref(peer->socket.tcptls_session, -1);
14795
		ao2_ref(peer->socket.tcptls_session, -1);
14670
		peer->socket.tcptls_session = NULL;
14796
		peer->socket.tcptls_session = NULL;

    
   
14797
	} else if (peer->socket.ws_session) {

    
   
14798
		ast_websocket_unref(peer->socket.ws_session);

    
   
14799
		peer->socket.ws_session = NULL;
14671
	}
14800
	}
14672

    
   
14801

   
14673
	manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", peer->name);
14802
	manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", peer->name);
14674
	register_peer_exten(peer, FALSE);	/* Remove regexten */
14803
	register_peer_exten(peer, FALSE);	/* Remove regexten */
14675
	ast_devstate_changed(AST_DEVICE_UNKNOWN, "SIP/%s", peer->name);
14804
	ast_devstate_changed(AST_DEVICE_UNKNOWN, "SIP/%s", peer->name);
[+20] [20] 2154 lines
[+20] [+] static void check_via(struct sip_pvt *p, struct sip_request *req)
16830
	struct ast_sockaddr tmp = { { 0, } };
16959
	struct ast_sockaddr tmp = { { 0, } };
16831
	uint16_t port;
16960
	uint16_t port;
16832

    
   
16961

   
16833
	ast_copy_string(via, sip_get_header(req, "Via"), sizeof(via));
16962
	ast_copy_string(via, sip_get_header(req, "Via"), sizeof(via));
16834

    
   
16963

   

    
   
16964
	/* If this is via WebSocket we don't use the Via header contents at all */

    
   
16965
	if (!strncasecmp(via, "SIP/2.0/WS", 10)) {

    
   
16966
		return;

    
   
16967
	}

    
   
16968

   
16835
	/* Work on the leftmost value of the topmost Via header */
16969
	/* Work on the leftmost value of the topmost Via header */
16836
	c = strchr(via, ',');
16970
	c = strchr(via, ',');
16837
	if (c)
16971
	if (c)
16838
		*c = '\0';
16972
		*c = '\0';
16839

    
   
16973

   
[+20] [20] 4129 lines
[+20] [+] static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req, char **name, char **number, int set_call_forward)
20969
	contact_number = remove_uri_parameters(contact_number);
21103
	contact_number = remove_uri_parameters(contact_number);
20970

    
   
21104

   
20971
	if (p->socket.tcptls_session) {
21105
	if (p->socket.tcptls_session) {
20972
		ao2_ref(p->socket.tcptls_session, -1);
21106
		ao2_ref(p->socket.tcptls_session, -1);
20973
		p->socket.tcptls_session = NULL;
21107
		p->socket.tcptls_session = NULL;

    
   
21108
	} else if (p->socket.ws_session) {

    
   
21109
		ast_websocket_unref(p->socket.ws_session);

    
   
21110
		p->socket.ws_session = NULL;
20974
	}
21111
	}
20975

    
   
21112

   
20976
	set_socket_transport(&p->socket, transport);
21113
	set_socket_transport(&p->socket, transport);
20977

    
   
21114

   
20978
	if (set_call_forward && ast_test_flag(&p->flags[0], SIP_PROMISCREDIR)) {
21115
	if (set_call_forward && ast_test_flag(&p->flags[0], SIP_PROMISCREDIR)) {
[+20] [20] 6190 lines
[+20] [+] static int sip_prepare_socket(struct sip_pvt *p)
27169
	if ((s->type & (SIP_TRANSPORT_TCP | SIP_TRANSPORT_TLS)) &&
27306
	if ((s->type & (SIP_TRANSPORT_TCP | SIP_TRANSPORT_TLS)) &&
27170
			(s->tcptls_session) &&
27307
			(s->tcptls_session) &&
27171
			(s->tcptls_session->fd != -1)) {
27308
			(s->tcptls_session->fd != -1)) {
27172
		return s->tcptls_session->fd;
27309
		return s->tcptls_session->fd;
27173
	}
27310
	}

    
   
27311
	if ((s->type & (SIP_TRANSPORT_WS | SIP_TRANSPORT_WSS))) {

    
   
27312
		return s->ws_session ? ast_websocket_fd(s->ws_session) : -1;

    
   
27313
	}
27174

    
   
27314

   
27175
	/*! \todo Check this... This might be wrong, depending on the proxy configuration
27315
	/*! \todo Check this... This might be wrong, depending on the proxy configuration
27176
		If proxy is in "force" mode its correct.
27316
		If proxy is in "force" mode its correct.
27177
	 */
27317
	 */
27178
	if (p->outboundproxy && p->outboundproxy->transport) {
27318
	if (p->outboundproxy && p->outboundproxy->transport) {
[+20] [20] 1964 lines
[+20] [+] static struct sip_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime, int devstate_only)
29143
				while ((trans = strsep(&val, ","))) {
29283
				while ((trans = strsep(&val, ","))) {
29144
					trans = ast_skip_blanks(trans);
29284
					trans = ast_skip_blanks(trans);
29145

    
   
29285

   
29146
					if (!strncasecmp(trans, "udp", 3)) {
29286
					if (!strncasecmp(trans, "udp", 3)) {
29147
						peer->transports |= SIP_TRANSPORT_UDP;
29287
						peer->transports |= SIP_TRANSPORT_UDP;

    
   
29288
					} else if (!strncasecmp(trans, "wss", 3)) {

    
   
29289
						peer->transports |= SIP_TRANSPORT_WSS;

    
   
29290
					} else if (!strncasecmp(trans, "ws", 2)) {

    
   
29291
						peer->transports |= SIP_TRANSPORT_WS;
29148
					} else if (sip_cfg.tcp_enabled && !strncasecmp(trans, "tcp", 3)) {
29292
					} else if (sip_cfg.tcp_enabled && !strncasecmp(trans, "tcp", 3)) {
29149
						peer->transports |= SIP_TRANSPORT_TCP;
29293
						peer->transports |= SIP_TRANSPORT_TCP;
29150
					} else if (default_tls_cfg.enabled && !strncasecmp(trans, "tls", 3)) {
29294
					} else if (default_tls_cfg.enabled && !strncasecmp(trans, "tls", 3)) {
29151
						peer->transports |= SIP_TRANSPORT_TLS;
29295
						peer->transports |= SIP_TRANSPORT_TLS;
29152
					} else if (!strncasecmp(trans, "tcp", 3) || !strncasecmp(trans, "tls", 3)) {
29296
					} else if (!strncasecmp(trans, "tcp", 3) || !strncasecmp(trans, "tls", 3)) {
[+20] [20] 340 lines
[+20] static struct sip_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime, int devstate_only)
29493
				ast_set2_flag(&peer->flags[1], ast_true(v->value), SIP_PAGE2_USE_SRTP);
29637
				ast_set2_flag(&peer->flags[1], ast_true(v->value), SIP_PAGE2_USE_SRTP);
29494
			} else if (!strcasecmp(v->name, "encryption_taglen")) {
29638
			} else if (!strcasecmp(v->name, "encryption_taglen")) {
29495
				ast_set2_flag(&peer->flags[2], !strcasecmp(v->value, "32"), SIP_PAGE3_SRTP_TAG_32);
29639
				ast_set2_flag(&peer->flags[2], !strcasecmp(v->value, "32"), SIP_PAGE3_SRTP_TAG_32);
29496
			} else if (!strcasecmp(v->name, "snom_aoc_enabled")) {
29640
			} else if (!strcasecmp(v->name, "snom_aoc_enabled")) {
29497
				ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_SNOM_AOC);
29641
				ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_SNOM_AOC);

    
   
29642
			} else if (!strcasecmp(v->name, "avpf")) {

    
   
29643
				ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_USE_AVPF);
29498
			}
29644
			}
29499
		}
29645
		}
29500

    
   
29646

   
29501
		/* These apply to devstate lookups */
29647
		/* These apply to devstate lookups */
29502
		if (realtime && !strcasecmp(v->name, "lastms")) {
29648
		if (realtime && !strcasecmp(v->name, "lastms")) {
[+20] [20] 103 lines
[+20] static struct sip_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime, int devstate_only)
29606
	 * 1. Registration is not present and the socket.type and default transport types are different.
29752
	 * 1. Registration is not present and the socket.type and default transport types are different.
29607
	 * 2. The socket.type is not an acceptable transport type after rebuilding peer.
29753
	 * 2. The socket.type is not an acceptable transport type after rebuilding peer.
29608
	 * 3. The socket.type is not set yet. */
29754
	 * 3. The socket.type is not set yet. */
29609
	if (((peer->socket.type != peer->default_outbound_transport) && (peer->expire == -1)) ||
29755
	if (((peer->socket.type != peer->default_outbound_transport) && (peer->expire == -1)) ||
29610
		!(peer->socket.type & peer->transports) || !(peer->socket.type)) {
29756
		!(peer->socket.type & peer->transports) || !(peer->socket.type)) {
29611

    
   

   
29612
		set_socket_transport(&peer->socket, peer->default_outbound_transport);
29757
		set_socket_transport(&peer->socket, peer->default_outbound_transport);
29613
	}
29758
	}
29614

    
   
29759

   
29615
	if (ast_str_strlen(fullcontact)) {
29760
	if (ast_str_strlen(fullcontact)) {
29616
		ast_string_field_set(peer, fullcontact, ast_str_buffer(fullcontact));
29761
		ast_string_field_set(peer, fullcontact, ast_str_buffer(fullcontact));
[+20] [20] 521 lines
[+20] [+] static int reload_config(enum channelreloadreason reason)
30138
					default_transports |= SIP_TRANSPORT_UDP;
30283
					default_transports |= SIP_TRANSPORT_UDP;
30139
				} else if (!strncasecmp(trans, "tcp", 3)) {
30284
				} else if (!strncasecmp(trans, "tcp", 3)) {
30140
					default_transports |= SIP_TRANSPORT_TCP;
30285
					default_transports |= SIP_TRANSPORT_TCP;
30141
				} else if (!strncasecmp(trans, "tls", 3)) {
30286
				} else if (!strncasecmp(trans, "tls", 3)) {
30142
					default_transports |= SIP_TRANSPORT_TLS;
30287
					default_transports |= SIP_TRANSPORT_TLS;

    
   
30288
				} else if (!strncasecmp(trans, "wss", 3)) {

    
   
30289
					default_transports |= SIP_TRANSPORT_WSS;

    
   
30290
				} else if (!strncasecmp(trans, "ws", 2)) {

    
   
30291
					default_transports |= SIP_TRANSPORT_WS;
30143
				} else {
30292
				} else {
30144
					ast_log(LOG_NOTICE, "'%s' is not a valid transport type. if no other is specified, udp will be used.\n", trans);
30293
					ast_log(LOG_NOTICE, "'%s' is not a valid transport type. if no other is specified, udp will be used.\n", trans);
30145
				}
30294
				}
30146
				if (default_primary_transport == 0) {
30295
				if (default_primary_transport == 0) {
30147
					default_primary_transport = default_transports;
30296
					default_primary_transport = default_transports;
[+20] [20] 2394 lines
[+20] [+] static int load_module(void)
32542

    
   
32691

   
32543

    
   
32692

   
32544
	sip_register_tests();
32693
	sip_register_tests();
32545
	network_change_event_subscribe();
32694
	network_change_event_subscribe();
32546

    
   
32695

   

    
   
32696
	ast_websocket_add_protocol("sip", sip_websocket_callback);

    
   
32697

   
32547
	return AST_MODULE_LOAD_SUCCESS;
32698
	return AST_MODULE_LOAD_SUCCESS;
32548
}
32699
}
32549

    
   
32700

   
32550
/*! \brief PBX unload module API */
32701
/*! \brief PBX unload module API */
32551
static int unload_module(void)
32702
static int unload_module(void)
32552
{
32703
{
32553
	struct sip_pvt *p;
32704
	struct sip_pvt *p;
32554
	struct sip_threadinfo *th;
32705
	struct sip_threadinfo *th;
32555
	struct ast_context *con;
32706
	struct ast_context *con;
32556
	struct ao2_iterator i;
32707
	struct ao2_iterator i;
32557
	int wait_count;
32708
	int wait_count;
32558

    
   
32709

   

    
   
32710
	ast_websocket_remove_protocol("sip", sip_websocket_callback);

    
   
32711

   
32559
	network_change_event_unsubscribe();
32712
	network_change_event_unsubscribe();
32560

    
   
32713

   
32561
	ast_sched_dump(sched);
32714
	ast_sched_dump(sched);
32562
	
32715
	
32563
	/* First, take us out of the channel type list */
32716
	/* First, take us out of the channel type list */
[+20] [20] 173 lines
/trunk/channels/sip/sdp_crypto.c
Revision 369768 New Change
 
/trunk/channels/sip/security_events.c
Revision 369768 New Change
 
/trunk/channels/sip/include/sip.h
Revision 369768 New Change
 
/trunk/configs/sip.conf.sample
Revision 369768 New Change
 
/trunk/include/asterisk/http_websocket.h
Revision 369768 New Change
 
/trunk/res/res_http_websocket.c
Revision 369768 New Change
 
  1. /trunk/channels/chan_sip.c: Loading...
  2. /trunk/channels/sip/sdp_crypto.c: Loading...
  3. /trunk/channels/sip/security_events.c: Loading...
  4. /trunk/channels/sip/include/sip.h: Loading...
  5. /trunk/configs/sip.conf.sample: Loading...
  6. /trunk/include/asterisk/http_websocket.h: Loading...
  7. /trunk/res/res_http_websocket.c: Loading...

https://reviewboard.asterisk.org/ runs on a server provided by Digium, Inc. and uses bandwidth donated to the open source Asterisk community by API Digital Communications in Huntsville, AL USA.
Please report problems with this site to asteriskteam@digium.com.