X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fstream_out%2Fraop.c;h=825f42e0e3b8dc3b41a30882b5de2b4f1fa21772;hb=5309b722119ce366a1b9ee318ca8ca79a4c3e55e;hp=1e8c46779eec33e49ca361207660c34ef855aa52;hpb=64964519f5427753746f94a5d838cdc8b2fbf2b4;p=vlc diff --git a/modules/stream_out/raop.c b/modules/stream_out/raop.c index 1e8c46779e..825f42e0e3 100644 --- a/modules/stream_out/raop.c +++ b/modules/stream_out/raop.c @@ -1,24 +1,24 @@ /***************************************************************************** * raop.c: Remote Audio Output Protocol streaming support ***************************************************************************** - * Copyright (C) 2008 the VideoLAN team + * Copyright (C) 2008 VLC authors and VideoLAN * $Id$ * * Author: Michael Hanselmann * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ /***************************************************************************** @@ -39,8 +39,11 @@ #include #include #include +#include #include #include +#include +#include #define RAOP_PORT 5000 #define RAOP_USER_AGENT "VLC " VERSION @@ -97,6 +100,7 @@ struct sout_stream_sys_t { /* Input parameters */ char *psz_host; + char *psz_password; int i_volume; /* Plugin status */ @@ -122,6 +126,8 @@ struct sout_stream_sys_t int i_audio_latency; int i_jack_type; + http_auth_t auth; + /* Send buffer */ size_t i_sendbuf_len; uint8_t *p_sendbuf; @@ -145,22 +151,34 @@ struct sout_stream_id_t #define VOLUME_LONGTEXT N_("Output volume for analog output: 0 for silence, " \ "1..255 from almost silent to very loud.") -vlc_module_begin() +#define PASSWORD_TEXT N_("Password") +#define PASSWORD_LONGTEXT N_("Password for target device.") + +#define PASSWORD_FILE_TEXT N_("Password file") +#define PASSWORD_FILE_LONGTEXT N_("Read password for target device from file.") + +vlc_module_begin(); set_shortname( N_("RAOP") ) set_description( N_("Remote Audio Output Protocol stream output") ) set_capability( "sout stream", 0 ) add_shortcut( "raop" ) set_category( CAT_SOUT ) set_subcategory( SUBCAT_SOUT_STREAM ) - add_string( SOUT_CFG_PREFIX "host", "", NULL, + add_string( SOUT_CFG_PREFIX "host", "", HOST_TEXT, HOST_LONGTEXT, false ) - add_integer_with_range( SOUT_CFG_PREFIX "volume", 100, 0, 255, NULL, + add_password( SOUT_CFG_PREFIX "password", NULL, + PASSWORD_TEXT, PASSWORD_LONGTEXT, false ) + add_loadfile( SOUT_CFG_PREFIX "password-file", NULL, + PASSWORD_FILE_TEXT, PASSWORD_FILE_LONGTEXT, false ) + add_integer_with_range( SOUT_CFG_PREFIX "volume", 100, 0, 255, VOLUME_TEXT, VOLUME_LONGTEXT, false ) set_callbacks( Open, Close ) vlc_module_end() static const char *const ppsz_sout_options[] = { "host", + "password", + "password-file", "volume", NULL }; @@ -185,6 +203,7 @@ static void FreeSys( vlc_object_t *p_this, sout_stream_sys_t *p_sys ) free( p_sys->p_sendbuf ); free( p_sys->psz_host ); + free( p_sys->psz_password ); free( p_sys->psz_url ); free( p_sys->psz_session ); free( p_sys->psz_client_instance ); @@ -428,7 +447,7 @@ static int EncryptAesKeyBase64( vlc_object_t *p_this, char **result ) unsigned char ps_padded_key[256]; unsigned char *ps_value; size_t i_value_size; - int i_err = VLC_SUCCESS; + int i_err; /* Add RSA-OAES-SHA1 padding */ i_err = AddOaepPadding( p_this, @@ -437,25 +456,20 @@ static int EncryptAesKeyBase64( vlc_object_t *p_this, char **result ) NULL, 0 ); if ( i_err != VLC_SUCCESS ) goto error; + i_err = VLC_EGENERIC; /* Read public key */ i_gcrypt_err = gcry_mpi_scan( &mpi_pubkey, GCRYMPI_FMT_USG, ps_raop_rsa_pubkey, sizeof( ps_raop_rsa_pubkey ) - 1, NULL ); if ( CheckForGcryptError( p_stream, i_gcrypt_err ) ) - { - i_err = VLC_EGENERIC; goto error; - } /* Read exponent */ i_gcrypt_err = gcry_mpi_scan( &mpi_exp, GCRYMPI_FMT_USG, ps_raop_rsa_exp, sizeof( ps_raop_rsa_exp ) - 1, NULL ); if ( CheckForGcryptError( p_stream, i_gcrypt_err ) ) - { - i_err = VLC_EGENERIC; goto error; - } /* If the input data starts with a set bit (0x80), gcrypt thinks it's a * signed integer and complains. Prefixing it with a zero byte (\0) @@ -466,45 +480,32 @@ static int EncryptAesKeyBase64( vlc_object_t *p_this, char **result ) ps_padded_key, sizeof( ps_padded_key ), NULL); if ( CheckForGcryptError( p_stream, i_gcrypt_err ) ) - { - i_err = VLC_EGENERIC; goto error; - } /* Build S-expression with RSA parameters */ i_gcrypt_err = gcry_sexp_build( &sexp_rsa_params, NULL, "(public-key(rsa(n %m)(e %m)))", mpi_pubkey, mpi_exp ); if ( CheckForGcryptError( p_stream, i_gcrypt_err ) ) - { - i_err = VLC_EGENERIC; goto error; - } /* Build S-expression for data */ i_gcrypt_err = gcry_sexp_build( &sexp_input, NULL, "(data(value %m))", mpi_input ); if ( CheckForGcryptError( p_stream, i_gcrypt_err ) ) - { - i_err = VLC_EGENERIC; goto error; - } /* Encrypt data */ i_gcrypt_err = gcry_pk_encrypt( &sexp_encrypted, sexp_input, sexp_rsa_params ); if ( CheckForGcryptError( p_stream, i_gcrypt_err ) ) - { - i_err = VLC_EGENERIC; goto error; - } /* Extract encrypted data */ sexp_token_a = gcry_sexp_find_token( sexp_encrypted, "a", 0 ); if ( !sexp_token_a ) { msg_Err( p_this , "Token 'a' not found in result S-expression" ); - i_err = VLC_EGENERIC; goto error; } @@ -512,7 +513,6 @@ static int EncryptAesKeyBase64( vlc_object_t *p_this, char **result ) if ( !mpi_output ) { msg_Err( p_this, "Unable to extract MPI from result" ); - i_err = VLC_EGENERIC; goto error; } @@ -521,12 +521,12 @@ static int EncryptAesKeyBase64( vlc_object_t *p_this, char **result ) mpi_output ); if ( CheckForGcryptError( p_stream, i_gcrypt_err ) ) { - i_err = VLC_EGENERIC; goto error; } /* Encode in Base64 */ *result = vlc_b64_encode_binary( ps_value, i_value_size ); + i_err = VLC_SUCCESS; error: gcry_sexp_release( sexp_rsa_params ); @@ -541,6 +541,58 @@ error: return i_err; } +static char *ReadPasswordFile( vlc_object_t *p_this, const char *psz_path ) +{ + FILE *p_file = NULL; + char *psz_password = NULL; + char *psz_newline; + char ps_buffer[256]; + + p_file = vlc_fopen( psz_path, "rt" ); + if ( p_file == NULL ) + { + msg_Err( p_this, "Unable to open password file '%s': %s", psz_path, + vlc_strerror_c(errno) ); + goto error; + } + + /* Read one line only */ + if ( fgets( ps_buffer, sizeof( ps_buffer ), p_file ) == NULL ) + { + if ( ferror( p_file ) ) + { + msg_Err( p_this, "Error reading '%s': %s", psz_path, + vlc_strerror_c(errno) ); + goto error; + } + + /* Nothing was read, but there was no error either. Maybe the file is + * empty. Not all implementations of fgets(3) write \0 to the output + * buffer in this case. + */ + ps_buffer[0] = '\0'; + + } else { + /* Replace first newline with '\0' */ + psz_newline = strchr( ps_buffer, '\n' ); + if ( psz_newline != NULL ) + *psz_newline = '\0'; + } + + if ( *ps_buffer == '\0' ) { + msg_Err( p_this, "No password could be read from '%s'", psz_path ); + goto error; + } + + psz_password = strdup( ps_buffer ); + +error: + if ( p_file != NULL ) + fclose( p_file ); + + return psz_password; +} + /* Splits the value of a received header. * * Example: "Transport: RTP/AVP/TCP;unicast;mode=record;server_port=6000" @@ -578,15 +630,12 @@ static int ReadStatusLine( vlc_object_t *p_this ) char *psz_line = NULL; char *psz_token; char *psz_next; - int i_result; + int i_result = VLC_EGENERIC; p_sys->psz_last_status_line = net_Gets( p_this, p_sys->i_control_fd, NULL ); if ( !p_sys->psz_last_status_line ) - { - i_result = VLC_EGENERIC; goto error; - } /* Create working copy */ psz_line = strdup( p_sys->psz_last_status_line ); @@ -598,7 +647,6 @@ static int ReadStatusLine( vlc_object_t *p_this ) { msg_Err( p_this, "Unknown protocol (%s)", p_sys->psz_last_status_line ); - i_result = VLC_EGENERIC; goto error; } @@ -608,7 +656,6 @@ static int ReadStatusLine( vlc_object_t *p_this ) { msg_Err( p_this, "Request failed (%s)", p_sys->psz_last_status_line ); - i_result = VLC_EGENERIC; goto error; } @@ -775,6 +822,30 @@ error: return i_err; } +static int ParseAuthenticateHeader( vlc_object_t *p_this, + vlc_dictionary_t *p_resp_headers ) +{ + sout_stream_t *p_stream = (sout_stream_t*)p_this; + sout_stream_sys_t *p_sys = p_stream->p_sys; + char *psz_auth; + int i_err = VLC_SUCCESS; + + psz_auth = vlc_dictionary_value_for_key( p_resp_headers, + "WWW-Authenticate" ); + if ( psz_auth == NULL ) + { + msg_Err( p_this, "HTTP 401 response missing " + "WWW-Authenticate header" ); + i_err = VLC_EGENERIC; + goto error; + } + + http_auth_ParseWwwAuthenticateHeader( p_this, &p_sys->auth, psz_auth ); + +error: + return i_err; +} + static int ExecRequest( vlc_object_t *p_this, const char *psz_method, const char *psz_content_type, const char *psz_body, vlc_dictionary_t *p_req_headers, @@ -782,9 +853,11 @@ static int ExecRequest( vlc_object_t *p_this, const char *psz_method, { sout_stream_t *p_stream = (sout_stream_t*)p_this; sout_stream_sys_t *p_sys = p_stream->p_sys; + char *psz_authorization = NULL; int headers_done; int i_err = VLC_SUCCESS; int i_status; + int i_auth_state; if ( p_sys->i_control_fd < 0 ) { @@ -793,8 +866,29 @@ static int ExecRequest( vlc_object_t *p_this, const char *psz_method, goto error; } + i_auth_state = 0; while ( 1 ) { + /* Send header only when Digest authentication is used */ + if ( p_sys->psz_password != NULL && p_sys->auth.psz_nonce != NULL ) + { + FREENULL( psz_authorization ); + + psz_authorization = + http_auth_FormatAuthorizationHeader( p_this, &p_sys->auth, + psz_method, + p_sys->psz_url, "", + p_sys->psz_password ); + if ( psz_authorization == NULL ) + { + i_err = VLC_EGENERIC; + goto error; + } + + vlc_dictionary_insert( p_req_headers, "Authorization", + psz_authorization ); + } + /* Send request */ i_err = SendRequest( p_this, psz_method, psz_content_type, psz_body, p_req_headers); @@ -823,6 +917,22 @@ static int ExecRequest( vlc_object_t *p_this, const char *psz_method, if ( i_status == 200 ) /* Request successful */ break; + else if ( i_status == 401 ) + { + /* Authorization required */ + if ( i_auth_state == 1 || p_sys->psz_password == NULL ) + { + msg_Err( p_this, "Access denied, password invalid" ); + i_err = VLC_EGENERIC; + goto error; + } + + i_err = ParseAuthenticateHeader( p_this, p_resp_headers ); + if ( i_err != VLC_SUCCESS ) + goto error; + + i_auth_state = 1; + } else { msg_Err( p_this, "Request failed (%s), status is %d", @@ -834,6 +944,7 @@ static int ExecRequest( vlc_object_t *p_this, const char *psz_method, error: FREENULL( p_sys->psz_last_status_line ); + free( psz_authorization ); return i_err; } @@ -1106,7 +1217,7 @@ static int UpdateVolume( vlc_object_t *p_this ) /* Our volume is 0..255, RAOP is -144..0 (-144 off, -30..0 on) */ /* Limit range */ - p_sys->i_volume = __MAX( 0, __MIN( p_sys->i_volume, 255 ) ); + p_sys->i_volume = VLC_CLIP( p_sys->i_volume, 0, 255 ); if ( p_sys->i_volume == 0 ) d_volume = -144.0; @@ -1192,7 +1303,7 @@ static void SendAudio( sout_stream_t *p_stream, block_t *p_buffer ) /* Grow in blocks of 4K */ i_realloc_len = (1 + (i_len / 4096)) * 4096; - p_sys->p_sendbuf = realloc( p_sys->p_sendbuf, i_realloc_len ); + p_sys->p_sendbuf = realloc_or_free( p_sys->p_sendbuf, i_realloc_len ); if ( p_sys->p_sendbuf == NULL ) goto error; @@ -1263,6 +1374,7 @@ static int Open( vlc_object_t *p_this ) sout_stream_t *p_stream = (sout_stream_t*)p_this; sout_stream_sys_t *p_sys; char psz_local[NI_MAXNUMERICHOST]; + char *psz_pwfile = NULL; gcry_error_t i_gcrypt_err; int i_err = VLC_SUCCESS; uint32_t i_session_id; @@ -1280,17 +1392,19 @@ static int Open( vlc_object_t *p_this ) goto error; } - p_stream->p_sys = p_sys; p_stream->pf_add = Add; p_stream->pf_del = Del; p_stream->pf_send = Send; - p_stream->p_sout->i_out_pace_nocontrol++; + p_stream->p_sys = p_sys; + p_stream->pace_nocontrol = true; p_sys->i_control_fd = -1; p_sys->i_stream_fd = -1; p_sys->i_volume = var_GetInteger( p_stream, SOUT_CFG_PREFIX "volume"); p_sys->i_jack_type = JACK_TYPE_NONE; + http_auth_Init( &p_sys->auth ); + p_sys->psz_host = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "host" ); if ( p_sys->psz_host == NULL ) @@ -1300,6 +1414,27 @@ static int Open( vlc_object_t *p_this ) goto error; } + p_sys->psz_password = var_GetNonEmptyString( p_stream, + SOUT_CFG_PREFIX "password" ); + if ( p_sys->psz_password == NULL ) + { + /* Try password file instead */ + psz_pwfile = var_GetNonEmptyString( p_stream, + SOUT_CFG_PREFIX "password-file" ); + if ( psz_pwfile != NULL ) + { + p_sys->psz_password = ReadPasswordFile( p_this, psz_pwfile ); + if ( p_sys->psz_password == NULL ) + { + i_err = VLC_EGENERIC; + goto error; + } + } + } + + if ( p_sys->psz_password != NULL ) + msg_Info( p_this, "Using password authentication" ); + var_AddCallback( p_stream, SOUT_CFG_PREFIX "volume", VolumeCallback, NULL ); p_sys->b_volume_callback = true; @@ -1309,8 +1444,8 @@ static int Open( vlc_object_t *p_this ) RAOP_PORT ); if ( p_sys->i_control_fd < 0 ) { - msg_Err( p_this, "Cannot establish control connection to %s:%d (%m)", - p_sys->psz_host, RAOP_PORT ); + msg_Err( p_this, "Cannot establish control connection to %s:%d (%s)", + p_sys->psz_host, RAOP_PORT, vlc_strerror_c(errno) ); i_err = VLC_EGENERIC; goto error; } @@ -1393,13 +1528,16 @@ static int Open( vlc_object_t *p_this ) p_sys->i_server_port ); if ( p_sys->i_stream_fd < 0 ) { - msg_Err( p_this, "Cannot establish stream connection to %s:%d (%m)", - p_sys->psz_host, p_sys->i_server_port ); + msg_Err( p_this, "Cannot establish stream connection to %s:%d (%s)", + p_sys->psz_host, p_sys->i_server_port, + vlc_strerror_c(errno) ); i_err = VLC_EGENERIC; goto error; } error: + free( psz_pwfile ); + if ( i_err != VLC_SUCCESS ) FreeSys( p_this, p_sys ); @@ -1419,8 +1557,6 @@ static void Close( vlc_object_t *p_this ) SendTeardown( p_this ); FreeSys( p_this, p_sys ); - - p_stream->p_sout->i_out_pace_nocontrol--; }