1 /*****************************************************************************
2 * raop.c: Remote Audio Output Protocol streaming support
3 *****************************************************************************
4 * Copyright (C) 2008 the VideoLAN team
7 * Author: Michael Hanselmann
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
35 #include <vlc_common.h>
36 #include <vlc_plugin.h>
38 #include <vlc_block.h>
39 #include <vlc_network.h>
40 #include <vlc_strings.h>
41 #include <vlc_charset.h>
42 #include <vlc_gcrypt.h>
46 #define RAOP_PORT 5000
47 #define RAOP_USER_AGENT "VLC " VERSION
50 static const char ps_raop_rsa_pubkey[] =
51 "\xe7\xd7\x44\xf2\xa2\xe2\x78\x8b\x6c\x1f\x55\xa0\x8e\xb7\x05\x44"
52 "\xa8\xfa\x79\x45\xaa\x8b\xe6\xc6\x2c\xe5\xf5\x1c\xbd\xd4\xdc\x68"
53 "\x42\xfe\x3d\x10\x83\xdd\x2e\xde\xc1\xbf\xd4\x25\x2d\xc0\x2e\x6f"
54 "\x39\x8b\xdf\x0e\x61\x48\xea\x84\x85\x5e\x2e\x44\x2d\xa6\xd6\x26"
55 "\x64\xf6\x74\xa1\xf3\x04\x92\x9a\xde\x4f\x68\x93\xef\x2d\xf6\xe7"
56 "\x11\xa8\xc7\x7a\x0d\x91\xc9\xd9\x80\x82\x2e\x50\xd1\x29\x22\xaf"
57 "\xea\x40\xea\x9f\x0e\x14\xc0\xf7\x69\x38\xc5\xf3\x88\x2f\xc0\x32"
58 "\x3d\xd9\xfe\x55\x15\x5f\x51\xbb\x59\x21\xc2\x01\x62\x9f\xd7\x33"
59 "\x52\xd5\xe2\xef\xaa\xbf\x9b\xa0\x48\xd7\xb8\x13\xa2\xb6\x76\x7f"
60 "\x6c\x3c\xcf\x1e\xb4\xce\x67\x3d\x03\x7b\x0d\x2e\xa3\x0c\x5f\xff"
61 "\xeb\x06\xf8\xd0\x8a\xdd\xe4\x09\x57\x1a\x9c\x68\x9f\xef\x10\x72"
62 "\x88\x55\xdd\x8c\xfb\x9a\x8b\xef\x5c\x89\x43\xef\x3b\x5f\xaa\x15"
63 "\xdd\xe6\x98\xbe\xdd\xf3\x59\x96\x03\xeb\x3e\x6f\x61\x37\x2b\xb6"
64 "\x28\xf6\x55\x9f\x59\x9a\x78\xbf\x50\x06\x87\xaa\x7f\x49\x76\xc0"
65 "\x56\x2d\x41\x29\x56\xf8\x98\x9e\x18\xa6\x35\x5b\xd8\x15\x97\x82"
66 "\x5e\x0f\xc8\x75\x34\x3e\xc7\x82\x11\x76\x25\xcd\xbf\x98\x44\x7b";
68 static const char ps_raop_rsa_exp[] = "\x01\x00\x01";
70 static const char psz_delim_space[] = " ";
71 static const char psz_delim_colon[] = ":";
72 static const char psz_delim_equal[] = "=";
73 static const char psz_delim_semicolon[] = ";";
76 /*****************************************************************************
78 *****************************************************************************/
79 static int Open( vlc_object_t * );
80 static void Close( vlc_object_t * );
82 static sout_stream_id_t *Add( sout_stream_t *, es_format_t * );
83 static int Del( sout_stream_t *, sout_stream_id_t * );
84 static int Send( sout_stream_t *, sout_stream_id_t *, block_t* );
86 static int VolumeCallback( vlc_object_t *p_this, char const *psz_cmd,
87 vlc_value_t oldval, vlc_value_t newval,
97 struct sout_stream_sys_t
99 /* Input parameters */
105 sout_stream_id_t *p_audio_stream;
107 bool b_volume_callback;
109 /* Connection state */
113 uint8_t ps_aes_key[16];
114 uint8_t ps_aes_iv[16];
115 gcry_cipher_hd_t aes_ctx;
118 char *psz_client_instance;
120 char *psz_last_status_line;
130 size_t i_sendbuf_len;
134 struct sout_stream_id_t
140 /*****************************************************************************
142 *****************************************************************************/
143 #define SOUT_CFG_PREFIX "sout-raop-"
145 #define HOST_TEXT N_("Host")
146 #define HOST_LONGTEXT N_("Hostname or IP address of target device")
148 #define VOLUME_TEXT N_("Volume")
149 #define VOLUME_LONGTEXT N_("Output volume for analog output: 0 for silence, " \
150 "1..255 from almost silent to very loud.")
152 #define PASSWORD_TEXT N_("Password")
153 #define PASSWORD_LONGTEXT N_("Password for target device.")
155 #define PASSWORD_FILE_TEXT N_("Password file")
156 #define PASSWORD_FILE_LONGTEXT N_("Read password for target device from file.")
159 set_shortname( N_("RAOP") )
160 set_description( N_("Remote Audio Output Protocol stream output") )
161 set_capability( "sout stream", 0 )
162 add_shortcut( "raop" )
163 set_category( CAT_SOUT )
164 set_subcategory( SUBCAT_SOUT_STREAM )
165 add_string( SOUT_CFG_PREFIX "host", "", NULL,
166 HOST_TEXT, HOST_LONGTEXT, false )
167 add_password( SOUT_CFG_PREFIX "password", NULL, NULL,
168 PASSWORD_TEXT, PASSWORD_LONGTEXT, false )
169 add_file( SOUT_CFG_PREFIX "password-file", NULL, NULL,
170 PASSWORD_FILE_TEXT, PASSWORD_FILE_LONGTEXT, false )
171 add_integer_with_range( SOUT_CFG_PREFIX "volume", 100, 0, 255, NULL,
172 VOLUME_TEXT, VOLUME_LONGTEXT, false )
173 set_callbacks( Open, Close )
176 static const char *const ppsz_sout_options[] = {
185 /*****************************************************************************
187 *****************************************************************************/
188 static void FreeSys( vlc_object_t *p_this, sout_stream_sys_t *p_sys )
190 sout_stream_t *p_stream = (sout_stream_t*)p_this;
192 if ( p_sys->i_control_fd >= 0 )
193 net_Close( p_sys->i_control_fd );
194 if ( p_sys->i_stream_fd >= 0 )
195 net_Close( p_sys->i_stream_fd );
196 if ( p_sys->b_volume_callback )
197 var_DelCallback( p_stream, SOUT_CFG_PREFIX "volume",
198 VolumeCallback, NULL );
200 gcry_cipher_close( p_sys->aes_ctx );
202 free( p_sys->p_sendbuf );
203 free( p_sys->psz_host );
204 free( p_sys->psz_password );
205 free( p_sys->psz_url );
206 free( p_sys->psz_session );
207 free( p_sys->psz_client_instance );
208 free( p_sys->psz_last_status_line );
212 static void FreeId( sout_stream_id_t *id )
217 static void RemoveBase64Padding( char *str )
219 char *ps_pos = strchr( str, '=' );
220 if ( ps_pos != NULL )
224 static int CheckForGcryptErrorWithLine( sout_stream_t *p_stream,
225 gcry_error_t i_gcrypt_err,
226 unsigned int i_line )
228 if ( i_gcrypt_err != GPG_ERR_NO_ERROR )
230 msg_Err( p_stream, "gcrypt error (line %d): %s", i_line,
231 gpg_strerror( i_gcrypt_err ) );
238 /* Wrapper to pass line number for easier debugging */
239 #define CheckForGcryptError( p_this, i_gcrypt_err ) \
240 CheckForGcryptErrorWithLine( p_this, i_gcrypt_err, __LINE__ )
242 /* MGF1 is specified in RFC2437, section 10.2.1. Variables are named after the
245 static int MGF1( vlc_object_t *p_this,
246 unsigned char *mask, size_t l,
247 const unsigned char *Z, const size_t zLen,
250 sout_stream_t *p_stream = (sout_stream_t*)p_this;
251 gcry_error_t i_gcrypt_err;
252 gcry_md_hd_t md_handle = NULL;
254 unsigned char *ps_md;
255 uint32_t counter = 0;
258 int i_err = VLC_SUCCESS;
260 assert( mask != NULL );
263 hLen = gcry_md_get_algo_dlen( Hash );
265 i_gcrypt_err = gcry_md_open( &md_handle, Hash, 0 );
266 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
268 i_err = VLC_EGENERIC;
274 /* 3. For counter from 0 to \lceil{l / hLen}\rceil-1, do the following:
275 * a. Convert counter to an octet string C of length 4 with the
276 * primitive I2OSP: C = I2OSP (counter, 4)
278 C[0] = (counter >> 24) & 0xff;
279 C[1] = (counter >> 16) & 0xff;
280 C[2] = (counter >> 8) & 0xff;
281 C[3] = counter & 0xff;
284 /* b. Concatenate the hash of the seed Z and C to the octet string T:
285 * T = T || Hash (Z || C)
287 gcry_md_reset( md_handle );
288 gcry_md_write( md_handle, Z, zLen );
289 gcry_md_write( md_handle, C, 4 );
290 ps_md = gcry_md_read( md_handle, Hash );
292 /* 4. Output the leading l octets of T as the octet string mask. */
293 i_copylen = __MIN( l, hLen );
294 memcpy( mask, ps_md, i_copylen );
300 gcry_md_close( md_handle );
305 /* EME-OAEP-ENCODE is specified in RFC2437, section 9.1.1.1. Variables are
306 * named after the specification.
308 static int AddOaepPadding( vlc_object_t *p_this,
309 unsigned char *EM, const size_t emLenWithPrefix,
310 const unsigned char *M, const size_t mLen,
311 const unsigned char *P, const size_t pLen )
313 const int Hash = GCRY_MD_SHA1;
314 const unsigned int hLen = gcry_md_get_algo_dlen( Hash );
315 unsigned char *seed = NULL;
316 unsigned char *DB = NULL;
317 unsigned char *dbMask = NULL;
318 unsigned char *seedMask = NULL;
322 int i_err = VLC_SUCCESS;
324 /* Space for 0x00 prefix in EM. */
325 emLen = emLenWithPrefix - 1;
328 * If ||M|| > emLen-2hLen-1 then output "message too long" and stop.
330 if ( mLen > (emLen - (2 * hLen) - 1) )
332 msg_Err( p_this , "Message too long" );
337 * Generate an octet string PS consisting of emLen-||M||-2hLen-1 zero
338 * octets. The length of PS may be 0.
340 psLen = emLen - mLen - (2 * hLen) - 1;
344 * Concatenate pHash, PS, the message M, and other padding to form a data
345 * block DB as: DB = pHash || PS || 01 || M
347 DB = calloc( 1, hLen + psLen + 1 + mLen );
348 dbMask = calloc( 1, emLen - hLen );
349 seedMask = calloc( 1, hLen );
351 if ( DB == NULL || dbMask == NULL || seedMask == NULL )
358 * Let pHash = Hash(P), an octet string of length hLen.
360 gcry_md_hash_buffer( Hash, DB, P, pLen );
363 * Generate an octet string PS consisting of emLen-||M||-2hLen-1 zero
364 * octets. The length of PS may be 0.
366 memset( DB + hLen, 0, psLen );
369 * Concatenate pHash, PS, the message M, and other padding to form a data
370 * block DB as: DB = pHash || PS || 01 || M
372 DB[hLen + psLen] = 0x01;
373 memcpy( DB + hLen + psLen + 1, M, mLen );
376 * Generate a random octet string seed of length hLen
378 seed = gcry_random_bytes( hLen, GCRY_STRONG_RANDOM );
386 * Let dbMask = MGF(seed, emLen-hLen).
388 i_err = MGF1( p_this, dbMask, emLen - hLen, seed, hLen, Hash );
389 if ( i_err != VLC_SUCCESS )
393 * Let maskedDB = DB \xor dbMask.
395 for ( i = 0; i < (emLen - hLen); ++i )
399 * Let seedMask = MGF(maskedDB, hLen).
401 i_err = MGF1( p_this, seedMask, hLen, DB, emLen - hLen, Hash );
402 if ( i_err != VLC_SUCCESS )
406 * Let maskedSeed = seed \xor seedMask.
408 for ( i = 0; i < hLen; ++i )
409 seed[i] ^= seedMask[i];
412 * Let EM = maskedSeed || maskedDB.
414 assert( (1 + hLen + (hLen + psLen + 1 + mLen)) == emLenWithPrefix );
416 memcpy( EM + 1, seed, hLen );
417 memcpy( EM + 1 + hLen, DB, hLen + psLen + 1 + mLen );
432 static int EncryptAesKeyBase64( vlc_object_t *p_this, char **result )
434 sout_stream_t *p_stream = (sout_stream_t*)p_this;
435 sout_stream_sys_t *p_sys = p_stream->p_sys;
436 gcry_error_t i_gcrypt_err;
437 gcry_sexp_t sexp_rsa_params = NULL;
438 gcry_sexp_t sexp_input = NULL;
439 gcry_sexp_t sexp_encrypted = NULL;
440 gcry_sexp_t sexp_token_a = NULL;
441 gcry_mpi_t mpi_pubkey = NULL;
442 gcry_mpi_t mpi_exp = NULL;
443 gcry_mpi_t mpi_input = NULL;
444 gcry_mpi_t mpi_output = NULL;
445 unsigned char ps_padded_key[256];
446 unsigned char *ps_value;
448 int i_err = VLC_SUCCESS;
450 /* Add RSA-OAES-SHA1 padding */
451 i_err = AddOaepPadding( p_this,
452 ps_padded_key, sizeof( ps_padded_key ),
453 p_sys->ps_aes_key, sizeof( p_sys->ps_aes_key ),
455 if ( i_err != VLC_SUCCESS )
458 /* Read public key */
459 i_gcrypt_err = gcry_mpi_scan( &mpi_pubkey, GCRYMPI_FMT_USG,
461 sizeof( ps_raop_rsa_pubkey ) - 1, NULL );
462 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
464 i_err = VLC_EGENERIC;
469 i_gcrypt_err = gcry_mpi_scan( &mpi_exp, GCRYMPI_FMT_USG, ps_raop_rsa_exp,
470 sizeof( ps_raop_rsa_exp ) - 1, NULL );
471 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
473 i_err = VLC_EGENERIC;
477 /* If the input data starts with a set bit (0x80), gcrypt thinks it's a
478 * signed integer and complains. Prefixing it with a zero byte (\0)
479 * works, but involves more work. Converting it to an MPI in our code is
482 i_gcrypt_err = gcry_mpi_scan( &mpi_input, GCRYMPI_FMT_USG,
483 ps_padded_key, sizeof( ps_padded_key ),
485 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
487 i_err = VLC_EGENERIC;
491 /* Build S-expression with RSA parameters */
492 i_gcrypt_err = gcry_sexp_build( &sexp_rsa_params, NULL,
493 "(public-key(rsa(n %m)(e %m)))",
494 mpi_pubkey, mpi_exp );
495 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
497 i_err = VLC_EGENERIC;
501 /* Build S-expression for data */
502 i_gcrypt_err = gcry_sexp_build( &sexp_input, NULL, "(data(value %m))",
504 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
506 i_err = VLC_EGENERIC;
511 i_gcrypt_err = gcry_pk_encrypt( &sexp_encrypted, sexp_input,
513 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
515 i_err = VLC_EGENERIC;
519 /* Extract encrypted data */
520 sexp_token_a = gcry_sexp_find_token( sexp_encrypted, "a", 0 );
523 msg_Err( p_this , "Token 'a' not found in result S-expression" );
524 i_err = VLC_EGENERIC;
528 mpi_output = gcry_sexp_nth_mpi( sexp_token_a, 1, GCRYMPI_FMT_USG );
531 msg_Err( p_this, "Unable to extract MPI from result" );
532 i_err = VLC_EGENERIC;
536 /* Copy encrypted data into char array */
537 i_gcrypt_err = gcry_mpi_aprint( GCRYMPI_FMT_USG, &ps_value, &i_value_size,
539 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
541 i_err = VLC_EGENERIC;
545 /* Encode in Base64 */
546 *result = vlc_b64_encode_binary( ps_value, i_value_size );
549 gcry_sexp_release( sexp_rsa_params );
550 gcry_sexp_release( sexp_input );
551 gcry_sexp_release( sexp_encrypted );
552 gcry_sexp_release( sexp_token_a );
553 gcry_mpi_release( mpi_pubkey );
554 gcry_mpi_release( mpi_exp );
555 gcry_mpi_release( mpi_input );
556 gcry_mpi_release( mpi_output );
561 static char *ReadPasswordFile( vlc_object_t *p_this, const char *psz_path )
564 char *psz_password = NULL;
568 p_file = utf8_fopen( psz_path, "rt" );
569 if ( p_file == NULL )
571 msg_Err( p_this, "Unable to open password file '%s': %m", psz_path );
575 /* Read one line only */
576 if ( fgets( ps_buffer, sizeof( ps_buffer ), p_file ) == NULL )
578 if ( ferror( p_file ) )
580 msg_Err( p_this, "Error reading '%s': %m", psz_path );
584 /* Nothing was read, but there was no error either. Maybe the file is
585 * empty. Not all implementations of fgets(3) write \0 to the output
586 * buffer in this case.
591 /* Replace first newline with '\0' */
592 psz_newline = strchr( ps_buffer, '\n' );
593 if ( psz_newline != NULL )
597 if ( strlen( ps_buffer ) == 0 ) {
598 msg_Err( p_this, "No password could be read from '%s'", psz_path );
602 psz_password = strdup( ps_buffer );
605 if ( p_file != NULL )
611 /* Splits the value of a received header.
613 * Example: "Transport: RTP/AVP/TCP;unicast;mode=record;server_port=6000"
615 static int SplitHeader( char **ppsz_next, char **ppsz_name,
618 /* Find semicolon (separator between assignments) */
619 *ppsz_name = strsep( ppsz_next, psz_delim_semicolon );
623 *ppsz_name += strspn( *ppsz_name, psz_delim_space );
626 *ppsz_value = *ppsz_name;
627 strsep( ppsz_value, psz_delim_equal );
635 static void FreeHeader( void *p_value, void *p_data )
637 VLC_UNUSED( p_data );
641 static int ReadStatusLine( vlc_object_t *p_this )
643 sout_stream_t *p_stream = (sout_stream_t*)p_this;
644 sout_stream_sys_t *p_sys = p_stream->p_sys;
645 char *psz_line = NULL;
650 p_sys->psz_last_status_line = net_Gets( p_this, p_sys->i_control_fd,
652 if ( !p_sys->psz_last_status_line )
654 i_result = VLC_EGENERIC;
658 /* Create working copy */
659 psz_line = strdup( p_sys->psz_last_status_line );
663 psz_token = strsep( &psz_next, psz_delim_space );
664 if ( !psz_token || strncmp( psz_token, "RTSP/1.", 7 ) != 0 )
666 msg_Err( p_this, "Unknown protocol (%s)",
667 p_sys->psz_last_status_line );
668 i_result = VLC_EGENERIC;
673 psz_token = strsep( &psz_next, psz_delim_space );
676 msg_Err( p_this, "Request failed (%s)",
677 p_sys->psz_last_status_line );
678 i_result = VLC_EGENERIC;
682 i_result = atoi( psz_token );
690 static int ReadHeader( vlc_object_t *p_this,
691 vlc_dictionary_t *p_resp_headers,
694 sout_stream_t *p_stream = (sout_stream_t*)p_this;
695 sout_stream_sys_t *p_sys = p_stream->p_sys;
696 char *psz_original = NULL;
697 char *psz_line = NULL;
702 int i_err = VLC_SUCCESS;
704 psz_line = net_Gets( p_this, p_sys->i_control_fd, NULL );
707 i_err = VLC_EGENERIC;
711 /* Empty line for response end */
712 if ( psz_line[0] == '\0' )
716 psz_original = strdup( psz_line );
719 psz_token = strsep( &psz_next, psz_delim_colon );
720 if ( !psz_token || psz_next[0] != ' ' )
722 msg_Err( p_this, "Invalid header format (%s)", psz_original );
723 i_err = VLC_EGENERIC;
727 psz_name = psz_token;
728 psz_value = psz_next + 1;
730 vlc_dictionary_insert( p_resp_headers, psz_name, strdup( psz_value ) );
734 free( psz_original );
740 static int WriteAuxHeaders( vlc_object_t *p_this,
741 vlc_dictionary_t *p_req_headers )
743 sout_stream_t *p_stream = (sout_stream_t*)p_this;
744 sout_stream_sys_t *p_sys = p_stream->p_sys;
745 char **ppsz_keys = NULL;
748 int i_err = VLC_SUCCESS;
752 ppsz_keys = vlc_dictionary_all_keys( p_req_headers );
753 for ( i = 0; ppsz_keys[i]; ++i )
755 psz_key = ppsz_keys[i];
756 psz_value = vlc_dictionary_value_for_key( p_req_headers, psz_key );
758 i_rc = net_Printf( p_this, p_sys->i_control_fd, NULL,
759 "%s: %s\r\n", psz_key, psz_value );
762 i_err = VLC_EGENERIC;
768 for ( i = 0; ppsz_keys[i]; ++i )
769 free( ppsz_keys[i] );
775 static int SendRequest( vlc_object_t *p_this, const char *psz_method,
776 const char *psz_content_type, const char *psz_body,
777 vlc_dictionary_t *p_req_headers )
779 sout_stream_t *p_stream = (sout_stream_t*)p_this;
780 sout_stream_sys_t *p_sys = p_stream->p_sys;
781 const unsigned char psz_headers_end[] = "\r\n";
782 size_t i_body_length = 0;
783 int i_err = VLC_SUCCESS;
786 i_rc = net_Printf( p_this, p_sys->i_control_fd, NULL,
788 "User-Agent: " RAOP_USER_AGENT "\r\n"
789 "Client-Instance: %s\r\n"
791 psz_method, p_sys->psz_url,
792 p_sys->psz_client_instance,
796 i_err = VLC_EGENERIC;
800 if ( psz_content_type )
802 i_rc = net_Printf( p_this, p_sys->i_control_fd, NULL,
803 "Content-Type: %s\r\n", psz_content_type );
813 i_body_length = strlen( psz_body );
815 i_rc = net_Printf( p_this, p_sys->i_control_fd, NULL,
816 "Content-Length: %u\r\n",
817 (unsigned int)i_body_length );
825 i_err = WriteAuxHeaders( p_this, p_req_headers );
826 if ( i_err != VLC_SUCCESS )
829 i_rc = net_Write( p_this, p_sys->i_control_fd, NULL,
830 psz_headers_end, sizeof( psz_headers_end ) - 1 );
838 net_Write( p_this, p_sys->i_control_fd, NULL,
839 psz_body, i_body_length );
845 static int ParseAuthenticateHeader( vlc_object_t *p_this,
846 vlc_dictionary_t *p_resp_headers )
848 sout_stream_t *p_stream = (sout_stream_t*)p_this;
849 sout_stream_sys_t *p_sys = p_stream->p_sys;
851 int i_err = VLC_SUCCESS;
853 psz_auth = vlc_dictionary_value_for_key( p_resp_headers,
854 "WWW-Authenticate" );
855 if ( psz_auth == NULL )
857 msg_Err( p_this, "HTTP 401 response missing "
858 "WWW-Authenticate header" );
859 i_err = VLC_EGENERIC;
863 http_auth_ParseWwwAuthenticateHeader( p_this, &p_sys->auth, psz_auth );
869 static int ExecRequest( vlc_object_t *p_this, const char *psz_method,
870 const char *psz_content_type, const char *psz_body,
871 vlc_dictionary_t *p_req_headers,
872 vlc_dictionary_t *p_resp_headers )
874 sout_stream_t *p_stream = (sout_stream_t*)p_this;
875 sout_stream_sys_t *p_sys = p_stream->p_sys;
876 char *psz_authorization = NULL;
878 int i_err = VLC_SUCCESS;
882 if ( p_sys->i_control_fd < 0 )
884 msg_Err( p_this, "Control connection not open" );
885 i_err = VLC_EGENERIC;
892 /* Send header only when Digest authentication is used */
893 if ( p_sys->psz_password != NULL && p_sys->auth.psz_nonce != NULL )
895 FREENULL( psz_authorization );
898 http_auth_FormatAuthorizationHeader( p_this, &p_sys->auth,
901 p_sys->psz_password );
902 if ( psz_authorization == NULL )
904 i_err = VLC_EGENERIC;
908 vlc_dictionary_insert( p_req_headers, "Authorization",
913 i_err = SendRequest( p_this, psz_method, psz_content_type, psz_body,
915 if ( i_err != VLC_SUCCESS )
918 /* Read status line */
919 i_status = ReadStatusLine( p_this );
926 vlc_dictionary_clear( p_resp_headers, FreeHeader, NULL );
930 while ( !headers_done )
932 i_err = ReadHeader( p_this, p_resp_headers, &headers_done );
933 if ( i_err != VLC_SUCCESS )
937 if ( i_status == 200 )
938 /* Request successful */
940 else if ( i_status == 401 )
942 /* Authorization required */
943 if ( i_auth_state == 1 || p_sys->psz_password == NULL )
945 msg_Err( p_this, "Access denied, password invalid" );
946 i_err = VLC_EGENERIC;
950 i_err = ParseAuthenticateHeader( p_this, p_resp_headers );
951 if ( i_err != VLC_SUCCESS )
958 msg_Err( p_this, "Request failed (%s), status is %d",
959 p_sys->psz_last_status_line, i_status );
960 i_err = VLC_EGENERIC;
966 FREENULL( p_sys->psz_last_status_line );
967 free( psz_authorization );
972 static int AnnounceSDP( vlc_object_t *p_this, char *psz_local,
973 uint32_t i_session_id )
975 sout_stream_t *p_stream = (sout_stream_t*)p_this;
976 sout_stream_sys_t *p_sys = p_stream->p_sys;
977 vlc_dictionary_t req_headers;
978 vlc_dictionary_t resp_headers;
979 unsigned char ps_sac[16];
980 char *psz_sdp = NULL;
981 char *psz_sac_base64 = NULL;
982 char *psz_aes_key_base64 = NULL;
983 char *psz_aes_iv_base64 = NULL;
984 int i_err = VLC_SUCCESS;
987 vlc_dictionary_init( &req_headers, 0 );
988 vlc_dictionary_init( &resp_headers, 0 );
990 /* Encrypt AES key and encode it in Base64 */
991 i_rc = EncryptAesKeyBase64( p_this, &psz_aes_key_base64 );
992 if ( i_rc != VLC_SUCCESS || psz_aes_key_base64 == NULL )
994 i_err = VLC_EGENERIC;
997 RemoveBase64Padding( psz_aes_key_base64 );
999 /* Encode AES IV in Base64 */
1000 psz_aes_iv_base64 = vlc_b64_encode_binary( p_sys->ps_aes_iv,
1001 sizeof( p_sys->ps_aes_iv ) );
1002 if ( psz_aes_iv_base64 == NULL )
1004 i_err = VLC_EGENERIC;
1007 RemoveBase64Padding( psz_aes_iv_base64 );
1009 /* Random bytes for Apple-Challenge header */
1010 gcry_randomize( ps_sac, sizeof( ps_sac ), GCRY_STRONG_RANDOM );
1012 psz_sac_base64 = vlc_b64_encode_binary( ps_sac, sizeof( ps_sac ) );
1013 if ( psz_sac_base64 == NULL )
1015 i_err = VLC_EGENERIC;
1018 RemoveBase64Padding( psz_sac_base64 );
1021 * Note: IPv6 addresses also use "IP4". Make sure not to include the
1024 i_rc = asprintf( &psz_sdp,
1026 "o=iTunes %u 0 IN IP4 %s\r\n"
1030 "m=audio 0 RTP/AVP 96\r\n"
1031 "a=rtpmap:96 AppleLossless\r\n"
1032 "a=fmtp:96 4096 0 16 40 10 14 2 255 0 0 44100\r\n"
1033 "a=rsaaeskey:%s\r\n"
1035 i_session_id, psz_local, p_sys->psz_host,
1036 psz_aes_key_base64, psz_aes_iv_base64 );
1044 /* Build and send request */
1045 vlc_dictionary_insert( &req_headers, "Apple-Challenge", psz_sac_base64 );
1047 i_err = ExecRequest( p_this, "ANNOUNCE", "application/sdp", psz_sdp,
1048 &req_headers, &resp_headers);
1049 if ( i_err != VLC_SUCCESS )
1053 vlc_dictionary_clear( &req_headers, NULL, NULL );
1054 vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
1057 free( psz_sac_base64 );
1058 free( psz_aes_key_base64 );
1059 free( psz_aes_iv_base64 );
1064 static int SendSetup( vlc_object_t *p_this )
1066 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1067 sout_stream_sys_t *p_sys = p_stream->p_sys;
1068 vlc_dictionary_t req_headers;
1069 vlc_dictionary_t resp_headers;
1070 int i_err = VLC_SUCCESS;
1076 vlc_dictionary_init( &req_headers, 0 );
1077 vlc_dictionary_init( &resp_headers, 0 );
1079 vlc_dictionary_insert( &req_headers, "Transport",
1080 ((void*)"RTP/AVP/TCP;unicast;interleaved=0-1;"
1083 i_err = ExecRequest( p_this, "SETUP", NULL, NULL,
1084 &req_headers, &resp_headers );
1085 if ( i_err != VLC_SUCCESS )
1088 psz_tmp = vlc_dictionary_value_for_key( &resp_headers, "Session" );
1091 msg_Err( p_this, "Missing 'Session' header during setup" );
1092 i_err = VLC_EGENERIC;
1096 free( p_sys->psz_session );
1097 p_sys->psz_session = strdup( psz_tmp );
1099 /* Get server_port */
1100 psz_next = vlc_dictionary_value_for_key( &resp_headers, "Transport" );
1101 while ( SplitHeader( &psz_next, &psz_name, &psz_value ) )
1103 if ( psz_value && strcmp( psz_name, "server_port" ) == 0 )
1105 p_sys->i_server_port = atoi( psz_value );
1110 if ( !p_sys->i_server_port )
1112 msg_Err( p_this, "Missing 'server_port' during setup" );
1113 i_err = VLC_EGENERIC;
1118 psz_next = vlc_dictionary_value_for_key( &resp_headers,
1119 "Audio-Jack-Status" );
1120 while ( SplitHeader( &psz_next, &psz_name, &psz_value ) )
1122 if ( strcmp( psz_name, "type" ) != 0 )
1125 if ( strcmp( psz_value, "analog" ) == 0 )
1126 p_sys->i_jack_type = JACK_TYPE_ANALOG;
1128 else if ( strcmp( psz_value, "digital" ) == 0 )
1129 p_sys->i_jack_type = JACK_TYPE_DIGITAL;
1135 vlc_dictionary_clear( &req_headers, NULL, NULL );
1136 vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
1141 static int SendRecord( vlc_object_t *p_this )
1143 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1144 sout_stream_sys_t *p_sys = p_stream->p_sys;
1145 vlc_dictionary_t req_headers;
1146 vlc_dictionary_t resp_headers;
1147 int i_err = VLC_SUCCESS;
1150 vlc_dictionary_init( &req_headers, 0 );
1151 vlc_dictionary_init( &resp_headers, 0 );
1153 vlc_dictionary_insert( &req_headers, "Range", (void *)"npt=0-" );
1154 vlc_dictionary_insert( &req_headers, "RTP-Info",
1155 (void *)"seq=0;rtptime=0" );
1156 vlc_dictionary_insert( &req_headers, "Session",
1157 (void *)p_sys->psz_session );
1159 i_err = ExecRequest( p_this, "RECORD", NULL, NULL,
1160 &req_headers, &resp_headers );
1161 if ( i_err != VLC_SUCCESS )
1164 psz_value = vlc_dictionary_value_for_key( &resp_headers, "Audio-Latency" );
1166 p_sys->i_audio_latency = atoi( psz_value );
1168 p_sys->i_audio_latency = 0;
1171 vlc_dictionary_clear( &req_headers, NULL, NULL );
1172 vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
1177 static int SendFlush( vlc_object_t *p_this )
1179 VLC_UNUSED( p_this );
1180 vlc_dictionary_t resp_headers;
1181 vlc_dictionary_t req_headers;
1182 int i_err = VLC_SUCCESS;
1184 vlc_dictionary_init( &req_headers, 0 );
1185 vlc_dictionary_init( &resp_headers, 0 );
1187 vlc_dictionary_insert( &req_headers, "RTP-Info",
1188 (void *)"seq=0;rtptime=0" );
1190 i_err = ExecRequest( p_this, "FLUSH", NULL, NULL,
1191 &req_headers, &resp_headers );
1192 if ( i_err != VLC_SUCCESS )
1196 vlc_dictionary_clear( &req_headers, NULL, NULL );
1197 vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
1202 static int SendTeardown( vlc_object_t *p_this )
1204 vlc_dictionary_t resp_headers;
1205 vlc_dictionary_t req_headers;
1206 int i_err = VLC_SUCCESS;
1208 vlc_dictionary_init( &req_headers, 0 );
1209 vlc_dictionary_init( &resp_headers, 0 );
1211 i_err = ExecRequest( p_this, "TEARDOWN", NULL, NULL,
1212 &req_headers, &resp_headers );
1213 if ( i_err != VLC_SUCCESS )
1217 vlc_dictionary_clear( &req_headers, NULL, NULL );
1218 vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
1223 static int UpdateVolume( vlc_object_t *p_this )
1225 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1226 sout_stream_sys_t *p_sys = p_stream->p_sys;
1227 vlc_dictionary_t req_headers;
1228 vlc_dictionary_t resp_headers;
1229 char *psz_parameters = NULL;
1231 int i_err = VLC_SUCCESS;
1234 vlc_dictionary_init( &req_headers, 0 );
1235 vlc_dictionary_init( &resp_headers, 0 );
1237 /* Our volume is 0..255, RAOP is -144..0 (-144 off, -30..0 on) */
1240 p_sys->i_volume = __MAX( 0, __MIN( p_sys->i_volume, 255 ) );
1242 if ( p_sys->i_volume == 0 )
1245 d_volume = -30 + ( ( (double)p_sys->i_volume ) * 30.0 / 255.0 );
1247 /* Format without using locales */
1248 i_rc = us_asprintf( &psz_parameters, "volume: %0.6f\r\n", d_volume );
1255 vlc_dictionary_insert( &req_headers, "Session",
1256 (void *)p_sys->psz_session );
1258 i_err = ExecRequest( p_this, "SET_PARAMETER",
1259 "text/parameters", psz_parameters,
1260 &req_headers, &resp_headers );
1261 if ( i_err != VLC_SUCCESS )
1265 vlc_dictionary_clear( &req_headers, NULL, NULL );
1266 vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
1267 free( psz_parameters );
1272 static void LogInfo( vlc_object_t *p_this )
1274 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1275 sout_stream_sys_t *p_sys = p_stream->p_sys;
1276 const char *psz_jack_name;
1278 msg_Info( p_this, "Audio latency: %d", p_sys->i_audio_latency );
1280 switch ( p_sys->i_jack_type )
1282 case JACK_TYPE_ANALOG:
1283 psz_jack_name = "analog";
1286 case JACK_TYPE_DIGITAL:
1287 psz_jack_name = "digital";
1290 case JACK_TYPE_NONE:
1292 psz_jack_name = "none";
1296 msg_Info( p_this, "Jack type: %s", psz_jack_name );
1299 static void SendAudio( sout_stream_t *p_stream, block_t *p_buffer )
1301 sout_stream_sys_t *p_sys = p_stream->p_sys;
1302 gcry_error_t i_gcrypt_err;
1305 size_t i_payload_len;
1306 size_t i_realloc_len;
1309 const uint8_t header[16] = {
1310 0x24, 0x00, 0x00, 0x00,
1311 0xf0, 0xff, 0x00, 0x00,
1312 0x00, 0x00, 0x00, 0x00,
1313 0x00, 0x00, 0x00, 0x00,
1318 i_len = sizeof( header ) + p_buffer->i_buffer;
1320 /* Buffer resize needed? */
1321 if ( i_len > p_sys->i_sendbuf_len || p_sys->p_sendbuf == NULL )
1323 /* Grow in blocks of 4K */
1324 i_realloc_len = (1 + (i_len / 4096)) * 4096;
1326 p_sys->p_sendbuf = realloc( p_sys->p_sendbuf, i_realloc_len );
1327 if ( p_sys->p_sendbuf == NULL )
1330 p_sys->i_sendbuf_len = i_realloc_len;
1334 memcpy( p_sys->p_sendbuf, header, sizeof( header ) );
1335 memcpy( p_sys->p_sendbuf + sizeof( header ),
1336 p_buffer->p_buffer, p_buffer->i_buffer );
1338 /* Calculate payload length and update header */
1339 i_payload_len = i_len - 4;
1340 if ( i_payload_len > 0xffff )
1342 msg_Err( p_stream, "Buffer is too long (%u bytes)",
1343 (unsigned int)i_payload_len );
1347 p_sys->p_sendbuf[2] = ( i_payload_len >> 8 ) & 0xff;
1348 p_sys->p_sendbuf[3] = i_payload_len & 0xff;
1351 i_gcrypt_err = gcry_cipher_reset( p_sys->aes_ctx );
1352 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
1356 i_gcrypt_err = gcry_cipher_setiv( p_sys->aes_ctx, p_sys->ps_aes_iv,
1357 sizeof( p_sys->ps_aes_iv ) );
1358 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
1361 /* Encrypt in place. Only full blocks of 16 bytes are encrypted,
1362 * the rest (0-15 bytes) is left unencrypted.
1365 gcry_cipher_encrypt( p_sys->aes_ctx,
1366 p_sys->p_sendbuf + sizeof( header ),
1367 ( p_buffer->i_buffer / 16 ) * 16,
1369 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
1373 rc = net_Write( p_stream, p_sys->i_stream_fd, NULL,
1374 p_sys->p_sendbuf, i_len );
1378 p_next = p_buffer->p_next;
1379 block_Release( p_buffer );
1384 block_ChainRelease( p_buffer );
1389 /*****************************************************************************
1391 *****************************************************************************/
1392 static int Open( vlc_object_t *p_this )
1394 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1395 sout_stream_sys_t *p_sys;
1396 char psz_local[NI_MAXNUMERICHOST];
1397 char *psz_pwfile = NULL;
1398 gcry_error_t i_gcrypt_err;
1399 int i_err = VLC_SUCCESS;
1400 uint32_t i_session_id;
1401 uint64_t i_client_instance;
1405 config_ChainParse( p_stream, SOUT_CFG_PREFIX, ppsz_sout_options,
1408 p_sys = calloc( 1, sizeof( *p_sys ) );
1409 if ( p_sys == NULL )
1415 p_stream->p_sys = p_sys;
1416 p_stream->pf_add = Add;
1417 p_stream->pf_del = Del;
1418 p_stream->pf_send = Send;
1419 p_stream->p_sout->i_out_pace_nocontrol++;
1421 p_sys->i_control_fd = -1;
1422 p_sys->i_stream_fd = -1;
1423 p_sys->i_volume = var_GetInteger( p_stream, SOUT_CFG_PREFIX "volume");
1424 p_sys->i_jack_type = JACK_TYPE_NONE;
1426 http_auth_Init( &p_sys->auth );
1428 p_sys->psz_host = var_GetNonEmptyString( p_stream,
1429 SOUT_CFG_PREFIX "host" );
1430 if ( p_sys->psz_host == NULL )
1432 msg_Err( p_this, "Missing host" );
1433 i_err = VLC_EGENERIC;
1437 p_sys->psz_password = var_GetNonEmptyString( p_stream,
1438 SOUT_CFG_PREFIX "password" );
1439 if ( p_sys->psz_password == NULL )
1441 /* Try password file instead */
1442 psz_pwfile = var_GetNonEmptyString( p_stream,
1443 SOUT_CFG_PREFIX "password-file" );
1444 if ( psz_pwfile != NULL )
1446 p_sys->psz_password = ReadPasswordFile( p_this, psz_pwfile );
1447 if ( p_sys->psz_password == NULL )
1449 i_err = VLC_EGENERIC;
1455 if ( p_sys->psz_password != NULL )
1456 msg_Info( p_this, "Using password authentication" );
1458 var_AddCallback( p_stream, SOUT_CFG_PREFIX "volume",
1459 VolumeCallback, NULL );
1460 p_sys->b_volume_callback = true;
1462 /* Open control connection */
1463 p_sys->i_control_fd = net_ConnectTCP( p_stream, p_sys->psz_host,
1465 if ( p_sys->i_control_fd < 0 )
1467 msg_Err( p_this, "Cannot establish control connection to %s:%d (%m)",
1468 p_sys->psz_host, RAOP_PORT );
1469 i_err = VLC_EGENERIC;
1473 /* Get local IP address */
1474 if ( net_GetSockAddress( p_sys->i_control_fd, psz_local, NULL ) )
1476 msg_Err( p_this, "cannot get local IP address" );
1477 i_err = VLC_EGENERIC;
1481 /* Random session ID */
1482 gcry_randomize( &i_session_id, sizeof( i_session_id ),
1483 GCRY_STRONG_RANDOM );
1485 /* Random client instance */
1486 gcry_randomize( &i_client_instance, sizeof( i_client_instance ),
1487 GCRY_STRONG_RANDOM );
1488 if ( asprintf( &p_sys->psz_client_instance, "%016"PRIX64,
1489 i_client_instance ) < 0 )
1495 /* Build session URL */
1496 if ( asprintf( &p_sys->psz_url, "rtsp://%s/%u",
1497 psz_local, i_session_id ) < 0 )
1503 /* Generate AES key and IV */
1504 gcry_randomize( p_sys->ps_aes_key, sizeof( p_sys->ps_aes_key ),
1505 GCRY_STRONG_RANDOM );
1506 gcry_randomize( p_sys->ps_aes_iv, sizeof( p_sys->ps_aes_iv ),
1507 GCRY_STRONG_RANDOM );
1510 i_gcrypt_err = gcry_cipher_open( &p_sys->aes_ctx, GCRY_CIPHER_AES,
1511 GCRY_CIPHER_MODE_CBC, 0 );
1512 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
1514 i_err = VLC_EGENERIC;
1519 i_gcrypt_err = gcry_cipher_setkey( p_sys->aes_ctx, p_sys->ps_aes_key,
1520 sizeof( p_sys->ps_aes_key ) );
1521 if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
1523 i_err = VLC_EGENERIC;
1527 /* Protocol handshake */
1528 i_err = AnnounceSDP( p_this, psz_local, i_session_id );
1529 if ( i_err != VLC_SUCCESS )
1532 i_err = SendSetup( p_this );
1533 if ( i_err != VLC_SUCCESS )
1536 i_err = SendRecord( p_this );
1537 if ( i_err != VLC_SUCCESS )
1540 i_err = UpdateVolume( p_this );
1541 if ( i_err != VLC_SUCCESS )
1546 /* Open stream connection */
1547 p_sys->i_stream_fd = net_ConnectTCP( p_stream, p_sys->psz_host,
1548 p_sys->i_server_port );
1549 if ( p_sys->i_stream_fd < 0 )
1551 msg_Err( p_this, "Cannot establish stream connection to %s:%d (%m)",
1552 p_sys->psz_host, p_sys->i_server_port );
1553 i_err = VLC_EGENERIC;
1560 if ( i_err != VLC_SUCCESS )
1561 FreeSys( p_this, p_sys );
1567 /*****************************************************************************
1569 *****************************************************************************/
1570 static void Close( vlc_object_t *p_this )
1572 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1573 sout_stream_sys_t *p_sys = p_stream->p_sys;
1575 SendFlush( p_this );
1576 SendTeardown( p_this );
1578 FreeSys( p_this, p_sys );
1580 p_stream->p_sout->i_out_pace_nocontrol--;
1584 /*****************************************************************************
1586 *****************************************************************************/
1587 static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
1589 sout_stream_sys_t *p_sys = p_stream->p_sys;
1590 sout_stream_id_t *id = NULL;
1592 id = calloc( 1, sizeof( *id ) );
1596 es_format_Copy( &id->fmt, p_fmt );
1598 switch ( id->fmt.i_cat )
1601 if ( id->fmt.i_codec == VLC_CODEC_ALAC )
1603 if ( p_sys->p_audio_stream )
1605 msg_Warn( p_stream, "Only the first Apple Lossless audio "
1608 else if ( id->fmt.audio.i_rate != 44100 ||
1609 id->fmt.audio.i_channels != 2 )
1611 msg_Err( p_stream, "The Apple Lossless audio stream must be "
1612 "encoded with 44100 Hz and 2 channels" );
1616 /* Use this stream */
1617 p_sys->p_audio_stream = id;
1620 else if ( !p_sys->b_alac_warning )
1622 msg_Err( p_stream, "Apple Lossless is the only codec supported. "
1623 "Use the \"transcode\" module for conversion "
1624 "(e.g. \"transcode{acodec=alac,"
1625 "channels=2}\")." );
1626 p_sys->b_alac_warning = true;
1632 /* Leave other stream types alone */
1645 /*****************************************************************************
1647 *****************************************************************************/
1648 static int Del( sout_stream_t *p_stream, sout_stream_id_t *id )
1650 sout_stream_sys_t *p_sys = p_stream->p_sys;
1651 int i_err = VLC_SUCCESS;
1653 if ( p_sys->p_audio_stream == id )
1654 p_sys->p_audio_stream = NULL;
1662 /*****************************************************************************
1664 *****************************************************************************/
1665 static int Send( sout_stream_t *p_stream, sout_stream_id_t *id,
1668 sout_stream_sys_t *p_sys = p_stream->p_sys;
1670 if ( id->fmt.i_cat == AUDIO_ES && id == p_sys->p_audio_stream )
1672 /* SendAudio takes care of releasing the buffers */
1673 SendAudio( p_stream, p_buffer );
1677 block_ChainRelease( p_buffer );
1684 /*****************************************************************************
1685 * VolumeCallback: called when the volume is changed on the fly.
1686 *****************************************************************************/
1687 static int VolumeCallback( vlc_object_t *p_this, char const *psz_cmd,
1688 vlc_value_t oldval, vlc_value_t newval,
1691 VLC_UNUSED(psz_cmd);
1695 sout_stream_t *p_stream = (sout_stream_t*)p_this;
1696 sout_stream_sys_t *p_sys = p_stream->p_sys;
1698 /* TODO: Implement volume change */