#define MODULE_LOG_PREFIX "webif" #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" #if defined(__linux__) #include #elif defined(__APPLE__) #include #endif 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 struct s_nonce *nonce_first[AUTHNONCEHASHBUCKETS]; static 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 + cs_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(cs_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]; uint8_t md5tmp[MD5_DIGEST_LENGTH]; snprintf(noncetmp, sizeof(noncetmp), "%d:%s:%d", (int32_t)time(NULL), cs_inet_ntoa(addr), (int16_t)rand()); char_to_hex(MD5((uint8_t *)noncetmp, cs_strlen(noncetmp), md5tmp), MD5_DIGEST_LENGTH, (uint8_t *)opaque); } void init_noncelocks(void) { int32_t i; for(i = 0; i < AUTHNONCEHASHBUCKETS; ++i) { cs_lock_create(__func__, &nonce_lock[i], "nonce_lock", 5000); 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(NULL); cs_writelock(__func__, &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) { NULLFREE(foundnonce); foundnonce = NULL; } if(!foundnonce && foundopaque) { memcpy(result, foundopaque->nonce, (MD5_DIGEST_LENGTH * 2) + 1); } if(!foundnonce && !foundopaque) { char noncetmp[128], randstr[16]; uint8_t 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((uint8_t *)noncetmp, cs_strlen(noncetmp), md5tmp), MD5_DIGEST_LENGTH, (uint8_t *)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(__func__, &nonce_lock[bucket]); while(foundexpired) { prev = foundexpired; foundexpired = foundexpired->next; NULLFREE(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, cs_strlen(path)) == 0) { uriok = 1; } else { pch2 = uri; for(pch = uri; pch[0] != '\0'; ++pch) { if(pch[0] == '/') { pch2 = pch; } if(strncmp(pch2, path, cs_strlen(path)) == 0) { uriok = 1; } } } if(uriok == 1 && streq(username, cfg.http_user)) { char A1tmp[3 + cs_strlen(username) + cs_strlen(AUTHREALM) + cs_strlen(expectedPassword)]; char A1[(MD5_DIGEST_LENGTH * 2) + 1], A2[(MD5_DIGEST_LENGTH * 2) + 1], A3[(MD5_DIGEST_LENGTH * 2) + 1]; uint8_t md5tmp[MD5_DIGEST_LENGTH]; snprintf(A1tmp, sizeof(A1tmp), "%s:%s:%s", username, AUTHREALM, expectedPassword); char_to_hex(MD5((uint8_t *)A1tmp, cs_strlen(A1tmp), md5tmp), MD5_DIGEST_LENGTH, (uint8_t *)A1); char A2tmp[2 + cs_strlen(method) + cs_strlen(uri)]; snprintf(A2tmp, sizeof(A2tmp), "%s:%s", method, uri); char_to_hex(MD5((uint8_t *)A2tmp, cs_strlen(A2tmp), md5tmp), MD5_DIGEST_LENGTH, (uint8_t *)A2); char A3tmp[10 + cs_strlen(A1) + cs_strlen(A2) + cs_strlen(authnonce) + cs_strlen(authnc) + cs_strlen(authcnonce)]; snprintf(A3tmp, sizeof(A3tmp), "%s:%s:%s:%s:auth:%s", A1, authnonce, authnc, authcnonce, A2); char_to_hex(MD5((uint8_t *)A3tmp, cs_strlen(A3tmp), md5tmp), MD5_DIGEST_LENGTH, (uint8_t *)A3); if(strcmp(A3, authresponse) == 0) { if(cs_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_log_dbg(D_TRACE, "WebIf: Received stale header from %s (nonce=%s, expectednonce=%s, opaque=%s).", cs_inet_ntoa(addr), authnonce, expectednonce, opaque); } } } if(!authok) { cs_log("unauthorized access from %s - invalid credentials", cs_inet_ntoa(addr)); } 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, cs_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) + cs_strlen(title) + (extra == NULL ? 0 : cs_strlen(extra) + 2) + (mime == NULL ? 0 : cs_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, (uint8_t *)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"); } snprintf(pos, sizeof(buf) - (pos - buf), "\r\n"); if(forcePlain == 1) { fwrite(buf, 1, cs_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 * cs_strlen(title)) + cs_strlen(text) + 128]; char *pos = buf; pos += snprintf(pos, sizeof(buf) - (pos - buf), "%d %s\r\n", status, title); pos += snprintf(pos, sizeof(buf) - (pos - buf), "

%d %s

