2006-09-19 19:58:09 +00:00
/*
2006-03-25 23:50:09 +00:00
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 1999 - 2006, Digium, Inc.
*
* Mark Spencer <markster@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
2006-03-26 16:48:47 +00:00
/*!
2009-04-23 20:36:35 +00:00
* \file
2006-04-03 18:38:28 +00:00
* \brief http server for AMI access
2006-03-26 16:48:47 +00:00
*
2006-04-03 18:38:28 +00:00
* \author Mark Spencer <markster@digium.com>
2007-04-06 21:16:38 +00:00
*
* This program implements a tiny http server
2009-04-23 20:36:35 +00:00
* and was inspired by micro-httpd by Jef Poskanzer
*
2012-09-21 17:14:59 +00:00
* GMime http://spruce.sourceforge.net/gmime/
2009-11-02 20:43:52 +00:00
*
2006-04-03 18:38:28 +00:00
* \ref AstHTTP - AMI over the http protocol
2006-03-26 16:48:47 +00:00
*/
2012-10-18 14:17:40 +00:00
/*! \li \ref http.c uses the configuration file \ref http.conf
2012-10-01 23:39:45 +00:00
* \addtogroup configuration_file
*/
/*! \page http.conf http.conf
* \verbinclude http.conf.sample
*/
2011-07-14 20:28:54 +00:00
/*** MODULEINFO
<support_level>core</support_level>
***/
2006-06-07 18:54:56 +00:00
#include "asterisk.h"
2006-03-25 23:50:09 +00:00
#include <time.h>
#include <sys/time.h>
2006-04-01 08:49:54 +00:00
#include <sys/stat.h>
2016-06-03 08:57:02 +03:00
#include <signal.h>
2006-03-25 23:50:09 +00:00
#include <fcntl.h>
2006-03-26 16:48:47 +00:00
2008-03-18 22:32:26 +00:00
#include "asterisk/paths.h" /* use ast_config_AST_DATA_DIR */
2006-03-26 16:48:47 +00:00
#include "asterisk/cli.h"
2008-01-18 22:04:33 +00:00
#include "asterisk/tcptls.h"
2006-03-26 16:48:47 +00:00
#include "asterisk/http.h"
#include "asterisk/utils.h"
#include "asterisk/strings.h"
2006-04-01 08:49:54 +00:00
#include "asterisk/config.h"
2006-12-23 20:13:14 +00:00
#include "asterisk/stringfields.h"
2008-02-26 20:02:14 +00:00
#include "asterisk/ast_version.h"
2007-04-06 21:16:38 +00:00
#include "asterisk/manager.h"
2018-02-16 22:11:42 -05:00
#include "asterisk/module.h"
2008-10-19 19:11:28 +00:00
#include "asterisk/astobj2.h"
2010-10-29 20:46:06 +00:00
#include "asterisk/netsock2.h"
2013-11-27 15:48:39 +00:00
#include "asterisk/json.h"
2006-03-25 23:50:09 +00:00
#define MAX_PREFIX 80
2010-10-29 20:46:06 +00:00
#define DEFAULT_PORT 8088
#define DEFAULT_TLS_PORT 8089
2011-04-21 18:32:50 +00:00
#define DEFAULT_SESSION_LIMIT 100
2014-07-03 17:16:55 +00:00
/*! (ms) Idle time waiting for data. */
#define DEFAULT_SESSION_INACTIVITY 30000
/*! (ms) Min timeout for initial HTTP request to start coming in. */
#define MIN_INITIAL_REQUEST_TIMEOUT 10000
/*! (ms) Idle time between HTTP requests */
#define DEFAULT_SESSION_KEEP_ALIVE 15000
2015-01-30 17:21:50 +00:00
/*! Max size for the http server name */
#define MAX_SERVER_NAME_LENGTH 128
/*! Max size for the http response header */
#define DEFAULT_RESPONSE_HEADER_LENGTH 512
2014-07-03 17:16:55 +00:00
/*! Maximum application/json or application/x-www-form-urlencoded body content length. */
#if !defined(LOW_MEMORY)
2019-01-09 10:27:03 +00:00
#define MAX_CONTENT_LENGTH 40960
2014-07-03 17:16:55 +00:00
#else
#define MAX_CONTENT_LENGTH 1024
#endif /* !defined(LOW_MEMORY) */
2019-01-09 10:27:03 +00:00
/*! Initial response body length. */
#if !defined(LOW_MEMORY)
#define INITIAL_RESPONSE_BODY_BUFFER 1024
#else
#define INITIAL_RESPONSE_BODY_BUFFER 512
#endif /* !defined(LOW_MEMORY) */
2014-07-03 17:16:55 +00:00
/*! Maximum line length for HTTP requests. */
#if !defined(LOW_MEMORY)
#define MAX_HTTP_LINE_LENGTH 4096
#else
#define MAX_HTTP_LINE_LENGTH 1024
#endif /* !defined(LOW_MEMORY) */
2006-03-25 23:50:09 +00:00
2015-01-30 17:21:50 +00:00
static char http_server_name [ MAX_SERVER_NAME_LENGTH ];
2011-04-21 18:32:50 +00:00
static int session_limit = DEFAULT_SESSION_LIMIT ;
2014-06-12 17:00:08 +00:00
static int session_inactivity = DEFAULT_SESSION_INACTIVITY ;
2014-07-03 17:16:55 +00:00
static int session_keep_alive = DEFAULT_SESSION_KEEP_ALIVE ;
2011-04-21 18:32:50 +00:00
static int session_count = 0 ;
2008-01-18 22:04:33 +00:00
static struct ast_tls_config http_tls_cfg ;
2006-10-22 12:02:35 +00:00
2006-11-25 17:37:04 +00:00
static void * httpd_helper_thread ( void * arg );
2006-10-22 12:02:35 +00:00
/*!
* we have up to two accepting threads, one for http, one for https
*/
2008-10-19 19:11:28 +00:00
static struct ast_tcptls_session_args http_desc = {
2006-10-22 12:02:35 +00:00
. accept_fd = - 1 ,
. master = AST_PTHREADT_NULL ,
2006-12-07 16:42:29 +00:00
. tls_cfg = NULL ,
2006-11-28 00:02:42 +00:00
. poll_timeout = - 1 ,
2006-11-25 17:37:04 +00:00
. name = "http server" ,
2008-03-04 22:23:21 +00:00
. accept_fn = ast_tcptls_server_root ,
2006-11-25 17:37:04 +00:00
. worker_fn = httpd_helper_thread ,
2006-10-22 12:02:35 +00:00
};
2008-10-19 19:11:28 +00:00
static struct ast_tcptls_session_args https_desc = {
2006-10-22 12:02:35 +00:00
. accept_fd = - 1 ,
. master = AST_PTHREADT_NULL ,
2006-12-07 16:42:29 +00:00
. tls_cfg = & http_tls_cfg ,
2006-11-28 00:02:42 +00:00
. poll_timeout = - 1 ,
2006-11-25 17:37:04 +00:00
. name = "https server" ,
2008-03-04 22:23:21 +00:00
. accept_fn = ast_tcptls_server_root ,
2006-11-25 17:37:04 +00:00
. worker_fn = httpd_helper_thread ,
2006-10-22 12:02:35 +00:00
};
2006-03-25 23:50:09 +00:00
2006-12-28 20:02:41 +00:00
static AST_RWLIST_HEAD_STATIC ( uris , ast_http_uri ); /*!< list of supported handlers */
2006-10-16 08:38:35 +00:00
/* all valid URIs must be prepended by the string in prefix. */
2006-03-25 23:50:09 +00:00
static char prefix [ MAX_PREFIX ];
2006-12-27 22:14:33 +00:00
static int enablestatic ;
2006-04-01 08:49:54 +00:00
2006-04-03 18:38:28 +00:00
/*! \brief Limit the kinds of files we're willing to serve up */
2006-04-01 08:49:54 +00:00
static struct {
2007-04-11 14:49:07 +00:00
const char * ext ;
const char * mtype ;
2006-04-01 08:49:54 +00:00
} mimetypes [] = {
{ "png" , "image/png" },
2009-04-23 20:36:35 +00:00
{ "xml" , "text/xml" },
2006-04-01 08:49:54 +00:00
{ "jpg" , "image/jpeg" },
{ "js" , "application/x-javascript" },
{ "wav" , "audio/x-wav" },
{ "mp3" , "audio/mpeg" },
2007-03-21 18:10:01 +00:00
{ "svg" , "image/svg+xml" },
2007-04-11 14:49:07 +00:00
{ "svgz" , "image/svg+xml" },
2007-04-05 15:48:17 +00:00
{ "gif" , "image/gif" },
2011-01-31 13:57:53 +00:00
{ "html" , "text/html" },
{ "htm" , "text/html" },
2011-02-19 14:07:38 +00:00
{ "css" , "text/css" },
2011-01-31 13:57:53 +00:00
{ "cnf" , "text/plain" },
{ "cfg" , "text/plain" },
{ "bin" , "application/octet-stream" },
{ "sbn" , "application/octet-stream" },
{ "ld" , "application/octet-stream" },
2006-04-01 08:49:54 +00:00
};
2006-12-23 20:13:14 +00:00
struct http_uri_redirect {
AST_LIST_ENTRY ( http_uri_redirect ) entry ;
2006-12-24 03:29:42 +00:00
char * dest ;
char target [ 0 ];
2006-12-23 20:13:14 +00:00
};
2006-12-28 20:05:00 +00:00
static AST_RWLIST_HEAD_STATIC ( uri_redirects , http_uri_redirect );
2006-12-23 20:13:14 +00:00
2009-06-15 17:34:30 +00:00
static const struct ast_cfhttp_methods_text {
2009-04-23 20:36:35 +00:00
enum ast_http_method method ;
2011-05-05 02:34:29 +00:00
const char * text ;
2009-04-23 20:36:35 +00:00
} ast_http_methods_text [] = {
{ AST_HTTP_UNKNOWN , "UNKNOWN" },
{ AST_HTTP_GET , "GET" },
{ AST_HTTP_POST , "POST" },
{ AST_HTTP_HEAD , "HEAD" },
{ AST_HTTP_PUT , "PUT" },
2013-04-22 14:58:53 +00:00
{ AST_HTTP_DELETE , "DELETE" },
{ AST_HTTP_OPTIONS , "OPTIONS" },
2009-04-23 20:36:35 +00:00
};
const char * ast_get_http_method ( enum ast_http_method method )
{
2011-05-05 02:34:29 +00:00
int x ;
for ( x = 0 ; x < ARRAY_LEN ( ast_http_methods_text ); x ++ ) {
if ( ast_http_methods_text [ x ]. method == method ) {
return ast_http_methods_text [ x ]. text ;
}
}
return NULL ;
2009-04-23 20:36:35 +00:00
}
const char * ast_http_ftype2mtype ( const char * ftype )
2006-04-01 08:49:54 +00:00
{
int x ;
2008-03-19 04:09:55 +00:00
2006-04-01 08:49:54 +00:00
if ( ftype ) {
2008-03-19 04:09:55 +00:00
for ( x = 0 ; x < ARRAY_LEN ( mimetypes ); x ++ ) {
2008-03-19 15:41:54 +00:00
if ( ! strcasecmp ( ftype , mimetypes [ x ]. ext )) {
2006-04-01 08:49:54 +00:00
return mimetypes [ x ]. mtype ;
2008-03-19 15:41:54 +00:00
}
2006-04-01 08:49:54 +00:00
}
}
2009-04-23 20:36:35 +00:00
return NULL ;
2006-04-01 08:49:54 +00:00
}
2009-04-23 20:36:35 +00:00
uint32_t ast_http_manid_from_vars ( struct ast_variable * headers )
{
uint32_t mngid = 0 ;
struct ast_variable * v , * cookies ;
2008-05-23 21:19:42 +00:00
2009-04-23 20:36:35 +00:00
cookies = ast_http_get_cookies ( headers );
for ( v = cookies ; v ; v = v -> next ) {
if ( ! strcasecmp ( v -> name , "mansession_id" )) {
2009-08-10 19:20:57 +00:00
sscanf ( v -> value , "%30x" , & mngid );
2009-04-23 20:36:35 +00:00
break ;
}
}
2014-03-10 17:21:01 +00:00
ast_variables_destroy ( cookies );
2008-05-23 21:19:42 +00:00
return mngid ;
}
2008-07-02 20:28:17 +00:00
void ast_http_prefix ( char * buf , int len )
{
if ( buf ) {
ast_copy_string ( buf , prefix , len );
}
}
2009-04-23 20:36:35 +00:00
static int static_callback ( struct ast_tcptls_session_instance * ser ,
const struct ast_http_uri * urih , const char * uri ,
enum ast_http_method method , struct ast_variable * get_vars ,
struct ast_variable * headers )
2006-04-01 08:49:54 +00:00
{
char * path ;
2009-04-23 20:36:35 +00:00
const char * ftype ;
2007-04-11 14:49:07 +00:00
const char * mtype ;
2006-04-01 08:49:54 +00:00
char wkspace [ 80 ];
struct stat st ;
int len ;
int fd ;
2009-04-23 20:36:35 +00:00
struct ast_str * http_header ;
struct timeval tv ;
2007-07-18 19:47:20 +00:00
struct ast_tm tm ;
2009-04-23 20:36:35 +00:00
char timebuf [ 80 ], etag [ 23 ];
struct ast_variable * v ;
int not_modified = 0 ;
2006-04-01 08:49:54 +00:00
2009-04-23 20:36:35 +00:00
if ( method != AST_HTTP_GET && method != AST_HTTP_HEAD ) {
ast_http_error ( ser , 501 , "Not Implemented" , "Attempt to use unimplemented / unsupported method" );
2014-07-03 17:16:55 +00:00
return 0 ;
2009-04-23 20:36:35 +00:00
}
/* Yuck. I'm not really sold on this, but if you don't deliver static content it makes your configuration
2006-04-01 08:49:54 +00:00
substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
2008-03-19 15:41:54 +00:00
if ( ! enablestatic || ast_strlen_zero ( uri )) {
2006-04-01 08:49:54 +00:00
goto out403 ;
2008-03-19 15:41:54 +00:00
}
2013-03-27 18:52:16 +00:00
/* Disallow any funny filenames at all (checking first character only??) */
2008-03-19 15:41:54 +00:00
if (( uri [ 0 ] < 33 ) || strchr ( "./|~@#$%^&*() \t " , uri [ 0 ])) {
2006-04-01 08:49:54 +00:00
goto out403 ;
2008-03-19 15:41:54 +00:00
}
if ( strstr ( uri , "/.." )) {
2006-04-01 08:49:54 +00:00
goto out403 ;
2008-03-19 15:41:54 +00:00
}
2009-04-23 20:36:35 +00:00
2008-03-19 15:41:54 +00:00
if (( ftype = strrchr ( uri , '.' ))) {
2006-04-01 08:49:54 +00:00
ftype ++ ;
2008-03-19 15:41:54 +00:00
}
2009-04-23 20:36:35 +00:00
if ( ! ( mtype = ast_http_ftype2mtype ( ftype ))) {
snprintf ( wkspace , sizeof ( wkspace ), "text/%s" , S_OR ( ftype , "plain" ));
2013-03-27 18:52:16 +00:00
mtype = wkspace ;
2009-04-23 20:36:35 +00:00
}
2006-04-01 08:49:54 +00:00
/* Cap maximum length */
2008-03-27 22:10:25 +00:00
if (( len = strlen ( uri ) + strlen ( ast_config_AST_DATA_DIR ) + strlen ( "/static-http/" ) + 5 ) > 1024 ) {
2006-04-01 08:49:54 +00:00
goto out403 ;
2008-03-19 15:41:54 +00:00
}
2009-04-23 20:36:35 +00:00
2012-07-31 20:21:43 +00:00
path = ast_alloca ( len );
2006-04-15 22:53:53 +00:00
sprintf ( path , "%s/static-http/%s" , ast_config_AST_DATA_DIR , uri );
2008-03-19 15:41:54 +00:00
if ( stat ( path , & st )) {
2006-04-01 08:49:54 +00:00
goto out404 ;
2008-03-19 15:41:54 +00:00
}
if ( S_ISDIR ( st . st_mode )) {
2006-04-01 08:49:54 +00:00
goto out404 ;
2009-04-23 20:36:35 +00:00
}
2008-03-19 15:41:54 +00:00
2013-03-27 18:52:16 +00:00
if ( strstr ( path , "/private/" ) && ! astman_is_authed ( ast_http_manid_from_vars ( headers ))) {
2006-04-01 08:49:54 +00:00
goto out403 ;
2008-03-19 15:41:54 +00:00
}
2006-12-16 09:33:31 +00:00
2013-03-27 18:52:16 +00:00
fd = open ( path , O_RDONLY );
if ( fd < 0 ) {
2008-05-23 21:19:42 +00:00
goto out403 ;
}
2009-04-23 20:36:35 +00:00
/* make "Etag:" http header value */
snprintf ( etag , sizeof ( etag ), " \" %ld \" " , ( long ) st . st_mtime );
/* make "Last-Modified:" http header value */
tv . tv_sec = st . st_mtime ;
tv . tv_usec = 0 ;
ast_strftime ( timebuf , sizeof ( timebuf ), "%a, %d %b %Y %H:%M:%S GMT" , ast_localtime ( & tv , & tm , "GMT" ));
2007-06-29 20:35:09 +00:00
2009-04-23 20:36:35 +00:00
/* check received "If-None-Match" request header and Etag value for file */
for ( v = headers ; v ; v = v -> next ) {
if ( ! strcasecmp ( v -> name , "If-None-Match" )) {
if ( ! strcasecmp ( v -> value , etag )) {
not_modified = 1 ;
}
break ;
2008-11-02 18:52:13 +00:00
}
2008-03-19 15:41:54 +00:00
}
2006-12-16 09:33:31 +00:00
2014-07-03 17:16:55 +00:00
http_header = ast_str_create ( 255 );
if ( ! http_header ) {
ast_http_request_close_on_completion ( ser );
ast_http_error ( ser , 500 , "Server Error" , "Out of memory" );
2013-03-27 18:52:16 +00:00
close ( fd );
2014-07-03 17:16:55 +00:00
return 0 ;
2009-04-23 20:36:35 +00:00
}
2008-03-19 15:41:54 +00:00
2009-04-23 20:36:35 +00:00
ast_str_set ( & http_header , 0 , "Content-type: %s \r\n "
"ETag: %s \r\n "
2011-03-01 22:26:37 +00:00
"Last-Modified: %s \r\n " ,
2009-04-23 20:36:35 +00:00
mtype ,
etag ,
timebuf );
/* ast_http_send() frees http_header, so we don't need to do it before returning */
if ( not_modified ) {
ast_http_send ( ser , method , 304 , "Not Modified" , http_header , NULL , 0 , 1 );
} else {
ast_http_send ( ser , method , 200 , NULL , http_header , NULL , fd , 1 ); /* static content flag is set */
}
close ( fd );
return 0 ;
2006-04-01 08:49:54 +00:00
out404 :
2009-04-23 20:36:35 +00:00
ast_http_error ( ser , 404 , "Not Found" , "The requested URL was not found on this server." );
2014-07-03 17:16:55 +00:00
return 0 ;
2006-04-01 08:49:54 +00:00
out403 :
2014-07-03 17:16:55 +00:00
ast_http_request_close_on_completion ( ser );
2009-04-23 20:36:35 +00:00
ast_http_error ( ser , 403 , "Access Denied" , "You do not have permission to access the requested URL." );
2014-07-03 17:16:55 +00:00
return 0 ;
2006-04-01 08:49:54 +00:00
}
2006-03-25 23:50:09 +00:00
2009-04-23 20:36:35 +00:00
static int httpstatus_callback ( struct ast_tcptls_session_instance * ser ,
const struct ast_http_uri * urih , const char * uri ,
enum ast_http_method method , struct ast_variable * get_vars ,
struct ast_variable * headers )
2006-03-25 23:50:09 +00:00
{
2009-04-23 20:36:35 +00:00
struct ast_str * out ;
struct ast_variable * v , * cookies = NULL ;
2006-03-25 23:50:09 +00:00
2009-04-23 20:36:35 +00:00
if ( method != AST_HTTP_GET && method != AST_HTTP_HEAD ) {
ast_http_error ( ser , 501 , "Not Implemented" , "Attempt to use unimplemented / unsupported method" );
2014-07-03 17:16:55 +00:00
return 0 ;
2009-04-23 20:36:35 +00:00
}
2014-07-03 17:16:55 +00:00
out = ast_str_create ( 512 );
if ( ! out ) {
ast_http_request_close_on_completion ( ser );
ast_http_error ( ser , 500 , "Server Error" , "Out of memory" );
return 0 ;
2008-03-19 15:41:54 +00:00
}
2006-12-16 09:33:31 +00:00
ast_str_append ( & out , 0 ,
2015-02-21 14:06:20 +00:00
"<html><title>Asterisk HTTP Status</title> \r\n "
2009-04-23 20:36:35 +00:00
"<body bgcolor= \" #ffffff \" > \r\n "
"<table bgcolor= \" #f1f1f1 \" align= \" center \" ><tr><td bgcolor= \" #e0e0ff \" colspan= \" 2 \" width= \" 500 \" > \r\n "
"<h2> Asterisk™ HTTP Status</h2></td></tr> \r\n " );
2015-01-30 17:21:50 +00:00
ast_str_append ( & out , 0 , "<tr><td><i>Server</i></td><td><b>%s</b></td></tr> \r\n " , http_server_name );
2006-12-16 09:33:31 +00:00
ast_str_append ( & out , 0 , "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr> \r\n " , prefix );
ast_str_append ( & out , 0 , "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr> \r\n " ,
2010-07-08 22:08:07 +00:00
ast_sockaddr_stringify_addr ( & http_desc . old_address ));
ast_str_append ( & out , 0 , "<tr><td><i>Bind Port</i></td><td><b>%s</b></td></tr> \r\n " ,
ast_sockaddr_stringify_port ( & http_desc . old_address ));
2008-03-19 15:41:54 +00:00
if ( http_tls_cfg . enabled ) {
2010-07-08 22:08:07 +00:00
ast_str_append ( & out , 0 , "<tr><td><i>SSL Bind Port</i></td><td><b>%s</b></td></tr> \r\n " ,
ast_sockaddr_stringify_port ( & https_desc . old_address ));
2008-03-19 15:41:54 +00:00
}
2006-12-16 09:33:31 +00:00
ast_str_append ( & out , 0 , "<tr><td colspan= \" 2 \" ><hr></td></tr> \r\n " );
2009-04-23 20:36:35 +00:00
for ( v = get_vars ; v ; v = v -> next ) {
ast_str_append ( & out , 0 , "<tr><td><i>Submitted GET Variable '%s'</i></td><td>%s</td></tr> \r\n " , v -> name , v -> value );
2006-04-01 08:49:54 +00:00
}
2006-12-16 09:33:31 +00:00
ast_str_append ( & out , 0 , "<tr><td colspan= \" 2 \" ><hr></td></tr> \r\n " );
2008-03-19 15:41:54 +00:00
2009-04-23 20:36:35 +00:00
cookies = ast_http_get_cookies ( headers );
for ( v = cookies ; v ; v = v -> next ) {
ast_str_append ( & out , 0 , "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr> \r\n " , v -> name , v -> value );
2006-03-25 23:50:09 +00:00
}
2009-04-23 20:36:35 +00:00
ast_variables_destroy ( cookies );
2008-03-19 15:41:54 +00:00
2015-02-21 14:06:20 +00:00
ast_str_append ( & out , 0 , "</table><center><font size= \" -1 \" ><i>Asterisk and Digium are registered trademarks of Digium, Inc.</i></font></center></body></html> \r\n " );
2009-04-23 20:36:35 +00:00
ast_http_send ( ser , method , 200 , NULL , NULL , out , 0 , 0 );
return 0 ;
2006-03-25 23:50:09 +00:00
}
static struct ast_http_uri statusuri = {
. callback = httpstatus_callback ,
. description = "Asterisk HTTP General Status" ,
. uri = "httpstatus" ,
2009-04-23 20:36:35 +00:00
. has_subtree = 0 ,
2008-04-02 15:25:48 +00:00
. data = NULL ,
. key = __FILE__ ,
2006-03-25 23:50:09 +00:00
};
2009-04-23 20:36:35 +00:00
2006-04-01 08:49:54 +00:00
static struct ast_http_uri staticuri = {
. callback = static_callback ,
. description = "Asterisk HTTP Static Delivery" ,
. uri = "static" ,
. has_subtree = 1 ,
2008-04-02 15:25:48 +00:00
. data = NULL ,
. key = __FILE__ ,
2006-04-01 08:49:54 +00:00
};
2009-04-23 20:36:35 +00:00
2014-07-03 17:16:55 +00:00
enum http_private_flags {
/*! TRUE if the HTTP request has a body. */
HTTP_FLAG_HAS_BODY = ( 1 << 0 ),
/*! TRUE if the HTTP request body has been read. */
HTTP_FLAG_BODY_READ = ( 1 << 1 ),
/*! TRUE if the HTTP request must close when completed. */
HTTP_FLAG_CLOSE_ON_COMPLETION = ( 1 << 2 ),
};
/*! HTTP tcptls worker_fn private data. */
struct http_worker_private_data {
/*! Body length or -1 if chunked. Valid if HTTP_FLAG_HAS_BODY is TRUE. */
int body_length ;
/*! HTTP body tracking flags */
struct ast_flags flags ;
};
2009-04-23 20:36:35 +00:00
void ast_http_send ( struct ast_tcptls_session_instance * ser ,
enum ast_http_method method , int status_code , const char * status_title ,
2014-07-03 17:16:55 +00:00
struct ast_str * http_header , struct ast_str * out , int fd ,
2009-04-23 20:36:35 +00:00
unsigned int static_content )
2006-03-25 23:50:09 +00:00
{
2009-04-23 20:36:35 +00:00
struct timeval now = ast_tvnow ();
struct ast_tm tm ;
char timebuf [ 80 ];
2016-06-02 22:10:06 +03:00
char buf [ 256 ];
int len ;
2009-04-23 20:36:35 +00:00
int content_length = 0 ;
2014-07-03 17:16:55 +00:00
int close_connection ;
2015-01-30 17:21:50 +00:00
struct ast_str * server_header_field = ast_str_create ( MAX_SERVER_NAME_LENGTH );
2016-12-01 16:49:03 -06:00
int send_content ;
2009-04-23 20:36:35 +00:00
2016-06-02 22:10:06 +03:00
if ( ! ser || ! server_header_field ) {
2014-07-03 17:16:55 +00:00
/* The connection is not open. */
ast_free ( http_header );
ast_free ( out );
2015-01-30 17:21:50 +00:00
ast_free ( server_header_field );
2009-04-23 20:36:35 +00:00
return ;
}
2015-01-30 17:21:50 +00:00
if ( ! ast_strlen_zero ( http_server_name )) {
ast_str_set ( & server_header_field ,
0 ,
"Server: %s \r\n " ,
http_server_name );
}
2014-07-03 17:16:55 +00:00
/*
* We shouldn't be sending non-final status codes to this
* function because we may close the connection before
* returning.
*/
ast_assert ( 200 <= status_code );
if ( session_keep_alive <= 0 ) {
close_connection = 1 ;
} else {
struct http_worker_private_data * request ;
request = ser -> private_data ;
if ( ! request
|| ast_test_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION )
|| ast_http_body_discard ( ser )) {
close_connection = 1 ;
} else {
close_connection = 0 ;
}
}
2009-04-23 20:36:35 +00:00
ast_strftime ( timebuf , sizeof ( timebuf ), "%a, %d %b %Y %H:%M:%S GMT" , ast_localtime ( & now , & tm , "GMT" ));
2011-03-01 22:26:37 +00:00
/* calc content length */
2009-04-23 20:36:35 +00:00
if ( out ) {
2014-04-04 15:13:55 +00:00
content_length += ast_str_strlen ( out );
2009-04-23 20:36:35 +00:00
}
if ( fd ) {
content_length += lseek ( fd , 0 , SEEK_END );
lseek ( fd , 0 , SEEK_SET );
}
2016-12-01 16:49:03 -06:00
send_content = method != AST_HTTP_HEAD || status_code >= 400 ;
2009-04-23 20:36:35 +00:00
/* send http header */
2017-07-05 14:31:43 -06:00
if ( ast_iostream_printf ( ser -> stream ,
2014-07-03 17:16:55 +00:00
"HTTP/1.1 %d %s \r\n "
2015-01-30 17:21:50 +00:00
"%s"
2009-04-23 20:36:35 +00:00
"Date: %s \r\n "
"%s"
2011-03-01 22:26:37 +00:00
"%s"
2014-07-03 17:16:55 +00:00
"%s"
"Content-Length: %d \r\n "
2016-12-01 16:49:03 -06:00
" \r\n "
"%s" ,
2009-04-23 20:36:35 +00:00
status_code , status_title ? status_title : "OK" ,
2015-01-30 17:21:50 +00:00
ast_str_buffer ( server_header_field ),
2009-04-23 20:36:35 +00:00
timebuf ,
2014-07-03 17:16:55 +00:00
close_connection ? "Connection: close \r\n " : "" ,
2009-04-23 20:36:35 +00:00
static_content ? "" : "Cache-Control: no-cache, no-store \r\n " ,
2014-07-03 17:16:55 +00:00
http_header ? ast_str_buffer ( http_header ) : "" ,
2016-12-01 16:49:03 -06:00
content_length ,
send_content && out && ast_str_strlen ( out ) ? ast_str_buffer ( out ) : ""
2017-07-05 14:31:43 -06:00
) <= 0 ) {
ast_debug ( 1 , "ast_iostream_printf() failed: %s \n " , strerror ( errno ));
close_connection = 1 ;
2017-10-25 09:23:55 -05:00
} else if ( send_content && fd ) {
/* send file content */
2016-12-01 16:49:03 -06:00
while (( len = read ( fd , buf , sizeof ( buf ))) > 0 ) {
if ( ast_iostream_write ( ser -> stream , buf , len ) != len ) {
2017-07-05 14:31:43 -06:00
ast_debug ( 1 , "ast_iostream_write() failed: %s \n " , strerror ( errno ));
2014-07-03 17:16:55 +00:00
close_connection = 1 ;
2016-12-01 16:49:03 -06:00
break ;
2009-04-23 20:36:35 +00:00
}
}
}
2014-07-03 17:16:55 +00:00
ast_free ( http_header );
ast_free ( out );
2015-01-30 17:21:50 +00:00
ast_free ( server_header_field );
2009-04-23 20:36:35 +00:00
2014-07-03 17:16:55 +00:00
if ( close_connection ) {
ast_debug ( 1 , "HTTP closing session. status_code:%d \n " , status_code );
ast_tcptls_close_session_file ( ser );
} else {
ast_debug ( 1 , "HTTP keeping session open. status_code:%d \n " , status_code );
}
2009-04-23 20:36:35 +00:00
}
2015-01-30 17:21:50 +00:00
void ast_http_create_response ( struct ast_tcptls_session_instance * ser , int status_code ,
const char * status_title , struct ast_str * http_header_data , const char * text )
2009-04-23 20:36:35 +00:00
{
2015-01-30 17:21:50 +00:00
char server_name [ MAX_SERVER_NAME_LENGTH ];
struct ast_str * server_address = ast_str_create ( MAX_SERVER_NAME_LENGTH );
2019-01-09 10:27:03 +00:00
struct ast_str * out = ast_str_create ( INITIAL_RESPONSE_BODY_BUFFER );
2008-03-19 15:41:54 +00:00
2015-01-30 17:21:50 +00:00
if ( ! http_header_data || ! server_address || ! out ) {
ast_free ( http_header_data );
ast_free ( server_address );
2009-04-23 20:36:35 +00:00
ast_free ( out );
2016-06-02 22:10:06 +03:00
if ( ser ) {
2015-01-30 17:21:50 +00:00
ast_debug ( 1 , "HTTP closing session. OOM. \n " );
2014-07-03 17:16:55 +00:00
ast_tcptls_close_session_file ( ser );
}
2009-04-23 20:36:35 +00:00
return ;
2008-03-19 15:41:54 +00:00
}
2015-01-30 17:21:50 +00:00
if ( ! ast_strlen_zero ( http_server_name )) {
ast_xml_escape ( http_server_name , server_name , sizeof ( server_name ));
ast_str_set ( & server_address ,
0 ,
"<address>%s</address> \r\n " ,
server_name );
}
ast_str_set ( & out ,
0 ,
"<!DOCTYPE HTML PUBLIC \" -//IETF//DTD HTML 2.0//EN \" > \r\n "
"<html><head> \r\n "
"<title>%d %s</title> \r\n "
"</head><body> \r\n "
"<h1>%s</h1> \r\n "
"<p>%s</p> \r\n "
"<hr /> \r\n "
"%s"
"</body></html> \r\n " ,
status_code ,
status_title ,
status_title ,
text ? text : "" ,
ast_str_buffer ( server_address ));
ast_free ( server_address );
ast_http_send ( ser ,
AST_HTTP_UNKNOWN ,
status_code ,
status_title ,
http_header_data ,
out ,
0 ,
0 );
2006-03-25 23:50:09 +00:00
}
2015-01-30 17:21:50 +00:00
void ast_http_auth ( struct ast_tcptls_session_instance * ser , const char * realm ,
const unsigned long nonce , const unsigned long opaque , int stale ,
const char * text )
2009-04-23 20:36:35 +00:00
{
2015-01-30 17:21:50 +00:00
int status_code = 401 ;
char * status_title = "Unauthorized" ;
struct ast_str * http_header_data = ast_str_create ( DEFAULT_RESPONSE_HEADER_LENGTH );
if ( http_header_data ) {
ast_str_set ( & http_header_data ,
0 ,
"WWW-authenticate: Digest algorithm=MD5, realm= \" %s \" , nonce= \" %08lx \" , qop= \" auth \" , opaque= \" %08lx \" %s \r\n "
"Content-type: text/html \r\n " ,
realm ? realm : "Asterisk" ,
nonce ,
opaque ,
stale ? ", stale=true" : "" );
}
ast_http_create_response ( ser ,
status_code ,
status_title ,
http_header_data ,
text );
}
2009-04-23 20:36:35 +00:00
2015-01-30 17:21:50 +00:00
void ast_http_error ( struct ast_tcptls_session_instance * ser , int status_code ,
const char * status_title , const char * text )
{
struct ast_str * http_header_data = ast_str_create ( DEFAULT_RESPONSE_HEADER_LENGTH );
2009-04-23 20:36:35 +00:00
2015-01-30 17:21:50 +00:00
if ( http_header_data ) {
ast_str_set ( & http_header_data , 0 , "Content-type: text/html \r\n " );
}
2009-04-23 20:36:35 +00:00
2015-01-30 17:21:50 +00:00
ast_http_create_response ( ser ,
status_code ,
status_title ,
http_header_data ,
text );
2009-04-23 20:36:35 +00:00
}
2014-07-03 17:16:55 +00:00
/*!
* \brief Link the new uri into the list.
2007-01-24 09:31:26 +00:00
*
* They are sorted by length of
2006-10-16 08:38:35 +00:00
* the string, not alphabetically. Duplicate entries are not replaced,
* but the insertion order (using <= and not just <) makes sure that
* more recent insertions hide older ones.
* On a lookup, we just scan the list and stop at the first matching entry.
*/
2006-03-25 23:50:09 +00:00
int ast_http_uri_link ( struct ast_http_uri * urih )
{
2006-12-23 20:13:14 +00:00
struct ast_http_uri * uri ;
2006-10-16 08:38:35 +00:00
int len = strlen ( urih -> uri );
2006-12-28 20:02:41 +00:00
AST_RWLIST_WRLOCK ( & uris );
2006-12-23 20:13:14 +00:00
2016-08-17 15:10:54 +02:00
urih -> prefix = prefix ;
2009-04-23 20:36:35 +00:00
if ( AST_RWLIST_EMPTY ( & uris ) || strlen ( AST_RWLIST_FIRST ( & uris ) -> uri ) <= len ) {
2006-12-28 20:02:41 +00:00
AST_RWLIST_INSERT_HEAD ( & uris , urih , entry );
AST_RWLIST_UNLOCK ( & uris );
2006-12-23 20:13:14 +00:00
return 0 ;
2006-03-25 23:50:09 +00:00
}
2006-12-23 20:13:14 +00:00
2006-12-28 20:02:41 +00:00
AST_RWLIST_TRAVERSE ( & uris , uri , entry ) {
2008-03-19 16:18:29 +00:00
if ( AST_RWLIST_NEXT ( uri , entry ) &&
2009-04-23 20:36:35 +00:00
strlen ( AST_RWLIST_NEXT ( uri , entry ) -> uri ) <= len ) {
2006-12-28 20:02:41 +00:00
AST_RWLIST_INSERT_AFTER ( & uris , uri , urih , entry );
2009-04-23 20:36:35 +00:00
AST_RWLIST_UNLOCK ( & uris );
2008-03-19 15:41:54 +00:00
2006-12-23 20:13:14 +00:00
return 0 ;
}
}
2006-12-28 20:02:41 +00:00
AST_RWLIST_INSERT_TAIL ( & uris , urih , entry );
2006-12-23 20:13:14 +00:00
2006-12-28 20:02:41 +00:00
AST_RWLIST_UNLOCK ( & uris );
2009-04-23 20:36:35 +00:00
2006-03-25 23:50:09 +00:00
return 0 ;
2009-04-23 20:36:35 +00:00
}
2006-03-25 23:50:09 +00:00
void ast_http_uri_unlink ( struct ast_http_uri * urih )
{
2006-12-28 20:02:41 +00:00
AST_RWLIST_WRLOCK ( & uris );
AST_RWLIST_REMOVE ( & uris , urih , entry );
AST_RWLIST_UNLOCK ( & uris );
2006-03-25 23:50:09 +00:00
}
2008-04-02 15:25:48 +00:00
void ast_http_uri_unlink_all_with_key ( const char * key )
2007-04-06 21:16:38 +00:00
{
2008-04-02 15:25:48 +00:00
struct ast_http_uri * urih ;
AST_RWLIST_WRLOCK ( & uris );
AST_RWLIST_TRAVERSE_SAFE_BEGIN ( & uris , urih , entry ) {
if ( ! strcmp ( urih -> key , key )) {
AST_RWLIST_REMOVE_CURRENT ( entry );
2012-04-10 19:58:04 +00:00
if ( urih -> dmallocd ) {
ast_free ( urih -> data );
}
if ( urih -> mallocd ) {
ast_free ( urih );
}
2008-08-04 16:34:04 +00:00
}
2007-04-06 21:16:38 +00:00
}
2009-04-10 03:55:27 +00:00
AST_RWLIST_TRAVERSE_SAFE_END ;
2008-07-29 21:23:43 +00:00
AST_RWLIST_UNLOCK ( & uris );
2007-04-06 21:16:38 +00:00
}
2013-12-24 16:50:48 +00:00
/*!
* \brief Retrieves the header with the given field name.
*
* \param headers Headers to search.
* \param field_name Name of the header to find.
* \return Associated header value.
* \return \c NULL if header is not present.
*/
2014-07-03 17:16:55 +00:00
static const char * get_header ( struct ast_variable * headers , const char * field_name )
2013-12-24 16:50:48 +00:00
{
struct ast_variable * v ;
for ( v = headers ; v ; v = v -> next ) {
if ( ! strcasecmp ( v -> name , field_name )) {
return v -> value ;
}
}
return NULL ;
}
2013-12-13 17:19:23 +00:00
/*!
* \brief Retrieves the content type specified in the "Content-Type" header.
*
* This function only returns the "type/subtype" and any trailing parameter is
* not included.
*
* \note the return value is an allocated string that needs to be freed.
*
* \retval the content type/subtype or NULL if the header is not found.
*/
static char * get_content_type ( struct ast_variable * headers )
2013-11-27 15:48:39 +00:00
{
2013-12-24 16:50:48 +00:00
const char * content_type = get_header ( headers , "Content-Type" );
const char * param ;
size_t size ;
2013-11-27 15:48:39 +00:00
2013-12-24 16:50:48 +00:00
if ( ! content_type ) {
return NULL ;
2013-11-27 15:48:39 +00:00
}
2013-12-24 16:50:48 +00:00
param = strchr ( content_type , ';' );
size = param ? param - content_type : strlen ( content_type );
return ast_strndup ( content_type , size );
2013-11-27 15:48:39 +00:00
}
2013-12-24 16:50:48 +00:00
/*!
* \brief Returns the value of the Content-Length header.
*
* \param headers HTTP headers.
2014-07-03 17:16:55 +00:00
*
* \retval length Value of the Content-Length header.
* \retval 0 if header is not present.
* \retval -1 if header is invalid.
2013-12-24 16:50:48 +00:00
*/
2013-11-27 15:48:39 +00:00
static int get_content_length ( struct ast_variable * headers )
{
2013-12-24 16:50:48 +00:00
const char * content_length = get_header ( headers , "Content-Length" );
2014-07-03 17:16:55 +00:00
int length ;
2013-11-27 15:48:39 +00:00
2013-12-24 16:50:48 +00:00
if ( ! content_length ) {
/* Missing content length; assume zero */
return 0 ;
2013-11-27 15:48:39 +00:00
}
2014-07-03 17:16:55 +00:00
length = 0 ;
if ( sscanf ( content_length , "%30d" , & length ) != 1 ) {
/* Invalid Content-Length value */
length = - 1 ;
}
return length ;
2013-12-24 16:50:48 +00:00
}
/*!
* \brief Returns the value of the Transfer-Encoding header.
*
* \param headers HTTP headers.
2014-07-03 17:16:55 +00:00
* \retval string Value of the Transfer-Encoding header.
* \retval NULL if header is not present.
2013-12-24 16:50:48 +00:00
*/
static const char * get_transfer_encoding ( struct ast_variable * headers )
{
return get_header ( headers , "Transfer-Encoding" );
2013-11-27 15:48:39 +00:00
}
2014-01-17 20:51:19 +00:00
/*!
2014-07-03 17:16:55 +00:00
* \internal
* \brief Determine if the HTTP peer wants the connection closed.
*
* \param headers List of HTTP headers
*
* \retval 0 keep connection open.
* \retval -1 close connection.
*/
static int http_check_connection_close ( struct ast_variable * headers )
{
const char * connection = get_header ( headers , "Connection" );
int close_connection = 0 ;
if ( connection && ! strcasecmp ( connection , "close" )) {
close_connection = - 1 ;
}
return close_connection ;
}
void ast_http_request_close_on_completion ( struct ast_tcptls_session_instance * ser )
{
struct http_worker_private_data * request = ser -> private_data ;
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
}
/*!
* \internal
* \brief Initialize the request tracking information in case of early failure.
* \since 12.4.0
*
* \param request Request tracking information.
*
* \return Nothing
*/
static void http_request_tracking_init ( struct http_worker_private_data * request )
{
ast_set_flags_to ( & request -> flags ,
HTTP_FLAG_HAS_BODY | HTTP_FLAG_BODY_READ | HTTP_FLAG_CLOSE_ON_COMPLETION ,
/* Assume close in case request fails early */
HTTP_FLAG_CLOSE_ON_COMPLETION );
}
/*!
* \internal
* \brief Setup the HTTP request tracking information.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
* \param headers List of HTTP headers.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_request_tracking_setup ( struct ast_tcptls_session_instance * ser , struct ast_variable * headers )
{
struct http_worker_private_data * request = ser -> private_data ;
const char * transfer_encoding ;
ast_set_flags_to ( & request -> flags ,
HTTP_FLAG_HAS_BODY | HTTP_FLAG_BODY_READ | HTTP_FLAG_CLOSE_ON_COMPLETION ,
http_check_connection_close ( headers ) ? HTTP_FLAG_CLOSE_ON_COMPLETION : 0 );
transfer_encoding = get_transfer_encoding ( headers );
if ( transfer_encoding && ! strcasecmp ( transfer_encoding , "chunked" )) {
request -> body_length = - 1 ;
ast_set_flag ( & request -> flags , HTTP_FLAG_HAS_BODY );
return 0 ;
}
request -> body_length = get_content_length ( headers );
if ( 0 < request -> body_length ) {
ast_set_flag ( & request -> flags , HTTP_FLAG_HAS_BODY );
} else if ( request -> body_length < 0 ) {
/* Invalid Content-Length */
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
ast_http_error ( ser , 400 , "Bad Request" , "Invalid Content-Length in request!" );
return - 1 ;
}
return 0 ;
}
void ast_http_body_read_status ( struct ast_tcptls_session_instance * ser , int read_success )
{
struct http_worker_private_data * request ;
request = ser -> private_data ;
if ( ! ast_test_flag ( & request -> flags , HTTP_FLAG_HAS_BODY )
|| ast_test_flag ( & request -> flags , HTTP_FLAG_BODY_READ )) {
/* No body to read. */
return ;
}
ast_set_flag ( & request -> flags , HTTP_FLAG_BODY_READ );
if ( ! read_success ) {
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
}
}
/*!
* \internal
* \brief Read the next length bytes from the HTTP body.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
* \param buf Where to put the contents reading.
* \param length How much contents to read.
* \param what_getting Name of the contents reading.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_body_read_contents ( struct ast_tcptls_session_instance * ser , char * buf , int length , const char * what_getting )
{
int res ;
2019-01-09 10:27:03 +00:00
int total = 0 ;
2014-07-03 17:16:55 +00:00
2016-06-02 22:10:06 +03:00
/* Stream is in exclusive mode so we get it all if possible. */
2019-01-09 10:27:03 +00:00
while ( total != length ) {
res = ast_iostream_read ( ser -> stream , buf + total , length - total );
if ( res <= 0 ) {
break ;
}
total += res ;
}
if ( total != length ) {
ast_log ( LOG_WARNING , "Wrong HTTP content read. Request %s (Wanted %d, Read %d) \n " ,
what_getting , length , res );
2014-07-03 17:16:55 +00:00
return - 1 ;
}
2019-01-09 10:27:03 +00:00
2014-07-03 17:16:55 +00:00
return 0 ;
}
/*!
* \internal
* \brief Read and discard the next length bytes from the HTTP body.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
* \param length How much contents to discard
* \param what_getting Name of the contents discarding.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_body_discard_contents ( struct ast_tcptls_session_instance * ser , int length , const char * what_getting )
{
2016-06-02 22:10:06 +03:00
ssize_t res ;
res = ast_iostream_discard ( ser -> stream , length );
if ( res < length ) {
ast_log ( LOG_WARNING , "Short HTTP request %s (Wanted %d but got %zd) \n " ,
what_getting , length , res );
2014-07-03 17:16:55 +00:00
return - 1 ;
}
return 0 ;
}
/*!
* \internal
2014-01-17 20:51:19 +00:00
* \brief decode chunked mode hexadecimal value
*
* \param s string to decode
* \param len length of string
2014-07-03 17:16:55 +00:00
*
* \retval length on success.
* \retval -1 on error.
2014-01-17 20:51:19 +00:00
*/
static int chunked_atoh ( const char * s , int len )
{
int value = 0 ;
char c ;
if ( * s < '0' ) {
/* zero value must be 0\n not just \n */
return - 1 ;
}
2014-07-03 17:16:55 +00:00
while ( len -- ) {
c = * s ++ ;
if ( c == '\x0D' ) {
2014-01-17 20:51:19 +00:00
return value ;
}
2014-07-03 17:16:55 +00:00
if ( c == ';' ) {
/* We have a chunk-extension that we don't care about. */
while ( len -- ) {
if ( * s ++ == '\x0D' ) {
return value ;
}
}
break ;
}
2014-01-17 20:51:19 +00:00
value <<= 4 ;
if ( c >= '0' && c <= '9' ) {
value += c - '0' ;
continue ;
}
if ( c >= 'a' && c <= 'f' ) {
value += 10 + c - 'a' ;
continue ;
}
if ( c >= 'A' && c <= 'F' ) {
value += 10 + c - 'A' ;
continue ;
}
/* invalid character */
return - 1 ;
}
/* end of string */
return - 1 ;
}
2014-07-03 17:16:55 +00:00
/*!
* \internal
* \brief Read and convert the chunked body header length.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
*
* \retval length Size of chunk to expect.
* \retval -1 on error.
*/
static int http_body_get_chunk_length ( struct ast_tcptls_session_instance * ser )
{
int length ;
char header_line [ MAX_HTTP_LINE_LENGTH ];
/* get the line of hexadecimal giving chunk-size w/ optional chunk-extension */
2016-06-02 22:10:06 +03:00
if ( ast_iostream_gets ( ser -> stream , header_line , sizeof ( header_line )) <= 0 ) {
2014-07-03 17:16:55 +00:00
ast_log ( LOG_WARNING , "Short HTTP read of chunked header \n " );
return - 1 ;
}
length = chunked_atoh ( header_line , strlen ( header_line ));
if ( length < 0 ) {
ast_log ( LOG_WARNING , "Invalid HTTP chunk size \n " );
return - 1 ;
}
return length ;
}
/*!
* \internal
* \brief Read and check the chunk contents line termination.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_body_check_chunk_sync ( struct ast_tcptls_session_instance * ser )
{
int res ;
char chunk_sync [ 2 ];
/* Stay in fread until get the expected CRLF or timeout. */
2016-06-02 22:10:06 +03:00
res = ast_iostream_read ( ser -> stream , chunk_sync , sizeof ( chunk_sync ));
if ( res < sizeof ( chunk_sync )) {
2014-07-03 17:16:55 +00:00
ast_log ( LOG_WARNING , "Short HTTP chunk sync read (Wanted %zu) \n " ,
sizeof ( chunk_sync ));
return - 1 ;
}
if ( chunk_sync [ 0 ] != 0x0D || chunk_sync [ 1 ] != 0x0A ) {
2014-12-17 10:23:32 +00:00
ast_log ( LOG_WARNING , "HTTP chunk sync bytes wrong (0x%02hhX, 0x%02hhX) \n " ,
( unsigned char ) chunk_sync [ 0 ], ( unsigned char ) chunk_sync [ 1 ]);
2014-07-03 17:16:55 +00:00
return - 1 ;
}
return 0 ;
}
/*!
* \internal
* \brief Read and discard any chunked trailer entity-header lines.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_body_discard_chunk_trailer_headers ( struct ast_tcptls_session_instance * ser )
{
char header_line [ MAX_HTTP_LINE_LENGTH ];
for (;;) {
2016-06-02 22:10:06 +03:00
if ( ast_iostream_gets ( ser -> stream , header_line , sizeof ( header_line )) <= 0 ) {
2014-07-03 17:16:55 +00:00
ast_log ( LOG_WARNING , "Short HTTP read of chunked trailer header \n " );
return - 1 ;
}
/* Trim trailing whitespace */
ast_trim_blanks ( header_line );
if ( ast_strlen_zero ( header_line )) {
/* A blank line ends the chunked-body */
break ;
}
}
return 0 ;
}
int ast_http_body_discard ( struct ast_tcptls_session_instance * ser )
{
struct http_worker_private_data * request ;
request = ser -> private_data ;
if ( ! ast_test_flag ( & request -> flags , HTTP_FLAG_HAS_BODY )
|| ast_test_flag ( & request -> flags , HTTP_FLAG_BODY_READ )) {
/* No body to read or it has already been read. */
return 0 ;
}
ast_set_flag ( & request -> flags , HTTP_FLAG_BODY_READ );
ast_debug ( 1 , "HTTP discarding unused request body \n " );
ast_assert ( request -> body_length != 0 );
if ( 0 < request -> body_length ) {
if ( http_body_discard_contents ( ser , request -> body_length , "body" )) {
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
return - 1 ;
}
return 0 ;
}
/* parse chunked-body */
for (;;) {
int length ;
length = http_body_get_chunk_length ( ser );
if ( length < 0 ) {
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
return - 1 ;
}
if ( length == 0 ) {
/* parsed last-chunk */
break ;
}
if ( http_body_discard_contents ( ser , length , "chunk-data" )
|| http_body_check_chunk_sync ( ser )) {
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
return - 1 ;
}
}
/* Read and discard any trailer entity-header lines. */
if ( http_body_discard_chunk_trailer_headers ( ser )) {
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
return - 1 ;
}
return 0 ;
}
2014-01-17 20:51:19 +00:00
/*!
* \brief Returns the contents (body) of the HTTP request
*
* \param return_length ptr to int that returns content length
2014-07-03 17:16:55 +00:00
* \param ser HTTP TCP/TLS session object
2014-01-17 20:51:19 +00:00
* \param headers List of HTTP headers
* \return ptr to content (zero terminated) or NULL on failure
* \note Since returned ptr is malloc'd, it should be free'd by caller
*/
static char * ast_http_get_contents ( int * return_length ,
struct ast_tcptls_session_instance * ser , struct ast_variable * headers )
{
2014-07-03 17:16:55 +00:00
struct http_worker_private_data * request ;
int content_length ;
int bufsize ;
2014-01-17 20:51:19 +00:00
char * buf ;
2014-07-03 17:16:55 +00:00
request = ser -> private_data ;
if ( ! ast_test_flag ( & request -> flags , HTTP_FLAG_HAS_BODY )) {
/* no content - not an error */
return NULL ;
}
if ( ast_test_flag ( & request -> flags , HTTP_FLAG_BODY_READ )) {
/* Already read the body. Cannot read again. Assume no content. */
ast_assert ( 0 );
return NULL ;
}
ast_set_flag ( & request -> flags , HTTP_FLAG_BODY_READ );
2014-01-17 20:51:19 +00:00
2014-07-03 17:16:55 +00:00
ast_debug ( 2 , "HTTP consuming request body \n " );
ast_assert ( request -> body_length != 0 );
if ( 0 < request -> body_length ) {
2014-01-17 20:51:19 +00:00
/* handle regular non-chunked content */
2014-07-03 17:16:55 +00:00
content_length = request -> body_length ;
if ( content_length > MAX_CONTENT_LENGTH ) {
ast_log ( LOG_WARNING , "Excessively long HTTP content. (%d > %d) \n " ,
content_length , MAX_CONTENT_LENGTH );
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
2014-01-17 20:51:19 +00:00
errno = EFBIG ;
return NULL ;
}
buf = ast_malloc ( content_length + 1 );
if ( ! buf ) {
/* Malloc sets ENOMEM */
2014-07-03 17:16:55 +00:00
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
2014-01-17 20:51:19 +00:00
return NULL ;
}
2014-07-03 17:16:55 +00:00
if ( http_body_read_contents ( ser , buf , content_length , "body" )) {
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
2014-01-17 20:51:19 +00:00
errno = EIO ;
ast_free ( buf );
return NULL ;
}
2014-07-03 17:16:55 +00:00
2014-01-17 20:51:19 +00:00
buf [ content_length ] = 0 ;
* return_length = content_length ;
return buf ;
}
/* pre-allocate buffer */
2014-07-03 17:16:55 +00:00
bufsize = 250 ;
2014-01-17 20:51:19 +00:00
buf = ast_malloc ( bufsize );
if ( ! buf ) {
2014-07-03 17:16:55 +00:00
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
2014-01-17 20:51:19 +00:00
return NULL ;
}
2014-07-03 17:16:55 +00:00
/* parse chunked-body */
content_length = 0 ;
for (;;) {
int chunk_length ;
chunk_length = http_body_get_chunk_length ( ser );
2014-01-17 20:51:19 +00:00
if ( chunk_length < 0 ) {
2014-07-03 17:16:55 +00:00
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
2014-01-17 20:51:19 +00:00
errno = EIO ;
ast_free ( buf );
return NULL ;
}
2014-07-03 17:16:55 +00:00
if ( chunk_length == 0 ) {
/* parsed last-chunk */
break ;
}
if ( content_length + chunk_length > MAX_CONTENT_LENGTH ) {
2014-01-17 20:51:19 +00:00
ast_log ( LOG_WARNING ,
2014-07-03 17:16:55 +00:00
"Excessively long HTTP accumulated chunked body. (%d + %d > %d) \n " ,
content_length , chunk_length , MAX_CONTENT_LENGTH );
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
2014-01-17 20:51:19 +00:00
errno = EFBIG ;
ast_free ( buf );
return NULL ;
}
/* insure buffer is large enough +1 */
2014-07-03 17:16:55 +00:00
if ( content_length + chunk_length >= bufsize ) {
char * new_buf ;
/* Increase bufsize until it can handle the expected data. */
do {
bufsize *= 2 ;
} while ( content_length + chunk_length >= bufsize );
new_buf = ast_realloc ( buf , bufsize );
if ( ! new_buf ) {
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
ast_free ( buf );
2014-01-17 20:51:19 +00:00
return NULL ;
}
2014-07-03 17:16:55 +00:00
buf = new_buf ;
2014-01-17 20:51:19 +00:00
}
2014-07-03 17:16:55 +00:00
if ( http_body_read_contents ( ser , buf + content_length , chunk_length , "chunk-data" )
|| http_body_check_chunk_sync ( ser )) {
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
2014-01-17 20:51:19 +00:00
errno = EIO ;
ast_free ( buf );
return NULL ;
}
content_length += chunk_length ;
2014-07-03 17:16:55 +00:00
}
2014-01-17 20:51:19 +00:00
2014-07-03 17:16:55 +00:00
/*
* Read and discard any trailer entity-header lines
* which we don't care about.
*
* XXX In the future we may need to add the trailer headers
* to the passed in headers list rather than discarding them.
*/
if ( http_body_discard_chunk_trailer_headers ( ser )) {
ast_set_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION );
errno = EIO ;
ast_free ( buf );
return NULL ;
}
2014-01-17 20:51:19 +00:00
buf [ content_length ] = 0 ;
* return_length = content_length ;
return buf ;
}
2013-11-27 15:48:39 +00:00
struct ast_json * ast_http_get_json (
struct ast_tcptls_session_instance * ser , struct ast_variable * headers )
{
int content_length = 0 ;
struct ast_json * body ;
RAII_VAR ( char * , buf , NULL , ast_free );
2013-12-13 17:19:23 +00:00
RAII_VAR ( char * , type , get_content_type ( headers ), ast_free );
2013-11-27 15:48:39 +00:00
/* Use errno to distinguish errors from no body */
errno = 0 ;
2013-12-13 17:19:23 +00:00
if ( ast_strlen_zero ( type ) || strcasecmp ( type , "application/json" )) {
2014-07-03 17:16:55 +00:00
/* Content type is not JSON. Don't read the body. */
2013-11-27 15:48:39 +00:00
return NULL ;
}
2014-01-17 20:51:19 +00:00
buf = ast_http_get_contents ( & content_length , ser , headers );
2014-07-03 17:16:55 +00:00
if ( ! buf || ! content_length ) {
/*
* errno already set
* or it is not an error to have zero content
*/
2014-03-18 15:45:04 +00:00
return NULL ;
}
2013-11-27 15:48:39 +00:00
body = ast_json_load_buf ( buf , content_length , NULL );
2014-07-03 17:16:55 +00:00
if ( ! body ) {
2013-11-27 15:48:39 +00:00
/* Failed to parse JSON; treat as an I/O error */
errno = EIO ;
return NULL ;
}
return body ;
}
2009-04-23 20:36:35 +00:00
/*
* get post variables from client Request Entity-Body, if content type is
* application/x-www-form-urlencoded
*/
struct ast_variable * ast_http_get_post_vars (
struct ast_tcptls_session_instance * ser , struct ast_variable * headers )
{
int content_length = 0 ;
struct ast_variable * v , * post_vars = NULL , * prev = NULL ;
2013-11-08 17:29:53 +00:00
char * var , * val ;
2014-07-03 17:16:55 +00:00
RAII_VAR ( char * , buf , NULL , ast_free );
2013-12-13 17:19:23 +00:00
RAII_VAR ( char * , type , get_content_type ( headers ), ast_free );
2009-04-23 20:36:35 +00:00
2013-11-08 17:29:53 +00:00
/* Use errno to distinguish errors from no params */
errno = 0 ;
2013-12-13 17:19:23 +00:00
if ( ast_strlen_zero ( type ) ||
strcasecmp ( type , "application/x-www-form-urlencoded" )) {
2014-07-03 17:16:55 +00:00
/* Content type is not form data. Don't read the body. */
2013-11-27 15:48:39 +00:00
return NULL ;
2009-04-23 20:36:35 +00:00
}
2014-01-17 20:51:19 +00:00
buf = ast_http_get_contents ( & content_length , ser , headers );
2014-07-03 17:16:55 +00:00
if ( ! buf || ! content_length ) {
/*
* errno already set
* or it is not an error to have zero content
*/
2013-11-08 17:29:53 +00:00
return NULL ;
2013-01-02 15:39:42 +00:00
}
2009-04-23 20:36:35 +00:00
while (( val = strsep ( & buf , "&" ))) {
var = strsep ( & val , "=" );
if ( val ) {
2011-01-24 18:59:22 +00:00
ast_uri_decode ( val , ast_uri_http_legacy );
2009-04-23 20:36:35 +00:00
} else {
val = "" ;
}
2011-01-24 18:59:22 +00:00
ast_uri_decode ( var , ast_uri_http_legacy );
2009-04-23 20:36:35 +00:00
if (( v = ast_variable_new ( var , val , "" ))) {
if ( post_vars ) {
prev -> next = v ;
} else {
post_vars = v ;
}
prev = v ;
}
}
2013-07-18 14:50:56 +00:00
2009-04-23 20:36:35 +00:00
return post_vars ;
}
static int handle_uri ( struct ast_tcptls_session_instance * ser , char * uri ,
enum ast_http_method method , struct ast_variable * headers )
2006-03-25 23:50:09 +00:00
{
char * c ;
2014-07-03 17:16:55 +00:00
int res = 0 ;
2006-10-18 04:05:56 +00:00
char * params = uri ;
2008-03-19 16:18:29 +00:00
struct ast_http_uri * urih = NULL ;
2006-10-18 04:05:56 +00:00
int l ;
2009-04-23 20:36:35 +00:00
struct ast_variable * get_vars = NULL , * v , * prev = NULL ;
2006-12-23 20:13:14 +00:00
struct http_uri_redirect * redirect ;
2009-04-23 20:36:35 +00:00
2011-02-22 15:33:56 +00:00
ast_debug ( 2 , "HTTP Request URI is %s \n " , uri );
2011-02-21 14:14:41 +00:00
2009-04-23 20:36:35 +00:00
strsep ( & params , "?" );
/* Extract arguments from the request and store them in variables. */
if ( params ) {
char * var , * val ;
while (( val = strsep ( & params , "&" ))) {
var = strsep ( & val , "=" );
if ( val ) {
2011-01-24 18:59:22 +00:00
ast_uri_decode ( val , ast_uri_http_legacy );
2009-04-23 20:36:35 +00:00
} else {
val = "" ;
}
2011-01-24 18:59:22 +00:00
ast_uri_decode ( var , ast_uri_http_legacy );
2009-04-23 20:36:35 +00:00
if (( v = ast_variable_new ( var , val , "" ))) {
if ( get_vars ) {
prev -> next = v ;
2008-03-19 15:41:54 +00:00
} else {
2009-04-23 20:36:35 +00:00
get_vars = v ;
2008-03-19 15:41:54 +00:00
}
2009-04-23 20:36:35 +00:00
prev = v ;
2006-03-25 23:50:09 +00:00
}
}
}
2006-10-16 08:38:35 +00:00
2006-12-28 20:05:00 +00:00
AST_RWLIST_RDLOCK ( & uri_redirects );
AST_RWLIST_TRAVERSE ( & uri_redirects , redirect , entry ) {
2006-12-23 20:13:14 +00:00
if ( ! strcasecmp ( uri , redirect -> target )) {
2009-04-23 20:36:35 +00:00
struct ast_str * http_header = ast_str_create ( 128 );
2014-07-03 17:16:55 +00:00
if ( ! http_header ) {
ast_http_request_close_on_completion ( ser );
ast_http_error ( ser , 500 , "Server Error" , "Out of memory" );
break ;
}
2009-04-23 20:36:35 +00:00
ast_str_set ( & http_header , 0 , "Location: %s \r\n " , redirect -> dest );
ast_http_send ( ser , method , 302 , "Moved Temporarily" , http_header , NULL , 0 , 0 );
2006-12-23 20:13:14 +00:00
break ;
}
}
2006-12-28 20:05:00 +00:00
AST_RWLIST_UNLOCK ( & uri_redirects );
2008-03-19 15:41:54 +00:00
if ( redirect ) {
2006-12-23 20:13:14 +00:00
goto cleanup ;
2008-03-19 15:41:54 +00:00
}
2006-12-23 20:13:14 +00:00
2008-03-31 20:45:05 +00:00
/* We want requests to start with the (optional) prefix and '/' */
2008-03-31 21:01:59 +00:00
l = strlen ( prefix );
if ( ! strncasecmp ( uri , prefix , l ) && uri [ l ] == '/' ) {
2006-10-18 04:05:56 +00:00
uri += l + 1 ;
2006-10-16 08:38:35 +00:00
/* scan registered uris to see if we match one. */
2006-12-28 20:02:41 +00:00
AST_RWLIST_RDLOCK ( & uris );
AST_RWLIST_TRAVERSE ( & uris , urih , entry ) {
2006-10-18 04:05:56 +00:00
l = strlen ( urih -> uri );
c = uri + l ; /* candidate */
2013-07-18 14:50:56 +00:00
ast_debug ( 2 , "match request [%s] with handler [%s] len %d \n " , uri , urih -> uri , l );
2009-04-23 20:36:35 +00:00
if ( strncasecmp ( urih -> uri , uri , l ) /* no match */
|| ( * c && * c != '/' )) { /* substring */
2006-10-18 04:05:56 +00:00
continue ;
2008-03-19 15:41:54 +00:00
}
if ( * c == '/' ) {
2006-10-18 04:05:56 +00:00
c ++ ;
2008-03-19 15:41:54 +00:00
}
2006-10-18 04:05:56 +00:00
if ( !* c || urih -> has_subtree ) {
2009-04-23 20:36:35 +00:00
uri = c ;
break ;
2006-03-25 23:50:09 +00:00
}
}
2006-12-28 20:02:41 +00:00
AST_RWLIST_UNLOCK ( & uris );
2009-04-23 20:36:35 +00:00
}
if ( urih ) {
2013-07-18 14:50:56 +00:00
ast_debug ( 1 , "Match made with [%s] \n " , urih -> uri );
2013-06-24 13:49:20 +00:00
if ( ! urih -> no_decode_uri ) {
ast_uri_decode ( uri , ast_uri_http_legacy );
}
2009-04-23 20:36:35 +00:00
res = urih -> callback ( ser , urih , uri , method , get_vars , headers );
2006-03-25 23:50:09 +00:00
} else {
2013-07-18 14:50:56 +00:00
ast_debug ( 1 , "Requested URI [%s] has no handler \n " , uri );
2009-04-23 20:36:35 +00:00
ast_http_error ( ser , 404 , "Not Found" , "The requested URL was not found on this server." );
2006-03-25 23:50:09 +00:00
}
2006-12-23 20:13:14 +00:00
cleanup :
2009-04-23 20:36:35 +00:00
ast_variables_destroy ( get_vars );
return res ;
2006-03-25 23:50:09 +00:00
}
2014-03-10 17:21:01 +00:00
static struct ast_variable * parse_cookies ( const char * cookies )
2008-04-23 22:53:20 +00:00
{
2014-03-10 17:21:01 +00:00
char * parse = ast_strdupa ( cookies );
2008-04-23 22:53:20 +00:00
char * cur ;
struct ast_variable * vars = NULL , * var ;
2014-03-10 17:21:01 +00:00
while (( cur = strsep ( & parse , ";" ))) {
2008-04-23 22:53:20 +00:00
char * name , * val ;
2009-04-23 20:36:35 +00:00
2008-04-23 22:53:20 +00:00
name = val = cur ;
strsep ( & val , "=" );
if ( ast_strlen_zero ( name ) || ast_strlen_zero ( val )) {
continue ;
}
name = ast_strip ( name );
val = ast_strip_quoted ( val , " \" " , " \" " );
if ( ast_strlen_zero ( name ) || ast_strlen_zero ( val )) {
continue ;
}
2011-02-22 15:33:56 +00:00
ast_debug ( 1 , "HTTP Cookie, Name: '%s' Value: '%s' \n " , name , val );
2008-04-23 22:53:20 +00:00
var = ast_variable_new ( name , val , __FILE__ );
var -> next = vars ;
vars = var ;
}
return vars ;
}
2009-04-23 20:36:35 +00:00
/* get cookie from Request headers */
struct ast_variable * ast_http_get_cookies ( struct ast_variable * headers )
{
2014-03-10 17:21:01 +00:00
struct ast_variable * v , * cookies = NULL ;
2009-04-23 20:36:35 +00:00
for ( v = headers ; v ; v = v -> next ) {
2013-07-21 03:10:41 +00:00
if ( ! strcasecmp ( v -> name , "Cookie" )) {
2014-03-10 17:21:01 +00:00
ast_variables_destroy ( cookies );
cookies = parse_cookies ( v -> value );
2009-04-23 20:36:35 +00:00
}
}
return cookies ;
}
2014-07-03 17:16:55 +00:00
static struct ast_http_auth * auth_create ( const char * userid , const char * password )
2013-07-03 16:33:13 +00:00
{
2014-07-03 17:16:55 +00:00
struct ast_http_auth * auth ;
2013-07-03 16:33:13 +00:00
size_t userid_len ;
size_t password_len ;
if ( ! userid || ! password ) {
ast_log ( LOG_ERROR , "Invalid userid/password \n " );
return NULL ;
}
userid_len = strlen ( userid ) + 1 ;
password_len = strlen ( password ) + 1 ;
/* Allocate enough room to store everything in one memory block */
auth = ao2_alloc ( sizeof ( * auth ) + userid_len + password_len , NULL );
if ( ! auth ) {
return NULL ;
}
/* Put the userid right after the struct */
auth -> userid = ( char * )( auth + 1 );
strcpy ( auth -> userid , userid );
/* Put the password right after the userid */
auth -> password = auth -> userid + userid_len ;
strcpy ( auth -> password , password );
return auth ;
}
#define BASIC_PREFIX "Basic "
#define BASIC_LEN 6 /*!< strlen(BASIC_PREFIX) */
struct ast_http_auth * ast_http_get_auth ( struct ast_variable * headers )
{
struct ast_variable * v ;
for ( v = headers ; v ; v = v -> next ) {
const char * base64 ;
char decoded [ 256 ] = {};
char * username ;
char * password ;
2013-09-06 18:53:32 +00:00
#ifdef AST_DEVMODE
2013-07-03 16:33:13 +00:00
int cnt ;
2013-09-06 18:53:32 +00:00
#endif /* AST_DEVMODE */
2013-07-03 16:33:13 +00:00
if ( strcasecmp ( "Authorization" , v -> name ) != 0 ) {
continue ;
}
if ( ! ast_begins_with ( v -> value , BASIC_PREFIX )) {
ast_log ( LOG_DEBUG ,
"Unsupported Authorization scheme \n " );
continue ;
}
/* Basic auth header parsing. RFC 2617, section 2.
* credentials = "Basic" basic-credentials
* basic-credentials = base64-user-pass
* base64-user-pass = <base64 encoding of user-pass,
* except not limited to 76 char/line>
* user-pass = userid ":" password
*/
base64 = v -> value + BASIC_LEN ;
/* This will truncate "userid:password" lines to
* sizeof(decoded). The array is long enough that this shouldn't
* be a problem */
2013-09-06 18:53:32 +00:00
#ifdef AST_DEVMODE
cnt =
#endif /* AST_DEVMODE */
ast_base64decode (( unsigned char * ) decoded , base64 ,
2013-07-03 16:33:13 +00:00
sizeof ( decoded ) - 1 );
ast_assert ( cnt < sizeof ( decoded ));
/* Split the string at the colon */
password = decoded ;
username = strsep ( & password , ":" );
if ( ! password ) {
ast_log ( LOG_WARNING , "Invalid Authorization header \n " );
return NULL ;
}
return auth_create ( username , password );
}
return NULL ;
}
2009-04-23 20:36:35 +00:00
2014-06-05 17:22:35 +00:00
int ast_http_response_status_line ( const char * buf , const char * version , int code )
{
int status_code ;
size_t size = strlen ( version );
if ( strncmp ( buf , version , size ) || buf [ size ] != ' ' ) {
ast_log ( LOG_ERROR , "HTTP version not supported - "
"expected %s \n " , version );
return - 1 ;
}
/* skip to status code (version + space) */
buf += size + 1 ;
if ( sscanf ( buf , "%d" , & status_code ) != 1 ) {
ast_log ( LOG_ERROR , "Could not read HTTP status code - "
"%s \n " , buf );
return - 1 ;
}
return status_code ;
}
static void remove_excess_lws ( char * s )
{
char * p , * res = s ;
char * buf = ast_malloc ( strlen ( s ) + 1 );
char * buf_end ;
if ( ! buf ) {
return ;
}
buf_end = buf ;
while ( * s && * ( s = ast_skip_blanks ( s ))) {
p = s ;
s = ast_skip_nonblanks ( s );
if ( buf_end != buf ) {
* buf_end ++ = ' ' ;
}
memcpy ( buf_end , p , s - p );
buf_end += s - p ;
}
* buf_end = '\0' ;
/* safe since buf will always be less than or equal to res */
strcpy ( res , buf );
ast_free ( buf );
}
int ast_http_header_parse ( char * buf , char ** name , char ** value )
{
ast_trim_blanks ( buf );
if ( ast_strlen_zero ( buf )) {
return - 1 ;
}
* value = buf ;
* name = strsep ( value , ":" );
if ( !* value ) {
return 1 ;
}
* value = ast_skip_blanks ( * value );
if ( ast_strlen_zero ( * value ) || ast_strlen_zero ( * name )) {
return 1 ;
}
remove_excess_lws ( * value );
return 0 ;
}
int ast_http_header_match ( const char * name , const char * expected_name ,
const char * value , const char * expected_value )
{
if ( strcasecmp ( name , expected_name )) {
/* no value to validate if names don't match */
return 0 ;
}
if ( strcasecmp ( value , expected_value )) {
ast_log ( LOG_ERROR , "Invalid header value - expected %s "
"received %s" , value , expected_value );
return - 1 ;
}
return 1 ;
}
int ast_http_header_match_in ( const char * name , const char * expected_name ,
const char * value , const char * expected_value )
{
if ( strcasecmp ( name , expected_name )) {
/* no value to validate if names don't match */
return 0 ;
}
if ( ! strcasestr ( expected_value , value )) {
ast_log ( LOG_ERROR , "Header '%s' - could not locate '%s' "
"in '%s' \n " , name , value , expected_value );
return - 1 ;
}
return 1 ;
}
2014-03-10 17:21:01 +00:00
/*! Limit the number of request headers in case the sender is being ridiculous. */
#define MAX_HTTP_REQUEST_HEADERS 100
2014-07-03 17:16:55 +00:00
/*!
* \internal
* \brief Read the request headers.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
* \param headers Where to put the request headers list pointer.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_request_headers_get ( struct ast_tcptls_session_instance * ser , struct ast_variable ** headers )
2006-11-27 19:19:48 +00:00
{
2014-07-03 17:16:55 +00:00
struct ast_variable * tail = * headers ;
2014-03-10 17:21:01 +00:00
int remaining_headers ;
2014-07-03 17:16:55 +00:00
char header_line [ MAX_HTTP_LINE_LENGTH ];
2014-06-12 17:00:08 +00:00
2014-03-10 17:21:01 +00:00
remaining_headers = MAX_HTTP_REQUEST_HEADERS ;
2014-06-12 17:00:08 +00:00
for (;;) {
2018-08-30 14:42:06 -05:00
ssize_t len ;
2014-06-12 17:00:08 +00:00
char * name ;
char * value ;
2018-08-30 14:42:06 -05:00
len = ast_iostream_gets ( ser -> stream , header_line , sizeof ( header_line ));
if ( len <= 0 ) {
2014-06-12 17:00:08 +00:00
ast_http_error ( ser , 400 , "Bad Request" , "Timeout" );
2014-07-03 17:16:55 +00:00
return - 1 ;
2014-06-12 17:00:08 +00:00
}
2018-08-30 14:42:06 -05:00
if ( header_line [ len - 1 ] != '\n' ) {
/* We didn't get a full line */
ast_http_error ( ser , 400 , "Bad Request" ,
( len == sizeof ( header_line ) - 1 ) ? "Header line too long" : "Timeout" );
return - 1 ;
}
2009-04-23 20:36:35 +00:00
2006-10-18 04:05:56 +00:00
/* Trim trailing characters */
2009-04-23 20:36:35 +00:00
ast_trim_blanks ( header_line );
if ( ast_strlen_zero ( header_line )) {
2014-06-12 17:00:08 +00:00
/* A blank line ends the request header section. */
2006-10-18 04:05:56 +00:00
break ;
2008-03-19 15:41:54 +00:00
}
2008-08-01 21:56:07 +00:00
2009-04-23 20:36:35 +00:00
value = header_line ;
name = strsep ( & value , ":" );
if ( ! value ) {
continue ;
}
2008-08-01 21:56:07 +00:00
2009-04-23 20:36:35 +00:00
value = ast_skip_blanks ( value );
if ( ast_strlen_zero ( value ) || ast_strlen_zero ( name )) {
continue ;
}
ast_trim_blanks ( name );
2014-03-10 17:21:01 +00:00
if ( ! remaining_headers -- ) {
/* Too many headers. */
ast_http_error ( ser , 413 , "Request Entity Too Large" , "Too many headers" );
2014-07-03 17:16:55 +00:00
return - 1 ;
2014-03-10 17:21:01 +00:00
}
2014-07-03 17:16:55 +00:00
if ( !* headers ) {
* headers = ast_variable_new ( name , value , __FILE__ );
tail = * headers ;
2009-04-23 20:36:35 +00:00
} else {
tail -> next = ast_variable_new ( name , value , __FILE__ );
tail = tail -> next ;
2006-03-25 23:50:09 +00:00
}
2014-03-10 17:21:01 +00:00
if ( ! tail ) {
/*
* Variable allocation failure.
* Try to make some room.
*/
2014-07-03 17:16:55 +00:00
ast_variables_destroy ( * headers );
* headers = NULL ;
2014-03-10 17:21:01 +00:00
ast_http_error ( ser , 500 , "Server Error" , "Out of memory" );
2014-07-03 17:16:55 +00:00
return - 1 ;
2014-03-10 17:21:01 +00:00
}
2006-03-25 23:50:09 +00:00
}
2006-10-18 04:05:56 +00:00
2014-07-03 17:16:55 +00:00
return 0 ;
}
/*!
* \internal
* \brief Process a HTTP request.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
*
* \retval 0 Continue and process the next HTTP request.
* \retval -1 Fatal HTTP connection error. Force the HTTP connection closed.
*/
static int httpd_process_request ( struct ast_tcptls_session_instance * ser )
{
RAII_VAR ( struct ast_variable * , headers , NULL , ast_variables_destroy );
char * uri ;
char * method ;
const char * transfer_encoding ;
struct http_worker_private_data * request ;
enum ast_http_method http_method = AST_HTTP_UNKNOWN ;
int res ;
2018-08-30 14:42:06 -05:00
ssize_t len ;
2014-07-03 17:16:55 +00:00
char request_line [ MAX_HTTP_LINE_LENGTH ];
2018-08-30 14:42:06 -05:00
len = ast_iostream_gets ( ser -> stream , request_line , sizeof ( request_line ));
if ( len <= 0 ) {
2014-07-03 17:16:55 +00:00
return - 1 ;
}
/* Re-initialize the request body tracking data. */
request = ser -> private_data ;
http_request_tracking_init ( request );
2018-08-30 14:42:06 -05:00
if ( request_line [ len - 1 ] != '\n' ) {
/* We didn't get a full line */
ast_http_error ( ser , 400 , "Bad Request" ,
( len == sizeof ( request_line ) - 1 ) ? "Request line too long" : "Timeout" );
return - 1 ;
}
2014-07-03 17:16:55 +00:00
/* Get method */
method = ast_skip_blanks ( request_line );
uri = ast_skip_nonblanks ( method );
if ( * uri ) {
* uri ++ = '\0' ;
}
if ( ! strcasecmp ( method , "GET" )) {
http_method = AST_HTTP_GET ;
} else if ( ! strcasecmp ( method , "POST" )) {
http_method = AST_HTTP_POST ;
} else if ( ! strcasecmp ( method , "HEAD" )) {
http_method = AST_HTTP_HEAD ;
} else if ( ! strcasecmp ( method , "PUT" )) {
http_method = AST_HTTP_PUT ;
} else if ( ! strcasecmp ( method , "DELETE" )) {
http_method = AST_HTTP_DELETE ;
} else if ( ! strcasecmp ( method , "OPTIONS" )) {
http_method = AST_HTTP_OPTIONS ;
}
uri = ast_skip_blanks ( uri ); /* Skip white space */
if ( * uri ) { /* terminate at the first blank */
char * c = ast_skip_nonblanks ( uri );
if ( * c ) {
* c = '\0' ;
}
} else {
ast_http_error ( ser , 400 , "Bad Request" , "Invalid Request" );
return - 1 ;
}
2015-02-11 17:39:13 +00:00
if ( ast_shutdown_final ()) {
ast_http_error ( ser , 503 , "Service Unavailable" , "Shutdown in progress" );
return - 1 ;
}
2014-07-03 17:16:55 +00:00
/* process "Request Headers" lines */
if ( http_request_headers_get ( ser , & headers )) {
return - 1 ;
}
2013-12-24 16:50:48 +00:00
transfer_encoding = get_transfer_encoding ( headers );
/* Transfer encoding defaults to identity */
if ( ! transfer_encoding ) {
transfer_encoding = "identity" ;
}
/*
* RFC 2616, section 3.6, we should respond with a 501 for any transfer-
* codings we don't understand.
*/
2014-01-17 20:51:19 +00:00
if ( strcasecmp ( transfer_encoding , "identity" ) != 0 &&
strcasecmp ( transfer_encoding , "chunked" ) != 0 ) {
2013-12-24 16:50:48 +00:00
/* Transfer encodings not supported */
ast_http_error ( ser , 501 , "Unimplemented" , "Unsupported Transfer-Encoding." );
2014-07-03 17:16:55 +00:00
return - 1 ;
}
if ( http_request_tracking_setup ( ser , headers )
|| handle_uri ( ser , uri , http_method , headers )
|| ast_test_flag ( & request -> flags , HTTP_FLAG_CLOSE_ON_COMPLETION )) {
res = - 1 ;
} else {
res = 0 ;
}
return res ;
}
static void * httpd_helper_thread ( void * data )
{
struct ast_tcptls_session_instance * ser = data ;
int timeout ;
2016-06-02 22:10:06 +03:00
int arg = 1 ;
2014-07-03 17:16:55 +00:00
2016-06-02 22:10:06 +03:00
if ( ! ser ) {
2014-07-03 17:16:55 +00:00
ao2_cleanup ( ser );
return NULL ;
}
if ( ast_atomic_fetchadd_int ( & session_count , + 1 ) >= session_limit ) {
ast_log ( LOG_WARNING , "HTTP session count exceeded %d sessions. \n " ,
session_limit );
2013-12-24 16:50:48 +00:00
goto done ;
}
2014-07-03 17:16:55 +00:00
ast_debug ( 1 , "HTTP opening session. Top level \n " );
2013-12-24 16:50:48 +00:00
2014-07-03 17:16:55 +00:00
/*
* Here we set TCP_NODELAY on the socket to disable Nagle's algorithm.
* This is necessary to prevent delays (caused by buffering) as we
* write to the socket in bits and pieces.
*/
2017-03-18 13:30:32 -04:00
if ( setsockopt ( ast_iostream_get_fd ( ser -> stream ), IPPROTO_TCP , TCP_NODELAY , ( char * ) & arg , sizeof ( arg )) < 0 ) {
2016-06-02 22:10:06 +03:00
ast_log ( LOG_WARNING , "Failed to set TCP_NODELAY on HTTP connection: %s \n " , strerror ( errno ));
2014-07-03 17:16:55 +00:00
}
2016-06-02 22:10:06 +03:00
ast_iostream_nonblock ( ser -> stream );
2014-07-03 17:16:55 +00:00
/* Setup HTTP worker private data to keep track of request body reading. */
ao2_cleanup ( ser -> private_data );
ser -> private_data = ao2_alloc_options ( sizeof ( struct http_worker_private_data ), NULL ,
AO2_ALLOC_OPT_LOCK_NOLOCK );
if ( ! ser -> private_data ) {
ast_http_error ( ser , 500 , "Server Error" , "Out of memory" );
goto done ;
}
http_request_tracking_init ( ser -> private_data );
/* Determine initial HTTP request wait timeout. */
timeout = session_keep_alive ;
if ( timeout <= 0 ) {
/* Persistent connections not enabled. */
timeout = session_inactivity ;
}
if ( timeout < MIN_INITIAL_REQUEST_TIMEOUT ) {
timeout = MIN_INITIAL_REQUEST_TIMEOUT ;
}
/* We can let the stream wait for data to arrive. */
2016-06-02 22:10:06 +03:00
ast_iostream_set_exclusive_input ( ser -> stream , 1 );
2014-07-03 17:16:55 +00:00
for (;;) {
/* Wait for next potential HTTP request message. */
2016-06-02 22:10:06 +03:00
ast_iostream_set_timeout_idle_inactivity ( ser -> stream , timeout , session_inactivity );
if ( httpd_process_request ( ser )) {
/* Break the connection or the connection closed */
2014-07-03 17:16:55 +00:00
break ;
}
2016-06-02 22:10:06 +03:00
if ( ! ser -> stream ) {
/* Web-socket or similar that took the connection */
2014-07-03 17:16:55 +00:00
break ;
}
timeout = session_keep_alive ;
if ( timeout <= 0 ) {
/* Persistent connections not enabled. */
break ;
}
}
2009-04-23 20:36:35 +00:00
2011-04-21 18:32:50 +00:00
done :
ast_atomic_fetchadd_int ( & session_count , - 1 );
2016-06-02 22:10:06 +03:00
ast_debug ( 1 , "HTTP closing session. Top level \n " );
ast_tcptls_close_session_file ( ser );
2008-06-17 21:46:57 +00:00
ao2_ref ( ser , - 1 );
2006-03-25 23:50:09 +00:00
return NULL ;
}
2006-12-23 20:13:14 +00:00
/*!
* \brief Add a new URI redirect
* The entries in the redirect list are sorted by length, just like the list
* of URI handlers.
*/
static void add_redirect ( const char * value )
{
char * target , * dest ;
struct http_uri_redirect * redirect , * cur ;
2006-12-24 03:29:42 +00:00
unsigned int target_len ;
unsigned int total_len ;
2006-12-23 20:13:14 +00:00
dest = ast_strdupa ( value );
2006-12-24 21:01:02 +00:00
dest = ast_skip_blanks ( dest );
target = strsep ( & dest , " " );
target = ast_skip_blanks ( target );
target = strsep ( & target , " " ); /* trim trailing whitespace */
2006-12-23 20:13:14 +00:00
if ( ! dest ) {
ast_log ( LOG_WARNING , "Invalid redirect '%s' \n " , value );
return ;
}
2006-12-24 03:29:42 +00:00
target_len = strlen ( target ) + 1 ;
total_len = sizeof ( * redirect ) + target_len + strlen ( dest ) + 1 ;
2006-12-23 20:13:14 +00:00
2008-03-19 15:41:54 +00:00
if ( ! ( redirect = ast_calloc ( 1 , total_len ))) {
2006-12-23 20:13:14 +00:00
return ;
2008-03-19 15:41:54 +00:00
}
2006-12-24 03:29:42 +00:00
redirect -> dest = redirect -> target + target_len ;
strcpy ( redirect -> target , target );
strcpy ( redirect -> dest , dest );
2006-12-23 20:13:14 +00:00
2006-12-28 20:05:00 +00:00
AST_RWLIST_WRLOCK ( & uri_redirects );
2006-12-23 20:13:14 +00:00
2006-12-24 03:29:42 +00:00
target_len -- ; /* So we can compare directly with strlen() */
2009-04-23 20:36:35 +00:00
if ( AST_RWLIST_EMPTY ( & uri_redirects )
|| strlen ( AST_RWLIST_FIRST ( & uri_redirects ) -> target ) <= target_len ) {
2006-12-28 20:05:00 +00:00
AST_RWLIST_INSERT_HEAD ( & uri_redirects , redirect , entry );
AST_RWLIST_UNLOCK ( & uri_redirects );
2008-03-19 15:41:54 +00:00
2006-12-23 20:13:14 +00:00
return ;
}
2006-12-28 20:05:00 +00:00
AST_RWLIST_TRAVERSE ( & uri_redirects , cur , entry ) {
2009-04-23 20:36:35 +00:00
if ( AST_RWLIST_NEXT ( cur , entry )
&& strlen ( AST_RWLIST_NEXT ( cur , entry ) -> target ) <= target_len ) {
2006-12-28 20:05:00 +00:00
AST_RWLIST_INSERT_AFTER ( & uri_redirects , cur , redirect , entry );
2009-04-23 20:36:35 +00:00
AST_RWLIST_UNLOCK ( & uri_redirects );
2006-12-23 20:13:14 +00:00
return ;
}
}
2006-12-28 20:05:00 +00:00
AST_RWLIST_INSERT_TAIL ( & uri_redirects , redirect , entry );
2006-12-23 20:13:14 +00:00
2006-12-28 20:05:00 +00:00
AST_RWLIST_UNLOCK ( & uri_redirects );
2006-12-23 20:13:14 +00:00
}
2006-03-25 23:50:09 +00:00
static int __ast_http_load ( int reload )
{
struct ast_config * cfg ;
struct ast_variable * v ;
int enabled = 0 ;
2006-04-01 08:49:54 +00:00
int newenablestatic = 0 ;
2008-03-12 22:49:26 +00:00
char newprefix [ MAX_PREFIX ] = "" ;
2015-01-30 17:21:50 +00:00
char server_name [ MAX_SERVER_NAME_LENGTH ];
2006-12-23 20:13:14 +00:00
struct http_uri_redirect * redirect ;
2007-08-16 21:09:46 +00:00
struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
2010-10-29 20:46:06 +00:00
uint32_t bindport = DEFAULT_PORT ;
2013-06-25 01:12:58 +00:00
RAII_VAR ( struct ast_sockaddr * , addrs , NULL , ast_free );
2010-10-29 20:46:06 +00:00
int num_addrs = 0 ;
2012-02-02 18:55:05 +00:00
int http_tls_was_enabled = 0 ;
2007-08-16 21:09:46 +00:00
2008-09-12 23:30:03 +00:00
cfg = ast_config_load2 ( "http.conf" , "http" , config_flags );
2018-10-03 08:56:34 -04:00
if ( ! cfg || cfg == CONFIG_STATUS_FILEINVALID ) {
return 0 ;
}
/* Even if the http.conf hasn't been updated, the TLS certs/keys may have been */
if ( cfg == CONFIG_STATUS_FILEUNCHANGED ) {
if ( http_tls_cfg . enabled && ast_ssl_setup ( https_desc . tls_cfg )) {
ast_tcptls_server_start ( & https_desc );
}
2007-08-16 21:09:46 +00:00
return 0 ;
2008-03-19 15:41:54 +00:00
}
2006-04-03 18:38:28 +00:00
2012-02-02 18:55:05 +00:00
http_tls_was_enabled = ( reload && http_tls_cfg . enabled );
2006-12-07 16:42:29 +00:00
http_tls_cfg . enabled = 0 ;
2017-02-15 13:44:32 -07:00
ast_free ( http_tls_cfg . certfile );
2006-12-07 16:42:29 +00:00
http_tls_cfg . certfile = ast_strdup ( AST_CERTFILE );
2009-04-24 21:22:31 +00:00
2017-02-15 13:44:32 -07:00
ast_free ( http_tls_cfg . capath );
http_tls_cfg . capath = ast_strdup ( "" );
ast_free ( http_tls_cfg . pvtfile );
2009-04-24 21:22:31 +00:00
http_tls_cfg . pvtfile = ast_strdup ( "" );
2016-02-03 14:05:20 -04:00
/* Apply modern intermediate settings according to the Mozilla OpSec team as of July 30th, 2015 but disable TLSv1 */
ast_set_flag ( & http_tls_cfg . flags , AST_SSL_DISABLE_TLSV1 | AST_SSL_SERVER_CIPHER_ORDER );
2017-02-15 13:44:32 -07:00
ast_free ( http_tls_cfg . cipher );
2016-02-03 14:05:20 -04:00
http_tls_cfg . cipher = ast_strdup ( "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA" );
2006-10-22 12:02:35 +00:00
2006-12-28 20:05:00 +00:00
AST_RWLIST_WRLOCK ( & uri_redirects );
2008-03-19 15:41:54 +00:00
while (( redirect = AST_RWLIST_REMOVE_HEAD ( & uri_redirects , entry ))) {
2007-06-06 21:20:11 +00:00
ast_free ( redirect );
2008-03-19 15:41:54 +00:00
}
2006-12-28 20:05:00 +00:00
AST_RWLIST_UNLOCK ( & uri_redirects );
2006-12-23 20:13:14 +00:00
2012-02-02 18:55:05 +00:00
ast_sockaddr_setnull ( & https_desc . local_address );
2014-06-12 17:00:08 +00:00
session_limit = DEFAULT_SESSION_LIMIT ;
session_inactivity = DEFAULT_SESSION_INACTIVITY ;
2014-07-03 17:16:55 +00:00
session_keep_alive = DEFAULT_SESSION_KEEP_ALIVE ;
2015-01-30 17:21:50 +00:00
snprintf ( server_name , sizeof ( server_name ), "Asterisk/%s" , ast_get_version ());
2014-07-03 17:16:55 +00:00
v = ast_variable_browse ( cfg , "general" );
for (; v ; v = v -> next ) {
/* read tls config options while preventing unsupported options from being set */
if ( strcasecmp ( v -> name , "tlscafile" )
&& strcasecmp ( v -> name , "tlscapath" )
&& strcasecmp ( v -> name , "tlscadir" )
&& strcasecmp ( v -> name , "tlsverifyclient" )
&& strcasecmp ( v -> name , "tlsdontverifyserver" )
&& strcasecmp ( v -> name , "tlsclientmethod" )
&& strcasecmp ( v -> name , "sslclientmethod" )
&& ! ast_tls_read_conf ( & http_tls_cfg , & https_desc , v -> name , v -> value )) {
continue ;
}
2014-06-12 17:00:08 +00:00
2015-01-30 17:21:50 +00:00
if ( ! strcasecmp ( v -> name , "servername" )) {
if ( ! ast_strlen_zero ( v -> value )) {
ast_copy_string ( server_name , v -> value , sizeof ( server_name ));
} else {
server_name [ 0 ] = '\0' ;
}
} else if ( ! strcasecmp ( v -> name , "enabled" )) {
2014-07-03 17:16:55 +00:00
enabled = ast_true ( v -> value );
} else if ( ! strcasecmp ( v -> name , "enablestatic" )) {
newenablestatic = ast_true ( v -> value );
} else if ( ! strcasecmp ( v -> name , "bindport" )) {
if ( ast_parse_arg ( v -> value , PARSE_UINT32 | PARSE_IN_RANGE | PARSE_DEFAULT ,
& bindport , DEFAULT_PORT , 0 , 65535 )) {
ast_log ( LOG_WARNING , "Invalid port %s specified. Using default port %" PRId32 " \n " ,
v -> value , DEFAULT_PORT );
2009-04-29 14:39:48 +00:00
}
2014-07-03 17:16:55 +00:00
} else if ( ! strcasecmp ( v -> name , "bindaddr" )) {
if ( ! ( num_addrs = ast_sockaddr_resolve ( & addrs , v -> value , 0 , AST_AF_UNSPEC ))) {
ast_log ( LOG_WARNING , "Invalid bind address %s \n " , v -> value );
}
} else if ( ! strcasecmp ( v -> name , "prefix" )) {
if ( ! ast_strlen_zero ( v -> value )) {
newprefix [ 0 ] = '/' ;
ast_copy_string ( newprefix + 1 , v -> value , sizeof ( newprefix ) - 1 );
2006-12-23 20:13:14 +00:00
} else {
2014-07-03 17:16:55 +00:00
newprefix [ 0 ] = '\0' ;
2006-04-02 09:10:43 +00:00
}
2014-07-03 17:16:55 +00:00
} else if ( ! strcasecmp ( v -> name , "redirect" )) {
add_redirect ( v -> value );
} else if ( ! strcasecmp ( v -> name , "sessionlimit" )) {
if ( ast_parse_arg ( v -> value , PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE ,
& session_limit , DEFAULT_SESSION_LIMIT , 1 , INT_MAX )) {
ast_log ( LOG_WARNING , "Invalid %s '%s' at line %d of http.conf \n " ,
v -> name , v -> value , v -> lineno );
}
} else if ( ! strcasecmp ( v -> name , "session_inactivity" )) {
if ( ast_parse_arg ( v -> value , PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE ,
& session_inactivity , DEFAULT_SESSION_INACTIVITY , 1 , INT_MAX )) {
ast_log ( LOG_WARNING , "Invalid %s '%s' at line %d of http.conf \n " ,
v -> name , v -> value , v -> lineno );
}
} else if ( ! strcasecmp ( v -> name , "session_keep_alive" )) {
if ( sscanf ( v -> value , "%30d" , & session_keep_alive ) != 1
|| session_keep_alive < 0 ) {
session_keep_alive = DEFAULT_SESSION_KEEP_ALIVE ;
ast_log ( LOG_WARNING , "Invalid %s '%s' at line %d of http.conf \n " ,
v -> name , v -> value , v -> lineno );
}
} else {
ast_log ( LOG_WARNING , "Ignoring unknown option '%s' in http.conf \n " , v -> name );
2006-03-25 23:50:09 +00:00
}
}
2010-10-29 20:46:06 +00:00
2014-07-03 17:16:55 +00:00
ast_config_destroy ( cfg );
2008-03-19 15:41:54 +00:00
if ( strcmp ( prefix , newprefix )) {
2006-03-25 23:50:09 +00:00
ast_copy_string ( prefix , newprefix , sizeof ( prefix ));
2008-03-19 15:41:54 +00:00
}
2015-01-30 17:21:50 +00:00
ast_copy_string ( http_server_name , server_name , sizeof ( http_server_name ));
2006-04-01 08:49:54 +00:00
enablestatic = newenablestatic ;
2010-10-29 20:46:06 +00:00
if ( num_addrs && enabled ) {
int i ;
for ( i = 0 ; i < num_addrs ; ++ i ) {
ast_sockaddr_copy ( & http_desc . local_address , & addrs [ i ]);
if ( ! ast_sockaddr_port ( & http_desc . local_address )) {
ast_sockaddr_set_port ( & http_desc . local_address , bindport );
}
ast_tcptls_server_start ( & http_desc );
if ( http_desc . accept_fd == - 1 ) {
ast_log ( LOG_WARNING , "Failed to start HTTP server for address %s \n " , ast_sockaddr_stringify ( & addrs [ i ]));
ast_sockaddr_setnull ( & http_desc . local_address );
} else {
ast_verb ( 1 , "Bound HTTP server to address %s \n " , ast_sockaddr_stringify ( & addrs [ i ]));
break ;
}
}
/* When no specific TLS bindaddr is specified, we just use
* the non-TLS bindaddress here.
*/
if ( ast_sockaddr_isnull ( & https_desc . local_address ) && http_desc . accept_fd != - 1 ) {
2016-06-20 15:05:09 +02:00
ast_sockaddr_copy ( & https_desc . local_address , & http_desc . local_address );
2010-10-29 20:46:06 +00:00
/* Of course, we can't use the same port though.
* Since no bind address was specified, we just use the
* default TLS port
*/
ast_sockaddr_set_port ( & https_desc . local_address , DEFAULT_TLS_PORT );
}
}
2012-02-02 18:55:05 +00:00
if ( http_tls_was_enabled && ! http_tls_cfg . enabled ) {
ast_tcptls_server_stop ( & https_desc );
} else if ( http_tls_cfg . enabled && ! ast_sockaddr_isnull ( & https_desc . local_address )) {
2010-10-29 20:46:06 +00:00
/* We can get here either because a TLS-specific address was specified
* or because we copied the non-TLS address here. In the case where
* we read an explicit address from the config, there may have been
* no port specified, so we'll just use the default TLS port.
*/
if ( ! ast_sockaddr_port ( & https_desc . local_address )) {
ast_sockaddr_set_port ( & https_desc . local_address , DEFAULT_TLS_PORT );
}
if ( ast_ssl_setup ( https_desc . tls_cfg )) {
ast_tcptls_server_start ( & https_desc );
}
2008-03-19 15:41:54 +00:00
}
2007-04-06 21:16:38 +00:00
2006-03-25 23:50:09 +00:00
return 0 ;
}
2007-09-18 22:43:45 +00:00
static char * handle_show_http ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a )
2006-03-25 23:50:09 +00:00
{
struct ast_http_uri * urih ;
2006-12-23 20:13:14 +00:00
struct http_uri_redirect * redirect ;
2008-03-17 22:10:06 +00:00
2007-09-18 22:43:45 +00:00
switch ( cmd ) {
case CLI_INIT :
e -> command = "http show status" ;
2009-04-23 20:36:35 +00:00
e -> usage =
2007-09-18 22:43:45 +00:00
"Usage: http show status \n "
" Lists status of internal HTTP engine \n " ;
return NULL ;
case CLI_GENERATE :
return NULL ;
}
2009-04-23 20:36:35 +00:00
2008-03-19 15:41:54 +00:00
if ( a -> argc != 3 ) {
2007-09-18 22:43:45 +00:00
return CLI_SHOWUSAGE ;
2008-03-19 15:41:54 +00:00
}
2007-09-18 22:43:45 +00:00
ast_cli ( a -> fd , "HTTP Server Status: \n " );
ast_cli ( a -> fd , "Prefix: %s \n " , prefix );
2015-01-30 17:21:50 +00:00
ast_cli ( a -> fd , "Server: %s \n " , http_server_name );
2010-10-29 20:46:06 +00:00
if ( ast_sockaddr_isnull ( & http_desc . old_address )) {
2007-09-18 22:43:45 +00:00
ast_cli ( a -> fd , "Server Disabled \n\n " );
2008-03-19 15:41:54 +00:00
} else {
2010-10-29 20:46:06 +00:00
ast_cli ( a -> fd , "Server Enabled and Bound to %s \n\n " ,
ast_sockaddr_stringify ( & http_desc . old_address ));
2008-03-19 15:41:54 +00:00
if ( http_tls_cfg . enabled ) {
2010-10-29 20:46:06 +00:00
ast_cli ( a -> fd , "HTTPS Server Enabled and Bound to %s \n\n " ,
ast_sockaddr_stringify ( & https_desc . old_address ));
2008-03-19 15:41:54 +00:00
}
2006-10-22 12:02:35 +00:00
}
2006-12-23 20:13:14 +00:00
2007-09-18 22:43:45 +00:00
ast_cli ( a -> fd , "Enabled URI's: \n " );
2006-12-28 20:02:41 +00:00
AST_RWLIST_RDLOCK ( & uris );
if ( AST_RWLIST_EMPTY ( & uris )) {
2007-09-18 22:43:45 +00:00
ast_cli ( a -> fd , "None. \n " );
2006-12-28 20:02:41 +00:00
} else {
2009-04-23 20:36:35 +00:00
AST_RWLIST_TRAVERSE ( & uris , urih , entry )
ast_cli ( a -> fd , "%s/%s%s => %s \n " , prefix , urih -> uri , ( urih -> has_subtree ? "/..." : "" ), urih -> description );
2006-12-28 20:02:41 +00:00
}
AST_RWLIST_UNLOCK ( & uris );
2006-12-23 20:13:14 +00:00
2007-09-18 22:43:45 +00:00
ast_cli ( a -> fd , " \n Enabled Redirects: \n " );
2006-12-28 20:05:00 +00:00
AST_RWLIST_RDLOCK ( & uri_redirects );
2009-04-23 20:36:35 +00:00
AST_RWLIST_TRAVERSE ( & uri_redirects , redirect , entry )
2007-09-18 22:43:45 +00:00
ast_cli ( a -> fd , " %s => %s \n " , redirect -> target , redirect -> dest );
2008-03-19 15:41:54 +00:00
if ( AST_RWLIST_EMPTY ( & uri_redirects )) {
2007-09-18 22:43:45 +00:00
ast_cli ( a -> fd , " None. \n " );
2008-03-19 15:41:54 +00:00
}
2006-12-28 20:05:00 +00:00
AST_RWLIST_UNLOCK ( & uri_redirects );
2006-12-23 20:13:14 +00:00
2007-09-18 22:43:45 +00:00
return CLI_SUCCESS ;
2006-03-25 23:50:09 +00:00
}
2018-02-16 22:11:42 -05:00
static int reload_module ( void )
2006-03-25 23:50:09 +00:00
{
return __ast_http_load ( 1 );
}
2006-09-18 19:54:18 +00:00
static struct ast_cli_entry cli_http [] = {
2007-10-22 20:05:18 +00:00
AST_CLI_DEFINE ( handle_show_http , "Display HTTP server status" ),
2006-03-25 23:50:09 +00:00
};
2018-02-16 22:11:42 -05:00
static int unload_module ( void )
2012-12-11 22:03:23 +00:00
{
2013-08-21 17:12:30 +00:00
struct http_uri_redirect * redirect ;
2012-12-11 22:03:23 +00:00
ast_cli_unregister_multiple ( cli_http , ARRAY_LEN ( cli_http ));
2013-08-21 17:12:30 +00:00
ast_tcptls_server_stop ( & http_desc );
if ( http_tls_cfg . enabled ) {
ast_tcptls_server_stop ( & https_desc );
}
ast_free ( http_tls_cfg . certfile );
2017-02-15 13:44:32 -07:00
ast_free ( http_tls_cfg . capath );
2013-08-21 17:12:30 +00:00
ast_free ( http_tls_cfg . pvtfile );
ast_free ( http_tls_cfg . cipher );
ast_http_uri_unlink ( & statusuri );
ast_http_uri_unlink ( & staticuri );
AST_RWLIST_WRLOCK ( & uri_redirects );
while (( redirect = AST_RWLIST_REMOVE_HEAD ( & uri_redirects , entry ))) {
ast_free ( redirect );
}
AST_RWLIST_UNLOCK ( & uri_redirects );
2018-02-16 22:11:42 -05:00
return 0 ;
2012-12-11 22:03:23 +00:00
}
2018-02-16 22:11:42 -05:00
static int load_module ( void )
2006-03-25 23:50:09 +00:00
{
ast_http_uri_link ( & statusuri );
2006-04-01 08:49:54 +00:00
ast_http_uri_link ( & staticuri );
2008-12-05 10:31:25 +00:00
ast_cli_register_multiple ( cli_http , ARRAY_LEN ( cli_http ));
2007-04-06 21:16:38 +00:00
2018-02-16 22:11:42 -05:00
return __ast_http_load ( 0 ) ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SUCCESS ;
2006-03-25 23:50:09 +00:00
}
2018-02-16 22:11:42 -05:00
AST_MODULE_INFO ( ASTERISK_GPL_KEY , AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER , "Built-in HTTP Server" ,
. support_level = AST_MODULE_SUPPORT_CORE ,
. load = load_module ,
. unload = unload_module ,
. reload = reload_module ,
. load_pri = AST_MODPRI_CORE ,
2018-07-26 13:52:46 -04:00
. requires = "extconfig" ,
2018-02-16 22:11:42 -05:00
);