commit 9e2328dacec718589179bdae799629bb26ec17f4 Author: upstream Date: Tue Oct 13 23:02:54 2026 +0200 Upstream videolan-videolan/vlc state of the touched files before 612b90c895 diff --git a/src/network/http_auth.c b/src/network/http_auth.c new file mode 100644 index 0000000..572db69 --- /dev/null +++ b/src/network/http_auth.c @@ -0,0 +1,484 @@ +/***************************************************************************** + * http_auth.c: HTTP authentication for clients as per RFC2617 + ***************************************************************************** + * Copyright (C) 2001-2008 VLC authors and VideoLAN + * + * Authors: Laurent Aimar + * Christophe Massiot + * Rémi Denis-Courmont + * Antoine Cellerier + * + * 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 Lesser General Public License for more details. + * + * 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. + *****************************************************************************/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "../libvlc.h" + + +/***************************************************************************** + * "RFC 2617: Basic and Digest Access Authentication" header parsing + *****************************************************************************/ +#define AuthGetParam( HEADER, PARAM ) AuthGetParamConcat( HEADER, PARAM "=\"" ) +static char *AuthGetParamConcat( const char *psz_header, const char *psz_what ) +{ + psz_header = strstr( psz_header, psz_what ); + if ( psz_header ) + { + const char *psz_end; + psz_header += strlen( psz_what ); + psz_end = strchr( psz_header, '"' ); + if ( !psz_end ) /* Invalid since we should have a closing quote */ + return strdup( psz_header ); + return strndup( psz_header, psz_end - psz_header ); + } + else + { + return NULL; + } +} + +#define AuthGetParamNoQuotes( HEADER, PARAM ) AuthGetParamNoQuotesConcat( HEADER, PARAM "=" ) +static char *AuthGetParamNoQuotesConcat( const char *psz_header, const char *psz_what ) +{ + psz_header = strstr( psz_header, psz_what ); + if ( psz_header ) + { + const char *psz_end; + psz_header += strlen( psz_what ); + psz_end = strchr( psz_header, ',' ); + /* XXX: Do we need to filter out trailing space between the value and + * the comma/end of line? */ + if ( !psz_end ) /* Can be valid if this is the last parameter */ + return strdup( psz_header ); + return strndup( psz_header, psz_end - psz_header ); + } + else + { + return NULL; + } +} + +static char *GenerateCnonce(void) +{ + char ps_random[32]; + char *md5_hex; + vlc_hash_md5_t md5; + + md5_hex = malloc( VLC_HASH_MD5_DIGEST_HEX_SIZE ); + if (unlikely( md5_hex == NULL )) + return NULL; + + vlc_rand_bytes( ps_random, sizeof( ps_random ) ); + + vlc_hash_md5_Init( &md5 ); + vlc_hash_md5_Update( &md5, ps_random, sizeof( ps_random ) ); + + vlc_hash_FinishHex( &md5, md5_hex ); + return md5_hex; +} + +static char *AuthDigest( vlc_object_t *p_this, vlc_http_auth_t *p_auth, + const char *psz_method, const char *psz_path, + const char *psz_username, const char *psz_password ) +{ + char psz_HA1[VLC_HASH_MD5_DIGEST_HEX_SIZE]; + char psz_HA2[VLC_HASH_MD5_DIGEST_HEX_SIZE]; + char psz_ent[VLC_HASH_MD5_DIGEST_HEX_SIZE]; + char *psz_result = NULL; + char psz_inonce[9]; + vlc_hash_md5_t md5; + vlc_hash_md5_t ent; + + if ( p_auth->psz_realm == NULL ) + { + msg_Warn( p_this, "Digest Authentication: " + "Mandatory 'realm' value not available" ); + return NULL; + } + + /* H(A1) */ + if ( p_auth->psz_HA1 ) + { + memcpy( psz_HA1, p_auth->psz_HA1, sizeof(psz_HA1) ); + } + else + { + vlc_hash_md5_Init( &md5 ); + vlc_hash_md5_Update( &md5, psz_username, strlen( psz_username ) ); + vlc_hash_md5_Update( &md5, ":", 1 ); + vlc_hash_md5_Update( &md5, p_auth->psz_realm, strlen( p_auth->psz_realm ) ); + vlc_hash_md5_Update( &md5, ":", 1 ); + vlc_hash_md5_Update( &md5, psz_password, strlen( psz_password ) ); + vlc_hash_FinishHex( &md5, psz_HA1 ); + + if ( p_auth->psz_algorithm && + strcmp( p_auth->psz_algorithm, "MD5-sess" ) == 0 ) + { + vlc_hash_md5_Init( &md5 ); + vlc_hash_md5_Update( &md5, psz_HA1, sizeof(psz_HA1) - 1 ); + vlc_hash_md5_Update( &md5, ":", 1 ); + vlc_hash_md5_Update( &md5, p_auth->psz_nonce, strlen( p_auth->psz_nonce ) ); + vlc_hash_md5_Update( &md5, ":", 1 ); + vlc_hash_md5_Update( &md5, p_auth->psz_cnonce, strlen( p_auth->psz_cnonce ) ); + vlc_hash_FinishHex( &md5, psz_HA1 ); + + p_auth->psz_HA1 = strdup( psz_HA1 ); + if ( p_auth->psz_HA1 == NULL ) + return NULL; + } + } + + /* H(A2) */ + vlc_hash_md5_Init( &md5 ); + if ( *psz_method ) + vlc_hash_md5_Update( &md5, psz_method, strlen( psz_method ) ); + vlc_hash_md5_Update( &md5, ":", 1 ); + if ( psz_path ) + vlc_hash_md5_Update( &md5, psz_path, strlen( psz_path ) ); + else + vlc_hash_md5_Update( &md5, "/", 1 ); + if ( p_auth->psz_qop && strcmp( p_auth->psz_qop, "auth-int" ) == 0 ) + { + vlc_hash_md5_Init( &ent ); + /* TODO: Support for "qop=auth-int" */ + vlc_hash_md5_Update( &ent, "", 0 ); + vlc_hash_FinishHex( &ent, psz_ent ); + vlc_hash_md5_Update( &md5, ":", 1 ); + vlc_hash_md5_Update( &md5, psz_ent, sizeof(psz_ent) - 1 ); + } + + vlc_hash_FinishHex( &md5, psz_HA2 ); + + /* Request digest */ + vlc_hash_md5_Init( &md5 ); + vlc_hash_md5_Update( &md5, psz_HA1, sizeof(psz_HA1) - 1 ); + vlc_hash_md5_Update( &md5, ":", 1 ); + vlc_hash_md5_Update( &md5, p_auth->psz_nonce, strlen( p_auth->psz_nonce ) ); + vlc_hash_md5_Update( &md5, ":", 1 ); + if ( p_auth->psz_qop && + ( strcmp( p_auth->psz_qop, "auth" ) == 0 || + strcmp( p_auth->psz_qop, "auth-int" ) == 0 ) ) + { + snprintf( psz_inonce, sizeof( psz_inonce ), "%08x", p_auth->i_nonce ); + vlc_hash_md5_Update( &md5, psz_inonce, 8 ); + vlc_hash_md5_Update( &md5, ":", 1 ); + vlc_hash_md5_Update( &md5, p_auth->psz_cnonce, strlen( p_auth->psz_cnonce ) ); + vlc_hash_md5_Update( &md5, ":", 1 ); + vlc_hash_md5_Update( &md5, p_auth->psz_qop, strlen( p_auth->psz_qop ) ); + vlc_hash_md5_Update( &md5, ":", 1 ); + } + vlc_hash_md5_Update( &md5, psz_HA2, sizeof(psz_HA2) - 1 ); + + psz_result = malloc(VLC_HASH_MD5_DIGEST_HEX_SIZE); + if (psz_result == NULL) + return NULL; + + vlc_hash_FinishHex( &md5, psz_result ); + return psz_result; +} + +/* RFC2617, section 3.2.1 The WWW-Authenticate Response Header + * + * If a server receives a request for an access-protected object, and an + * acceptable Authorization header is not sent, the server responds with a "401 + * Unauthorized" status code, and a WWW-Authenticate header [...] + */ +void vlc_http_auth_ParseWwwAuthenticateHeader( + vlc_object_t *p_this, vlc_http_auth_t *p_auth, + const char *psz_header ) +{ + static const char psz_basic_prefix[] = "Basic "; + static const char psz_digest_prefix[] = "Digest "; + + /* FIXME: multiple auth methods can be listed (comma separated) */ + + if ( strncasecmp( psz_header, psz_basic_prefix, + sizeof( psz_basic_prefix ) - 1 ) == 0 ) + { + /* 2 Basic Authentication Scheme */ + msg_Dbg( p_this, "Using Basic Authentication" ); + psz_header += sizeof( psz_basic_prefix ) - 1; + p_auth->psz_realm = AuthGetParam( psz_header, "realm" ); + if ( p_auth->psz_realm == NULL ) + msg_Warn( p_this, "Basic Authentication: " + "Mandatory 'realm' parameter is missing" ); + } + else if ( strncasecmp( psz_header, psz_digest_prefix, + sizeof( psz_digest_prefix ) - 1 ) == 0 ) + { + /* 3 Digest Access Authentication Scheme */ + msg_Dbg( p_this, "Using Digest Access Authentication" ); + + if ( p_auth->psz_nonce ) + /* FIXME */ + return; + + psz_header += sizeof( psz_digest_prefix ) - 1; + p_auth->psz_realm = AuthGetParam( psz_header, "realm" ); + p_auth->psz_domain = AuthGetParam( psz_header, "domain" ); + p_auth->psz_nonce = AuthGetParam( psz_header, "nonce" ); + p_auth->psz_opaque = AuthGetParam( psz_header, "opaque" ); + p_auth->psz_stale = AuthGetParamNoQuotes( psz_header, "stale" ); + p_auth->psz_algorithm = AuthGetParamNoQuotes( psz_header, "algorithm" ); + p_auth->psz_qop = AuthGetParam( psz_header, "qop" ); + p_auth->i_nonce = 0; + + /* printf("realm: |%s|\ndomain: |%s|\nnonce: |%s|\nopaque: |%s|\n" + "stale: |%s|\nalgorithm: |%s|\nqop: |%s|\n", + p_auth->psz_realm,p_auth->psz_domain,p_auth->psz_nonce, + p_auth->psz_opaque,p_auth->psz_stale,p_auth->psz_algorithm, + p_auth->psz_qop); */ + + if ( p_auth->psz_realm == NULL ) + msg_Warn( p_this, "Digest Access Authentication: " + "Mandatory 'realm' parameter is missing" ); + if ( p_auth->psz_nonce == NULL ) + msg_Warn( p_this, "Digest Access Authentication: " + "Mandatory 'nonce' parameter is missing" ); + + /* FIXME: parse the qop list */ + if ( p_auth->psz_qop ) + { + char *psz_tmp = strchr( p_auth->psz_qop, ',' ); + if ( psz_tmp ) + *psz_tmp = '\0'; + } + } + else + { + const char *psz_end = strchr( psz_header, ' ' ); + if ( psz_end ) + msg_Warn( p_this, "Unknown authentication scheme: '%*s'", + (int)(psz_end - psz_header), psz_header ); + else + msg_Warn( p_this, "Unknown authentication scheme: '%s'", + psz_header ); + } +} + +/* RFC2617, section 3.2.3: The Authentication-Info Header + * + * The Authentication-Info header is used by the server to communicate some + * information regarding the successful authentication in the response. + */ +int vlc_http_auth_ParseAuthenticationInfoHeader( + vlc_object_t *p_this, vlc_http_auth_t *p_auth, + const char *psz_header, const char *psz_method, const char *psz_path, + const char *psz_username, const char *psz_password ) +{ + char *psz_nextnonce = AuthGetParam( psz_header, "nextnonce" ); + char *psz_qop = AuthGetParamNoQuotes( psz_header, "qop" ); + char *psz_rspauth = AuthGetParam( psz_header, "rspauth" ); + char *psz_cnonce = AuthGetParam( psz_header, "cnonce" ); + char *psz_nc = AuthGetParamNoQuotes( psz_header, "nc" ); + char *psz_digest = NULL; + int i_err = VLC_SUCCESS; + int i_nonce; + + if ( psz_cnonce ) + { + if ( strcmp( psz_cnonce, p_auth->psz_cnonce ) != 0 ) + { + msg_Err( p_this, "HTTP Digest Access Authentication: server " + "replied with a different client nonce value." ); + i_err = VLC_EGENERIC; + goto error; + } + + if ( psz_nc ) + { + i_nonce = strtol( psz_nc, NULL, 16 ); + + if ( i_nonce != p_auth->i_nonce ) + { + msg_Err( p_this, "HTTP Digest Access Authentication: server " + "replied with a different nonce count " + "value." ); + i_err = VLC_EGENERIC; + goto error; + } + } + + if ( psz_qop && p_auth->psz_qop && + strcmp( psz_qop, p_auth->psz_qop ) != 0 ) + msg_Warn( p_this, "HTTP Digest Access Authentication: server " + "replied using a different 'quality of " + "protection' option" ); + + /* All the clear text values match, let's now check the response + * digest. + * + * TODO: Support for "qop=auth-int" + */ + psz_digest = AuthDigest( p_this, p_auth, psz_method, psz_path, + psz_username, psz_password ); + if( psz_digest == NULL || strcmp( psz_digest, psz_rspauth ) != 0 ) + { + msg_Err( p_this, "HTTP Digest Access Authentication: server " + "replied with an invalid response digest " + "(expected value: %s).", psz_digest ); + i_err = VLC_EGENERIC; + goto error; + } + } + + if ( psz_nextnonce ) + { + free( p_auth->psz_nonce ); + p_auth->psz_nonce = psz_nextnonce; + psz_nextnonce = NULL; + } + +error: + free( psz_nextnonce ); + free( psz_qop ); + free( psz_rspauth ); + free( psz_cnonce ); + free( psz_nc ); + free( psz_digest ); + + return i_err; +} + +char *vlc_http_auth_FormatAuthorizationHeader( + vlc_object_t *p_this, vlc_http_auth_t *p_auth, + const char *psz_method, const char *psz_path, + const char *psz_username, const char *psz_password ) +{ + char *psz_result = NULL; + char *psz_buffer = NULL; + char *psz_base64 = NULL; + int i_rc; + + if ( p_auth->psz_nonce ) + { + /* Digest Access Authentication */ + if ( p_auth->psz_algorithm && + strcmp( p_auth->psz_algorithm, "MD5" ) != 0 && + strcmp( p_auth->psz_algorithm, "MD5-sess" ) != 0 ) + { + msg_Err( p_this, "Digest Access Authentication: " + "Unknown algorithm '%s'", p_auth->psz_algorithm ); + goto error; + } + + if ( p_auth->psz_qop != NULL || p_auth->psz_cnonce == NULL ) + { + free( p_auth->psz_cnonce ); + + p_auth->psz_cnonce = GenerateCnonce(); + if ( p_auth->psz_cnonce == NULL ) + goto error; + } + + ++p_auth->i_nonce; + + psz_buffer = AuthDigest( p_this, p_auth, psz_method, psz_path, + psz_username, psz_password ); + if ( psz_buffer == NULL ) + goto error; + + i_rc = asprintf( &psz_result, + "Digest " + /* Mandatory parameters */ + "username=\"%s\", " + "realm=\"%s\", " + "nonce=\"%s\", " + "uri=\"%s\", " + "response=\"%s\", " + /* Optional parameters */ + "%s%s%s" /* algorithm */ + "%s%s%s" /* cnonce */ + "%s%s%s" /* opaque */ + "%s%s%s" /* message qop */ + "%s=\"%08x\"", /* nonce count */ + /* Mandatory parameters */ + psz_username, + p_auth->psz_realm, + p_auth->psz_nonce, + psz_path ? psz_path : "/", + psz_buffer, + /* Optional parameters */ + p_auth->psz_algorithm ? "algorithm=\"" : "", + p_auth->psz_algorithm ? p_auth->psz_algorithm : "", + p_auth->psz_algorithm ? "\", " : "", + p_auth->psz_cnonce ? "cnonce=\"" : "", + p_auth->psz_cnonce ? p_auth->psz_cnonce : "", + p_auth->psz_cnonce ? "\", " : "", + p_auth->psz_opaque ? "opaque=\"" : "", + p_auth->psz_opaque ? p_auth->psz_opaque : "", + p_auth->psz_opaque ? "\", " : "", + p_auth->psz_qop ? "qop=\"" : "", + p_auth->psz_qop ? p_auth->psz_qop : "", + p_auth->psz_qop ? "\", " : "", + /* "uglyhack" will be parsed as an unhandled extension */ + p_auth->i_nonce ? "nc" : "uglyhack", + p_auth->i_nonce + ); + if ( i_rc < 0 ) + goto error; + } + else + { + /* Basic Access Authentication */ + i_rc = asprintf( &psz_buffer, "%s:%s", psz_username, psz_password ); + if ( i_rc < 0 ) + goto error; + + psz_base64 = vlc_b64_encode( psz_buffer ); + if ( psz_base64 == NULL ) + goto error; + + i_rc = asprintf( &psz_result, "Basic %s", psz_base64 ); + if ( i_rc < 0 ) + goto error; + } + +error: + free( psz_buffer ); + free( psz_base64 ); + + return psz_result; +} + +void vlc_http_auth_Init( vlc_http_auth_t *p_auth ) +{ + memset( p_auth, 0, sizeof( *p_auth ) ); +} + +void vlc_http_auth_Deinit( vlc_http_auth_t *p_auth ) +{ + free( p_auth->psz_realm ); + free( p_auth->psz_domain ); + free( p_auth->psz_nonce ); + free( p_auth->psz_opaque ); + free( p_auth->psz_stale ); + free( p_auth->psz_algorithm ); + free( p_auth->psz_qop ); + free( p_auth->psz_cnonce ); + free( p_auth->psz_HA1 ); +}