#include "globals.h" #ifdef WEBIF #include "cscrypt/md5.h" #include "module-webif-lib.h" #include "module-webif-tpl.h" #include "oscam-config.h" #include "oscam-files.h" #include "oscam-lock.h" #include "oscam-string.h" #include "oscam-time.h" #include "oscam-net.h" extern int32_t ssl_active; extern pthread_key_t getkeepalive; extern pthread_key_t getssl; extern CS_MUTEX_LOCK *lock_cs; extern char noncekey[33]; static int8_t b64decoder[256]; struct s_nonce *nonce_first[AUTHNONCEHASHBUCKETS]; CS_MUTEX_LOCK nonce_lock[AUTHNONCEHASHBUCKETS]; /* Parses a value in an authentication string by removing all quotes/whitespace. Note that the original array is modified. */ static char *parse_auth_value(char *value){ char *pch = value; char *pch2; value = strstr(value, "="); if(value != NULL){ do{ ++value; } while (value[0] == ' ' || value[0] == '"'); pch = value; for(pch2 = value + strlen(value) - 1; pch2 >= value && (pch2[0] == ' ' || pch2[0] == '"' || pch2[0] == '\r' || pch2[0] == '\n'); --pch2) pch2[0] = '\0'; } return pch; } /* Parses the date out of a "If-Modified-Since"-header. Note that the original string is modified. */ time_t parse_modifiedsince(char * value){ int32_t day = -1, month = -1, year = -1, hour = -1, minutes = -1, seconds = -1; char months[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; char *str, *saveptr1 = NULL; time_t modifiedheader = 0; value += 18; // Parse over weekday at beginning... while(value[0] == ' ' && value[0] != '\0') ++value; while(value[0] != ' ' && value[0] != '\0') ++value; // According to http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1 three different timeformats are allowed so we need a bit logic to parse all of them... if(value[0] != '\0'){ ++value; for(month = 0; month < 12; ++month){ if(strstr(value, months[month])) break; } if(month > 11) month = -1; for (str=strtok_r(value, " ", &saveptr1); str; str=strtok_r(NULL, " ", &saveptr1)){ switch(strlen(str)){ case 1: case 2: day = atoi(str); break; case 4: if(str[0] != 'G') year = atoi(str); break; case 8: if(str[2] == ':' && str[5] == ':'){ hour = atoi(str); minutes = atoi(str + 3); seconds = atoi(str + 6); } break; case 9: if(str[2] == '-' && str[6] == '-'){ day = atoi(str); year = atoi(str + 7) + 2000; } break; } } if(day > 0 && day < 32 && month > 0 && year > 0 && year < 9999 && hour > -1 && hour < 24 && minutes > -1 && minutes < 60 && seconds > -1 && seconds < 60){ struct tm timeinfo; memset(&timeinfo, 0, sizeof(timeinfo)); timeinfo.tm_mday = day; timeinfo.tm_mon = month; timeinfo.tm_year = year - 1900; timeinfo.tm_hour = hour; timeinfo.tm_min = minutes; timeinfo.tm_sec = seconds; modifiedheader = cs_timegm(&timeinfo); } } return modifiedheader; } /* Calculates a new opaque value. Please note that opaque needs to be at least (MD5_DIGEST_LENGTH * 2) + 1 large. */ void calculate_opaque(IN_ADDR_T addr, char *opaque){ char noncetmp[128]; unsigned char md5tmp[MD5_DIGEST_LENGTH]; snprintf(noncetmp, sizeof(noncetmp), "%d:%s:%d", (int32_t)time((time_t)0), cs_inet_ntoa(addr), (int16_t)rand()); char_to_hex(MD5((unsigned char*)noncetmp, strlen(noncetmp), md5tmp), MD5_DIGEST_LENGTH, (unsigned char*)opaque); } void init_noncelocks(void){ int32_t i; for(i = 0; i < AUTHNONCEHASHBUCKETS; ++i){ cs_lock_create(&nonce_lock[i], 5, "nonce_lock"); nonce_first[i] = NULL; } } /* Calculates the currently valid nonce value and copies it to result. Please note that nonce (may be NULL), opaque and result needs to be at least (MD5_DIGEST_LENGTH * 2) + 1 large. */ void calculate_nonce(char *nonce, char *result, char *opaque){ struct s_nonce *noncelist, *prev, *foundnonce = NULL, *foundopaque = NULL, *foundexpired = NULL; int32_t bucket = opaque[0] % AUTHNONCEHASHBUCKETS; time_t now = time((time_t)0); cs_writelock(&nonce_lock[bucket]); for(noncelist = nonce_first[bucket], prev = NULL; noncelist; prev = noncelist, noncelist = noncelist->next){ if(now > noncelist->expirationdate){ if(prev) prev->next = NULL; else { nonce_first[bucket] = NULL; } foundexpired = noncelist; break; } if(nonce && !memcmp(noncelist->nonce, nonce, (MD5_DIGEST_LENGTH * 2) + 1)) { memcpy(result, noncelist->nonce, (MD5_DIGEST_LENGTH * 2) + 1); foundnonce = noncelist; if(!noncelist->firstuse) noncelist->firstuse = now; else if(now - foundnonce->firstuse > AUTHNONCEVALIDSECS){ if(prev) prev->next = noncelist->next; else { nonce_first[bucket] = noncelist->next; } } break; } else if (!noncelist->firstuse && !memcmp(noncelist->opaque, opaque, (MD5_DIGEST_LENGTH * 2) + 1)){ foundopaque = noncelist; } } if(foundnonce && now - foundnonce->firstuse > AUTHNONCEVALIDSECS){ free(foundnonce); foundnonce = NULL; } if(!foundnonce && foundopaque) memcpy(result, foundopaque->nonce, (MD5_DIGEST_LENGTH * 2) + 1); if(!foundnonce && !foundopaque){ char noncetmp[128], randstr[16]; unsigned char md5tmp[MD5_DIGEST_LENGTH]; get_random_bytes((uint8_t*)randstr, sizeof(randstr)-1); randstr[sizeof(randstr)-1] = '\0'; snprintf(noncetmp, sizeof(noncetmp), "%d:%s:%s", (int32_t)now, randstr, noncekey); char_to_hex(MD5((unsigned char*)noncetmp, strlen(noncetmp), md5tmp), MD5_DIGEST_LENGTH, (unsigned char*)result); if(cs_malloc(&noncelist, sizeof(struct s_nonce))){ noncelist->expirationdate = now + AUTHNONCEEXPIRATION; memcpy(noncelist->nonce, result, (MD5_DIGEST_LENGTH * 2) + 1); memcpy(noncelist->opaque, opaque, (MD5_DIGEST_LENGTH * 2) + 1); noncelist->next = nonce_first[bucket]; nonce_first[bucket] = noncelist; } } cs_writeunlock(&nonce_lock[bucket]); while(foundexpired){ prev = foundexpired; foundexpired = foundexpired->next; free(prev); } } /* Checks if authentication is correct. Returns -1 if not correct, 1 if correct and 2 if nonce isn't valid anymore. Note that authstring will be modified. */ int32_t check_auth(char *authstring, char *method, char *path, IN_ADDR_T addr, char *expectednonce, char *opaque){ int32_t authok = 0, uriok = 0; char authnonce[(MD5_DIGEST_LENGTH * 2) + 1]; memset(authnonce, 0, sizeof(authnonce)); char *authnc = ""; char *authcnonce = ""; char *authresponse = ""; char *uri = ""; char *username = ""; char *expectedPassword = cfg.http_pwd; char *pch = authstring + 22; char *pch2; char *saveptr1=NULL; memset(opaque, 0, (MD5_DIGEST_LENGTH * 2) + 1); for(pch = strtok_r (pch, ",", &saveptr1); pch; pch = strtok_r (NULL, ",", &saveptr1)){ pch2 = pch; while(pch2[0] == ' ' && pch2[0] != '\0') ++pch2; if(strncmp(pch2, "nonce", 5) == 0){ cs_strncpy(authnonce, parse_auth_value(pch2), sizeof(authnonce)); } else if (strncmp(pch2, "nc", 2) == 0){ authnc=parse_auth_value(pch2); } else if (strncmp(pch2, "cnonce", 6) == 0){ authcnonce=parse_auth_value(pch2); } else if (strncmp(pch2, "response", 8) == 0){ authresponse=parse_auth_value(pch2); } else if (strncmp(pch2, "uri", 3) == 0){ uri=parse_auth_value(pch2); } else if (strncmp(pch2, "username", 8) == 0){ username=parse_auth_value(pch2); } else if (strncmp(pch2, "opaque", 6) == 0){ char *tmp=parse_auth_value(pch2); cs_strncpy(opaque, tmp, (MD5_DIGEST_LENGTH * 2) + 1); } } if(strncmp(uri, path, strlen(path)) == 0) uriok = 1; else { pch2 = uri; for(pch = uri; pch[0] != '\0'; ++pch) { if(pch[0] == '/') pch2 = pch; if(strncmp(pch2, path, strlen(path)) == 0) uriok = 1; } } if (uriok == 1 && streq(username, cfg.http_user)) { char A1tmp[3 + strlen(username) + strlen(AUTHREALM) + strlen(expectedPassword)]; char A1[(MD5_DIGEST_LENGTH * 2) + 1], A2[(MD5_DIGEST_LENGTH * 2) + 1], A3[(MD5_DIGEST_LENGTH * 2) + 1]; unsigned char md5tmp[MD5_DIGEST_LENGTH]; snprintf(A1tmp, sizeof(A1tmp), "%s:%s:%s", username, AUTHREALM, expectedPassword); char_to_hex(MD5((unsigned char*)A1tmp, strlen(A1tmp), md5tmp), MD5_DIGEST_LENGTH, (unsigned char*)A1); char A2tmp[2 + strlen(method) + strlen(uri)]; snprintf(A2tmp, sizeof(A2tmp), "%s:%s", method, uri); char_to_hex(MD5((unsigned char*)A2tmp, strlen(A2tmp), md5tmp), MD5_DIGEST_LENGTH, (unsigned char*)A2); char A3tmp[10 + strlen(A1) + strlen(A2) + strlen(authnonce) + strlen(authnc) + strlen(authcnonce)]; snprintf(A3tmp, sizeof(A3tmp), "%s:%s:%s:%s:auth:%s", A1, authnonce, authnc, authcnonce, A2); char_to_hex(MD5((unsigned char*)A3tmp, strlen(A3tmp), md5tmp), MD5_DIGEST_LENGTH, (unsigned char*)A3); if(strcmp(A3, authresponse) == 0) { if(strlen(opaque) != MD5_DIGEST_LENGTH*2) calculate_opaque(addr, opaque); calculate_nonce(authnonce, expectednonce, opaque); if(strcmp(expectednonce, authnonce) == 0) authok = 1; else { authok = 2; cs_debug_mask(D_TRACE, "WebIf: Received stale header from %s (nonce=%s, expectednonce=%s, opaque=%s).", cs_inet_ntoa(addr), authnonce, expectednonce, opaque); } } } return authok; } int32_t webif_write_raw(char *buf, FILE* f, int32_t len) { errno=0; #ifdef WITH_SSL if (ssl_active) { return SSL_write((SSL*)f, buf, len); } else #endif return fwrite(buf, 1, len, f); } int32_t webif_write(char *buf, FILE* f) { return webif_write_raw(buf, f, strlen(buf)); } int32_t webif_read(char *buf, int32_t num, FILE *f) { errno=0; #ifdef WITH_SSL if (ssl_active) { return SSL_read((SSL*)f, buf, num); } else #endif return read(fileno(f), buf, num); } void send_headers(FILE *f, int32_t status, char *title, char *extra, char *mime, int32_t cache, int32_t length, char *content, int8_t forcePlain){ time_t now; char timebuf[32]; char buf[sizeof(PROTOCOL) + sizeof(SERVER) + strlen(title) + (extra == NULL?0:strlen(extra)+2) + (mime == NULL?0:strlen(mime)+2) + 350]; char *pos = buf; struct tm timeinfo; pos += snprintf(pos, sizeof(buf)-(pos-buf), "%s %d %s\r\n", PROTOCOL, status, title); pos += snprintf(pos, sizeof(buf)-(pos-buf), "Server: %s\r\n", SERVER); now = time(NULL); cs_gmtime_r(&now, &timeinfo); strftime(timebuf, sizeof(timebuf), RFC1123FMT, &timeinfo); pos += snprintf(pos, sizeof(buf)-(pos-buf), "Date: %s\r\n", timebuf); if (extra) pos += snprintf(pos, sizeof(buf)-(pos-buf),"%s\r\n", extra); if (mime) pos += snprintf(pos, sizeof(buf)-(pos-buf),"Content-Type: %s\r\n", mime); if(status != 304){ if(!cache){ pos += snprintf(pos, sizeof(buf)-(pos-buf),"Cache-Control: no-store, no-cache, must-revalidate\r\n"); pos += snprintf(pos, sizeof(buf)-(pos-buf),"Expires: Sat, 10 Jan 2000 05:00:00 GMT\r\n"); } else { pos += snprintf(pos, sizeof(buf)-(pos-buf),"Cache-Control: public, max-age=7200\r\n"); } pos += snprintf(pos, sizeof(buf)-(pos-buf),"Content-Length: %d\r\n", length); pos += snprintf(pos, sizeof(buf)-(pos-buf),"Last-Modified: %s\r\n", timebuf); if(content){ uint32_t checksum = (uint32_t)crc32(0L, (uchar *)content, length); pos += snprintf(pos, sizeof(buf)-(pos-buf),"ETag: \"%u\"\r\n", checksum==0?1:checksum); } } if(*(int8_t *)pthread_getspecific(getkeepalive)) pos += snprintf(pos, sizeof(buf)-(pos-buf), "Connection: Keep-Alive\r\n"); else pos += snprintf(pos, sizeof(buf)-(pos-buf), "Connection: close\r\n"); pos += snprintf(pos, sizeof(buf)-(pos-buf),"\r\n"); if(forcePlain == 1) fwrite(buf, 1, strlen(buf), f); else webif_write(buf, f); } void send_error(FILE *f, int32_t status, char *title, char *extra, char *text, int8_t forcePlain){ char buf[(2* strlen(title)) + strlen(text) + 128]; char *pos = buf; pos += snprintf(pos, sizeof(buf)-(pos-buf), "