From: Björn Axelsson Date: Tue, 20 Mar 2012 17:12:23 +0000 (+0200) Subject: Add support for limelight authentication X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=9ecf540e4d5bdc85c17668fa7ead93cc375111ca;p=rtmpdump Add support for limelight authentication --- diff --git a/librtmp/rtmp.c b/librtmp/rtmp.c index 391a3b4..5ea1c76 100644 --- a/librtmp/rtmp.c +++ b/librtmp/rtmp.c @@ -2511,6 +2511,18 @@ static void md5sum(const unsigned char *data, int len, unsigned char *out) #endif static const AVal av_authmod_adobe = AVC("authmod=adobe"); +static const AVal av_authmod_llnw = AVC("authmod=llnw"); + +static char *hexenc(unsigned char *inbuf, int len) +{ + char *dst = malloc(len * 2 + 1), *ptr = dst; + while(len--) { + sprintf(ptr, "%02x", *inbuf++); + ptr += 2; + } + *ptr = '\0'; + return dst; +} static int PublisherAuth(RTMP *r, AVal *description) @@ -2666,6 +2678,178 @@ PublisherAuth(RTMP *r, AVal *description) free(pubToken.av_val); r->Link.pFlags |= RTMP_PUB_ALLOC; + RTMP_Log(RTMP_LOGDEBUG, "%s, new app: %.*s tcUrl: %.*s playpath: %s", __FUNCTION__, + r->Link.app.av_len, r->Link.app.av_val, + r->Link.tcUrl.av_len, r->Link.tcUrl.av_val, + r->Link.playpath.av_val); + } + else if (strstr(description->av_val, av_authmod_llnw.av_val) != NULL) + { + if(strstr(description->av_val, "code=403 need auth") != NULL) + { + /* This part seems to be the same for llnw and adobe */ + + if (strstr(r->Link.app.av_val, av_authmod_llnw.av_val) != NULL) { + RTMP_Log(RTMP_LOGERROR, "%s, wrong pubUser & pubPasswd for publisher auth", __FUNCTION__); + r->Link.pFlags |= RTMP_PUB_CLEAN; + return 0; + } else if(r->Link.pubUser.av_len && r->Link.pubPasswd.av_len) { + pubToken.av_val = malloc(r->Link.pubUser.av_len + av_authmod_llnw.av_len + 8); + pubToken.av_len = sprintf(pubToken.av_val, "?%s&user=%s", + av_authmod_llnw.av_val, + r->Link.pubUser.av_val); + RTMP_Log(RTMP_LOGDEBUG, "%s, pubToken1: %s", __FUNCTION__, pubToken.av_val); + r->Link.pFlags |= RTMP_PUB_NAME; + } else { + RTMP_Log(RTMP_LOGERROR, "%s, need to set pubUser & pubPasswd for publisher auth", __FUNCTION__); + r->Link.pFlags |= RTMP_PUB_CLEAN; + return 0; + } + } + else if((token_in = strstr(description->av_val, "?reason=needauth")) != NULL) + { + char *orig_ptr; + char *par, *val = NULL; + char *user = NULL; + char *nonce = NULL; + + ptr = orig_ptr = strdup(token_in); + /* Extract parameters (we need user and nonce) */ + while (ptr) + { + par = ptr; + ptr = strchr(par, '&'); + if(ptr) + *ptr++ = '\0'; + + val = strchr(par, '='); + if(val) + *val++ = '\0'; + + if (strcmp(par, "user") == 0){ + user = val; + } else if (strcmp(par, "nonce") == 0){ + nonce = val; + } + + RTMP_Log(RTMP_LOGDEBUG, "%s, par:\"%s\" = val:\"%s\"", __FUNCTION__, par, val); + } + + // FIXME: handle case where user==NULL or nonce==NULL + + /* llnw auth method + * Seems to be closely based on HTTP Digest Auth: + * http://tools.ietf.org/html/rfc2617 + * http://en.wikipedia.org/wiki/Digest_access_authentication + */ + + const char *authmod = "llnw"; + const char *realm = "live"; + const char *method = "publish"; + const char *qop = "auth"; + char *tmpbuf; + + /* nc = 1..connection count (or rather, number of times cnonce has been reused) */ + int nc = 1; + /* nchex = hexenc(nc) (8 hex digits according to RFC 2617) */ + char nchex[9]; + sprintf(nchex, "%08x", nc); + /* cnonce = hexenc(4 random bytes) (initialized on first connection) */ + char cnonce[9]; + srand( time(NULL) ); // FIXME: a lib shouldn't call srand() + sprintf(cnonce, "%08x", rand()); + + /* hash1 = hexenc(md5(user + ":" + realm + ":" + password)) */ + tmpbuf = malloc(strlen(user) + 1 + strlen(realm) + 1 + r->Link.pubPasswd.av_len + 1); + sprintf(tmpbuf, "%s:%s:%s", user, realm, r->Link.pubPasswd.av_val); + md5sum((unsigned char*)tmpbuf, strlen(tmpbuf), md5sum_val); + RTMP_Log(RTMP_LOGDEBUG, "%s, md5(%s) =>", __FUNCTION__, tmpbuf); + RTMP_LogHexString(RTMP_LOGDEBUG, md5sum_val, MD5_DIGEST_LENGTH); + free(tmpbuf); + char *hash1 = hexenc(md5sum_val, MD5_DIGEST_LENGTH); + + /* hash2 = hexenc(md5(method + ":/" + app + "/" + appInstance)) */ + /* Extract appname + appinstance without query parameters */ + char *apptmp = malloc(r->Link.app.av_len + 1), *qpos; + memcpy(apptmp, r->Link.app.av_val, r->Link.app.av_len); + apptmp[r->Link.app.av_len] = '\0'; + if((qpos = strchr(apptmp, '?'))) + *qpos = '\0'; + + tmpbuf = malloc(strlen(method) + 2 + strlen(apptmp) + 1); + sprintf(tmpbuf, "%s:/%s", method, apptmp); + md5sum((unsigned char*)tmpbuf, strlen(tmpbuf), md5sum_val); + RTMP_Log(RTMP_LOGDEBUG, "%s, md5(%s) =>", __FUNCTION__, tmpbuf); + RTMP_LogHexString(RTMP_LOGDEBUG, md5sum_val, MD5_DIGEST_LENGTH); + free(tmpbuf); + char *hash2 = hexenc(md5sum_val, MD5_DIGEST_LENGTH); + + free(apptmp); + + /* hash3 = hexenc(md5(hash1 + ":" + nonce + ":" + nchex + ":" + cnonce + ":" + qop + ":" + hash2)) */ + tmpbuf = malloc(strlen(hash1) + 1 + strlen(nonce) + 1 + strlen(nchex) + 1 + strlen(cnonce) + 1 + strlen(qop) + 1 + strlen(hash2) + 1); + sprintf(tmpbuf, "%s:%s:%s:%s:%s:%s", hash1, nonce, nchex, cnonce, qop, hash2); + md5sum((unsigned char*)tmpbuf, strlen(tmpbuf), md5sum_val); + RTMP_Log(RTMP_LOGDEBUG, "%s, md5(%s) =>", __FUNCTION__, tmpbuf); + RTMP_LogHexString(RTMP_LOGDEBUG, md5sum_val, MD5_DIGEST_LENGTH); + free(tmpbuf); + char *hash3 = hexenc(md5sum_val, MD5_DIGEST_LENGTH); + + /* pubToken = &authmod=&user=&nonce=&cnonce=&nc=&response= */ + /* Append nonces and response to query string which already contains + * user + authmod */ + pubToken.av_val = malloc(64 + strlen(authmod) + strlen(user) + strlen(nonce) + strlen(cnonce) + strlen(nchex) + strlen(hash3)); + sprintf(pubToken.av_val, + "&nonce=%s&cnonce=%s&nc=%s&response=%s", + nonce, cnonce, nchex, hash3); + pubToken.av_len = strlen(pubToken.av_val); + RTMP_Log(RTMP_LOGDEBUG, "%s, pubToken2: %s", __FUNCTION__, pubToken.av_val); + r->Link.pFlags |= RTMP_PUB_RESP|RTMP_PUB_CLATE; + + free(hash1); + free(hash2); + free(hash3); + free(orig_ptr); + } + else if(strstr(description->av_val, "?reason=authfail") != NULL) + { + RTMP_Log(RTMP_LOGERROR, "%s, Authentication failed", __FUNCTION__); + r->Link.pFlags |= RTMP_PUB_CLEAN; + return 0; + } + else if(strstr(description->av_val, "?reason=nosuchuser") != NULL) + { + RTMP_Log(RTMP_LOGERROR, "%s, Authentication failed: no such user", __FUNCTION__); + r->Link.pFlags |= RTMP_PUB_CLEAN; + return 0; + } + else + { + RTMP_Log(RTMP_LOGERROR, "%s, Authentication failed: unknown auth mode: %s", + __FUNCTION__, description->av_val); + r->Link.pFlags |= RTMP_PUB_CLEAN; + return 0; + } + + ptr = malloc(r->Link.app.av_len + pubToken.av_len); + strncpy(ptr, r->Link.app.av_val, r->Link.app.av_len); + strncpy(ptr + r->Link.app.av_len, pubToken.av_val, pubToken.av_len); + r->Link.app.av_len += pubToken.av_len; + if(r->Link.pFlags & RTMP_PUB_ALLOC) + free(r->Link.app.av_val); + r->Link.app.av_val = ptr; + + ptr = malloc(r->Link.tcUrl.av_len + pubToken.av_len); + strncpy(ptr, r->Link.tcUrl.av_val, r->Link.tcUrl.av_len); + strncpy(ptr + r->Link.tcUrl.av_len, pubToken.av_val, pubToken.av_len); + r->Link.tcUrl.av_len += pubToken.av_len; + if(r->Link.pFlags & RTMP_PUB_ALLOC) + free(r->Link.tcUrl.av_val); + r->Link.tcUrl.av_val = ptr; + + free(pubToken.av_val); + r->Link.pFlags |= RTMP_PUB_ALLOC; + RTMP_Log(RTMP_LOGDEBUG, "%s, new app: %.*s tcUrl: %.*s playpath: %s", __FUNCTION__, r->Link.app.av_len, r->Link.app.av_val, r->Link.tcUrl.av_len, r->Link.tcUrl.av_val,