yading@11: /* yading@11: * HTTP authentication yading@11: * Copyright (c) 2010 Martin Storsjo yading@11: * yading@11: * This file is part of FFmpeg. yading@11: * yading@11: * FFmpeg is free software; you can redistribute it and/or yading@11: * modify it under the terms of the GNU Lesser General Public yading@11: * License as published by the Free Software Foundation; either yading@11: * version 2.1 of the License, or (at your option) any later version. yading@11: * yading@11: * FFmpeg is distributed in the hope that it will be useful, yading@11: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@11: * Lesser General Public License for more details. yading@11: * yading@11: * You should have received a copy of the GNU Lesser General Public yading@11: * License along with FFmpeg; if not, write to the Free Software yading@11: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@11: */ yading@11: yading@11: #include "httpauth.h" yading@11: #include "libavutil/base64.h" yading@11: #include "libavutil/avstring.h" yading@11: #include "internal.h" yading@11: #include "libavutil/random_seed.h" yading@11: #include "libavutil/md5.h" yading@11: #include "urldecode.h" yading@11: #include "avformat.h" yading@11: yading@11: static void handle_basic_params(HTTPAuthState *state, const char *key, yading@11: int key_len, char **dest, int *dest_len) yading@11: { yading@11: if (!strncmp(key, "realm=", key_len)) { yading@11: *dest = state->realm; yading@11: *dest_len = sizeof(state->realm); yading@11: } yading@11: } yading@11: yading@11: static void handle_digest_params(HTTPAuthState *state, const char *key, yading@11: int key_len, char **dest, int *dest_len) yading@11: { yading@11: DigestParams *digest = &state->digest_params; yading@11: yading@11: if (!strncmp(key, "realm=", key_len)) { yading@11: *dest = state->realm; yading@11: *dest_len = sizeof(state->realm); yading@11: } else if (!strncmp(key, "nonce=", key_len)) { yading@11: *dest = digest->nonce; yading@11: *dest_len = sizeof(digest->nonce); yading@11: } else if (!strncmp(key, "opaque=", key_len)) { yading@11: *dest = digest->opaque; yading@11: *dest_len = sizeof(digest->opaque); yading@11: } else if (!strncmp(key, "algorithm=", key_len)) { yading@11: *dest = digest->algorithm; yading@11: *dest_len = sizeof(digest->algorithm); yading@11: } else if (!strncmp(key, "qop=", key_len)) { yading@11: *dest = digest->qop; yading@11: *dest_len = sizeof(digest->qop); yading@11: } else if (!strncmp(key, "stale=", key_len)) { yading@11: *dest = digest->stale; yading@11: *dest_len = sizeof(digest->stale); yading@11: } yading@11: } yading@11: yading@11: static void handle_digest_update(HTTPAuthState *state, const char *key, yading@11: int key_len, char **dest, int *dest_len) yading@11: { yading@11: DigestParams *digest = &state->digest_params; yading@11: yading@11: if (!strncmp(key, "nextnonce=", key_len)) { yading@11: *dest = digest->nonce; yading@11: *dest_len = sizeof(digest->nonce); yading@11: } yading@11: } yading@11: yading@11: static void choose_qop(char *qop, int size) yading@11: { yading@11: char *ptr = strstr(qop, "auth"); yading@11: char *end = ptr + strlen("auth"); yading@11: yading@11: if (ptr && (!*end || av_isspace(*end) || *end == ',') && yading@11: (ptr == qop || av_isspace(ptr[-1]) || ptr[-1] == ',')) { yading@11: av_strlcpy(qop, "auth", size); yading@11: } else { yading@11: qop[0] = 0; yading@11: } yading@11: } yading@11: yading@11: void ff_http_auth_handle_header(HTTPAuthState *state, const char *key, yading@11: const char *value) yading@11: { yading@11: if (!strcmp(key, "WWW-Authenticate") || !strcmp(key, "Proxy-Authenticate")) { yading@11: const char *p; yading@11: if (av_stristart(value, "Basic ", &p) && yading@11: state->auth_type <= HTTP_AUTH_BASIC) { yading@11: state->auth_type = HTTP_AUTH_BASIC; yading@11: state->realm[0] = 0; yading@11: state->stale = 0; yading@11: ff_parse_key_value(p, (ff_parse_key_val_cb) handle_basic_params, yading@11: state); yading@11: } else if (av_stristart(value, "Digest ", &p) && yading@11: state->auth_type <= HTTP_AUTH_DIGEST) { yading@11: state->auth_type = HTTP_AUTH_DIGEST; yading@11: memset(&state->digest_params, 0, sizeof(DigestParams)); yading@11: state->realm[0] = 0; yading@11: state->stale = 0; yading@11: ff_parse_key_value(p, (ff_parse_key_val_cb) handle_digest_params, yading@11: state); yading@11: choose_qop(state->digest_params.qop, yading@11: sizeof(state->digest_params.qop)); yading@11: if (!av_strcasecmp(state->digest_params.stale, "true")) yading@11: state->stale = 1; yading@11: } yading@11: } else if (!strcmp(key, "Authentication-Info")) { yading@11: ff_parse_key_value(value, (ff_parse_key_val_cb) handle_digest_update, yading@11: state); yading@11: } yading@11: } yading@11: yading@11: yading@11: static void update_md5_strings(struct AVMD5 *md5ctx, ...) yading@11: { yading@11: va_list vl; yading@11: yading@11: va_start(vl, md5ctx); yading@11: while (1) { yading@11: const char* str = va_arg(vl, const char*); yading@11: if (!str) yading@11: break; yading@11: av_md5_update(md5ctx, str, strlen(str)); yading@11: } yading@11: va_end(vl); yading@11: } yading@11: yading@11: /* Generate a digest reply, according to RFC 2617. */ yading@11: static char *make_digest_auth(HTTPAuthState *state, const char *username, yading@11: const char *password, const char *uri, yading@11: const char *method) yading@11: { yading@11: DigestParams *digest = &state->digest_params; yading@11: int len; yading@11: uint32_t cnonce_buf[2]; yading@11: char cnonce[17]; yading@11: char nc[9]; yading@11: int i; yading@11: char A1hash[33], A2hash[33], response[33]; yading@11: struct AVMD5 *md5ctx; yading@11: uint8_t hash[16]; yading@11: char *authstr; yading@11: yading@11: digest->nc++; yading@11: snprintf(nc, sizeof(nc), "%08x", digest->nc); yading@11: yading@11: /* Generate a client nonce. */ yading@11: for (i = 0; i < 2; i++) yading@11: cnonce_buf[i] = av_get_random_seed(); yading@11: ff_data_to_hex(cnonce, (const uint8_t*) cnonce_buf, sizeof(cnonce_buf), 1); yading@11: cnonce[2*sizeof(cnonce_buf)] = 0; yading@11: yading@11: md5ctx = av_md5_alloc(); yading@11: if (!md5ctx) yading@11: return NULL; yading@11: yading@11: av_md5_init(md5ctx); yading@11: update_md5_strings(md5ctx, username, ":", state->realm, ":", password, NULL); yading@11: av_md5_final(md5ctx, hash); yading@11: ff_data_to_hex(A1hash, hash, 16, 1); yading@11: A1hash[32] = 0; yading@11: yading@11: if (!strcmp(digest->algorithm, "") || !strcmp(digest->algorithm, "MD5")) { yading@11: } else if (!strcmp(digest->algorithm, "MD5-sess")) { yading@11: av_md5_init(md5ctx); yading@11: update_md5_strings(md5ctx, A1hash, ":", digest->nonce, ":", cnonce, NULL); yading@11: av_md5_final(md5ctx, hash); yading@11: ff_data_to_hex(A1hash, hash, 16, 1); yading@11: A1hash[32] = 0; yading@11: } else { yading@11: /* Unsupported algorithm */ yading@11: av_free(md5ctx); yading@11: return NULL; yading@11: } yading@11: yading@11: av_md5_init(md5ctx); yading@11: update_md5_strings(md5ctx, method, ":", uri, NULL); yading@11: av_md5_final(md5ctx, hash); yading@11: ff_data_to_hex(A2hash, hash, 16, 1); yading@11: A2hash[32] = 0; yading@11: yading@11: av_md5_init(md5ctx); yading@11: update_md5_strings(md5ctx, A1hash, ":", digest->nonce, NULL); yading@11: if (!strcmp(digest->qop, "auth") || !strcmp(digest->qop, "auth-int")) { yading@11: update_md5_strings(md5ctx, ":", nc, ":", cnonce, ":", digest->qop, NULL); yading@11: } yading@11: update_md5_strings(md5ctx, ":", A2hash, NULL); yading@11: av_md5_final(md5ctx, hash); yading@11: ff_data_to_hex(response, hash, 16, 1); yading@11: response[32] = 0; yading@11: yading@11: av_free(md5ctx); yading@11: yading@11: if (!strcmp(digest->qop, "") || !strcmp(digest->qop, "auth")) { yading@11: } else if (!strcmp(digest->qop, "auth-int")) { yading@11: /* qop=auth-int not supported */ yading@11: return NULL; yading@11: } else { yading@11: /* Unsupported qop value. */ yading@11: return NULL; yading@11: } yading@11: yading@11: len = strlen(username) + strlen(state->realm) + strlen(digest->nonce) + yading@11: strlen(uri) + strlen(response) + strlen(digest->algorithm) + yading@11: strlen(digest->opaque) + strlen(digest->qop) + strlen(cnonce) + yading@11: strlen(nc) + 150; yading@11: yading@11: authstr = av_malloc(len); yading@11: if (!authstr) yading@11: return NULL; yading@11: snprintf(authstr, len, "Authorization: Digest "); yading@11: yading@11: /* TODO: Escape the quoted strings properly. */ yading@11: av_strlcatf(authstr, len, "username=\"%s\"", username); yading@11: av_strlcatf(authstr, len, ",realm=\"%s\"", state->realm); yading@11: av_strlcatf(authstr, len, ",nonce=\"%s\"", digest->nonce); yading@11: av_strlcatf(authstr, len, ",uri=\"%s\"", uri); yading@11: av_strlcatf(authstr, len, ",response=\"%s\"", response); yading@11: if (digest->algorithm[0]) yading@11: av_strlcatf(authstr, len, ",algorithm=%s", digest->algorithm); yading@11: if (digest->opaque[0]) yading@11: av_strlcatf(authstr, len, ",opaque=\"%s\"", digest->opaque); yading@11: if (digest->qop[0]) { yading@11: av_strlcatf(authstr, len, ",qop=\"%s\"", digest->qop); yading@11: av_strlcatf(authstr, len, ",cnonce=\"%s\"", cnonce); yading@11: av_strlcatf(authstr, len, ",nc=%s", nc); yading@11: } yading@11: yading@11: av_strlcatf(authstr, len, "\r\n"); yading@11: yading@11: return authstr; yading@11: } yading@11: yading@11: char *ff_http_auth_create_response(HTTPAuthState *state, const char *auth, yading@11: const char *path, const char *method) yading@11: { yading@11: char *authstr = NULL; yading@11: yading@11: /* Clear the stale flag, we assume the auth is ok now. It is reset yading@11: * by the server headers if there's a new issue. */ yading@11: state->stale = 0; yading@11: if (!auth || !strchr(auth, ':')) yading@11: return NULL; yading@11: yading@11: if (state->auth_type == HTTP_AUTH_BASIC) { yading@11: int auth_b64_len, len; yading@11: char *ptr, *decoded_auth = ff_urldecode(auth); yading@11: yading@11: if (!decoded_auth) yading@11: return NULL; yading@11: yading@11: auth_b64_len = AV_BASE64_SIZE(strlen(decoded_auth)); yading@11: len = auth_b64_len + 30; yading@11: yading@11: authstr = av_malloc(len); yading@11: if (!authstr) { yading@11: av_free(decoded_auth); yading@11: return NULL; yading@11: } yading@11: yading@11: snprintf(authstr, len, "Authorization: Basic "); yading@11: ptr = authstr + strlen(authstr); yading@11: av_base64_encode(ptr, auth_b64_len, decoded_auth, strlen(decoded_auth)); yading@11: av_strlcat(ptr, "\r\n", len - (ptr - authstr)); yading@11: av_free(decoded_auth); yading@11: } else if (state->auth_type == HTTP_AUTH_DIGEST) { yading@11: char *username = ff_urldecode(auth), *password; yading@11: yading@11: if (!username) yading@11: return NULL; yading@11: yading@11: if ((password = strchr(username, ':'))) { yading@11: *password++ = 0; yading@11: authstr = make_digest_auth(state, username, password, path, method); yading@11: } yading@11: av_free(username); yading@11: } yading@11: return authstr; yading@11: }