\r\n", status, title); pos += snprintf(pos, sizeof(buf) - (pos - buf), "%s\r\n", text); snprintf(pos, sizeof(buf) - (pos - buf), "\r\n"); send_headers(f, status, title, extra, "text/html", 0, cs_strlen(buf), NULL, forcePlain); if(forcePlain == 1) { fwrite(buf, 1, cs_strlen(buf), f); } else { webif_write(buf, f); } } void send_error500(FILE *f) { send_error(f, 500, "Internal Server Error", NULL, "The server encountered an internal error that prevented it from fulfilling this request.", 0); } void send_header304(FILE *f, char *extraheader) { send_headers(f, 304, "Not Modified", extraheader, NULL, 1, 0, NULL, 0); } /* * function for sending files. */ void send_file(FILE *f, char *filename, char *subdir, time_t modifiedheader, uint32_t etagheader, char *extraheader) { int8_t filen = 0; int32_t size = 0; char *mimetype = ""; char *result = " "; char *allocated = NULL; time_t moddate; char path[255]; char *CSS = NULL; char *JSCRIPT = NULL; char *JQUERY = NULL; if(!strcmp(filename, "CSS")) { filename = cfg.http_css ? cfg.http_css : ""; if(subdir && cs_strlen(subdir) > 0) { filename = tpl_getFilePathInSubdir(cfg.http_tpl ? cfg.http_tpl : "", subdir, "site", ".css", path, 255); } mimetype = "text/css"; filen = 1; } else if(!strcmp(filename, "JS")) { filename = cfg.http_jscript ? cfg.http_jscript : ""; if(subdir && cs_strlen(subdir) > 0) { filename = tpl_getFilePathInSubdir(cfg.http_tpl ? cfg.http_tpl : "", subdir, "oscam", ".js", path, 255); } mimetype = "text/javascript"; filen = 2; } else if(!strcmp(filename, "JQ")) { if(subdir && cs_strlen(subdir) > 0) { filename = tpl_getFilePathInSubdir(cfg.http_tpl ? cfg.http_tpl : "", subdir, "jquery", ".js", path, 255); } mimetype = "text/javascript"; filen = 3; } if(cs_strlen(filename) > 0 && file_exists(filename)) { struct stat st; FILE *fp = NULL; int32_t readen = 0; uint32_t CSS_sz = 0; char separator[255]; stat(filename, &st); moddate = st.st_mtime; memset(separator, 0, sizeof(separator)); if(filen == 1 && cfg.http_prepend_embedded_css) // Prepend Embedded CSS { CSS = tpl_getUnparsedTpl("CSS", 1, ""); snprintf(separator, sizeof(separator), "\n/* Begin embedded CSS File: %s */\n", cfg.http_css); } // We need at least size 1 or keepalive gets problems on some browsers... if(st.st_size > 0) { if((fp = fopen(filename, "r")) == NULL) return; if (CSS) CSS_sz += cs_strlen(CSS); if(!cs_malloc(&allocated, st.st_size + CSS_sz + cs_strlen(separator) + 1)) { send_error500(f); fclose(fp); return; } if((readen = fread(allocated + CSS_sz + cs_strlen(separator), 1, st.st_size, fp)) == st.st_size) { allocated[readen + CSS_sz + cs_strlen(separator)] = '\0'; } fclose(fp); } if(filen == 1 && cfg.http_prepend_embedded_css) // Prepend Embedded CSS { if (CSS && allocated) { memcpy(allocated, CSS, CSS_sz); memcpy(allocated + CSS_sz, separator, cs_strlen(separator)); allocated[readen + CSS_sz + cs_strlen(separator)] = '\0'; } } if(allocated) { result = allocated; } } else { CSS = tpl_getUnparsedTpl("CSS", 1, ""); JSCRIPT = tpl_getUnparsedTpl("JSCRIPT", 1, ""); JQUERY = tpl_getUnparsedTpl("JQUERY", 1, ""); if(filen == 1 && cs_strlen(CSS) > 0){ result = CSS;} else if(filen == 2 && cs_strlen(JSCRIPT) > 0){result = JSCRIPT;} else if(filen == 3 && cs_strlen(JQUERY) > 0){result = JQUERY;} moddate = first_client->login; } size = cs_strlen(result); if((etagheader == 0 && moddate < modifiedheader) || (etagheader > 0 && (uint32_t)crc32(0L, (uint8_t *)result, size) == etagheader)) { send_header304(f, extraheader); } else { send_headers(f, 200, "OK", NULL, mimetype, 1, size, result, 0); webif_write(result, f); } if(allocated) { NULLFREE(allocated); } NULLFREE(CSS); NULLFREE(JSCRIPT); NULLFREE(JQUERY); } /* Parse url parameters and save them to params array. The pch pointer is increased to the position where parsing stopped. */ void parseParams(struct uriparams *params, char *pch) { char *pch2; // parsemode = 1 means parsing next param, parsemode = -1 parsing next //value; pch2 points to the beginning of the currently parsed string, pch is the current position int32_t parsemode = 1; pch2 = pch; while(pch[0] != '\0') { if((parsemode == 1 && pch[0] == '=') || (parsemode == -1 && pch[0] == '&')) { pch[0] = '\0'; urldecode(pch2); if(parsemode == 1) { if(params->paramcount >= MAXGETPARAMS) { break; } ++params->paramcount; params->params[params->paramcount - 1] = pch2; } else { params->values[params->paramcount - 1] = pch2; } parsemode = -parsemode; pch2 = pch + 1; } ++pch; } /* last value wasn't processed in the loop yet... */ if(parsemode == -1 && params->paramcount <= MAXGETPARAMS) { urldecode(pch2); params->values[params->paramcount - 1] = pch2; } } /* Returns the value of the parameter called name or an empty string if it doesn't exist. */ char *getParam(struct uriparams *params, char *name) { int32_t i; for(i = (*params).paramcount - 1; i >= 0; --i) { if(strcmp((*params).params[i], name) == 0) { return (*params).values[i]; } } return ""; } /* * returns uptime in sec on success, -1 on error */ int32_t oscam_get_uptime(void) { #if defined(__linux__) struct sysinfo uptime; if(!sysinfo(&uptime)){ return (int32_t)uptime.uptime; } else{ return -1; } #elif defined(__APPLE__) struct timeval boottime; size_t len = sizeof(boottime); int mib[2] = { CTL_KERN, KERN_BOOTTIME }; if(sysctl(mib, 2, &boottime, &len, NULL, 0) < 0 ){ return -1; } time_t bsec = boottime.tv_sec, csec = time(NULL); return difftime(csec, bsec); #else return -1; #endif } #if defined(__linux__) /* * read /proc data into the passed struct pstat * returns 0 on success, -1 on error */ int8_t get_stats_linux(const pid_t pid, struct pstat* result) { // convert pid to string char pid_s[20]; snprintf(pid_s, sizeof(pid_s), "%d", pid); char stat_filepath[30] = "/proc/"; if (!cs_strncat(stat_filepath, pid_s, sizeof(stat_filepath))) { return -1; } if (!cs_strncat(stat_filepath, "/stat", sizeof(stat_filepath))) { return -1; } FILE *f_pstat = fopen(stat_filepath, "r"); if (f_pstat == NULL) { cs_log("FOPEN ERROR %s",stat_filepath); return -1; } FILE *f_stat = fopen("/proc/stat", "r"); if (!f_stat) { fclose(f_pstat); cs_log("ERROR: Can't open /proc/stat for reading: %s", strerror(errno)); return -1; } // read values from /proc/pid/stat uint64_t rss; if (fscanf(f_pstat, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %" SCNd64 "%" SCNd64 "%" SCNd64 "%" SCNd64 "%*d %*d %*d %*d %*u %" SCNu64 "%" SCNu64, &result->utime_ticks,&result->stime_ticks, &result->cutime_ticks,&result->cstime_ticks,&result->vsize, &rss) == EOF) { fclose(f_pstat); fclose(f_stat); return -1; } fclose(f_pstat); result->rss = rss * getpagesize(); // read+calc cpu total time from /proc/stat int64_t cpu_time[10] = {0,0,0,0,0,0,0,0,0,0}; if (fscanf(f_stat, "%*s %" SCNd64 "%" SCNd64 "%" SCNd64 "%" SCNd64 "%" SCNd64 "%" SCNd64 "%" SCNd64 "%" SCNd64 "%" SCNd64 "%" SCNd64, &cpu_time[0], &cpu_time[1], &cpu_time[2], &cpu_time[3], &cpu_time[4], &cpu_time[5], &cpu_time[6], &cpu_time[7], &cpu_time[8], &cpu_time[9]) == EOF) { fclose(f_stat); return -1; } fclose(f_stat); int8_t i; result->cpu_total_time = 0; // FIXME: On 32 Bit platforms, the single cpu times can overflow quite easily (clock_t from /proc/stat normally refers to a int32 here) resulting in useless calculation results! for(i = 0; i < 10; i++) { result->cpu_total_time += cpu_time[i]; } // read cached from /proc/meminfo uint64_t meminfo_cached = 0; FILE *fd = fopen("/proc/meminfo", "r"); if (fd ) { char line[256]; while(fgets(line, sizeof(line), fd)) { if(sscanf(line, "Cached: %" PRId64" \n kB", &meminfo_cached) == 1){ break; } } fclose(fd); } // read processes from /proc uint info_procs = 0; struct dirent **entries; int n = scandir("/proc", &entries, NULL, NULL); n = MAX(n, 0); while(n--) { if (entries[n]->d_name[0] > '0' && entries[n]->d_name[0] <= '9') { info_procs++; } free(entries[n]); } free(entries); // read cpu/meminfo from sysinfo() struct sysinfo info; float shiftfloat = (float)(1 << SI_LOAD_SHIFT); if (!sysinfo(&info)) { // processes result->info_procs = info_procs; // cpu load result->cpu_avg[0] = (float) info.loads[0] / shiftfloat; result->cpu_avg[1] = (float) info.loads[1] / shiftfloat; result->cpu_avg[2] = (float) info.loads[2] / shiftfloat; // meminfo result->mem_total = info.totalram * info.mem_unit; result->mem_free = info.freeram * info.mem_unit; result->mem_used = result->mem_total - result->mem_free; result->mem_buff = info.bufferram * info.mem_unit; result->mem_cached = meminfo_cached * 1024; result->mem_freem = result->mem_free + result->mem_buff + result->mem_cached; result->mem_share = info.sharedram * info.mem_unit; result->mem_total_swap = info.totalswap * info.mem_unit; result->mem_free_swap = info.freeswap * info.mem_unit; result->mem_used_swap = result->mem_total_swap - result->mem_free_swap; } // set timestamp for function call cs_ftime(&result->time_started); return 0; } /* * calculates the elapsed CPU usage between 2 measuring points. in percent and stores to cur_usage */ void calc_cpu_usage_pct(struct pstat* cur_usage, struct pstat* last_usage) { const double total_time_diff = cur_usage->cpu_total_time - last_usage->cpu_total_time; //time difference between cur_usage/last_usage when created / in sec cur_usage->gone_refresh = comp_timeb(&cur_usage->time_started, &last_usage->time_started)/1000; if(cur_usage->gone_refresh < 1){ //set to N/A since result may provide wrong results (/proc not updated) cur_usage->check_available |= (1 << 9); cur_usage->check_available |= (1 << 10); cur_usage->check_available |= (1 << 11); } else{ int64_t cur_ticks = cur_usage->utime_ticks + cur_usage->cutime_ticks; int64_t last_ticks = last_usage->utime_ticks + last_usage->cutime_ticks; //reset flags if set bevore cur_usage->check_available &= ~(1 << 9); cur_usage->check_available &= ~(1 << 10); cur_usage->check_available &= ~(1 << 11); cur_usage->cpu_usage_user = 100.0 * llabs(cur_ticks - last_ticks) / total_time_diff; cur_ticks = cur_usage->stime_ticks + cur_usage->cstime_ticks; last_ticks = last_usage->stime_ticks + last_usage->cstime_ticks; cur_usage->cpu_usage_sys = 100.0 * llabs(cur_ticks - last_ticks) / total_time_diff; } } #endif #ifdef WITH_SSL SSL *cur_ssl(void) { return (SSL *) pthread_getspecific(getssl); } /* Locking functions for SSL multithreading */ struct CRYPTO_dynlock_value { pthread_mutex_t mutex; }; /* function really needs unsigned long to prevent compiler warnings... */ unsigned long SSL_id_function(void) { return ((unsigned long) pthread_self()); } void SSL_locking_function(int32_t mode, int32_t type, const char *file, int32_t line) { if(mode & CRYPTO_LOCK) { cs_writelock(__func__, &lock_cs[type]); } else { cs_writeunlock(__func__, &lock_cs[type]); } // just to remove compiler warnings... if(file || line) { return; } } struct CRYPTO_dynlock_value *SSL_dyn_create_function(const char *file, int32_t line) { struct CRYPTO_dynlock_value *l; if(!cs_malloc(&l, sizeof(struct CRYPTO_dynlock_value))) { return NULL; } if(pthread_mutex_init(&l->mutex, NULL)) { // Initialization of mutex failed. NULLFREE(l); return (NULL); } // just to remove compiler warnings... if(file || line) { return l; } return l; } void SSL_dyn_lock_function(int32_t mode, struct CRYPTO_dynlock_value *l, const char *file, int32_t line) { if(mode & CRYPTO_LOCK) { SAFE_MUTEX_LOCK(&l->mutex); } else { SAFE_MUTEX_UNLOCK(&l->mutex); } // just to remove compiler warnings... if(file || line) { return; } } void SSL_dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int32_t line) { pthread_mutex_destroy(&l->mutex); NULLFREE(l); // just to remove compiler warnings... if(file || line) { return; } } #if defined(OPENSSL_NO_EC) #pragma message "WARNING: OpenSSL was built without support for elliptic curve cryptography (no-ec). Webserver certificate generation at runtime will not work!" static bool create_certificate(const char *path) { cs_log("generating webserver ssl certificate file %s (%s)", path, "SKIPPED"); return false; } #else /* add X.509 V3 extensions */ static bool add_ext(X509 *cert, int nid, char *value) { X509_EXTENSION *ex; X509V3_CTX ctx; X509V3_set_ctx_nodb(&ctx); X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0); ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value); if (!ex) return false; X509_add_ext(cert, ex, -1); X509_EXTENSION_free(ex); return true; } /* Create a self-signed certificate for basic https webif usage */ static bool create_certificate(const char *path) { X509 *pcert = NULL; X509_NAME * subject_name; X509_NAME * issuer_name; EVP_PKEY *pkey = NULL; #if OPENSSL_VERSION_NUMBER < 0x10100000L RSA * rsa_key = NULL; #elif OPENSSL_VERSION_NUMBER < 0x30000000L EC_KEY *ec_key = NULL; #endif ASN1_INTEGER *asn1_serial_number; BIGNUM *serial_number = NULL; char san[256]; struct utsname buffer; bool ret = false; const char *cn = !uname(&buffer) ? buffer.nodename : "localhost"; size_t cn_len = MIN(strlen(cn), 63); cs_log("generating webserver ssl certificate file %s (%s)", path, #if OPENSSL_VERSION_NUMBER < 0x10100000L "RSA" #else "ECDSA" #endif ); if ((pkey = EVP_PKEY_new())) { #if OPENSSL_VERSION_NUMBER < 0x10100000L if (!(rsa_key = RSA_generate_key(4096, RSA_F4, NULL, NULL))) { goto err; } if (!EVP_PKEY_assign_RSA(pkey, rsa_key)) { goto err; } rsa_key = NULL; #elif OPENSSL_VERSION_NUMBER < 0x30000000L if (!(ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1))) //prime256v1 { goto err; } if (!EC_KEY_generate_key(ec_key)) { goto err; } if (!EVP_PKEY_assign_EC_KEY(pkey, ec_key)) { goto err; } ec_key = NULL; #else pkey = EVP_EC_gen(SN_X9_62_prime256v1); //prime256v1 #endif if ((pcert = X509_new())) { X509_set_pubkey(pcert, pkey); // serialNumber if (!X509_set_version(pcert, 2L)) { goto err; } // serialNumber if ((serial_number = BN_new())) { #if OPENSSL_VERSION_NUMBER < 0x10100000L if (!BN_pseudo_rand(serial_number, 64, 0, 0)) { goto err; } #else if (!BN_rand(serial_number, 64, 0, 0)) { goto err; } #endif asn1_serial_number = X509_get_serialNumber(pcert); if (!asn1_serial_number) { goto err; } if (!BN_to_ASN1_INTEGER(serial_number, asn1_serial_number)) { goto err; } // subject + issuer if ((subject_name = X509_NAME_new()) && (issuer_name = X509_NAME_new())) { if (!X509_NAME_add_entry_by_NID(subject_name, NID_commonName, MBSTRING_UTF8, (unsigned char *) cn, cn_len, -1, 0)) { goto err; } if (!X509_set_subject_name(pcert, subject_name)) { goto err; } if (!X509_NAME_add_entry_by_NID(issuer_name, NID_commonName, MBSTRING_UTF8, (unsigned char *) cn, cn_len, -1, 0)) { goto err; } if (!X509_set_issuer_name(pcert, issuer_name)) { goto err; } // expiration X509_gmtime_adj(X509_getm_notBefore(pcert), 0); X509_gmtime_adj(X509_getm_notAfter(pcert), CERT_EXPIRY_TIME); // X.509 V3 extensions add_ext(pcert, NID_basic_constraints, "CA:FALSE" ); add_ext(pcert, NID_key_usage, "nonRepudiation, digitalSignature, keyEncipherment" ); add_ext(pcert, NID_ext_key_usage, "clientAuth, serverAuth" ); snprintf(san, sizeof(san), "DNS:%s, DNS:%s.local, IP:127.0.0.1, IP:::1", cn, cn); add_ext(pcert, NID_subject_alt_name, san); // sign certificate with private key X509_sign(pcert, pkey, EVP_sha256()); // write private key and certificate to file FILE * pemfile; if ((pemfile = fopen(path, "w"))) { PEM_write_PrivateKey(pemfile, pkey, NULL, NULL, 0, NULL, NULL); PEM_write_X509(pemfile, pcert); fclose(pemfile); ret = true; } else { cs_log("can't write to file %s", path); goto err; } } else { cs_log("Error: X509_NAME_new() failed"); } } else { cs_log("Error: BN_new() failed"); } } else { cs_log("Error: X509_new() failed"); } } else { cs_log("Error: EVP_PKEY_new() failed"); } err: ERR_print_errors_fp(stderr); if (pkey) { EVP_PKEY_free(pkey); } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && OPENSSL_VERSION_NUMBER < 0x30000000L if (ec_key) { EC_KEY_free(ec_key); } #endif if (pcert) { X509_free(pcert); } if (serial_number) { BN_free(serial_number); } return ret; } #endif /* Init necessary structures for SSL in WebIf*/ SSL_CTX *SSL_Webif_Init(void) { SSL_CTX *ctx; static const char *cs_cert = "oscam.pem"; // set locking callbacks for SSL int32_t i, num = CRYPTO_num_locks(); lock_cs = (CS_MUTEX_LOCK *) OPENSSL_malloc(num * sizeof(CS_MUTEX_LOCK)); for(i = 0; i < num; ++i) { cs_lock_create(__func__, &lock_cs[i], "ssl_lock_cs", 10000); } /* static lock callbacks */ CRYPTO_set_id_callback(SSL_id_function); CRYPTO_set_locking_callback(SSL_locking_function); /* dynamic lock callbacks */ CRYPTO_set_dynlock_create_callback(SSL_dyn_create_function); CRYPTO_set_dynlock_lock_callback(SSL_dyn_lock_function); CRYPTO_set_dynlock_destroy_callback(SSL_dyn_destroy_function); ctx = SSL_CTX_new(SSLv23_server_method()); #if defined(SSL_CTX_set_ecdh_auto) (void) SSL_CTX_set_ecdh_auto(ctx, 1); #elif defined(EC_PKEY_NO_PARAMETERS) && defined(NID_X9_62_prime256v1) EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); if(ecdh) { SSL_CTX_set_tmp_ecdh(ctx, ecdh); EC_KEY_free(ecdh); } #endif if(cfg.https_force_secure_mode) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L || defined SSL_CTX_clear_options // makro removed in OpenSSL 1.1.0+ SSL_CTX_clear_options(ctx, SSL_OP_ALL); //we CLEAR all bug workarounds! This is for security reason #else cs_log("WARNING: You enabled to force secure HTTPS but your system does not support to clear the ssl workarounds! SSL security will be reduced!"); #endif } #if OPENSSL_VERSION_NUMBER >= 0x10100000L SSL_CTX_set_min_proto_version(ctx, SSL3_VERSION); #elif defined SSL_OP_NO_TLSv1_1 SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1); #else SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); #endif char path[128]; if(!cfg.http_cert) { get_config_filename(path, sizeof(path), cs_cert); } else { cs_strncpy(path, cfg.http_cert, sizeof(path)); } if(!file_exists(path) && cfg.https_auto_create_cert) //generate a ready-to-use SSL certificate if no certificate file is available { if(!create_certificate(path)) { goto out_err; } } if(!ctx) goto out_err; if(SSL_CTX_use_certificate_chain_file(ctx, path) <=0) goto out_err; if(SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM) <= 0) goto out_err; if(!SSL_CTX_check_private_key(ctx)) { cs_log("SSL: Private key does not match the certificate public key"); goto out_err; } cs_log("loading ssl certificate file %s", path); return ctx; out_err: ERR_print_errors_fp(stderr); #if OPENSSL_VERSION_NUMBER < 0x1010005fL // fix build "OpenSSL 1.1.0e 16 Feb 2017" ERR_remove_state(0); #endif SSL_CTX_free(ctx); return NULL; } #endif #endif