1/* 2 * HTTP authentication 3 * Copyright (c) 2010 Martin Storsjo 4 * 5 * This file is part of FFmpeg. 6 * 7 * FFmpeg is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * FFmpeg is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with FFmpeg; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 */ 21 22#include "httpauth.h" 23#include "libavutil/base64.h" 24#include "libavutil/avstring.h" 25#include "internal.h" 26#include "libavutil/random_seed.h" 27#include "libavutil/md5.h" 28#include "urldecode.h" 29#include "avformat.h" 30 31static void handle_basic_params(HTTPAuthState *state, const char *key, 32 int key_len, char **dest, int *dest_len) 33{ 34 if (!strncmp(key, "realm=", key_len)) { 35 *dest = state->realm; 36 *dest_len = sizeof(state->realm); 37 } 38} 39 40static void handle_digest_params(HTTPAuthState *state, const char *key, 41 int key_len, char **dest, int *dest_len) 42{ 43 DigestParams *digest = &state->digest_params; 44 45 if (!strncmp(key, "realm=", key_len)) { 46 *dest = state->realm; 47 *dest_len = sizeof(state->realm); 48 } else if (!strncmp(key, "nonce=", key_len)) { 49 *dest = digest->nonce; 50 *dest_len = sizeof(digest->nonce); 51 } else if (!strncmp(key, "opaque=", key_len)) { 52 *dest = digest->opaque; 53 *dest_len = sizeof(digest->opaque); 54 } else if (!strncmp(key, "algorithm=", key_len)) { 55 *dest = digest->algorithm; 56 *dest_len = sizeof(digest->algorithm); 57 } else if (!strncmp(key, "qop=", key_len)) { 58 *dest = digest->qop; 59 *dest_len = sizeof(digest->qop); 60 } else if (!strncmp(key, "stale=", key_len)) { 61 *dest = digest->stale; 62 *dest_len = sizeof(digest->stale); 63 } 64} 65 66static void handle_digest_update(HTTPAuthState *state, const char *key, 67 int key_len, char **dest, int *dest_len) 68{ 69 DigestParams *digest = &state->digest_params; 70 71 if (!strncmp(key, "nextnonce=", key_len)) { 72 *dest = digest->nonce; 73 *dest_len = sizeof(digest->nonce); 74 } 75} 76 77static void choose_qop(char *qop, int size) 78{ 79 char *ptr = strstr(qop, "auth"); 80 char *end = ptr + strlen("auth"); 81 82 if (ptr && (!*end || av_isspace(*end) || *end == ',') && 83 (ptr == qop || av_isspace(ptr[-1]) || ptr[-1] == ',')) { 84 av_strlcpy(qop, "auth", size); 85 } else { 86 qop[0] = 0; 87 } 88} 89 90void ff_http_auth_handle_header(HTTPAuthState *state, const char *key, 91 const char *value) 92{ 93 if (!strcmp(key, "WWW-Authenticate") || !strcmp(key, "Proxy-Authenticate")) { 94 const char *p; 95 if (av_stristart(value, "Basic ", &p) && 96 state->auth_type <= HTTP_AUTH_BASIC) { 97 state->auth_type = HTTP_AUTH_BASIC; 98 state->realm[0] = 0; 99 state->stale = 0; 100 ff_parse_key_value(p, (ff_parse_key_val_cb) handle_basic_params, 101 state); 102 } else if (av_stristart(value, "Digest ", &p) && 103 state->auth_type <= HTTP_AUTH_DIGEST) { 104 state->auth_type = HTTP_AUTH_DIGEST; 105 memset(&state->digest_params, 0, sizeof(DigestParams)); 106 state->realm[0] = 0; 107 state->stale = 0; 108 ff_parse_key_value(p, (ff_parse_key_val_cb) handle_digest_params, 109 state); 110 choose_qop(state->digest_params.qop, 111 sizeof(state->digest_params.qop)); 112 if (!av_strcasecmp(state->digest_params.stale, "true")) 113 state->stale = 1; 114 } 115 } else if (!strcmp(key, "Authentication-Info")) { 116 ff_parse_key_value(value, (ff_parse_key_val_cb) handle_digest_update, 117 state); 118 } 119} 120 121 122static void update_md5_strings(struct AVMD5 *md5ctx, ...) 123{ 124 va_list vl; 125 126 va_start(vl, md5ctx); 127 while (1) { 128 const char* str = va_arg(vl, const char*); 129 if (!str) 130 break; 131 av_md5_update(md5ctx, str, strlen(str)); 132 } 133 va_end(vl); 134} 135 136/* Generate a digest reply, according to RFC 2617. */ 137static char *make_digest_auth(HTTPAuthState *state, const char *username, 138 const char *password, const char *uri, 139 const char *method) 140{ 141 DigestParams *digest = &state->digest_params; 142 int len; 143 uint32_t cnonce_buf[2]; 144 char cnonce[17]; 145 char nc[9]; 146 int i; 147 char A1hash[33], A2hash[33], response[33]; 148 struct AVMD5 *md5ctx; 149 uint8_t hash[16]; 150 char *authstr; 151 152 digest->nc++; 153 snprintf(nc, sizeof(nc), "%08x", digest->nc); 154 155 /* Generate a client nonce. */ 156 for (i = 0; i < 2; i++) 157 cnonce_buf[i] = av_get_random_seed(); 158 ff_data_to_hex(cnonce, (const uint8_t*) cnonce_buf, sizeof(cnonce_buf), 1); 159 cnonce[2*sizeof(cnonce_buf)] = 0; 160 161 md5ctx = av_md5_alloc(); 162 if (!md5ctx) 163 return NULL; 164 165 av_md5_init(md5ctx); 166 update_md5_strings(md5ctx, username, ":", state->realm, ":", password, NULL); 167 av_md5_final(md5ctx, hash); 168 ff_data_to_hex(A1hash, hash, 16, 1); 169 A1hash[32] = 0; 170 171 if (!strcmp(digest->algorithm, "") || !strcmp(digest->algorithm, "MD5")) { 172 } else if (!strcmp(digest->algorithm, "MD5-sess")) { 173 av_md5_init(md5ctx); 174 update_md5_strings(md5ctx, A1hash, ":", digest->nonce, ":", cnonce, NULL); 175 av_md5_final(md5ctx, hash); 176 ff_data_to_hex(A1hash, hash, 16, 1); 177 A1hash[32] = 0; 178 } else { 179 /* Unsupported algorithm */ 180 av_free(md5ctx); 181 return NULL; 182 } 183 184 av_md5_init(md5ctx); 185 update_md5_strings(md5ctx, method, ":", uri, NULL); 186 av_md5_final(md5ctx, hash); 187 ff_data_to_hex(A2hash, hash, 16, 1); 188 A2hash[32] = 0; 189 190 av_md5_init(md5ctx); 191 update_md5_strings(md5ctx, A1hash, ":", digest->nonce, NULL); 192 if (!strcmp(digest->qop, "auth") || !strcmp(digest->qop, "auth-int")) { 193 update_md5_strings(md5ctx, ":", nc, ":", cnonce, ":", digest->qop, NULL); 194 } 195 update_md5_strings(md5ctx, ":", A2hash, NULL); 196 av_md5_final(md5ctx, hash); 197 ff_data_to_hex(response, hash, 16, 1); 198 response[32] = 0; 199 200 av_free(md5ctx); 201 202 if (!strcmp(digest->qop, "") || !strcmp(digest->qop, "auth")) { 203 } else if (!strcmp(digest->qop, "auth-int")) { 204 /* qop=auth-int not supported */ 205 return NULL; 206 } else { 207 /* Unsupported qop value. */ 208 return NULL; 209 } 210 211 len = strlen(username) + strlen(state->realm) + strlen(digest->nonce) + 212 strlen(uri) + strlen(response) + strlen(digest->algorithm) + 213 strlen(digest->opaque) + strlen(digest->qop) + strlen(cnonce) + 214 strlen(nc) + 150; 215 216 authstr = av_malloc(len); 217 if (!authstr) 218 return NULL; 219 snprintf(authstr, len, "Authorization: Digest "); 220 221 /* TODO: Escape the quoted strings properly. */ 222 av_strlcatf(authstr, len, "username=\"%s\"", username); 223 av_strlcatf(authstr, len, ",realm=\"%s\"", state->realm); 224 av_strlcatf(authstr, len, ",nonce=\"%s\"", digest->nonce); 225 av_strlcatf(authstr, len, ",uri=\"%s\"", uri); 226 av_strlcatf(authstr, len, ",response=\"%s\"", response); 227 228 // we are violating the RFC and use "" because all others seem to do that too. 229 if (digest->algorithm[0]) 230 av_strlcatf(authstr, len, ",algorithm=\"%s\"", digest->algorithm); 231 232 if (digest->opaque[0]) 233 av_strlcatf(authstr, len, ",opaque=\"%s\"", digest->opaque); 234 if (digest->qop[0]) { 235 av_strlcatf(authstr, len, ",qop=\"%s\"", digest->qop); 236 av_strlcatf(authstr, len, ",cnonce=\"%s\"", cnonce); 237 av_strlcatf(authstr, len, ",nc=%s", nc); 238 } 239 240 av_strlcatf(authstr, len, "\r\n"); 241 242 return authstr; 243} 244 245char *ff_http_auth_create_response(HTTPAuthState *state, const char *auth, 246 const char *path, const char *method) 247{ 248 char *authstr = NULL; 249 250 /* Clear the stale flag, we assume the auth is ok now. It is reset 251 * by the server headers if there's a new issue. */ 252 state->stale = 0; 253 if (!auth || !strchr(auth, ':')) 254 return NULL; 255 256 if (state->auth_type == HTTP_AUTH_BASIC) { 257 int auth_b64_len, len; 258 char *ptr, *decoded_auth = ff_urldecode(auth); 259 260 if (!decoded_auth) 261 return NULL; 262 263 auth_b64_len = AV_BASE64_SIZE(strlen(decoded_auth)); 264 len = auth_b64_len + 30; 265 266 authstr = av_malloc(len); 267 if (!authstr) { 268 av_free(decoded_auth); 269 return NULL; 270 } 271 272 snprintf(authstr, len, "Authorization: Basic "); 273 ptr = authstr + strlen(authstr); 274 av_base64_encode(ptr, auth_b64_len, decoded_auth, strlen(decoded_auth)); 275 av_strlcat(ptr, "\r\n", len - (ptr - authstr)); 276 av_free(decoded_auth); 277 } else if (state->auth_type == HTTP_AUTH_DIGEST) { 278 char *username = ff_urldecode(auth), *password; 279 280 if (!username) 281 return NULL; 282 283 if ((password = strchr(username, ':'))) { 284 *password++ = 0; 285 authstr = make_digest_auth(state, username, password, path, method); 286 } 287 av_free(username); 288 } 289 return authstr; 290} 291