2022-01-20 18:27:56 +00:00
// This file Copyright © 2008-2022 Mnemosyne LLC.
2022-02-07 16:25:02 +00:00
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
2022-01-20 18:27:56 +00:00
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
2008-05-18 16:44:30 +00:00
2021-09-19 20:41:35 +00:00
# include <algorithm>
# include <cerrno>
# include <cstring> /* memcpy */
2021-12-15 21:25:42 +00:00
# include <ctime>
2021-09-27 15:03:54 +00:00
# include <list>
# include <string>
2021-12-15 21:25:42 +00:00
# include <string_view>
2021-11-09 03:30:03 +00:00
# include <vector>
2008-09-25 05:03:39 +00:00
2022-01-17 16:20:12 +00:00
# include <libdeflate.h>
2008-10-01 15:53:56 +00:00
2022-02-23 21:09:54 +00:00
# ifndef _WIN32
# include <sys/un.h>
# include <sys/stat.h>
# include <unistd.h>
# endif
2010-12-24 08:58:41 +00:00
# include <event2/buffer.h>
# include <event2/event.h>
# include <event2/http.h>
# include <event2/http_struct.h> /* TODO: eventually remove this */
2022-02-23 21:09:54 +00:00
# include <event2/listener.h>
2008-05-18 16:44:30 +00:00
# include "transmission.h"
2021-11-09 03:30:03 +00:00
2017-04-21 07:40:57 +00:00
# include "crypto-utils.h" /* tr_rand_buffer() */
2021-11-09 03:30:03 +00:00
# include "crypto.h" /* tr_ssha1_matches() */
2015-04-11 14:54:01 +00:00
# include "error.h"
2010-12-20 02:07:51 +00:00
# include "fdlimit.h"
2013-01-25 23:34:20 +00:00
# include "log.h"
2009-07-01 14:58:57 +00:00
# include "net.h"
2017-04-21 07:40:57 +00:00
# include "platform.h" /* tr_getWebClientDir() */
2022-01-13 02:13:58 +00:00
# include "quark.h"
2008-05-18 16:44:30 +00:00
# include "rpc-server.h"
2021-11-09 03:30:03 +00:00
# include "rpcimpl.h"
2016-09-05 19:06:26 +00:00
# include "session-id.h"
2021-11-09 03:30:03 +00:00
# include "session.h"
2017-06-08 07:24:12 +00:00
# include "tr-assert.h"
2008-09-29 04:26:52 +00:00
# include "trevent.h"
2008-05-18 16:44:30 +00:00
# include "utils.h"
2012-12-14 04:34:42 +00:00
# include "variant.h"
2021-11-09 03:30:03 +00:00
# include "web-utils.h"
2008-09-25 18:48:09 +00:00
# include "web.h"
2008-05-18 16:44:30 +00:00
2021-11-14 06:02:45 +00:00
using namespace std : : literals ;
2009-05-08 14:56:11 +00:00
/* session-id is used to make cross-site request forgery attacks difficult.
* Don ' t disable this feature unless you really know what you ' re doing !
* http : //en.wikipedia.org/wiki/Cross-site_request_forgery
* http : //shiflett.org/articles/cross-site-request-forgeries
* http : //www.webappsec.org/lists/websecurity/archive/2008-04/msg00037.html */
# define REQUIRE_SESSION_ID
2022-01-17 18:39:50 +00:00
static char constexpr MyName [ ] = " RPC Server " ;
2022-02-27 22:05:08 +00:00
static auto constexpr TrUnixSocketPrefix = " unix: " sv ;
2022-02-23 21:09:54 +00:00
/* The maximum size of a unix socket path is defined per-platform based on sockaddr_un.sun_path.
2022-03-04 05:17:28 +00:00
* On Windows the fallback is the length of an ipv6 address . Subtracting one at the end is for
2022-02-23 21:09:54 +00:00
* double counting null terminators from sun_path and TrUnixSocketPrefix . */
# ifdef _WIN32
2022-03-04 05:17:28 +00:00
auto inline constexpr TrUnixAddrStrLen = size_t { INET6_ADDRSTRLEN } ;
2022-02-23 21:09:54 +00:00
# else
2022-02-27 22:05:08 +00:00
auto inline constexpr TrUnixAddrStrLen = size_t { sizeof ( ( ( struct sockaddr_un * ) nullptr ) - > sun_path ) +
std : : size ( TrUnixSocketPrefix ) } ;
2022-02-23 21:09:54 +00:00
# endif
enum tr_rpc_address_type
{
TR_RPC_AF_INET ,
TR_RPC_AF_INET6 ,
TR_RPC_AF_UNIX
} ;
struct tr_rpc_address
{
tr_rpc_address_type type ;
union
{
2022-02-27 22:05:08 +00:00
struct in_addr addr4 ;
struct in6_addr addr6 ;
2022-02-23 21:09:54 +00:00
char unixSocketPath [ TrUnixAddrStrLen ] ;
} addr ;
2022-02-27 22:05:08 +00:00
void set_inaddr_any ( )
{
type = TR_RPC_AF_INET ;
addr . addr4 = { INADDR_ANY } ;
}
} ;
2022-02-23 21:09:54 +00:00
2008-08-22 23:04:16 +00:00
# define MY_REALM "Transmission"
2008-09-25 05:03:39 +00:00
2022-01-17 18:39:50 +00:00
# define dbgmsg(...) tr_logAddDeepNamed(MyName, __VA_ARGS__)
2008-06-05 04:02:46 +00:00
2022-01-17 16:20:12 +00:00
static int constexpr DeflateLevel = 6 ; // medium / default
2022-02-27 22:05:08 +00:00
static bool constexpr tr_rpc_address_is_valid ( tr_rpc_address const & a )
2022-02-23 21:09:54 +00:00
{
2022-02-27 22:05:08 +00:00
return a . type = = TR_RPC_AF_INET | | a . type = = TR_RPC_AF_INET6 | | a . type = = TR_RPC_AF_UNIX ;
2022-02-23 21:09:54 +00:00
}
2009-05-14 17:21:07 +00:00
/***
* * * *
* * */
2021-11-14 21:14:37 +00:00
static char const * get_current_session_id ( tr_rpc_server * server )
2009-05-14 17:21:07 +00:00
{
2017-04-19 12:04:45 +00:00
return tr_session_id_get_current ( server - > session - > session_id ) ;
2009-05-14 17:21:07 +00:00
}
2008-09-26 15:40:24 +00:00
/**
* * *
* */
2017-04-20 16:02:19 +00:00
static void send_simple_response ( struct evhttp_request * req , int code , char const * text )
2008-09-26 15:40:24 +00:00
{
2017-04-20 16:02:19 +00:00
char const * code_text = tr_webGetResponseStr ( code ) ;
2017-04-19 12:04:45 +00:00
struct evbuffer * body = evbuffer_new ( ) ;
evbuffer_add_printf ( body , " <h1>%d: %s</h1> " , code , code_text ) ;
2021-09-15 00:18:09 +00:00
if ( text ! = nullptr )
2017-04-19 12:04:45 +00:00
{
evbuffer_add_printf ( body , " %s " , text ) ;
}
2008-09-26 15:40:24 +00:00
2017-04-19 12:04:45 +00:00
evhttp_send_reply ( req , code , code_text , body ) ;
2008-12-30 20:32:00 +00:00
2017-04-19 12:04:45 +00:00
evbuffer_free ( body ) ;
2008-09-26 15:40:24 +00:00
}
2013-02-10 18:33:04 +00:00
/***
* * * *
* * */
2017-04-20 16:02:19 +00:00
static char const * mimetype_guess ( char const * path )
2008-08-06 00:24:05 +00:00
{
2017-04-20 16:02:19 +00:00
struct
2017-04-19 12:04:45 +00:00
{
2017-04-20 16:02:19 +00:00
char const * suffix ;
char const * mime_type ;
2021-08-15 09:41:48 +00:00
} const types [ ] = {
2017-04-19 12:04:45 +00:00
/* these are the ones we need for serving the web client's files... */
{ " css " , " text/css " } ,
{ " gif " , " image/gif " } ,
{ " html " , " text/html " } ,
{ " ico " , " image/vnd.microsoft.icon " } ,
{ " js " , " application/javascript " } ,
2020-10-24 01:04:25 +00:00
{ " png " , " image/png " } ,
2021-08-15 09:41:48 +00:00
{ " svg " , " image/svg+xml " } ,
2017-04-19 12:04:45 +00:00
} ;
2017-04-20 16:02:19 +00:00
char const * dot = strrchr ( path , ' . ' ) ;
2017-04-19 12:04:45 +00:00
2021-09-15 00:18:09 +00:00
for ( unsigned int i = 0 ; dot ! = nullptr & & i < TR_N_ELEMENTS ( types ) ; + + i )
2017-04-19 12:04:45 +00:00
{
if ( strcmp ( dot + 1 , types [ i ] . suffix ) = = 0 )
{
return types [ i ] . mime_type ;
}
}
return " application/octet-stream " ;
2008-08-06 00:24:05 +00:00
}
2021-11-14 21:14:37 +00:00
static void add_response ( struct evhttp_request * req , tr_rpc_server * server , struct evbuffer * out , struct evbuffer * content )
2008-10-01 15:53:56 +00:00
{
2017-04-20 16:02:19 +00:00
char const * key = " Accept-Encoding " ;
char const * encoding = evhttp_find_header ( req - > input_headers , key ) ;
2021-09-15 00:18:09 +00:00
bool const do_compress = encoding ! = nullptr & & strstr ( encoding , " gzip " ) ! = nullptr ;
2008-10-03 17:38:14 +00:00
2017-04-19 12:04:45 +00:00
if ( ! do_compress )
2008-10-08 13:33:19 +00:00
{
2017-04-19 12:04:45 +00:00
evbuffer_add_buffer ( out , content ) ;
2008-10-01 20:23:57 +00:00
}
2017-04-19 12:04:45 +00:00
else
2008-10-08 13:33:19 +00:00
{
2022-01-17 16:20:12 +00:00
auto const * const content_ptr = evbuffer_pullup ( content , - 1 ) ;
2017-04-20 16:02:19 +00:00
size_t const content_len = evbuffer_get_length ( content ) ;
2022-01-17 16:20:12 +00:00
auto const max_compressed_len = libdeflate_deflate_compress_bound ( server - > compressor . get ( ) , content_len ) ;
2009-06-02 18:21:23 +00:00
2022-01-17 16:20:12 +00:00
struct evbuffer_iovec iovec [ 1 ] ;
evbuffer_reserve_space ( out , std : : max ( content_len , max_compressed_len ) , iovec , 1 ) ;
2022-01-25 05:40:01 +00:00
auto const compressed_len = libdeflate_gzip_compress (
2022-01-17 16:20:12 +00:00
server - > compressor . get ( ) ,
content_ptr ,
content_len ,
iovec [ 0 ] . iov_base ,
iovec [ 0 ] . iov_len ) ;
if ( 0 < compressed_len & & compressed_len < content_len )
2008-10-08 13:39:44 +00:00
{
2022-01-17 16:20:12 +00:00
iovec [ 0 ] . iov_len = compressed_len ;
2017-04-30 16:30:03 +00:00
evhttp_add_header ( req - > output_headers , " Content-Encoding " , " gzip " ) ;
2008-10-08 13:33:19 +00:00
}
2017-04-19 12:04:45 +00:00
else
2008-10-08 13:39:44 +00:00
{
2022-01-17 16:20:12 +00:00
std : : copy_n ( content_ptr , content_len , static_cast < char * > ( iovec [ 0 ] . iov_base ) ) ;
2017-04-19 12:04:45 +00:00
iovec [ 0 ] . iov_len = content_len ;
2008-10-08 13:39:44 +00:00
}
2008-10-08 13:33:19 +00:00
2017-04-19 12:04:45 +00:00
evbuffer_commit_space ( out , iovec , 1 ) ;
2008-10-08 13:33:19 +00:00
}
2008-10-01 15:53:56 +00:00
}
2017-04-20 16:02:19 +00:00
static void add_time_header ( struct evkeyvalq * headers , char const * key , time_t value )
2009-05-08 16:41:32 +00:00
{
2020-09-11 21:07:45 +00:00
char buf [ 128 ] ;
struct tm tm ;
2017-04-19 12:04:45 +00:00
/* According to RFC 2616 this must follow RFC 1123's date format,
so use gmtime instead of localtime . . . */
2020-09-11 21:07:45 +00:00
tr_gmtime_r ( & value , & tm ) ;
2017-04-19 12:04:45 +00:00
strftime ( buf , sizeof ( buf ) , " %a, %d %b %Y %H:%M:%S GMT " , & tm ) ;
evhttp_add_header ( headers , key , buf ) ;
2009-05-08 16:41:32 +00:00
}
2021-10-24 16:41:54 +00:00
static void evbuffer_ref_cleanup_tr_free ( void const * /*data*/ , size_t /*datalen*/ , void * extra )
2011-03-16 17:42:32 +00:00
{
2017-04-19 12:04:45 +00:00
tr_free ( extra ) ;
2011-03-16 17:42:32 +00:00
}
2021-11-14 21:14:37 +00:00
static void serve_file ( struct evhttp_request * req , tr_rpc_server * server , char const * filename )
2008-05-18 16:44:30 +00:00
{
2017-04-19 12:04:45 +00:00
if ( req - > type ! = EVHTTP_REQ_GET )
2008-05-18 16:44:30 +00:00
{
2017-04-19 12:04:45 +00:00
evhttp_add_header ( req - > output_headers , " Allow " , " GET " ) ;
2021-09-15 00:18:09 +00:00
send_simple_response ( req , 405 , nullptr ) ;
2008-05-18 16:44:30 +00:00
}
2017-04-19 12:04:45 +00:00
else
2008-05-18 16:44:30 +00:00
{
2021-10-22 13:51:36 +00:00
auto file_len = size_t { } ;
2021-09-15 00:18:09 +00:00
tr_error * error = nullptr ;
2021-10-22 13:51:36 +00:00
void * const file = tr_loadFile ( filename , & file_len , & error ) ;
2013-02-02 16:22:21 +00:00
2021-09-15 00:18:09 +00:00
if ( file = = nullptr )
2009-05-08 20:52:12 +00:00
{
2021-12-16 02:09:46 +00:00
auto const tmp = tr_strvJoin ( filename , " ( " sv , error - > message , " ) " sv ) ;
send_simple_response ( req , HTTP_NOTFOUND , tmp . c_str ( ) ) ;
2017-04-19 12:04:45 +00:00
tr_error_free ( error ) ;
2009-05-08 20:52:12 +00:00
}
2017-04-19 12:04:45 +00:00
else
2008-09-25 05:03:39 +00:00
{
2021-10-22 13:51:36 +00:00
auto const now = tr_time ( ) ;
2017-04-19 12:04:45 +00:00
2021-10-22 13:51:36 +00:00
auto * const content = evbuffer_new ( ) ;
2017-04-19 12:04:45 +00:00
evbuffer_add_reference ( content , file , file_len , evbuffer_ref_cleanup_tr_free , file ) ;
2021-10-22 13:51:36 +00:00
auto * const out = evbuffer_new ( ) ;
2017-04-19 12:04:45 +00:00
evhttp_add_header ( req - > output_headers , " Content-Type " , mimetype_guess ( filename ) ) ;
add_time_header ( req - > output_headers , " Date " , now ) ;
add_time_header ( req - > output_headers , " Expires " , now + ( 24 * 60 * 60 ) ) ;
add_response ( req , server , out , content ) ;
evhttp_send_reply ( req , HTTP_OK , " OK " , out ) ;
evbuffer_free ( out ) ;
evbuffer_free ( content ) ;
2008-09-25 05:03:39 +00:00
}
2008-07-16 17:47:20 +00:00
}
2008-05-18 16:44:30 +00:00
}
2021-11-14 21:14:37 +00:00
static void handle_web_client ( struct evhttp_request * req , tr_rpc_server * server )
2008-05-18 16:44:30 +00:00
{
2017-04-20 16:02:19 +00:00
char const * webClientDir = tr_getWebClientDir ( server - > session ) ;
2008-09-25 05:03:39 +00:00
2019-07-13 08:52:44 +00:00
if ( tr_str_is_empty ( webClientDir ) )
2008-10-07 01:25:29 +00:00
{
2021-08-15 09:41:48 +00:00
send_simple_response (
req ,
HTTP_NOTFOUND ,
2017-04-19 12:04:45 +00:00
" <p>Couldn't find Transmission's web interface files!</p> "
" <p>Users: to tell Transmission where to look, "
" set the TRANSMISSION_WEB_HOME environment "
" variable to the folder where the web interface's "
" index.html is located.</p> "
" <p>Package Builders: to set a custom default at compile time, "
" #define PACKAGE_DATA_DIR in libtransmission/platform.c "
2017-04-21 07:40:57 +00:00
" or tweak tr_getClutchDir() by hand.</p> " ) ;
2008-10-07 01:25:29 +00:00
}
2017-04-19 12:04:45 +00:00
else
2008-10-07 01:25:29 +00:00
{
2021-10-22 13:51:36 +00:00
// TODO: string_view
2021-11-14 21:14:37 +00:00
char * const subpath = tr_strdup ( req - > uri + std : : size ( server - > url ) + 4 ) ;
2022-02-08 05:44:31 +00:00
if ( char * pch = strchr ( subpath , ' ? ' ) ; pch ! = nullptr )
2017-04-19 12:04:45 +00:00
{
* pch = ' \0 ' ;
}
2008-09-26 15:40:24 +00:00
2021-09-15 00:18:09 +00:00
if ( strstr ( subpath , " .. " ) ! = nullptr )
2009-04-10 15:09:31 +00:00
{
2017-04-19 12:04:45 +00:00
send_simple_response ( req , HTTP_NOTFOUND , " <p>Tsk, tsk.</p> " ) ;
2009-04-10 15:09:31 +00:00
}
2017-04-19 12:04:45 +00:00
else
2009-04-10 15:09:31 +00:00
{
2021-12-16 02:09:46 +00:00
auto const filename = tr_strvJoin (
2021-08-15 09:41:48 +00:00
webClientDir ,
TR_PATH_DELIMITER_STR ,
2019-07-13 08:52:44 +00:00
tr_str_is_empty ( subpath ) ? " index.html " : subpath ) ;
2021-12-16 02:09:46 +00:00
serve_file ( req , server , filename . c_str ( ) ) ;
2009-04-10 15:09:31 +00:00
}
2008-05-18 16:44:30 +00:00
2017-04-19 12:04:45 +00:00
tr_free ( subpath ) ;
2008-10-07 01:25:29 +00:00
}
2008-06-05 16:23:03 +00:00
}
2009-01-18 15:24:26 +00:00
struct rpc_response_data
{
2017-04-19 12:04:45 +00:00
struct evhttp_request * req ;
2021-11-14 21:14:37 +00:00
tr_rpc_server * server ;
2009-01-18 15:24:26 +00:00
} ;
2021-10-24 16:41:54 +00:00
static void rpc_response_func ( tr_session * /*session*/ , tr_variant * response , void * user_data )
2009-01-18 15:24:26 +00:00
{
2021-09-12 17:41:49 +00:00
auto * data = static_cast < struct rpc_response_data * > ( user_data ) ;
2017-04-19 12:04:45 +00:00
struct evbuffer * response_buf = tr_variantToBuf ( response , TR_VARIANT_FMT_JSON_LEAN ) ;
struct evbuffer * buf = evbuffer_new ( ) ;
add_response ( data - > req , data - > server , buf , response_buf ) ;
evhttp_add_header ( data - > req - > output_headers , " Content-Type " , " application/json; charset=UTF-8 " ) ;
evhttp_send_reply ( data - > req , HTTP_OK , " OK " , buf ) ;
evbuffer_free ( buf ) ;
evbuffer_free ( response_buf ) ;
tr_free ( data ) ;
2009-01-18 15:24:26 +00:00
}
2021-11-15 07:21:57 +00:00
static void handle_rpc_from_json ( struct evhttp_request * req , tr_rpc_server * server , std : : string_view json )
2008-05-18 16:44:30 +00:00
{
2021-10-22 13:51:36 +00:00
auto top = tr_variant { } ;
2021-11-18 05:37:35 +00:00
auto const have_content = tr_variantFromBuf ( & top , TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE , json ) ;
2008-06-05 04:02:46 +00:00
2021-10-22 13:51:36 +00:00
auto * const data = tr_new0 ( struct rpc_response_data , 1 ) ;
2017-04-19 12:04:45 +00:00
data - > req = req ;
data - > server = server ;
2009-08-12 14:40:32 +00:00
2021-09-15 00:18:09 +00:00
tr_rpc_request_exec_json ( server - > session , have_content ? & top : nullptr , rpc_response_func , data ) ;
2015-07-13 00:32:48 +00:00
2017-04-19 12:04:45 +00:00
if ( have_content )
{
tr_variantFree ( & top ) ;
}
2013-02-10 18:33:04 +00:00
}
2021-11-14 21:14:37 +00:00
static void handle_rpc ( struct evhttp_request * req , tr_rpc_server * server )
2013-02-10 18:33:04 +00:00
{
2017-04-19 12:04:45 +00:00
if ( req - > type = = EVHTTP_REQ_POST )
2008-05-18 16:44:30 +00:00
{
2021-11-15 07:21:57 +00:00
auto json = std : : string_view { reinterpret_cast < char const * > ( evbuffer_pullup ( req - > input_buffer , - 1 ) ) ,
evbuffer_get_length ( req - > input_buffer ) } ;
handle_rpc_from_json ( req , server , json ) ;
2019-03-17 06:09:08 +00:00
return ;
2008-09-25 05:03:39 +00:00
}
2019-03-17 06:09:08 +00:00
if ( req - > type = = EVHTTP_REQ_GET )
2013-02-10 18:33:04 +00:00
{
2019-03-17 06:09:08 +00:00
char const * q = strchr ( req - > uri , ' ? ' ) ;
2021-09-15 00:18:09 +00:00
if ( q ! = nullptr )
2019-03-17 06:09:08 +00:00
{
2022-01-17 18:39:50 +00:00
auto * const data = tr_new0 ( struct rpc_response_data , 1 ) ;
2019-03-17 06:09:08 +00:00
data - > req = req ;
data - > server = server ;
2021-12-16 02:09:46 +00:00
tr_rpc_request_exec_uri ( server - > session , q + 1 , rpc_response_func , data ) ;
2019-03-17 06:09:08 +00:00
return ;
}
2008-12-30 22:07:39 +00:00
}
2019-03-17 06:09:08 +00:00
2021-09-15 00:18:09 +00:00
send_simple_response ( req , 405 , nullptr ) ;
2008-09-25 05:03:39 +00:00
}
2008-07-17 14:45:31 +00:00
2017-04-20 16:02:19 +00:00
static bool isAddressAllowed ( tr_rpc_server const * server , char const * address )
2008-09-26 15:40:24 +00:00
{
2021-09-27 15:03:54 +00:00
auto const & src = server - > whitelist ;
2008-09-26 15:40:24 +00:00
2021-09-27 15:03:54 +00:00
return ! server - > isWhitelistEnabled | |
std : : any_of ( std : : begin ( src ) , std : : end ( src ) , [ & address ] ( auto const & s ) { return tr_wildmat ( address , s . c_str ( ) ) ; } ) ;
2008-09-26 15:40:24 +00:00
}
2018-01-25 00:07:52 +00:00
static bool isIPAddressWithOptionalPort ( char const * host )
2018-01-17 20:32:14 +00:00
{
2018-01-25 00:07:52 +00:00
struct sockaddr_storage address ;
int address_len = sizeof ( address ) ;
2018-01-17 20:32:14 +00:00
2018-01-25 00:07:52 +00:00
/* TODO: move to net.{c,h} */
2019-02-10 11:05:16 +00:00
return evutil_parse_sockaddr_port ( host , ( struct sockaddr * ) & address , & address_len ) ! = - 1 ;
2018-01-17 20:32:14 +00:00
}
2022-02-01 17:30:51 +00:00
static bool isHostnameAllowed ( tr_rpc_server const * server , struct evhttp_request * req )
2018-01-11 18:00:41 +00:00
{
/* If password auth is enabled, any hostname is permitted. */
if ( server - > isPasswordEnabled )
{
return true ;
}
/* If whitelist is disabled, no restrictions. */
if ( ! server - > isHostWhitelistEnabled )
{
return true ;
}
char const * const host = evhttp_find_header ( req - > input_headers , " Host " ) ;
/* No host header, invalid request. */
2021-09-15 00:18:09 +00:00
if ( host = = nullptr )
2018-01-11 18:00:41 +00:00
{
return false ;
}
2018-01-25 00:07:52 +00:00
/* IP address is always acceptable. */
if ( isIPAddressWithOptionalPort ( host ) )
{
return true ;
}
2018-01-17 20:32:14 +00:00
2018-01-11 18:00:41 +00:00
/* Host header might include the port. */
2021-09-27 15:03:54 +00:00
auto const hostname = std : : string ( host , strcspn ( host , " : " ) ) ;
2018-01-11 18:00:41 +00:00
2018-01-25 00:07:52 +00:00
/* localhost is always acceptable. */
2021-09-27 15:03:54 +00:00
if ( hostname = = " localhost " | | hostname = = " localhost. " )
2018-01-11 18:00:41 +00:00
{
return true ;
}
2021-09-27 15:03:54 +00:00
auto const & src = server - > hostWhitelist ;
return std : : any_of (
std : : begin ( src ) ,
std : : end ( src ) ,
[ & hostname ] ( auto const & str ) { return tr_wildmat ( hostname . c_str ( ) , str . c_str ( ) ) ; } ) ;
2018-01-11 18:00:41 +00:00
}
2021-11-18 00:17:09 +00:00
static bool test_session_id ( tr_rpc_server * server , evhttp_request const * req )
2009-05-08 14:56:11 +00:00
{
2017-04-20 16:02:19 +00:00
char const * ours = get_current_session_id ( server ) ;
char const * theirs = evhttp_find_header ( req - > input_headers , TR_RPC_SESSION_ID_HEADER ) ;
2021-09-15 00:18:09 +00:00
bool const success = theirs ! = nullptr & & strcmp ( theirs , ours ) = = 0 ;
2017-04-19 12:04:45 +00:00
return success ;
2009-05-08 14:56:11 +00:00
}
2021-11-15 03:54:48 +00:00
static bool isAuthorized ( tr_rpc_server const * server , char const * auth_header )
{
if ( ! server - > isPasswordEnabled )
{
return true ;
}
// https://datatracker.ietf.org/doc/html/rfc7617
// `Basic ${base64(username)}:${base64(password)}`
auto constexpr Prefix = " Basic " sv ;
auto auth = std : : string_view { auth_header ! = nullptr ? auth_header : " " } ;
if ( ! tr_strvStartsWith ( auth , Prefix ) )
{
return false ;
}
auth . remove_prefix ( std : : size ( Prefix ) ) ;
2022-01-08 12:46:25 +00:00
auto const decoded_str = tr_base64_decode ( auth ) ;
2021-11-15 03:54:48 +00:00
auto decoded = std : : string_view { decoded_str } ;
auto const username = tr_strvSep ( & decoded , ' : ' ) ;
auto const password = decoded ;
return server - > username = = username & & tr_ssha1_matches ( server - > salted_password , password ) ;
}
2017-04-19 12:04:45 +00:00
static void handle_request ( struct evhttp_request * req , void * arg )
2008-09-25 05:03:39 +00:00
{
2021-11-14 21:14:37 +00:00
auto * server = static_cast < tr_rpc_server * > ( arg ) ;
2008-07-17 14:45:31 +00:00
2021-09-15 00:18:09 +00:00
if ( req ! = nullptr & & req - > evcon ! = nullptr )
2008-09-25 05:03:39 +00:00
{
2017-04-19 12:04:45 +00:00
evhttp_add_header ( req - > output_headers , " Server " , MY_REALM ) ;
2008-09-26 15:40:24 +00:00
2020-11-14 18:43:42 +00:00
if ( server - > isAntiBruteForceEnabled & & server - > loginattempts > = server - > antiBruteForceThreshold )
2017-08-12 14:36:43 +00:00
{
2017-08-13 11:53:41 +00:00
send_simple_response ( req , 403 , " <p>Too many unsuccessful login attempts. Please restart transmission-daemon.</p> " ) ;
2017-08-12 14:36:43 +00:00
return ;
}
if ( ! isAddressAllowed ( server , req - > remote_host ) )
{
2021-08-15 09:41:48 +00:00
send_simple_response (
req ,
403 ,
2017-08-12 14:36:43 +00:00
" <p>Unauthorized IP Address.</p> "
" <p>Either disable the IP address whitelist or add your address to it.</p> "
" <p>If you're editing settings.json, see the 'rpc-whitelist' and 'rpc-whitelist-enabled' entries.</p> "
" <p>If you're still using ACLs, use a whitelist instead. See the transmission-daemon manpage for details.</p> " ) ;
return ;
}
2021-10-15 20:10:09 +00:00
evhttp_add_header ( req - > output_headers , " Access-Control-Allow-Origin " , " * " ) ;
if ( req - > type = = EVHTTP_REQ_OPTIONS )
{
char const * headers = evhttp_find_header ( req - > input_headers , " Access-Control-Request-Headers " ) ;
if ( headers ! = nullptr )
{
evhttp_add_header ( req - > output_headers , " Access-Control-Allow-Headers " , headers ) ;
}
evhttp_add_header ( req - > output_headers , " Access-Control-Allow-Methods " , " GET, POST, OPTIONS " ) ;
send_simple_response ( req , 200 , " " ) ;
return ;
}
2021-11-15 03:54:48 +00:00
if ( ! isAuthorized ( server , evhttp_find_header ( req - > input_headers , " Authorization " ) ) )
2008-07-16 23:55:49 +00:00
{
2017-04-19 12:04:45 +00:00
evhttp_add_header ( req - > output_headers , " WWW-Authenticate " , " Basic realm= \" " MY_REALM " \" " ) ;
2020-11-14 18:43:42 +00:00
if ( server - > isAntiBruteForceEnabled )
{
2021-11-15 03:54:48 +00:00
+ + server - > loginattempts ;
2020-11-14 18:43:42 +00:00
}
2021-12-16 02:09:46 +00:00
auto const unauthuser = tr_strvJoin (
" <p>Unauthorized User. " sv ,
std : : to_string ( server - > loginattempts ) ,
" unsuccessful login attempts.</p> " sv ) ;
send_simple_response ( req , 401 , unauthuser . c_str ( ) ) ;
2017-08-12 14:36:43 +00:00
return ;
}
2017-08-13 11:53:41 +00:00
server - > loginattempts = 0 ;
2017-08-12 14:36:43 +00:00
2021-11-14 21:14:37 +00:00
auto uri = std : : string_view { req - > uri } ;
auto const location = tr_strvStartsWith ( uri , server - > url ) ? uri . substr ( std : : size ( server - > url ) ) : " " sv ;
2021-10-12 14:28:46 +00:00
2021-11-14 21:14:37 +00:00
if ( std : : empty ( location ) | | location = = " web " sv )
2008-07-16 23:55:49 +00:00
{
2021-11-14 21:14:37 +00:00
auto const new_location = tr_strvJoin ( server - > url , " web/ " ) ;
evhttp_add_header ( req - > output_headers , " Location " , new_location . c_str ( ) ) ;
2021-10-12 19:45:18 +00:00
send_simple_response ( req , HTTP_MOVEPERM , nullptr ) ;
2008-06-05 16:23:03 +00:00
}
2021-11-14 21:14:37 +00:00
else if ( tr_strvStartsWith ( location , " web/ " sv ) )
2008-09-25 05:03:39 +00:00
{
2017-04-19 12:04:45 +00:00
handle_web_client ( req , server ) ;
2008-09-25 05:03:39 +00:00
}
2018-01-11 18:00:41 +00:00
else if ( ! isHostnameAllowed ( server , req ) )
{
2021-12-16 02:09:46 +00:00
char const * const tmp =
2018-01-11 18:00:41 +00:00
" <p>Transmission received your request, but the hostname was unrecognized.</p> "
" <p>To fix this, choose one of the following options: "
" <ul> "
" <li>Enable password authentication, then any hostname is allowed.</li> "
" <li>Add the hostname you want to use to the whitelist in settings.</li> "
" </ul></p> "
" <p>If you're editing settings.json, see the 'rpc-host-whitelist' and 'rpc-host-whitelist-enabled' entries.</p> "
" <p>This requirement has been added to help prevent "
" <a href= \" https://en.wikipedia.org/wiki/DNS_rebinding \" >DNS Rebinding</a> "
2021-12-16 02:09:46 +00:00
" attacks.</p> " ;
2018-01-11 18:00:41 +00:00
send_simple_response ( req , 421 , tmp ) ;
}
2018-01-17 20:34:40 +00:00
# ifdef REQUIRE_SESSION_ID
2017-04-19 12:04:45 +00:00
else if ( ! test_session_id ( server , req ) )
2009-05-08 14:56:11 +00:00
{
2017-04-20 16:02:19 +00:00
char const * sessionId = get_current_session_id ( server ) ;
2021-12-16 02:09:46 +00:00
auto const tmp = tr_strvJoin (
2017-04-19 12:04:45 +00:00
" <p>Your request had an invalid session-id header.</p> "
" <p>To fix this, follow these steps: "
" <ol><li> When reading a response, get its X-Transmission-Session-Id header and remember it "
" <li> Add the updated header to your outgoing requests "
" <li> When you get this 409 error message, resend your request with the updated header "
" </ol></p> "
" <p>This requirement has been added to help prevent "
2018-01-11 18:00:41 +00:00
" <a href= \" https://en.wikipedia.org/wiki/Cross-site_request_forgery \" >CSRF</a> "
2017-04-19 12:04:45 +00:00
" attacks.</p> "
2021-12-16 02:09:46 +00:00
" <p><code> " TR_RPC_SESSION_ID_HEADER ,
" : " sv ,
sessionId ,
" </code></p> " ) ;
2017-04-19 12:04:45 +00:00
evhttp_add_header ( req - > output_headers , TR_RPC_SESSION_ID_HEADER , sessionId ) ;
2021-10-15 20:10:09 +00:00
evhttp_add_header ( req - > output_headers , " Access-Control-Expose-Headers " , TR_RPC_SESSION_ID_HEADER ) ;
2021-12-16 02:09:46 +00:00
send_simple_response ( req , 409 , tmp . c_str ( ) ) ;
2009-05-08 14:56:11 +00:00
}
# endif
2021-11-15 03:54:48 +00:00
else if ( tr_strvStartsWith ( location , " rpc " sv ) )
2008-09-25 05:03:39 +00:00
{
2017-04-19 12:04:45 +00:00
handle_rpc ( req , server ) ;
2008-09-25 05:03:39 +00:00
}
2017-04-19 12:04:45 +00:00
else
2008-09-03 19:59:09 +00:00
{
2017-04-19 12:04:45 +00:00
send_simple_response ( req , HTTP_NOTFOUND , req - > uri ) ;
2008-09-03 19:59:09 +00:00
}
2008-05-18 16:44:30 +00:00
}
}
2021-10-21 00:39:05 +00:00
static auto constexpr ServerStartRetryCount = int { 10 } ;
static auto constexpr ServerStartRetryDelayIncrement = int { 5 } ;
static auto constexpr ServerStartRetryDelayStep = int { 3 } ;
static auto constexpr ServerStartRetryMaxDelay = int { 60 } ;
2016-01-07 15:28:58 +00:00
2022-02-27 22:05:08 +00:00
static char const * tr_rpc_address_to_string ( tr_rpc_address const & addr , char * buf , size_t buflen )
2022-02-23 21:09:54 +00:00
{
TR_ASSERT ( tr_rpc_address_is_valid ( addr ) ) ;
2022-02-27 22:05:08 +00:00
switch ( addr . type )
2022-02-23 21:09:54 +00:00
{
case TR_RPC_AF_INET :
2022-02-27 22:05:08 +00:00
return evutil_inet_ntop ( AF_INET , & addr . addr , buf , buflen ) ;
2022-02-23 21:09:54 +00:00
case TR_RPC_AF_INET6 :
2022-02-27 22:05:08 +00:00
return evutil_inet_ntop ( AF_INET6 , & addr . addr , buf , buflen ) ;
2022-02-23 21:09:54 +00:00
case TR_RPC_AF_UNIX :
2022-02-27 22:05:08 +00:00
tr_strlcpy ( buf , addr . addr . unixSocketPath , buflen ) ;
2022-02-23 21:09:54 +00:00
return buf ;
default :
return nullptr ;
}
}
static std : : string tr_rpc_address_with_port ( tr_rpc_server const * server )
{
char addr_buf [ TrUnixAddrStrLen ] ;
2022-02-27 22:05:08 +00:00
tr_rpc_address_to_string ( * server - > bindAddress , addr_buf , sizeof ( addr_buf ) ) ;
2022-02-23 21:09:54 +00:00
std : : string addr_port_str { addr_buf } ;
2022-02-27 22:05:08 +00:00
if ( server - > bindAddress - > type ! = TR_RPC_AF_UNIX )
2022-02-23 21:09:54 +00:00
{
addr_port_str . append ( " : " + std : : to_string ( tr_rpcGetPort ( server ) ) ) ;
}
return addr_port_str ;
}
2022-02-27 22:05:08 +00:00
static bool tr_rpc_address_from_string ( tr_rpc_address & dst , std : : string_view src )
2022-02-23 21:09:54 +00:00
{
if ( tr_strvStartsWith ( src , TrUnixSocketPrefix ) )
{
2022-02-27 22:05:08 +00:00
if ( std : : size ( src ) > = TrUnixAddrStrLen )
2022-02-23 21:09:54 +00:00
{
tr_logAddNamedError (
MyName ,
2022-02-27 22:05:08 +00:00
_ ( " Unix socket path must be fewer than %zu characters (including \" % " TR_PRIsv " \" prefix) " ) ,
2022-02-23 21:09:54 +00:00
TrUnixAddrStrLen - 1 ,
2022-02-27 22:05:08 +00:00
TR_PRIsv_ARG ( TrUnixSocketPrefix ) ) ;
2022-02-23 21:09:54 +00:00
return false ;
}
2022-02-27 22:05:08 +00:00
dst . type = TR_RPC_AF_UNIX ;
tr_strlcpy ( dst . addr . unixSocketPath , std : : string { src } . c_str ( ) , TrUnixAddrStrLen ) ;
2022-02-23 21:09:54 +00:00
return true ;
}
2022-02-27 22:05:08 +00:00
if ( evutil_inet_pton ( AF_INET , std : : string { src } . c_str ( ) , & dst . addr ) = = 1 )
2022-02-23 21:09:54 +00:00
{
2022-02-27 22:05:08 +00:00
dst . type = TR_RPC_AF_INET ;
2022-02-23 21:09:54 +00:00
return true ;
}
2022-02-27 22:05:08 +00:00
if ( evutil_inet_pton ( AF_INET6 , std : : string { src } . c_str ( ) , & dst . addr ) = = 1 )
2022-02-23 21:09:54 +00:00
{
2022-02-27 22:05:08 +00:00
dst . type = TR_RPC_AF_INET6 ;
2022-02-23 21:09:54 +00:00
return true ;
}
return false ;
}
2022-02-27 22:05:08 +00:00
static bool bindUnixSocket (
[[maybe_unused]] struct event_base * base ,
[[maybe_unused]] struct evhttp * httpd ,
[[maybe_unused]] char const * path ,
[[maybe_unused]] int socket_mode )
2022-02-23 21:09:54 +00:00
{
# ifdef _WIN32
tr_logAddNamedError (
MyName ,
_ ( " Unix sockets are not supported on Windows. Please change \" %s \" in your configuration file. " ) ,
2022-02-27 22:05:08 +00:00
tr_quark_get_string ( TR_KEY_rpc_bind_address , nullptr ) ) ;
2022-02-23 21:09:54 +00:00
return false ;
# else
struct sockaddr_un addr ;
addr . sun_family = AF_UNIX ;
2022-02-27 22:05:08 +00:00
tr_strlcpy ( addr . sun_path , path + std : : size ( TrUnixSocketPrefix ) , sizeof ( addr . sun_path ) ) ;
2022-02-23 21:09:54 +00:00
unlink ( addr . sun_path ) ;
2022-02-27 22:05:08 +00:00
struct evconnlistener * lev = evconnlistener_new_bind (
base ,
nullptr ,
nullptr ,
LEV_OPT_CLOSE_ON_FREE ,
- 1 ,
reinterpret_cast < sockaddr const * > ( & addr ) ,
sizeof ( addr ) ) ;
2022-02-23 21:09:54 +00:00
if ( lev = = nullptr )
{
return false ;
}
if ( chmod ( addr . sun_path , ( mode_t ) socket_mode ) ! = 0 )
{
tr_logAddNamedError ( MyName , _ ( " Could not set RPC socket mode to %o, defaulting to 755 " ) , socket_mode ) ;
}
return evhttp_bind_listener ( httpd , lev ) ! = nullptr ;
# endif
}
2017-04-19 12:04:45 +00:00
static void startServer ( void * vserver ) ;
2016-01-07 15:28:58 +00:00
2021-10-24 16:41:54 +00:00
static void rpc_server_on_start_retry ( evutil_socket_t /*fd*/ , short /*type*/ , void * context )
2008-09-25 05:03:39 +00:00
{
2017-04-19 12:04:45 +00:00
startServer ( context ) ;
2016-01-07 15:28:58 +00:00
}
2017-04-19 12:04:45 +00:00
static int rpc_server_start_retry ( tr_rpc_server * server )
2016-01-07 15:28:58 +00:00
{
2021-10-21 00:39:05 +00:00
int retry_delay = ( server - > start_retry_counter / ServerStartRetryDelayStep + 1 ) * ServerStartRetryDelayIncrement ;
retry_delay = std : : min ( retry_delay , int { ServerStartRetryMaxDelay } ) ;
2016-01-07 15:28:58 +00:00
2021-09-15 00:18:09 +00:00
if ( server - > start_retry_timer = = nullptr )
2017-04-19 12:04:45 +00:00
{
server - > start_retry_timer = evtimer_new ( server - > session - > event_base , rpc_server_on_start_retry , server ) ;
}
2008-10-01 04:26:38 +00:00
2017-04-19 12:04:45 +00:00
tr_timerAdd ( server - > start_retry_timer , retry_delay , 0 ) ;
+ + server - > start_retry_counter ;
2016-01-07 15:28:58 +00:00
2017-04-19 12:04:45 +00:00
return retry_delay ;
2016-01-07 15:28:58 +00:00
}
2017-04-19 12:04:45 +00:00
static void rpc_server_start_retry_cancel ( tr_rpc_server * server )
2016-01-07 15:28:58 +00:00
{
2021-09-15 00:18:09 +00:00
if ( server - > start_retry_timer ! = nullptr )
2008-09-29 03:02:27 +00:00
{
2017-04-19 12:04:45 +00:00
event_free ( server - > start_retry_timer ) ;
2021-09-15 00:18:09 +00:00
server - > start_retry_timer = nullptr ;
2008-09-29 03:02:27 +00:00
}
2016-01-07 15:28:58 +00:00
2017-04-19 12:04:45 +00:00
server - > start_retry_counter = 0 ;
2008-09-25 05:03:39 +00:00
}
2017-04-19 12:04:45 +00:00
static void startServer ( void * vserver )
2008-05-18 16:44:30 +00:00
{
2021-09-12 17:41:49 +00:00
auto * server = static_cast < tr_rpc_server * > ( vserver ) ;
2017-04-19 12:04:45 +00:00
2021-09-15 00:18:09 +00:00
if ( server - > httpd ! = nullptr )
2017-04-19 12:04:45 +00:00
{
return ;
}
2022-02-23 21:09:54 +00:00
struct event_base * base = server - > session - > event_base ;
struct evhttp * httpd = evhttp_new ( base ) ;
2021-10-15 20:10:09 +00:00
evhttp_set_allowed_methods ( httpd , EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_OPTIONS ) ;
2016-01-07 15:28:58 +00:00
2017-04-20 16:02:19 +00:00
char const * address = tr_rpcGetBindAddress ( server ) ;
2016-01-07 15:28:58 +00:00
2020-11-05 22:46:21 +00:00
tr_port const port = server - > port ;
2016-01-07 15:28:58 +00:00
2022-02-23 21:09:54 +00:00
bool const success = server - > bindAddress - > type = = TR_RPC_AF_UNIX ?
bindUnixSocket ( base , httpd , address , server - > rpc_socket_mode ) :
( evhttp_bind_socket ( httpd , address , port ) ! = - 1 ) ;
auto const addr_port_str = tr_rpc_address_with_port ( server ) ;
if ( ! success )
2016-01-07 15:28:58 +00:00
{
2017-04-19 12:04:45 +00:00
evhttp_free ( httpd ) ;
2016-01-07 15:28:58 +00:00
2021-10-21 00:39:05 +00:00
if ( server - > start_retry_counter < ServerStartRetryCount )
2016-01-07 15:28:58 +00:00
{
2017-04-20 16:02:19 +00:00
int const retry_delay = rpc_server_start_retry ( server ) ;
2016-01-07 15:28:58 +00:00
2022-02-23 21:09:54 +00:00
tr_logAddNamedDbg ( MyName , " Unable to bind to %s, retrying in %d seconds " , addr_port_str . c_str ( ) , retry_delay ) ;
2017-04-19 12:04:45 +00:00
return ;
2016-01-07 15:28:58 +00:00
}
2021-08-15 09:41:48 +00:00
tr_logAddNamedError (
2022-01-17 18:39:50 +00:00
MyName ,
2022-02-23 21:09:54 +00:00
" Unable to bind to %s after %d attempts, giving up " ,
addr_port_str . c_str ( ) ,
2021-10-21 00:39:05 +00:00
ServerStartRetryCount ) ;
2016-01-07 15:28:58 +00:00
}
2017-04-19 12:04:45 +00:00
else
2008-05-18 16:44:30 +00:00
{
2017-04-19 12:04:45 +00:00
evhttp_set_gencb ( httpd , handle_request , server ) ;
server - > httpd = httpd ;
2016-01-07 15:28:58 +00:00
2022-02-23 21:09:54 +00:00
tr_logAddNamedDbg ( MyName , " Started listening on %s " , addr_port_str . c_str ( ) ) ;
2008-05-18 16:44:30 +00:00
}
2016-01-07 15:28:58 +00:00
2017-04-19 12:04:45 +00:00
rpc_server_start_retry_cancel ( server ) ;
2016-01-07 15:28:58 +00:00
}
2017-04-19 12:04:45 +00:00
static void stopServer ( tr_rpc_server * server )
2016-01-07 15:28:58 +00:00
{
2021-11-14 21:14:37 +00:00
TR_ASSERT ( tr_amInEventThread ( server - > session ) ) ;
2017-04-19 12:04:45 +00:00
rpc_server_start_retry_cancel ( server ) ;
struct evhttp * httpd = server - > httpd ;
2016-01-07 15:28:58 +00:00
2021-09-15 00:18:09 +00:00
if ( httpd = = nullptr )
2017-04-19 12:04:45 +00:00
{
return ;
}
2016-01-07 15:28:58 +00:00
2017-04-20 16:02:19 +00:00
char const * address = tr_rpcGetBindAddress ( server ) ;
2016-01-07 15:28:58 +00:00
2021-09-15 00:18:09 +00:00
server - > httpd = nullptr ;
2017-04-19 12:04:45 +00:00
evhttp_free ( httpd ) ;
2016-01-07 15:28:58 +00:00
2022-02-23 21:09:54 +00:00
if ( server - > bindAddress - > type = = TR_RPC_AF_UNIX )
{
2022-02-27 22:05:08 +00:00
unlink ( address + std : : size ( TrUnixSocketPrefix ) ) ;
2022-02-23 21:09:54 +00:00
}
tr_logAddNamedDbg ( MyName , " Stopped listening on %s " , tr_rpc_address_with_port ( server ) . c_str ( ) ) ;
2008-05-18 16:44:30 +00:00
}
2017-04-19 12:04:45 +00:00
static void onEnabledChanged ( void * vserver )
2008-10-01 04:26:38 +00:00
{
2021-09-12 17:41:49 +00:00
auto * server = static_cast < tr_rpc_server * > ( vserver ) ;
2008-10-01 04:26:38 +00:00
2017-04-19 12:04:45 +00:00
if ( ! server - > isEnabled )
{
stopServer ( server ) ;
}
else
{
startServer ( server ) ;
}
2008-10-01 04:26:38 +00:00
}
2017-04-19 12:04:45 +00:00
void tr_rpcSetEnabled ( tr_rpc_server * server , bool isEnabled )
2008-05-18 16:44:30 +00:00
{
2017-04-19 12:04:45 +00:00
server - > isEnabled = isEnabled ;
2008-05-18 16:44:30 +00:00
2017-04-19 12:04:45 +00:00
tr_runInEventThread ( server - > session , onEnabledChanged , server ) ;
2008-05-18 16:44:30 +00:00
}
2017-04-20 16:02:19 +00:00
bool tr_rpcIsEnabled ( tr_rpc_server const * server )
2008-05-18 16:44:30 +00:00
{
2017-04-19 12:04:45 +00:00
return server - > isEnabled ;
2008-05-18 16:44:30 +00:00
}
2017-04-19 12:04:45 +00:00
static void restartServer ( void * vserver )
2008-10-01 04:26:38 +00:00
{
2021-09-12 17:41:49 +00:00
auto * server = static_cast < tr_rpc_server * > ( vserver ) ;
2008-10-01 04:26:38 +00:00
2017-04-19 12:04:45 +00:00
if ( server - > isEnabled )
2008-10-01 04:26:38 +00:00
{
2017-04-19 12:04:45 +00:00
stopServer ( server ) ;
startServer ( server ) ;
2008-10-01 04:26:38 +00:00
}
}
2017-04-19 12:04:45 +00:00
void tr_rpcSetPort ( tr_rpc_server * server , tr_port port )
2008-05-18 16:44:30 +00:00
{
2021-09-15 00:18:09 +00:00
TR_ASSERT ( server ! = nullptr ) ;
2010-01-16 22:46:38 +00:00
2017-04-19 12:04:45 +00:00
if ( server - > port ! = port )
2008-05-18 16:44:30 +00:00
{
2017-04-19 12:04:45 +00:00
server - > port = port ;
2008-05-18 16:44:30 +00:00
2017-04-19 12:04:45 +00:00
if ( server - > isEnabled )
{
tr_runInEventThread ( server - > session , restartServer , server ) ;
}
2008-05-18 16:44:30 +00:00
}
}
2017-04-20 16:02:19 +00:00
tr_port tr_rpcGetPort ( tr_rpc_server const * server )
2008-05-18 16:44:30 +00:00
{
2017-04-19 12:04:45 +00:00
return server - > port ;
2008-05-18 16:44:30 +00:00
}
2021-11-14 21:14:37 +00:00
void tr_rpcSetUrl ( tr_rpc_server * server , std : : string_view url )
2010-12-12 18:22:11 +00:00
{
2021-11-14 21:14:37 +00:00
server - > url = url ;
dbgmsg ( " setting our URL to [%s] " , server - > url . c_str ( ) ) ;
2010-12-12 18:22:11 +00:00
}
2021-11-14 21:14:37 +00:00
std : : string const & tr_rpcGetUrl ( tr_rpc_server const * server )
2010-12-12 18:22:11 +00:00
{
2021-11-14 21:14:37 +00:00
return server - > url ;
2010-12-12 18:22:11 +00:00
}
2021-11-14 06:02:45 +00:00
static auto parseWhitelist ( std : : string_view whitelist )
2008-12-21 19:23:41 +00:00
{
2021-09-27 15:03:54 +00:00
auto list = std : : list < std : : string > { } ;
2008-12-21 19:23:41 +00:00
2021-11-14 06:02:45 +00:00
while ( ! std : : empty ( whitelist ) )
2013-02-02 16:22:21 +00:00
{
2021-11-14 06:02:45 +00:00
auto const pos = whitelist . find_first_of ( " ,; " sv ) ;
auto const token = tr_strvStrip ( whitelist . substr ( 0 , pos ) ) ;
list . emplace_back ( token ) ;
2022-02-07 04:28:36 +00:00
whitelist = pos = = std : : string_view : : npos ? " " sv : whitelist . substr ( pos + 1 ) ;
2017-04-19 12:04:45 +00:00
2022-02-07 04:28:36 +00:00
if ( token . find_first_of ( " +- " sv ) ! = std : : string_view : : npos )
2017-04-19 12:04:45 +00:00
{
2021-08-15 09:41:48 +00:00
tr_logAddNamedInfo (
2022-01-17 18:39:50 +00:00
MyName ,
2021-11-14 06:02:45 +00:00
" Adding address to whitelist: % " TR_PRIsv " (And it has a '+' or '-'! Are you using an old ACL by mistake?) " ,
TR_PRIsv_ARG ( token ) ) ;
2017-04-19 12:04:45 +00:00
}
else
{
2022-01-17 18:39:50 +00:00
tr_logAddNamedInfo ( MyName , " Adding address to whitelist: % " TR_PRIsv , TR_PRIsv_ARG ( token ) ) ;
2017-04-19 12:04:45 +00:00
}
2008-12-21 19:23:41 +00:00
}
2021-09-27 15:03:54 +00:00
return list ;
2008-05-18 16:44:30 +00:00
}
2021-11-14 06:02:45 +00:00
static void tr_rpcSetHostWhitelist ( tr_rpc_server * server , std : : string_view whitelist )
2018-01-11 18:00:41 +00:00
{
2021-11-14 06:02:45 +00:00
server - > hostWhitelist = parseWhitelist ( whitelist ) ;
2018-01-11 18:00:41 +00:00
}
2021-11-14 06:02:45 +00:00
void tr_rpcSetWhitelist ( tr_rpc_server * server , std : : string_view whitelist )
2018-01-11 18:00:41 +00:00
{
2021-11-14 06:02:45 +00:00
server - > whitelistStr = whitelist ;
server - > whitelist = parseWhitelist ( whitelist ) ;
2018-01-11 18:00:41 +00:00
}
2021-11-14 06:02:45 +00:00
std : : string const & tr_rpcGetWhitelist ( tr_rpc_server const * server )
2008-05-18 16:44:30 +00:00
{
2021-11-14 06:02:45 +00:00
return server - > whitelistStr ;
2008-06-05 16:23:03 +00:00
}
2017-04-19 12:04:45 +00:00
void tr_rpcSetWhitelistEnabled ( tr_rpc_server * server , bool isEnabled )
2008-10-01 22:59:29 +00:00
{
2017-04-19 12:04:45 +00:00
server - > isWhitelistEnabled = isEnabled ;
2008-10-01 22:59:29 +00:00
}
2017-04-20 16:02:19 +00:00
bool tr_rpcGetWhitelistEnabled ( tr_rpc_server const * server )
2008-10-01 22:59:29 +00:00
{
2017-04-19 12:04:45 +00:00
return server - > isWhitelistEnabled ;
2008-10-01 22:59:29 +00:00
}
2021-11-14 06:02:45 +00:00
static void tr_rpcSetHostWhitelistEnabled ( tr_rpc_server * server , bool isEnabled )
2018-01-11 18:00:41 +00:00
{
server - > isHostWhitelistEnabled = isEnabled ;
}
2022-02-23 21:09:54 +00:00
int tr_rpcGetRPCSocketMode ( tr_rpc_server const * server )
{
return server - > rpc_socket_mode ;
}
static void tr_rpcSetRPCSocketMode ( tr_rpc_server * server , int socket_mode )
{
server - > rpc_socket_mode = socket_mode ;
}
2008-06-05 16:23:03 +00:00
/****
* * * * * PASSWORD
* * * */
2021-11-14 21:14:37 +00:00
void tr_rpcSetUsername ( tr_rpc_server * server , std : : string_view username )
2008-06-05 16:23:03 +00:00
{
2021-11-14 21:14:37 +00:00
server - > username = username ;
dbgmsg ( " setting our Username to [%s] " , server - > username . c_str ( ) ) ;
2008-06-05 16:23:03 +00:00
}
2021-11-14 21:14:37 +00:00
std : : string const & tr_rpcGetUsername ( tr_rpc_server const * server )
2008-06-05 16:23:03 +00:00
{
2021-11-14 21:14:37 +00:00
return server - > username ;
2008-06-05 16:23:03 +00:00
}
2021-12-21 22:14:15 +00:00
static bool isSalted ( std : : string_view password )
2021-11-15 03:54:48 +00:00
{
2021-12-21 22:14:15 +00:00
return tr_ssha1_test ( password ) ;
2021-11-15 03:54:48 +00:00
}
2021-11-14 21:14:37 +00:00
void tr_rpcSetPassword ( tr_rpc_server * server , std : : string_view password )
2008-06-05 16:23:03 +00:00
{
2021-11-15 03:54:48 +00:00
server - > salted_password = isSalted ( password ) ? password : tr_ssha1 ( password ) ;
2017-04-19 12:04:45 +00:00
2021-11-15 03:54:48 +00:00
dbgmsg ( " setting our salted password to [%s] " , server - > salted_password . c_str ( ) ) ;
2008-06-05 16:23:03 +00:00
}
2021-11-14 21:14:37 +00:00
std : : string const & tr_rpcGetPassword ( tr_rpc_server const * server )
2008-06-05 16:23:03 +00:00
{
2021-11-15 03:54:48 +00:00
return server - > salted_password ;
2008-06-05 16:23:03 +00:00
}
2017-04-19 12:04:45 +00:00
void tr_rpcSetPasswordEnabled ( tr_rpc_server * server , bool isEnabled )
2008-06-05 16:23:03 +00:00
{
2017-04-19 12:04:45 +00:00
server - > isPasswordEnabled = isEnabled ;
dbgmsg ( " setting 'password enabled' to %d " , ( int ) isEnabled ) ;
2008-06-05 16:23:03 +00:00
}
2017-04-20 16:02:19 +00:00
bool tr_rpcIsPasswordEnabled ( tr_rpc_server const * server )
2008-06-05 16:23:03 +00:00
{
2017-04-19 12:04:45 +00:00
return server - > isPasswordEnabled ;
2008-05-18 16:44:30 +00:00
}
2017-04-20 16:02:19 +00:00
char const * tr_rpcGetBindAddress ( tr_rpc_server const * server )
2009-04-15 21:05:58 +00:00
{
2022-02-23 21:09:54 +00:00
static char addr_buf [ TrUnixAddrStrLen ] ;
2022-02-27 22:05:08 +00:00
return tr_rpc_address_to_string ( * server - > bindAddress , addr_buf , sizeof ( addr_buf ) ) ;
2009-04-15 21:05:58 +00:00
}
2020-11-14 18:43:42 +00:00
bool tr_rpcGetAntiBruteForceEnabled ( tr_rpc_server const * server )
{
return server - > isAntiBruteForceEnabled ;
}
void tr_rpcSetAntiBruteForceEnabled ( tr_rpc_server * server , bool isEnabled )
{
server - > isAntiBruteForceEnabled = isEnabled ;
if ( ! isEnabled )
{
server - > loginattempts = 0 ;
}
}
int tr_rpcGetAntiBruteForceThreshold ( tr_rpc_server const * server )
{
return server - > antiBruteForceThreshold ;
}
void tr_rpcSetAntiBruteForceThreshold ( tr_rpc_server * server , int badRequests )
{
server - > antiBruteForceThreshold = badRequests ;
}
2008-06-05 16:23:03 +00:00
/****
* * * * * LIFE CYCLE
* * * */
2017-04-20 16:02:19 +00:00
static void missing_settings_key ( tr_quark const q )
2012-12-22 20:35:19 +00:00
{
2021-11-02 01:01:27 +00:00
char const * str = tr_quark_get_string ( q ) ;
2022-01-17 18:39:50 +00:00
tr_logAddNamedError ( MyName , _ ( " Couldn't find settings key \" %s \" " ) , str ) ;
2015-01-02 11:15:31 +00:00
}
2012-12-22 20:35:19 +00:00
2021-11-14 21:14:37 +00:00
tr_rpc_server : : tr_rpc_server ( tr_session * session_in , tr_variant * settings )
2022-01-17 16:20:12 +00:00
: compressor { libdeflate_alloc_compressor ( DeflateLevel ) , libdeflate_free_compressor }
2022-02-27 22:05:08 +00:00
, bindAddress ( std : : make_unique < struct tr_rpc_address > ( ) )
2022-01-17 16:20:12 +00:00
, session { session_in }
2008-05-18 16:44:30 +00:00
{
2021-10-22 13:51:36 +00:00
auto boolVal = bool { } ;
auto i = int64_t { } ;
2021-11-14 22:06:32 +00:00
auto sv = std : : string_view { } ;
2017-04-19 12:04:45 +00:00
2021-11-14 21:14:37 +00:00
auto key = TR_KEY_rpc_enabled ;
2017-04-19 12:04:45 +00:00
if ( ! tr_variantDictFindBool ( settings , key , & boolVal ) )
{
missing_settings_key ( key ) ;
}
else
{
2021-11-14 21:14:37 +00:00
this - > isEnabled = boolVal ;
2017-04-19 12:04:45 +00:00
}
key = TR_KEY_rpc_port ;
if ( ! tr_variantDictFindInt ( settings , key , & i ) )
2013-02-02 16:22:21 +00:00
{
2017-04-19 12:04:45 +00:00
missing_settings_key ( key ) ;
2013-02-02 16:22:21 +00:00
}
2017-04-19 12:04:45 +00:00
else
{
2021-11-14 21:14:37 +00:00
this - > port = ( tr_port ) i ;
2017-04-19 12:04:45 +00:00
}
key = TR_KEY_rpc_url ;
2021-11-14 21:14:37 +00:00
if ( ! tr_variantDictFindStrView ( settings , key , & sv ) )
2013-02-02 16:22:21 +00:00
{
2017-04-19 12:04:45 +00:00
missing_settings_key ( key ) ;
2013-02-02 16:22:21 +00:00
}
2021-11-14 21:14:37 +00:00
else if ( std : : empty ( sv ) | | sv . back ( ) ! = ' / ' )
2021-11-05 06:29:19 +00:00
{
2021-11-14 21:14:37 +00:00
this - > url = tr_strvJoin ( sv , " / " sv ) ;
2021-11-05 06:29:19 +00:00
}
2017-04-19 12:04:45 +00:00
else
2013-02-02 16:22:21 +00:00
{
2021-11-14 21:14:37 +00:00
this - > url = sv ;
2009-04-15 21:05:58 +00:00
}
2017-04-19 12:04:45 +00:00
key = TR_KEY_rpc_whitelist_enabled ;
if ( ! tr_variantDictFindBool ( settings , key , & boolVal ) )
2008-12-16 16:13:21 +00:00
{
2017-04-19 12:04:45 +00:00
missing_settings_key ( key ) ;
}
else
{
2021-11-14 21:14:37 +00:00
tr_rpcSetWhitelistEnabled ( this , boolVal ) ;
2017-04-19 12:04:45 +00:00
}
2008-12-16 16:13:21 +00:00
2018-01-11 18:00:41 +00:00
key = TR_KEY_rpc_host_whitelist_enabled ;
if ( ! tr_variantDictFindBool ( settings , key , & boolVal ) )
{
missing_settings_key ( key ) ;
}
else
{
2021-11-14 21:14:37 +00:00
tr_rpcSetHostWhitelistEnabled ( this , boolVal ) ;
2018-01-11 18:00:41 +00:00
}
key = TR_KEY_rpc_host_whitelist ;
2021-11-14 06:02:45 +00:00
if ( ! tr_variantDictFindStrView ( settings , key , & sv ) & & ! std : : empty ( sv ) )
2018-01-11 18:00:41 +00:00
{
missing_settings_key ( key ) ;
}
else
{
2021-11-14 21:14:37 +00:00
tr_rpcSetHostWhitelist ( this , sv ) ;
2018-01-11 18:00:41 +00:00
}
2017-04-19 12:04:45 +00:00
key = TR_KEY_rpc_authentication_required ;
2008-12-16 16:13:21 +00:00
2017-04-19 12:04:45 +00:00
if ( ! tr_variantDictFindBool ( settings , key , & boolVal ) )
{
missing_settings_key ( key ) ;
}
else
{
2021-11-14 21:14:37 +00:00
tr_rpcSetPasswordEnabled ( this , boolVal ) ;
2017-04-19 12:04:45 +00:00
}
key = TR_KEY_rpc_whitelist ;
2021-11-14 06:02:45 +00:00
if ( ! tr_variantDictFindStrView ( settings , key , & sv ) & & ! std : : empty ( sv ) )
2017-04-19 12:04:45 +00:00
{
missing_settings_key ( key ) ;
}
else
{
2021-11-14 21:14:37 +00:00
tr_rpcSetWhitelist ( this , sv ) ;
2017-04-19 12:04:45 +00:00
}
key = TR_KEY_rpc_username ;
2021-11-14 22:06:32 +00:00
if ( ! tr_variantDictFindStrView ( settings , key , & sv ) )
2017-04-19 12:04:45 +00:00
{
missing_settings_key ( key ) ;
}
else
{
2021-11-14 22:06:32 +00:00
tr_rpcSetUsername ( this , sv ) ;
2017-04-19 12:04:45 +00:00
}
key = TR_KEY_rpc_password ;
2021-11-14 21:14:37 +00:00
if ( ! tr_variantDictFindStrView ( settings , key , & sv ) )
2017-04-19 12:04:45 +00:00
{
missing_settings_key ( key ) ;
}
else
{
2021-11-14 21:14:37 +00:00
tr_rpcSetPassword ( this , sv ) ;
2017-04-19 12:04:45 +00:00
}
2020-11-14 18:43:42 +00:00
key = TR_KEY_anti_brute_force_enabled ;
if ( ! tr_variantDictFindBool ( settings , key , & boolVal ) )
{
missing_settings_key ( key ) ;
}
else
{
2021-11-14 21:14:37 +00:00
tr_rpcSetAntiBruteForceEnabled ( this , boolVal ) ;
2020-11-14 18:43:42 +00:00
}
key = TR_KEY_anti_brute_force_threshold ;
if ( ! tr_variantDictFindInt ( settings , key , & i ) )
{
missing_settings_key ( key ) ;
}
else
{
2021-11-14 21:14:37 +00:00
tr_rpcSetAntiBruteForceThreshold ( this , i ) ;
2020-11-14 18:43:42 +00:00
}
2022-02-23 21:09:54 +00:00
key = TR_KEY_rpc_socket_mode ;
if ( ! tr_variantDictFindInt ( settings , key , & i ) )
{
missing_settings_key ( key ) ;
}
else
{
tr_rpcSetRPCSocketMode ( this , i ) ;
}
2017-04-19 12:04:45 +00:00
key = TR_KEY_rpc_bind_address ;
2021-11-14 22:06:32 +00:00
if ( ! tr_variantDictFindStrView ( settings , key , & sv ) )
2017-04-19 12:04:45 +00:00
{
missing_settings_key ( key ) ;
2022-02-27 22:05:08 +00:00
bindAddress - > set_inaddr_any ( ) ;
2017-04-19 12:04:45 +00:00
}
2022-02-27 22:05:08 +00:00
else if ( ! tr_rpc_address_from_string ( * bindAddress , sv ) )
2017-04-19 12:04:45 +00:00
{
2022-02-27 22:05:08 +00:00
tr_logAddNamedError (
MyName ,
_ ( " % " TR_PRIsv
" is not an IPv4 address, an IPv6 address, or a unix socket path. RPC listeners must be one of the previously mentioned types. Falling back to 0.0.0.0. " ) ,
TR_PRIsv_ARG ( sv ) ) ;
bindAddress - > set_inaddr_any ( ) ;
2017-04-19 12:04:45 +00:00
}
2022-02-27 22:05:08 +00:00
if ( bindAddress - > type = = TR_RPC_AF_UNIX )
2022-02-23 21:09:54 +00:00
{
this - > isWhitelistEnabled = false ;
this - > isHostWhitelistEnabled = false ;
}
2021-11-14 21:14:37 +00:00
if ( this - > isEnabled )
2017-04-19 12:04:45 +00:00
{
2022-02-27 22:05:08 +00:00
auto const rpc_uri = tr_rpc_address_with_port ( this ) + this - > url ;
tr_logAddNamedInfo ( MyName , _ ( " Serving RPC and Web requests on %s " ) , rpc_uri . c_str ( ) ) ;
2021-11-14 21:14:37 +00:00
tr_runInEventThread ( session , startServer , this ) ;
2017-04-19 12:04:45 +00:00
2021-11-14 21:14:37 +00:00
if ( this - > isWhitelistEnabled )
2017-04-19 12:04:45 +00:00
{
2022-01-17 18:39:50 +00:00
tr_logAddNamedInfo ( MyName , " %s " , _ ( " Whitelist enabled " ) ) ;
2017-04-19 12:04:45 +00:00
}
2021-11-14 21:14:37 +00:00
if ( this - > isPasswordEnabled )
2017-04-19 12:04:45 +00:00
{
2022-01-17 18:39:50 +00:00
tr_logAddNamedInfo ( MyName , " %s " , _ ( " Password required " ) ) ;
2017-04-19 12:04:45 +00:00
}
2008-12-16 16:13:21 +00:00
}
2021-11-14 21:14:37 +00:00
char const * webClientDir = tr_getWebClientDir ( this - > session ) ;
2020-10-24 01:04:25 +00:00
if ( ! tr_str_is_empty ( webClientDir ) )
{
2022-01-17 18:39:50 +00:00
tr_logAddNamedInfo ( MyName , _ ( " Serving RPC and Web requests from directory '%s' " ) , webClientDir ) ;
2020-10-24 01:04:25 +00:00
}
2021-11-14 21:14:37 +00:00
}
tr_rpc_server : : ~ tr_rpc_server ( )
{
TR_ASSERT ( tr_amInEventThread ( this - > session ) ) ;
stopServer ( this ) ;
2008-05-18 16:44:30 +00:00
}