diff --git a/CMakeLists.txt b/CMakeLists.txt index 491077c..8108971 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,14 +35,14 @@ find_package(Threads REQUIRED) ###### pkgconfig ###### # pkg-config modules (for pkg-check-modules) -set(MODULES usefull_macros sqlite3) +set(MODULES usefull_macros sqlite3 onion) # find packages: find_package(PkgConfig REQUIRED) pkg_check_modules(${PROJ} REQUIRED ${MODULES}) ###### additional flags ###### -list(APPEND ${PROJ}_LIBRARIES "-lonion -lcrypt") +list(APPEND ${PROJ}_LIBRARIES "-lcrypt") # change wrong behaviour with install prefix if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND CMAKE_INSTALL_PREFIX MATCHES "/usr/local") diff --git a/README.md b/README.md index 1fb131b..2ca263a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # onionserver Simple web-server based on libonion + +default admin: toor, password: p@ssw0rd \ No newline at end of file diff --git a/auth.c b/auth.c index 9c06e02..5ad8325 100644 --- a/auth.c +++ b/auth.c @@ -149,6 +149,7 @@ sessinfo *qookieSession(onion_request *req){ } onion_connection_status auth(_U_ onion_handler *h, onion_request *req, onion_response *res){ + int retcode = OCS_CLOSE_CONNECTION; if(!req || !res) return OCS_CLOSE_CONNECTION; if(onion_request_get_flags(req) & OR_HEAD) { onion_response_write_headers(res); @@ -195,33 +196,27 @@ onion_connection_status auth(_U_ onion_handler *h, onion_request *req, onion_res userinfo *U = getUserData(username); if(!U){ WARNX("User %s not found", username); - return OCS_FORBIDDEN; + retcode = OCS_FORBIDDEN; + goto closeconn; } char *pass = strdup(crypt(passwd, "$6$")); if(!pass){ WARN("Error in ctypt or strdup"); freeUserInfo(&U); - return OCS_FORBIDDEN; + retcode = OCS_FORBIDDEN; + goto closeconn; } int comp = strcmp(pass, U->password); freeUserInfo(&U); FREE(pass); if(comp){ WARNX("User %s give wrong password", username); - return OCS_FORBIDDEN; + retcode = OCS_FORBIDDEN; + goto closeconn; } session = MALLOC(sessinfo, 1); session->atime = time(NULL); session->username = strdup(username); -/* - onion_dict *data = onion_dict_new(); - onion_dict_add(data, "UA", UA, 0); - onion_dict_add(data, "IP", host, 0); - onion_block *bl = onion_dict_to_json(data); - if(bl){ - const char *json = onion_block_data(bl); - if(json) session->data = strdup(json); - }*/ session->data = strdup(json); do{ FREE(session->sessID); @@ -238,7 +233,7 @@ onion_connection_status auth(_U_ onion_handler *h, onion_request *req, onion_res onion_response_write0(res, AUTH_ANS_AUTHOK); closeconn: freeSessInfo(&session); - return OCS_CLOSE_CONNECTION; + return retcode; } /** @@ -552,22 +547,21 @@ int addSession(sessinfo *s, int modify){ if(!s) return 1; sessinfo *byID = getSession(s->sessID); sessinfo *bysID = getSession(s->sockID); + int errfound = 0; if(byID || bysID){ // found session with same IDs if(modify){ if(bysID && strcmp(bysID->sessID, s->sessID)){ - freeSessInfo(&byID); - freeSessInfo(&bysID); - WARNX("Found another session with the same sockID"); - return 2; + errfound = 2; } }else{ if(byID) WARNX("Found another session with the same sessID"); if(bysID) WARNX("Found another session with the same sockID"); - freeSessInfo(&byID); - freeSessInfo(&bysID); - return 3; + errfound = 3; } } + freeSessInfo(&byID); + freeSessInfo(&bysID); + if(errfound) return errfound; pthread_mutex_lock(&sessionDB->mutex); // 1-sessID, 2-sockID, 3-atime, 4-username, 5-data sqlite3_reset(sessionDB->add); diff --git a/auth.h b/auth.h index d8d1355..16539bf 100644 --- a/auth.h +++ b/auth.h @@ -29,6 +29,8 @@ #define AUTH_ANS_AUTHOK "AuthOK" #define AUTH_ANS_LOGOUT "LogOut" #define AUTH_ANS_NOPASSWD "NoPassword" +#define AUTH_ANS_NOUSERDATA "NoUserData" +#define AUTH_ANS_WRONGIP "WrongIPUA" typedef struct{ char *username; // user name diff --git a/main.c b/main.c index 7c29b3b..04399fb 100644 --- a/main.c +++ b/main.c @@ -56,77 +56,64 @@ onion_connection_status get(_U_ onion_handler *h, onion_request *req, onion_resp return OCS_CLOSE_CONNECTION; } -static onion *os = NULL, *ow = NULL; +static onion *os = NULL;//, *ow = NULL; +static int STOP = 0; void signals(int signo){ + signal(signo, SIG_IGN); + STOP = 1; + if(os){ + onion_free(os); + } + /*if(ow){ + onion_free(ow); + }*/ closeSQLite(); - if(os) onion_free(os); - if(ow) onion_free(ow); + sleep(1); exit(signo); } // POST/GET server static void *runPostGet(_U_ void *data){ - os = onion_new(O_THREADED); + FNAME(); + if(STOP) return NULL; + os = onion_new(O_POOL); if(!(onion_flags(os) & O_SSL_AVAILABLE)){ ONION_ERROR("SSL support is not available"); signals(1); } int error = onion_set_certificate(os, O_SSL_CERTIFICATE_KEY, G.certfile, G.keyfile); if(error){ - ONION_ERROR("Cant set certificate and key files (%s, %s)", G.certfile, G.keyfile); + ONION_ERROR("Can't set certificate and key files (%s, %s)", G.certfile, G.keyfile); signals(1); } + DBG("PostGet @ port %s", G.port); onion_set_port(os, G.port); onion_url *url = onion_root_url(os); onion_url_add_handler(url, "^static/", onion_handler_export_local_new("static")); onion_url_add_with_data(url, "", onion_shortcut_internal_redirect, "static/index.html", NULL); onion_url_add(url, "^auth/", auth); onion_url_add(url, "^get/", get); + onion_url_add(url, "ws", websocket_run); error = onion_listen(os); - if(error) ONION_ERROR("Cant create POST/GET server: %s", strerror(errno)); + if(error) ONION_ERROR("Can't create POST/GET server: %s", strerror(errno)); onion_free(os); return NULL; } -// Websocket server -static void *runWS(_U_ void *data){ - ow = onion_new(O_THREADED); - if(!(onion_flags(ow) & O_SSL_AVAILABLE)){ - ONION_ERROR("SSL support is not available"); - signals(1); - } - int error = onion_set_certificate(ow, O_SSL_CERTIFICATE_KEY, G.certfile, G.keyfile); - if(error){ - ONION_ERROR("Cant set certificate and key files (%s, %s)", G.certfile, G.keyfile); - signals(1); - } - onion_set_port(ow, G.wsport); - onion_url *url = onion_root_url(ow); - onion_url_add(url, "", websocket_run); - DBG("Listen websocket"); - error = onion_listen(ow); - if(error) ONION_ERROR("Cant create POST/GET server: %s", strerror(errno)); - onion_free(ow); - return NULL; -} - static void runServer(){ - // if(G.logfilename) Cl_createlog(); signal(SIGTERM, signals); signal(SIGINT, signals); signal(SIGQUIT, signals); signal(SIGTSTP, SIG_IGN); signal(SIGHUP, SIG_IGN); - - pthread_t pg_thread, ws_thread; + // if(G.logfilename) Cl_createlog(); + pthread_t pg_thread;//, ws_thread; if(pthread_create(&pg_thread, NULL, runPostGet, NULL)){ ERR("pthread_create()"); } - if(pthread_create(&ws_thread, NULL, runWS, NULL)){ - ERR("pthread_create()"); - } do{ - if(pthread_kill(pg_thread, 0) == ESRCH){ // POST/GET died + if(STOP) return; + if(pthread_kill(pg_thread, 0) == ESRCH){ // server died WARNX("POST/GET server thread died"); putlog("POST/GET server thread died"); pthread_join(pg_thread, NULL); @@ -135,15 +122,6 @@ static void runServer(){ ERR("pthread_create()"); } } - if((pthread_kill(pg_thread, 0) == ESRCH) || (pthread_kill(ws_thread, 0) == ESRCH)){ // died - WARNX("Websocket server thread died"); - putlog("Websocket server thread died"); - pthread_join(ws_thread, NULL); - if(pthread_create(&ws_thread, NULL, runWS, NULL)){ - putlog("pthread_create() failed"); - ERR("pthread_create()"); - } - } #if 0 usleep(1000); // sleep a little or thread's won't be able to lock mutex if(dtime() - tgot < T_INTERVAL) continue; @@ -231,24 +209,6 @@ int main(int argc, char **argv){ } } if(G.useradd) adduser(); - /* - sessinfo *x = MALLOC(sessinfo, 1); - x->data = strdup("some data"); - x->atime = time(NULL); - x->username = strdup("luser"); - do{ - FREE(x->sessID); - FREE(x->sockID); - x->sessID = onion_sessions_generate_id(); - x->sockID = onion_sessions_generate_id(); - if(!addSession(x)){ - green("Insert session: ID=%s, sockid=%s, atime=%lld, user=%s, data=%s\n", - x->sessID, x->sockID, x->atime, x->username, x->data); - break; - } - }while(1); - freeSessInfo(&x); - */ if(G.dumpSessDB) showAllSessions(); if(G.delsession){ if(!deleteSession(G.delsession)) diff --git a/static/auth.js b/static/auth.js index 7aabd33..6bec214 100644 --- a/static/auth.js +++ b/static/auth.js @@ -52,6 +52,7 @@ auth = function(){ } function logout1(){ sendrequest("auth/?LogOut=1", _ilogin); + ws.close(); } function sendlogpass(){ $("shadow").style.display = "none"; @@ -67,7 +68,7 @@ auth = function(){ var ws; function wsinit(){ delete(ws); - ws = new WebSocket('wss://localhost:8081'); + ws = new WebSocket('wss://localhost:8080/ws'); ws.onopen = function(){ws.send("Akey="+wsKey);}; // send key after init ws.onclose = function(evt){ var text = "WebSocket closed: "; diff --git a/websockets.c b/websockets.c index e26956c..135b04e 100644 --- a/websockets.c +++ b/websockets.c @@ -20,6 +20,7 @@ #include "websockets.h" #include +#include #include #include #include @@ -29,12 +30,26 @@ // bit-fields of `data` field (websocket_cont) #define WS_FLAG_NOTAUTHORIZED 1 +typedef struct{ + uint64_t UAhash; + uint64_t IPhash; + uint64_t flags; +} WSdata; -TODO: add logout! +// https://stackoverflow.com/a/57960443/1965803 +static uint64_t MurmurOAAT64(const char *key){ + uint64_t h = 525201411107845655ull; + for (;*key;++key){ + h ^= (uint64_t)*key; + h *= 0x5bd1e9955bd1e995; + h ^= h >> 47; + } + return h; +} static onion_connection_status websocket_cont(void *data, onion_websocket *ws, ssize_t dlen){ FNAME(); - uint32_t flags = *((uint32_t*)data); + WSdata *wsdata = (WSdata*)data; char tmp[BUFLEN+1]; if(dlen > BUFLEN) dlen = BUFLEN; @@ -46,19 +61,36 @@ static onion_connection_status websocket_cont(void *data, onion_websocket *ws, s tmp[len] = 0; //ONION_INFO("Read from websocket: %s (len=%d)", tmp, len); DBG("WS: got %s", tmp); - if(flags & WS_FLAG_NOTAUTHORIZED){ // not authorized over websocket + if(wsdata->flags & WS_FLAG_NOTAUTHORIZED){ // not authorized over websocket sessinfo *session = NULL; if(strncmp(tmp, "Akey=", 5) == 0){ // got authorized key - check it char *key = tmp + 5; session = getSession(key); - /* here we should make a proper check, but for now do simplest */ } if(!session){ onion_websocket_printf(ws, AUTH_ANS_NEEDAUTH); WARNX("Wrong websocket session ID"); return OCS_FORBIDDEN; } - flags &= ~WS_FLAG_NOTAUTHORIZED; // clear non-authorized flag + // + onion_dict *json = onion_dict_from_json(session->data); + freeSessInfo(&session); + if(json){ + uint64_t UAhash = MurmurOAAT64(onion_dict_get(json, "User-Agent")); + uint64_t IPhash = MurmurOAAT64(onion_dict_get(json, "User-IP")); + if(wsdata->IPhash != IPhash || wsdata->UAhash != UAhash){ + onion_websocket_printf(ws, AUTH_ANS_WRONGIP); + WARNX("Websocket IP/UA are wrong"); + return OCS_FORBIDDEN; + } + red("WSdata checked!\n"); + onion_dict_free(json); + }else{ + onion_websocket_printf(ws, AUTH_ANS_NOUSERDATA); + WARNX("No user IP and/or UA in database"); + return OCS_FORBIDDEN; + } + wsdata->flags &= ~WS_FLAG_NOTAUTHORIZED; // clear non-authorized flag return OCS_NEED_MORE_DATA; } char *eq = strchr(tmp, '='); @@ -75,7 +107,6 @@ onion_connection_status websocket_run(_U_ void *data, onion_request *req, onion_ FNAME(); onion_websocket *ws = onion_websocket_new(req, res); if (!ws){ - green("PROC\n"); DBG("Processed"); return OCS_PROCESSED; } @@ -83,8 +114,11 @@ onion_connection_status websocket_run(_U_ void *data, onion_request *req, onion_ const char *host = onion_request_get_client_description(req); const char *UA = onion_request_get_header(req, "User-Agent"); green("Got WS connection from %s (UA: %s)\n", host, UA); - uint32_t *flags = calloc(1, 4); - onion_websocket_set_userdata(ws, (void*)flags, free); + WSdata *wsdata = calloc(1, sizeof(WSdata)); + wsdata->flags = WS_FLAG_NOTAUTHORIZED; + wsdata->IPhash = MurmurOAAT64(host); + wsdata->UAhash = MurmurOAAT64(UA); + onion_websocket_set_userdata(ws, (void*)wsdata, free); onion_websocket_set_callback(ws, websocket_cont); return OCS_WEBSOCKET; }