diff --git a/src/sbbs3/websrvr.c b/src/sbbs3/websrvr.c index 478f753b45b5fd35df6e004cfd433da431999c4e..82be0f3a71118edc64a274608a261035655388b5 100644 --- a/src/sbbs3/websrvr.c +++ b/src/sbbs3/websrvr.c @@ -85,8 +85,11 @@ static const char* server_name="Synchronet Web Server"; static const char* newline="\r\n"; static const char* http_scheme="http://"; static const size_t http_scheme_len=7; +static const char* https_scheme="https://"; +static const size_t https_scheme_len=8; static const char* error_301="301 Moved Permanently"; static const char* error_302="302 Moved Temporarily"; +static const char* error_307="307 Temporary Redirect"; static const char* error_404="404 Not Found"; static const char* error_416="416 Requested Range Not Satisfiable"; static const char* error_500="500 Internal Server Error"; @@ -206,6 +209,9 @@ typedef struct { char vhost[128]; /* The requested host. (virtual host) */ int send_location; BOOL send_content; + BOOL upgrading; + char* location_to_send; + char* vary_list; const char* mime_type; str_list_t headers; char status[MAX_REQUEST_LINE+1]; @@ -360,6 +366,10 @@ enum { ,HEAD_RANGE ,HEAD_IFRANGE ,HEAD_COOKIE + ,HEAD_STS + ,HEAD_UPGRADEINSECURE + ,HEAD_VARY + ,HEAD_CSP }; static struct { @@ -389,6 +399,10 @@ static struct { { HEAD_RANGE, "Range" }, { HEAD_IFRANGE, "If-Range" }, { HEAD_COOKIE, "Cookie" }, + { HEAD_STS, "Strict-Transport-Security" }, + { HEAD_UPGRADEINSECURE, "Upgrade-Insecure-Requests" }, + { HEAD_VARY, "Vary" }, + { HEAD_CSP, "Content-Security-Policy" }, { -1, NULL /* terminator */ }, }; @@ -396,6 +410,7 @@ static struct { enum { NO_LOCATION ,MOVED_PERM + ,MOVED_TEMPREDIR ,MOVED_TEMP ,MOVED_STAT }; @@ -1088,6 +1103,8 @@ static void close_request(http_session_t * session) FREE_AND_NULL(session->req.realm); FREE_AND_NULL(session->req.digest_realm); FREE_AND_NULL(session->req.fastcgi_socket); + FREE_AND_NULL(session->req.location_to_send); + FREE_AND_NULL(session->req.vary_list); FREE_AND_NULL(session->req.auth_list); FREE_AND_NULL(session->req.auth.digest_uri); @@ -1274,17 +1291,21 @@ static BOOL send_headers(http_session_t *session, const char *status, int chunke session->req.range_start=0; session->req.range_end=0; } - if(session->req.send_location==MOVED_PERM) { - status_line=error_301; - ret=-1; - session->req.send_content = FALSE; - send_entity = FALSE; - } - if(session->req.send_location==MOVED_TEMP) { - status_line=error_302; + if (session->req.send_location) { ret=-1; session->req.send_content = FALSE; send_entity = FALSE; + switch (session->req.send_location) { + case MOVED_PERM: + status_line=error_301; + break; + case MOVED_TEMPREDIR: + status_line=error_307; + break; + case MOVED_TEMP: + status_line=error_302; + break; + } } stat_code=atoi(status_line); @@ -1305,6 +1326,12 @@ static BOOL send_headers(http_session_t *session, const char *status, int chunke safecat(headers,header,MAX_HEADERS_SIZE); /* General Headers */ + if(session->is_tls && startup->options & WEB_OPT_HSTS_SAFE) { + safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_STS),"max-age=10886400; preload"); + safecat(headers,header,MAX_HEADERS_SIZE); + safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CSP),"block-all-mixed-content"); + safecat(headers,header,MAX_HEADERS_SIZE); + } ti=time(NULL); if(gmtime_r(&ti,&tm)==NULL) memset(&tm,0,sizeof(tm)); @@ -1325,6 +1352,10 @@ static BOOL send_headers(http_session_t *session, const char *status, int chunke /* Response Headers */ safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE); safecat(headers,header,MAX_HEADERS_SIZE); + if (session->req.vary_list) { + safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_VARY), session->req.vary_list); + safecat(headers,header,MAX_HEADERS_SIZE); + } /* Entity Headers */ if(session->req.dynamic) { @@ -1341,7 +1372,10 @@ static BOOL send_headers(http_session_t *session, const char *status, int chunke } if(session->req.send_location) { - safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path)); + if (session->req.location_to_send) + safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.location_to_send)); + else + safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path)); safecat(headers,header,MAX_HEADERS_SIZE); } @@ -2522,6 +2556,7 @@ static BOOL parse_headers(http_session_t * session) size_t idx; size_t content_len=0; char env_name[128]; + char portstr[7]; // ":65535" for(idx=0;session->req.headers[idx]!=NULL;idx++) { /* TODO: strdup() is possibly too slow here... */ @@ -2725,6 +2760,33 @@ static BOOL parse_headers(http_session_t * session) case HEAD_TYPE: add_env(session,"CONTENT_TYPE",value); break; + case HEAD_UPGRADEINSECURE: + if (startup->options & WEB_OPT_HSTS_SAFE) { + if (strcmp(value, "1") == 0) { + if (!session->is_tls) { + portstr[0] = 0; + if (startup->tls_port != 443) + sprintf(portstr, ":%hu", startup->tls_port); + p = realloc(session->req.vary_list, (session->req.vary_list ? strlen(session->req.vary_list) + 2 : 0) + strlen(get_header(HEAD_UPGRADEINSECURE)) + 1); + if (p == NULL) + send_error(session, __LINE__, error_500); + else { + if (session->req.vary_list) + strcat(p, ", "); + strcat(p, get_header(HEAD_UPGRADEINSECURE)); + session->req.vary_list = p; + + session->req.send_location = MOVED_TEMPREDIR; + session->req.upgrading = TRUE; + session->req.keep_alive = FALSE; + FREE_AND_NULL(session->req.location_to_send); + if (asprintf(&session->req.location_to_send, "https://%s%s%s", session->req.vhost, portstr, session->req.virtual_path) < 0) + send_error(session, __LINE__, error_500); + } + } + } + } + break; default: break; } @@ -2925,6 +2987,8 @@ static char *get_request(http_session_t * session, char *req_line) char* retval; char* last; int offset; + const char* scheme = NULL; + size_t scheme_len; SKIP_WHITESPACE(req_line); SAFECOPY(session->req.virtual_path,req_line); @@ -2944,9 +3008,17 @@ static char *get_request(http_session_t * session, char *req_line) SAFECOPY(session->req.physical_path,session->req.virtual_path); unescape(session->req.physical_path); - if(!strnicmp(session->req.physical_path,http_scheme,http_scheme_len)) { + if (strnicmp(session->req.physical_path,http_scheme,http_scheme_len) == 0) { + scheme = http_scheme; + scheme_len = http_scheme_len; + } + else if (strnicmp(session->req.physical_path,https_scheme,https_scheme_len) == 0) { + scheme = https_scheme; + scheme_len = https_scheme_len; + } + if(scheme != NULL) { /* Remove http:// from start of physical_path */ - memmove(session->req.physical_path, session->req.physical_path+http_scheme_len, strlen(session->req.physical_path+http_scheme_len)+1); + memmove(session->req.physical_path, session->req.physical_path+scheme_len, strlen(session->req.physical_path+scheme_len)+1); /* Set HOST value... ignore HOST header */ SAFECOPY(session->req.host,session->req.physical_path); @@ -3459,6 +3531,8 @@ static BOOL check_request(http_session_t * session) if(session->req.finished) return(FALSE); + if (session->req.upgrading) + return(TRUE); SAFECOPY(path,session->req.physical_path); if(startup->options&WEB_OPT_DEBUG_TX) diff --git a/src/sbbs3/websrvr.h b/src/sbbs3/websrvr.h index a7af55623ca1e386ee479120dd783ddebadaf389..5b83581c98b8bce7a323f82dfe55aa88ea5bb7e7 100644 --- a/src/sbbs3/websrvr.h +++ b/src/sbbs3/websrvr.h @@ -127,6 +127,7 @@ static struct init_field web_init_fields[] = { #define WEB_OPT_NO_CGI (1<<5) /* Disable CGI support */ #define WEB_OPT_HTTP_LOGGING (1<<6) /* Create/write-to HttpLogFile */ #define WEB_OPT_ALLOW_TLS (1<<7) /* Enable HTTPS */ +#define WEB_OPT_HSTS_SAFE (1<<8) /* All URLs can be served over HTTPS*/ /* web_startup_t.options bits that require re-init/recycle when changed */ #define WEB_INIT_OPTS (WEB_OPT_HTTP_LOGGING) @@ -141,6 +142,7 @@ static ini_bitdesc_t web_options[] = { { WEB_OPT_NO_CGI ,"NO_CGI" }, { WEB_OPT_HTTP_LOGGING ,"HTTP_LOGGING" }, { WEB_OPT_ALLOW_TLS ,"ALLOW_TLS" }, + { WEB_OPT_HSTS_SAFE ,"HSTS_SAFE" }, /* shared bits */ { BBS_OPT_NO_HOST_LOOKUP ,"NO_HOST_LOOKUP" },