Add library functions for HTTP client authentication
[vlc.git] / src / misc / http_auth.c
1 /*****************************************************************************
2  * http_auth.c: HTTP authentication for clients as per RFC2617
3  *****************************************************************************
4  * Copyright (C) 2001-2008 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8  *          Christophe Massiot <massiot@via.ecp.fr>
9  *          RĂ©mi Denis-Courmont <rem # videolan.org>
10  *          Antoine Cellerier <dionoea at videolan dot org>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 /*****************************************************************************
28  * Preamble
29  *****************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #include <vlc_common.h>
35 #include <vlc_http.h>
36 #include <vlc_md5.h>
37 #include <vlc_rand.h>
38 #include <vlc_strings.h>
39
40 #include "libvlc.h"
41
42
43 /*****************************************************************************
44  * "RFC 2617: Basic and Digest Access Authentication" header parsing
45  *****************************************************************************/
46 static char *AuthGetParam( const char *psz_header, const char *psz_param )
47 {
48     char psz_what[strlen(psz_param)+3];
49     sprintf( psz_what, "%s=\"", psz_param );
50     psz_header = strstr( psz_header, psz_what );
51     if ( psz_header )
52     {
53         const char *psz_end;
54         psz_header += strlen( psz_what );
55         psz_end = strchr( psz_header, '"' );
56         if ( !psz_end ) /* Invalid since we should have a closing quote */
57             return strdup( psz_header );
58         return strndup( psz_header, psz_end - psz_header );
59     }
60     else
61     {
62         return NULL;
63     }
64 }
65
66 static char *AuthGetParamNoQuotes( const char *psz_header, const char *psz_param )
67 {
68     char psz_what[strlen(psz_param)+2];
69     sprintf( psz_what, "%s=", psz_param );
70     psz_header = strstr( psz_header, psz_what );
71     if ( psz_header )
72     {
73         const char *psz_end;
74         psz_header += strlen( psz_what );
75         psz_end = strchr( psz_header, ',' );
76         /* XXX: Do we need to filter out trailing space between the value and
77          * the comma/end of line? */
78         if ( !psz_end ) /* Can be valid if this is the last parameter */
79             return strdup( psz_header );
80         return strndup( psz_header, psz_end - psz_header );
81     }
82     else
83     {
84         return NULL;
85     }
86 }
87
88 static char *GenerateCnonce()
89 {
90     char ps_random[32];
91     struct md5_s md5;
92
93     vlc_rand_bytes( ps_random, sizeof( ps_random ) );
94
95     InitMD5( &md5 );
96     AddMD5( &md5, ps_random, sizeof( ps_random ) );
97     EndMD5( &md5 );
98
99     return psz_md5_hash( &md5 );
100 }
101
102 static char *AuthDigest( vlc_object_t *p_this, http_auth_t *p_auth,
103                          const char *psz_method, const char *psz_path,
104                          const char *psz_username, const char *psz_password )
105 {
106     char *psz_HA1 = NULL;
107     char *psz_HA2 = NULL;
108     char *psz_ent = NULL;
109     char *psz_result = NULL;
110     char psz_inonce[9];
111     struct md5_s md5;
112     struct md5_s ent;
113
114     if ( p_auth->psz_realm == NULL )
115     {
116         msg_Warn( p_this, "Digest Authentication: "
117                   "Mandatory 'realm' value not available" );
118         goto error;
119     }
120
121     /* H(A1) */
122     if ( p_auth->psz_HA1 )
123     {
124         psz_HA1 = strdup( p_auth->psz_HA1 );
125         if ( psz_HA1 == NULL )
126             goto error;
127     }
128     else
129     {
130         InitMD5( &md5 );
131         AddMD5( &md5, psz_username, strlen( psz_username ) );
132         AddMD5( &md5, ":", 1 );
133         AddMD5( &md5, p_auth->psz_realm, strlen( p_auth->psz_realm ) );
134         AddMD5( &md5, ":", 1 );
135         AddMD5( &md5, psz_password, strlen( psz_password ) );
136         EndMD5( &md5 );
137
138         psz_HA1 = psz_md5_hash( &md5 );
139         if ( psz_HA1 == NULL )
140             goto error;
141
142         if ( p_auth->psz_algorithm &&
143              strcmp( p_auth->psz_algorithm, "MD5-sess" ) == 0 )
144         {
145             InitMD5( &md5 );
146             AddMD5( &md5, psz_HA1, 32 );
147             AddMD5( &md5, ":", 1 );
148             AddMD5( &md5, p_auth->psz_nonce, strlen( p_auth->psz_nonce ) );
149             AddMD5( &md5, ":", 1 );
150             AddMD5( &md5, p_auth->psz_cnonce, strlen( p_auth->psz_cnonce ) );
151             EndMD5( &md5 );
152
153             free( psz_HA1 );
154
155             psz_HA1 = psz_md5_hash( &md5 );
156             if ( psz_HA1 == NULL )
157                 goto error;
158
159             p_auth->psz_HA1 = strdup( psz_HA1 );
160             if ( p_auth->psz_HA1 == NULL )
161                 goto error;
162         }
163     }
164
165     /* H(A2) */
166     InitMD5( &md5 );
167     if ( *psz_method )
168         AddMD5( &md5, psz_method, strlen( psz_method ) );
169     AddMD5( &md5, ":", 1 );
170     if ( psz_path )
171         AddMD5( &md5, psz_path, strlen( psz_path ) );
172     else
173         AddMD5( &md5, "/", 1 );
174     if ( p_auth->psz_qop && strcmp( p_auth->psz_qop, "auth-int" ) == 0 )
175     {
176         InitMD5( &ent );
177         /* TODO: Support for "qop=auth-int" */
178         AddMD5( &ent, "", 0 );
179         EndMD5( &ent );
180
181         psz_ent = psz_md5_hash( &ent );
182         if ( psz_ent == NULL )
183             goto error;
184
185         AddMD5( &md5, ":", 1 );
186         AddMD5( &md5, psz_ent, 32 );
187     }
188     EndMD5( &md5 );
189
190     psz_HA2 = psz_md5_hash( &md5 );
191     if ( psz_HA2 == NULL )
192         goto error;
193
194     /* Request digest */
195     InitMD5( &md5 );
196     AddMD5( &md5, psz_HA1, 32 );
197     AddMD5( &md5, ":", 1 );
198     AddMD5( &md5, p_auth->psz_nonce, strlen( p_auth->psz_nonce ) );
199     AddMD5( &md5, ":", 1 );
200     if ( p_auth->psz_qop &&
201          ( strcmp( p_auth->psz_qop, "auth" ) == 0 ||
202            strcmp( p_auth->psz_qop, "auth-int" ) == 0 ) )
203     {
204         snprintf( psz_inonce, sizeof( psz_inonce ), "%08x", p_auth->i_nonce );
205         AddMD5( &md5, psz_inonce, 8 );
206         AddMD5( &md5, ":", 1 );
207         AddMD5( &md5, p_auth->psz_cnonce, strlen( p_auth->psz_cnonce ) );
208         AddMD5( &md5, ":", 1 );
209         AddMD5( &md5, p_auth->psz_qop, strlen( p_auth->psz_qop ) );
210         AddMD5( &md5, ":", 1 );
211     }
212     AddMD5( &md5, psz_HA2, 32 );
213     EndMD5( &md5 );
214
215     psz_result = psz_md5_hash( &md5 );
216
217 error:
218     free( psz_HA1 );
219     free( psz_HA2 );
220     free( psz_ent );
221
222     return psz_result;
223 }
224
225 /* RFC2617, section 3.2.1 The WWW-Authenticate Response Header
226  *
227  * If a server receives a request for an access-protected object, and an
228  * acceptable Authorization header is not sent, the server responds with a "401
229  * Unauthorized" status code, and a WWW-Authenticate header [...]
230  */
231 void http_auth_ParseWwwAuthenticateHeader(
232         vlc_object_t *p_this, http_auth_t *p_auth,
233         const char *psz_header )
234 {
235     static const char psz_basic_prefix[] = "Basic ";
236     static const char psz_digest_prefix[] = "Digest ";
237
238     /* FIXME: multiple auth methods can be listed (comma separated) */
239
240     if ( strncasecmp( psz_header, psz_basic_prefix,
241                       sizeof( psz_basic_prefix ) - 1 ) == 0 )
242     {
243         /* 2 Basic Authentication Scheme */
244         msg_Dbg( p_this, "Using Basic Authentication" );
245         psz_header += sizeof( psz_basic_prefix ) - 1;
246         p_auth->psz_realm = AuthGetParam( psz_header, "realm" );
247         if ( p_auth->psz_realm == NULL )
248             msg_Warn( p_this, "Basic Authentication: "
249                       "Mandatory 'realm' parameter is missing" );
250     }
251     else if ( strncasecmp( psz_header, psz_digest_prefix,
252                            sizeof( psz_digest_prefix ) - 1 ) == 0 )
253     {
254         /* 3 Digest Access Authentication Scheme */
255         msg_Dbg( p_this, "Using Digest Access Authentication" );
256
257         if ( p_auth->psz_nonce )
258             /* FIXME */
259             return;
260
261         psz_header += sizeof( psz_digest_prefix ) - 1;
262         p_auth->psz_realm = AuthGetParam( psz_header, "realm" );
263         p_auth->psz_domain = AuthGetParam( psz_header, "domain" );
264         p_auth->psz_nonce = AuthGetParam( psz_header, "nonce" );
265         p_auth->psz_opaque = AuthGetParam( psz_header, "opaque" );
266         p_auth->psz_stale = AuthGetParamNoQuotes( psz_header, "stale" );
267         p_auth->psz_algorithm = AuthGetParamNoQuotes( psz_header, "algorithm" );
268         p_auth->psz_qop = AuthGetParam( psz_header, "qop" );
269         p_auth->i_nonce = 0;
270
271         /* printf("realm: |%s|\ndomain: |%s|\nnonce: |%s|\nopaque: |%s|\n"
272                   "stale: |%s|\nalgorithm: |%s|\nqop: |%s|\n",
273                   p_auth->psz_realm,p_auth->psz_domain,p_auth->psz_nonce,
274                   p_auth->psz_opaque,p_auth->psz_stale,p_auth->psz_algorithm,
275                   p_auth->psz_qop); */
276
277         if ( p_auth->psz_realm == NULL )
278             msg_Warn( p_this, "Digest Access Authentication: "
279                       "Mandatory 'realm' parameter is missing" );
280         if ( p_auth->psz_nonce == NULL )
281             msg_Warn( p_this, "Digest Access Authentication: "
282                       "Mandatory 'nonce' parameter is missing" );
283
284         /* FIXME: parse the qop list */
285         if ( p_auth->psz_qop )
286         {
287             char *psz_tmp = strchr( p_auth->psz_qop, ',' );
288             if ( psz_tmp )
289                 *psz_tmp = '\0';
290         }
291     }
292     else
293     {
294         const char *psz_end = strchr( psz_header, ' ' );
295         if ( psz_end )
296             msg_Warn( p_this, "Unknown authentication scheme: '%*s'",
297                       psz_end - psz_header, psz_header );
298         else
299             msg_Warn( p_this, "Unknown authentication scheme: '%s'",
300                       psz_header );
301     }
302 }
303
304 /* RFC2617, section 3.2.3: The Authentication-Info Header
305  *
306  * The Authentication-Info header is used by the server to communicate some
307  * information regarding the successful authentication in the response.
308  */
309 int http_auth_ParseAuthenticationInfoHeader(
310         vlc_object_t *p_this, http_auth_t *p_auth,
311         const char *psz_header, const char *psz_method, const char *psz_path,
312         const char *psz_username, const char *psz_password )
313 {
314     char *psz_nextnonce = AuthGetParam( psz_header, "nextnonce" );
315     char *psz_qop = AuthGetParamNoQuotes( psz_header, "qop" );
316     char *psz_rspauth = AuthGetParam( psz_header, "rspauth" );
317     char *psz_cnonce = AuthGetParam( psz_header, "cnonce" );
318     char *psz_nc = AuthGetParamNoQuotes( psz_header, "nc" );
319     char *psz_digest = NULL;
320     int i_err = VLC_SUCCESS;
321     int i_nonce;
322
323     if ( psz_cnonce )
324     {
325         if ( strcmp( psz_cnonce, p_auth->psz_cnonce ) != 0 )
326         {
327             msg_Err( p_this, "HTTP Digest Access Authentication: server "
328                              "replied with a different client nonce value." );
329             i_err = VLC_EGENERIC;
330             goto error;
331         }
332
333         if ( psz_nc )
334         {
335             i_nonce = strtol( psz_nc, NULL, 16 );
336
337             if ( i_nonce != p_auth->i_nonce )
338             {
339                 msg_Err( p_this, "HTTP Digest Access Authentication: server "
340                                  "replied with a different nonce count "
341                                  "value." );
342                 i_err = VLC_EGENERIC;
343                 goto error;
344             }
345         }
346
347         if ( psz_qop && p_auth->psz_qop &&
348              strcmp( psz_qop, p_auth->psz_qop ) != 0 )
349             msg_Warn( p_this, "HTTP Digest Access Authentication: server "
350                               "replied using a different 'quality of "
351                               "protection' option" );
352
353         /* All the clear text values match, let's now check the response
354          * digest.
355          *
356          * TODO: Support for "qop=auth-int"
357          */
358         psz_digest = AuthDigest( p_this, p_auth, psz_method, psz_path,
359                                  psz_username, psz_password );
360         if ( strcmp( psz_digest, psz_rspauth ) != 0 )
361         {
362             msg_Err( p_this, "HTTP Digest Access Authentication: server "
363                              "replied with an invalid response digest "
364                              "(expected value: %s).", psz_digest );
365             i_err = VLC_EGENERIC;
366             goto error;
367         }
368     }
369
370     if ( psz_nextnonce )
371     {
372         free( p_auth->psz_nonce );
373         p_auth->psz_nonce = psz_nextnonce;
374         psz_nextnonce = NULL;
375     }
376
377 error:
378     free( psz_nextnonce );
379     free( psz_qop );
380     free( psz_rspauth );
381     free( psz_cnonce );
382     free( psz_nc );
383     free( psz_digest );
384
385     return i_err;
386 }
387
388 char *http_auth_FormatAuthorizationHeader(
389         vlc_object_t *p_this, http_auth_t *p_auth,
390         const char *psz_method, const char *psz_path,
391         const char *psz_username, const char *psz_password )
392 {
393     char *psz_result = NULL;
394     char *psz_buffer = NULL;
395     char *psz_base64 = NULL;
396     int i_rc;
397
398     if ( p_auth->psz_nonce )
399     {
400         /* Digest Access Authentication */
401         if ( p_auth->psz_algorithm &&
402              strcmp( p_auth->psz_algorithm, "MD5" ) != 0 &&
403              strcmp( p_auth->psz_algorithm, "MD5-sess" ) != 0 )
404         {
405             msg_Err( p_this, "Digest Access Authentication: "
406                      "Unknown algorithm '%s'", p_auth->psz_algorithm );
407             goto error;
408         }
409
410         if ( p_auth->psz_qop != NULL || p_auth->psz_cnonce == NULL )
411         {
412             free( p_auth->psz_cnonce );
413
414             p_auth->psz_cnonce = GenerateCnonce();
415             if ( p_auth->psz_cnonce == NULL )
416                 goto error;
417         }
418
419         ++p_auth->i_nonce;
420
421         psz_buffer = AuthDigest( p_this, p_auth, psz_method, psz_path,
422                                  psz_username, psz_password );
423         if ( psz_buffer == NULL )
424             goto error;
425
426         i_rc = asprintf( &psz_result,
427             "Digest "
428             /* Mandatory parameters */
429             "username=\"%s\", "
430             "realm=\"%s\", "
431             "nonce=\"%s\", "
432             "uri=\"%s\", "
433             "response=\"%s\", "
434             /* Optional parameters */
435             "%s%s%s" /* algorithm */
436             "%s%s%s" /* cnonce */
437             "%s%s%s" /* opaque */
438             "%s%s%s" /* message qop */
439             "%s%08x%s", /* nonce count */
440             /* Mandatory parameters */
441             psz_username,
442             p_auth->psz_realm,
443             p_auth->psz_nonce,
444             psz_path ? psz_path : "/",
445             psz_buffer,
446             /* Optional parameters */
447             p_auth->psz_algorithm ? "algorithm=\"" : "",
448             p_auth->psz_algorithm ? p_auth->psz_algorithm : "",
449             p_auth->psz_algorithm ? "\", " : "",
450             p_auth->psz_cnonce ? "cnonce=\"" : "",
451             p_auth->psz_cnonce ? p_auth->psz_cnonce : "",
452             p_auth->psz_cnonce ? "\", " : "",
453             p_auth->psz_opaque ? "opaque=\"" : "",
454             p_auth->psz_opaque ? p_auth->psz_opaque : "",
455             p_auth->psz_opaque ? "\", " : "",
456             p_auth->psz_qop ? "qop=\"" : "",
457             p_auth->psz_qop ? p_auth->psz_qop : "",
458             p_auth->psz_qop ? "\", " : "",
459             /* "uglyhack" will be parsed as an unhandled extension */
460             p_auth->i_nonce ? "nc=\"" : "uglyhack=\"",
461             p_auth->i_nonce,
462             p_auth->i_nonce ? "\"" : "\""
463         );
464         if ( i_rc < 0 )
465             goto error;
466     }
467     else
468     {
469         /* Basic Access Authentication */
470         i_rc = asprintf( &psz_buffer, "%s:%s", psz_username, psz_password );
471         if ( i_rc < 0 )
472             goto error;
473
474         psz_base64 = vlc_b64_encode( psz_buffer );
475         if ( psz_base64 == NULL )
476             goto error;
477
478         i_rc = asprintf( &psz_result, "Basic %s", psz_base64 );
479         if ( i_rc < 0 )
480             goto error;
481     }
482
483 error:
484     free( psz_buffer );
485     free( psz_base64 );
486
487     return psz_result;
488 }
489
490 void http_auth_Init( http_auth_t *p_auth )
491 {
492     memset( p_auth, 0, sizeof( *p_auth ) );
493 }
494
495 void http_auth_Reset( http_auth_t *p_auth )
496 {
497     p_auth->i_nonce = 0;
498
499     FREENULL( p_auth->psz_realm );
500     FREENULL( p_auth->psz_domain );
501     FREENULL( p_auth->psz_nonce );
502     FREENULL( p_auth->psz_opaque );
503     FREENULL( p_auth->psz_stale );
504     FREENULL( p_auth->psz_algorithm );
505     FREENULL( p_auth->psz_qop );
506     FREENULL( p_auth->psz_cnonce );
507     FREENULL( p_auth->psz_HA1 );
508 }