/** * ======================== legal notice ====================== * * File: Authorization.cc * Created: 3. Juli 2012, 17 * Author: Geronimo * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling * * CMP - compound media player * * is a client/server mediaplayer intended to play any media from any workstation * without the need to export or mount shares. cmps is an easy to use backend * with a (ready to use) HTML-interface. Additionally the backend supports * authentication via HTTP-digest authorization. * cmpc is a client with vdr-like osd-menues. * * Copyright (c) 2012 Reinhard Mantey, some rights reserved! * published under Creative Commons by-sa * For details see http://creativecommons.org/licenses/by-sa/3.0/ * * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp * * -------------------------------------------------------------- */ #include #include #include #include #include #include #include #include static bool useSessionHash = true; cAuthorization::cAuthorization(const cHTTPRequest &OriginalRequest, char *NOnceFromHeap) ///< use this constructor to create an authorization request for responses to ///< clients without valid authorization headers. : tmp(NULL) , principal(NULL) , method(OriginalRequest.Method()) , serverID(NULL) , sessAlgo(useSessionHash) , nonce(NOnceFromHeap) , uri(OriginalRequest.Url().ToString()) , response(NULL) , opaque(NULL) , cnonce(NULL) , qop(NULL) , counter(0) , authTime(time(NULL)) { tmp = new cPrincipal("unset", Credentials.ApplicationRealm()); opaque = cAuthorizations::CreateSessionID(OriginalRequest, authTime); } cAuthorization::cAuthorization(cHTTPRequest::HTTPRequestMethod Method, const char *Raw) ///< use this constructor to create an authorization from clients request message : tmp(NULL) , principal(NULL) , method(Method) , serverID(NULL) , sessAlgo(false) , nonce(NULL) , uri(NULL) , response(NULL) , opaque(NULL) , cnonce(NULL) , qop(NULL) , counter(0) { ParseRawBuffer(Raw); } cAuthorization::cAuthorization(const cPrincipal *Principal, cHTTPRequest::HTTPRequestMethod Method, const cAuthorization &other) : tmp(other.tmp ? new cPrincipal(*other.tmp) : NULL) , principal(Principal) , method(Method) , serverID(other.serverID ? strdup(other.serverID) : NULL) , sessAlgo(other.sessAlgo) , nonce(other.nonce ? strdup(other.nonce) : NULL) , uri(other.uri ? strdup(other.uri) : NULL) , response(other.response ? strdup(other.response) : NULL) , opaque(other.opaque ? strdup(other.opaque) : NULL) , cnonce(other.cnonce ? strdup(other.cnonce) : NULL) , qop(other.qop ? strdup(other.qop) : NULL) , counter(other.counter) { } cAuthorization::cAuthorization(const cAuthorization& other) : tmp(other.tmp ? new cPrincipal(*other.tmp) : NULL) , principal(other.principal) , method(other.method) , serverID(other.serverID ? strdup(other.serverID) : NULL) , sessAlgo(other.sessAlgo) , nonce(other.nonce ? strdup(other.nonce) : NULL) , uri(other.uri ? strdup(other.uri) : NULL) , response(other.response ? strdup(other.response) : NULL) , opaque(other.opaque ? strdup(other.opaque) : NULL) , cnonce(other.cnonce ? strdup(other.cnonce) : NULL) , qop(other.qop ? strdup(other.qop) : NULL) , counter(other.counter + 1) { } cAuthorization::~cAuthorization() { if (tmp) delete tmp; FREE(nonce); FREE(uri); FREE(serverID); FREE(response); FREE(opaque); FREE(cnonce); FREE(qop); } cAuthorization& cAuthorization::operator=(const cAuthorization& other) { if (&other == this) return *this; if (tmp) delete tmp; FREE(nonce); FREE(uri); FREE(serverID); FREE(response); FREE(opaque); FREE(cnonce); principal = other.principal; sessAlgo = other.sessAlgo; method = other.method; serverID = other.serverID ? strdup(other.serverID) : NULL; nonce = other.nonce ? strdup(other.nonce) : NULL; uri = other.uri ? strdup(other.uri) : NULL; response = other.response ? strdup(other.response) : NULL; opaque = other.opaque ? strdup(other.opaque) : NULL; cnonce = other.cnonce ? strdup(other.cnonce) : NULL; counter = other.counter; qop = other.qop ? strdup(other.qop) : NULL; tmp = other.tmp ? new cPrincipal(*other.tmp) : NULL; return *this; } void cAuthorization::CreateClientHash() { cMD5Calculator hc; char buf[40]; snprintf(buf, sizeof(buf), "%lu", random()); hc.AddContent(serverID); hc.AddContent(":"); hc.AddContent(buf); cnonce = hc.Hash(); } static const char *getAttrName(char *buf, int bufSize, const char *src) { const char *s = src; char *d = buf; while (*s && isspace(*s)) ++s; while (*s && ((d - buf) < bufSize) && *s != '=' && !isspace(*s)) *d++ = *s++; *d = 0; return *s ? s : NULL; } static const char *getValue(char *buf, int bufSize, const char *src) { const char *s = src; char *d = buf; if (*s == '"') { ++s; while (*s && ((d - buf) < bufSize) && *s != '"') *d++ = *s++; if (*s != '"') return NULL; ++s; } else { while (*s && ((d - buf) < bufSize) && *s != ',') *d++ = *s++; } *d = 0; return *s ? s : NULL; } size_t cAuthorization::Write(char* Buffer, size_t BufSize) { static const char *UnAuthorizedMask = "WWW-Authenticate: Digest realm=\"%s\", " "nonce=\"%s\", opaque=\"%s\", algorithm=\"MD5%s\", qop=\"auth\"\n"; static const char *AuthorizedMask = "Authorization: Digest username=\"%s\", " "realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", opaque=\"%s\", qop=\"auth\", nc=%08X, algorithm=\"MD5%s\", cnonce=\"%s\""; static const char *AuthenticationMaskDef = "Authentication-Info: qop=auth, nc=%08X, rspauth=\"%s\", cnonce=\"%s\""; static const char *AuthenticationMaskNew = "Authentication-Info: nextnounce=\"%s\", qop=auth, nc=%08X, rspauth=\"%s\", cnonce=\"%s\""; size_t n=0; if (principal && strcmp(principal->Name(), "unset")) { static bool timeout = 0; //TODO: authorization info, should be sent after certain timeout if (timeout) n = snprintf(Buffer, BufSize, AuthenticationMaskNew, nonce, counter, response, cnonce); else n = snprintf(Buffer, BufSize, AuthenticationMaskDef, counter, response, cnonce); } else if (!principal && tmp && tmp->Name() && tmp->Hash()) { n = snprintf(Buffer, BufSize, AuthorizedMask, tmp->Name(), tmp->Realm(), nonce, uri, response, opaque, counter, sessAlgo ? "-sess" : "", cnonce); } else { n = snprintf(Buffer, BufSize, UnAuthorizedMask, Credentials.ApplicationRealm(), nonce, opaque, sessAlgo ? "-sess" : ""); } return n; } // authorization is one line, so conforms to header value. Digest has been broken for readability // Digest username="ich oder", // realm="Schnarcher@my.box", // nonce="59F980BB771C1CC31EE7360C7B06F15D", // uri="/favicon.ico", // response="7d060b3de37d132e2f4e4b014b127818", // opaque="33B9703065AD7E1A1DED1DFE07AA357C", // qop=auth, // nc=00000001, // cnonce="4dd872a6c6debac8" void cAuthorization::ParseRawBuffer(const char* Raw) { static const char *eyecatch = "Digest"; if (strncmp(Raw, eyecatch, strlen(eyecatch))) { // esyslog("Authentication is not of type Digest! - not supported!"); return; } char nameBuf[30]; char scratch[128]; const char *p = Raw + strlen(eyecatch); bool done=false; if (tmp) { delete tmp; tmp = NULL; } while (!done) { p = getAttrName(nameBuf, sizeof(nameBuf), p); if (*p != '=') { esyslog("invalid digest format! Expected '=' and found %C (%02X)", *p, *p); return; } p = getValue(scratch, sizeof(scratch), ++p); SetAttribute(nameBuf, scratch); if (!p) break; if (*p != ',') break; else ++p; } } void cAuthorization::SetUser(const char *UserID, const char *Password) { if (principal) { //TODO: do we really want to support change of user/password on existing principal? } else if (tmp) { tmp->SetName(UserID); tmp->CreateHash(Password); CreateClientHash(); } } void cAuthorization::SetAttribute(char* Name, const char* Value) { char **p = NULL; // isyslog("cAuthorization::SetAttribute(\"%s\") => [%s]", Name, Value); if (!strcasecmp(Name, "username")) { if (!tmp) tmp = new cPrincipal(Value, "unset"); else tmp->SetName(Value); } else if (!strcasecmp(Name, "realm")) { if (!tmp) tmp = new cPrincipal("unset", Value); else tmp->SetRealm(Value); } else if (!strcasecmp(Name, "algorithm")) { if (!strcmp("MD5-sess", Value)) sessAlgo = true; } else if (!strcasecmp(Name, "nonce")) p = &nonce; else if (!strcasecmp(Name, "uri")) p = &uri; else if (!strcasecmp(Name, "response")) p = &response; else if (!strcasecmp(Name, "opaque")) p = &opaque; else if (!strcasecmp(Name, "cnonce")) p = &cnonce; else if (!strcasecmp(Name, "qop")) { if (!strncmp(Value, "auth", 4)) p = &qop; else esyslog("invalid/unsupported auth method! - only \"auth\" supported!"); } else if (!strcasecmp(Name, "nc")) { counter = strtol(Value, NULL, 16); } if (p) { free(*p); *p = strdup(Value); } if (tmp) { principal = Credentials.FindPrincipal(tmp->Name(), tmp->Realm()); if (principal) { delete tmp; tmp = NULL; } } } char * cAuthorization::CalculateA1(const char *Username, const char *Password) { if (Username && Password) SetUser(Username, Password); char *principalHash = (char *) (principal ? principal->Hash() : tmp->Hash()); if (sessAlgo) { cMD5Calculator hc; hc.AddContent(principalHash); hc.AddContent(":"); hc.AddContent(nonce); hc.AddContent(":"); hc.AddContent(cnonce); return hc.Hash(); } return strdup(principalHash); } char * cAuthorization::CalculateA2(const char *Uri) { cMD5Calculator hc; // don't send method on authorized response if (!principal || strcmp(principal->Name(), "unset")) hc.AddContent(requestMethod2String(method)); hc.AddContent(":"); hc.AddContent(Uri); return hc.Hash(); } const char * cAuthorization::CalculateResponse(const char *Uri, const char *Username, const char *Password) { free(response); char *a1 = CalculateA1(Username, Password); char *a2 = CalculateA2(Uri ? Uri : uri); cMD5Calculator hc; char buf[12]; snprintf(buf, sizeof(buf), "%08x", counter); hc.AddContent(a1); hc.AddContent(":"); hc.AddContent(nonce); if (qop) { hc.AddContent(":"); hc.AddContent(buf); hc.AddContent(":"); hc.AddContent(cnonce); hc.AddContent(":"); hc.AddContent(qop); } hc.AddContent(":"); hc.AddContent(a2); response = hc.Hash(); // printf("CalculateResponse: Realm-Digest (A1) =>%s<\n", a1); // printf("CalculateResponse: URL-Digest (A2) =>%s<\n", a2); // printf("CalculateResponse: Request-Digest =>%s<\n", response); free(a1); free(a2); return response; } void cAuthorization::Dump(void) const { printf(">>>---------- Authorization ------------------\n"); if (principal) principal->Dump(); else if (tmp) tmp->Dump(); else printf("principal is NULL\n"); printf("nonce ...: >%s<\n", nonce); printf("method ..: >%s<\n", requestMethod2String(method)); printf("uri .....: >%s<\n", uri); printf("response : >%s<\n", response); printf("opaque ..: >%s<\n", opaque); printf("cnonce ..: >%s<\n", cnonce); printf("counter .: >%d<\n", counter); printf("============= Authorization ===============<<<\n"); } // --- Authorizations --------------------------------------------------------- static void freeAuthorizationCallback(void *elem) { delete (cAuthorization *)elem; } cAuthorizations::cAuthorizations() : cManagedVector(freeAuthorizationCallback) { } cAuthorizations::~cAuthorizations() { } cAuthorization *cAuthorizations::FindAuthorization(const char *SessionID) { cAuthorization *a; for (size_t i=0; i < size(); ++i) { a = (cAuthorization *) (*this)[i]; if (!strcmp(a->Opaque(), SessionID)) return a; } return NULL; } const cAuthorization &cAuthorizations::CreateAuthorization(const cHTTPRequest &originalRequest, const cConnectionPoint &client, unsigned long connectionNumber) { cAuthorization *rv = new cAuthorization(originalRequest, CreateNOnce(client, connectionNumber)); push_back(rv); return *rv; } char *cAuthorizations::CreateNOnce(const cConnectionPoint &client, unsigned long connectionNumber) { char buf[40]; cMD5Calculator hc; snprintf(buf, sizeof(buf), "%lu:%lu", connectionNumber, random()); hc.AddContent(buf); hc.AddContent(":"); hc.AddContent(client.HostName()); return hc.Hash(); } char *cAuthorizations::CreateSessionID(const cHTTPRequest &OriginalRequest, time_t AuthTime) { char buf[40]; cMD5Calculator hc; const char *p0, *p1; snprintf(buf, sizeof(buf), "%lu", AuthTime); p0 = OriginalRequest.ClientHost(); p1 = OriginalRequest.UserAgent(); hc.AddContent(p0 ? p0 : "localhost"); hc.AddContent(":"); hc.AddContent(buf); hc.AddContent(":"); hc.AddContent(p1 ? p1 : "unknown"); return hc.Hash(); } void cAuthorizations::Del(cAuthorization *Auth2Invalidate) { //TODO: }