diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..60663b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Prerequisites +*.d + +# Object files +*.o + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.so +*.so.* + +# qt-creator +*.config +*.cflags +*.cxxflags +*.creator* +*.files +*.includes diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0b3b70a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,68 @@ +cmake_minimum_required(VERSION 3.0) +set(PROJ Onion_test) +set(MINOR_VERSION "1") +set(MID_VERSION "0") +set(MAJOR_VERSION "0") +set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}") + +project(${PROJ} VERSION ${PROJ_VERSION} LANGUAGES C) + +message("VER: ${VERSION}") + +# default flags +set(CMAKE_C_FLAGS_RELEASE "") +set(CMAKE_C_FLAGS_DEBUG "") +set(CMAKE_C_FLAGS "-O2 -std=gnu99") + +set(CMAKE_COLOR_MAKEFILE ON) + +# here is one of two variants: all .c in directory or .c files in list +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCES) + +# cmake -DDEBUG=1 -> debugging +if(DEFINED EBUG) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wall -Werror -W") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wextra -Wall -Werror -W") + set(CMAKE_BUILD_TYPE DEBUG) + set(CMAKE_VERBOSE_MAKEFILE "ON") + add_definitions(-DEBUG) +else() + set(CMAKE_BUILD_TYPE RELEASE) +endif() + +###### pkgconfig ###### +# pkg-config modules (for pkg-check-modules) +set(MODULES usefull_macros sqlite3) + +# find packages: +find_package(PkgConfig REQUIRED) +pkg_check_modules(${PROJ} REQUIRED ${MODULES}) + +###### additional flags ###### +list(APPEND ${PROJ}_LIBRARIES "-lonion -lcrypt") + +# change wrong behaviour with install prefix +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND CMAKE_INSTALL_PREFIX MATCHES "/usr/local") +else() + message("Change default install path to /usr/local") + set(CMAKE_INSTALL_PREFIX "/usr/local") +endif() +message("Install dir prefix: ${CMAKE_INSTALL_PREFIX}") + +# exe file +add_executable(${PROJ} ${SOURCES}) +# -I +include_directories(${${PROJ}_INCLUDE_DIRS}) +# -L +link_directories(${${PROJ}_LIBRARY_DIRS}) +# -D +add_definitions(${CFLAGS} -DLOCALEDIR=\"${LOCALEDIR}\" + -DPACKAGE_VERSION=\"${VERSION}\" -DGETTEXT_PACKAGE=\"${PROJ}\" + -DMINOR_VERSION=\"${MINOR_VERSION}\" -DMID_VERSION=\"${MID_VERSION}\" + -DMAJOR_VERSION=\"${MAJOR_VESION}\") + +# -l +target_link_libraries(${PROJ} ${${PROJ}_LIBRARIES} -lm) + +# Installation of the program +INSTALL(TARGETS ${PROJ} DESTINATION "bin") diff --git a/HOWTO.cert b/HOWTO.cert new file mode 100644 index 0000000..8b03d23 --- /dev/null +++ b/HOWTO.cert @@ -0,0 +1,5 @@ +create: +openssl req -newkey rsa:2048 -nodes -keyout cert.key -x509 -days 365 -out cert.pem + +review: +openssl x509 -text -noout -in cert.pem diff --git a/auth.c b/auth.c new file mode 100644 index 0000000..630a1de --- /dev/null +++ b/auth.c @@ -0,0 +1,682 @@ +/* + * This file is part of the Onion_test project. + * Copyright 2020 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "auth.h" + +extern char *onion_sessions_generate_id(); +extern void onion_random_init(); + +typedef struct{ + sqlite3 *db; // session database + char *name; // database filename + sqlite3_stmt *add; // template to add/modify session + sqlite3_stmt *getsid; // get data by session ID + sqlite3_stmt *getsockid;// get data by socked ID + sqlite3_stmt *delold; // delete all old sessions (with atime < XX) + sqlite3_stmt *del; // delete session + pthread_mutex_t mutex; // locking mutex +} _sessionDB; + +typedef struct{ + sqlite3 *db; // auth database + char *name; // database filename + sqlite3_stmt *add; // add user data + sqlite3_stmt *get; // get user data by username + sqlite3_stmt *del; // delete user data + pthread_mutex_t mutex; // locking mutex +} _authDB; + +static _sessionDB *sessionDB = NULL; +static _authDB *authDB = NULL; + +/** + * @brief getQdata - find data in POST or GET parts + * @param req - request + * @param key - searching key + * @return value or NULL + */ +const char *getQdata(onion_request *req, const char *key){ + if(!req || !key) return NULL; + printf("key %s, ", key); + const char *data = onion_request_get_query(req, key); + printf("GET: '%s' ", data); + if(!data){ + data = onion_request_get_post(req, key); + if(data && *data == 0) data = NULL; + printf("POST: '%s'", data); + } + printf("\n"); + fflush(stdout); + return data; +} + +/** + * @brief sqprep - prepare SQLite statement + * @param db - database + * @param str - text string for statement + * @param stmt - the statement + * @return 0 if all OK + */ +static int sqprep(sqlite3 *db, const char *str, sqlite3_stmt **stmt){ + int rc = sqlite3_prepare_v2(db, str, (int)strlen(str), stmt, NULL); + if(rc != SQLITE_OK){ + WARNX("Can't prepare statement to save (%d)", rc); + return 1; + } + return 0; +} + +/** + * @brief addtext - bind text phrase to statement + * @param stmt - the statement + * @param narg - argument number for binding + * @param str - the text itself + * @return 0 if all OK + */ +static int addtext(sqlite3_stmt *stmt, int narg, const char *str){ + if(str == NULL) str = ""; + int rc = sqlite3_bind_text(stmt, narg, str, -1, SQLITE_TRANSIENT); + if(rc != SQLITE_OK){ + WARNX("Can't bind %s: %d", str, rc); + return 1; + } + return 0; +} + + +/** + * @brief addWSkey - add new websocket ID to session & send this ID to user + * @param res - web response + * @param session - session information to modify + */ +void addWSkey(onion_response *res, sessinfo *session){ + if(!res || !session) return; + do{ + FREE(session->sockID); + session->sockID = onion_sessions_generate_id(); + DBG("Try to modify session %s", session->sessID); + if(!addSession(session, 1)){ + DBG("Modify session: ID=%s, sockid=%s, atime=%" PRId64 ", user=%s, data=%s\n", + session->sessID, session->sockID, session->atime, session->username, session->data); + break; + } + }while(1); + onion_response_write0(res, session->sockID); +} + +/** + * @brief qookieSession - find session ID from SESSION_COOKIE_NAME and search session in DB + * @param req - web request + * @return session data (allocated here) or NULL if session not found + */ +sessinfo *qookieSession(onion_request *req){ + if(!req) return NULL; + /*const char *path = onion_request_get_path(req); + printf("Ask path %s\n", path);*/ + onion_dict *cookies = onion_request_get_cookies_dict(req); + const char *skey = onion_dict_get(cookies, SESSION_COOKIE_NAME); + if(!skey){ + WARNX("No session cookie found\n"); + return NULL; + } + return getSession(skey); +} + +onion_connection_status auth(_U_ onion_handler *h, onion_request *req, onion_response *res){ + if(!req || !res) return OCS_CLOSE_CONNECTION; + if(onion_request_get_flags(req) & OR_HEAD) { + onion_response_write_headers(res); + return OCS_PROCESSED; + } + const char *host = onion_request_get_client_description(req); + char json[2048]; // json buffer for UA & IP + const char *UA = onion_request_get_header(req, "User-Agent"); + snprintf(json, 2048, "{\"User-Agent\": \"%s\", \"User-IP\": \"%s\"}", UA, host); + DBG("Client: %s, UA: %s\n", host, UA); + const char *logout = getQdata(req, "LogOut"); + DBG("logout=%s\n", logout); + sessinfo *session = qookieSession(req); + if(!session) DBG("No cookie, need to create\n"); + else if(!logout){ + onion_response_write0(res, "AuthOK"); + goto closeconn; + } + const char *username = NULL, *passwd = NULL; + if(logout){ + DBG("User logged out\n"); + if(session){ + if(deleteSession(session->sessID)) + WARNX("Can't delete session with ID=%s from database", session->sessID); + } + onion_response_write0(res, "LogOut"); + onion_response_add_cookie(res, SESSION_COOKIE_NAME, "clear", 0, "/", NULL, OC_HTTP_ONLY|OC_SECURE); + goto closeconn; + }else{ // log in + freeSessInfo(&session); + username = getQdata(req, "login"); + if(!username){ + ONION_WARNING("no login field -> need auth"); + onion_response_write0(res, "NeedAuth"); + return OCS_CLOSE_CONNECTION; + } + passwd = getQdata(req, "passwd"); + if(!passwd){ + ONION_WARNING("Trying to enter authenticated area without password"); + onion_response_write0(res, "No password"); + return OCS_FORBIDDEN; + } + } + userinfo *U = getUserData(username); + if(!U){ + WARNX("User %s not found", username); + return OCS_FORBIDDEN; + } + char *pass = strdup(crypt(passwd, "$6$")); + if(!pass){ + WARN("Error in ctypt or strdup"); + freeUserInfo(&U); + return OCS_FORBIDDEN; + } + int comp = strcmp(pass, U->password); + freeUserInfo(&U); + FREE(pass); + if(comp){ + WARNX("User %s give wrong password", username); + return OCS_FORBIDDEN; + } + 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); + session->sessID = onion_sessions_generate_id(); + DBG("Try to add session %s", session->sessID); + if(!addSession(session, 0)){ + DBG("New session: ID=%s, atime=%" PRId64 ", user=%s, data=%s\n", + session->sessID, session->atime, session->username, session->data); + break; + } + sleep(2); + }while(1); + onion_response_add_cookie(res, SESSION_COOKIE_NAME, session->sessID, 366*86400, "/", NULL, OC_HTTP_ONLY|OC_SECURE); + onion_response_write0(res, "AuthOK"); +closeconn: + freeSessInfo(&session); + return OCS_CLOSE_CONNECTION; +} + +/** + * @brief opendb - open SQLite database + * @param name - database filename + * @param db - database itself + * @return 0 if all OK + */ +static int opendb(const char *name, sqlite3 **db){ + if(!name || !db) return 2; + if(SQLITE_OK != sqlite3_open(name, db)){ + ONION_ERROR("Can't open database: %s", sqlite3_errmsg(*db)); + sqlite3_close(*db); + return 1; + } + return 0; +} + +/** + * @brief close_sessDB - close session database & free memory + */ +static void close_sessDB(){ + if(!sessionDB) return; + pthread_mutex_lock(&sessionDB->mutex); + sqlite3_finalize(sessionDB->add); + sqlite3_finalize(sessionDB->del); + sqlite3_finalize(sessionDB->getsid); + sqlite3_finalize(sessionDB->getsockid); + sqlite3_close(sessionDB->db); + pthread_mutex_unlock(&sessionDB->mutex); + pthread_mutex_destroy(&sessionDB->mutex); + FREE(sessionDB->name); + FREE(sessionDB); +} +/** + * @brief close_authDB - close user database & free memory + */ +static void close_authDB(){ + if(!authDB) return; + pthread_mutex_lock(&authDB->mutex); + sqlite3_finalize(authDB->add); + sqlite3_finalize(authDB->del); + sqlite3_finalize(authDB->get); + sqlite3_close(authDB->db); + pthread_mutex_unlock(&authDB->mutex); + pthread_mutex_destroy(&authDB->mutex); + FREE(authDB->name); + FREE(authDB); +} + +/** + * @brief closeSQLite - close all databases + */ +void closeSQLite(){ + close_authDB(); + close_sessDB(); +} + +/** + * @brief initSQLite - init SQL databases + * @param auth_filename - database with auth data (username, password hash, access level) + * @param sess_filename - session database (session ID, websosket ID, access time, data {UA, IP, username, access level - JSON}) + * @return error code or 0 if OK + */ +int initSQLite(const char *auth_filename, const char *sess_filename){ + if(!auth_filename || !sess_filename) return 5; + int rc; + char *zErrMsg = NULL; + onion_random_init(); + closeSQLite(); + /******************************* users database *******************************/ + authDB = MALLOC(_authDB, 1); + if(opendb(auth_filename, &authDB->db)) return 2; + authDB->name = strdup(auth_filename); + rc = sqlite3_exec(authDB->db, "CREATE TABLE passwd (user TEXT PRIMARY KEY NOT NULL, passhash TEXT, level INT, comment TEXT)", NULL, NULL, &zErrMsg); + if(rc != SQLITE_OK){ + if(strcmp(zErrMsg, "table passwd already exists")){ + WARNX("SQL CREATE error %d: %s", rc, zErrMsg); + goto ret; + } + }else{ + fprintf(stderr, "Add default user\n"); + // default admin: toor, password: p@ssw0rd + rc = sqlite3_exec(authDB->db, "INSERT INTO passwd VALUES (\"toor\", \"$6$$F55h0JlnNG19zS6YHUDX6h4zksblEBCFRqzCoCt6Y3VqE7zBeM/ifdGbUNMK.dLIwu9jq3fCmLBYEpzEpNCej0\", 0, \"\")", NULL, NULL, &zErrMsg); + if(rc != SQLITE_OK){ + fprintf(stderr, "SQL INSERT error %d: %s\n", rc, zErrMsg); + sqlite3_free(zErrMsg); + goto ret; + } + } + // get user information + if(sqprep(authDB->db, "SELECT passhash, level, comment FROM passwd WHERE user=?", + &authDB->get)) goto ret; + // add or modify user + if(sqprep(authDB->db, "INSERT OR REPLACE INTO passwd (user, passhash, level, comment) VALUES (?, ?, ?, ?)", + &authDB->add)) goto ret; + // delete user + if(sqprep(authDB->db, "DELETE FROM passwd WHERE user=?", &authDB->del)) goto ret; + if(pthread_mutex_init(&authDB->mutex, NULL)){ + WARN("Can't init authDB mutex"); + goto ret; + } + /****************************** session database ******************************/ + sessionDB = MALLOC(_sessionDB, 1); + if(opendb(sess_filename, &sessionDB->db)) goto ret; + sessionDB->name = strdup(sess_filename); + rc = sqlite3_exec(sessionDB->db, "CREATE TABLE sessions (sessID TEXT PRIMARY KEY NOT NULL, sockID TEXT, atime INT, username TEXT, data TEXT)", NULL, NULL, &zErrMsg); + if(rc != SQLITE_OK){ + if(strcmp(zErrMsg, "table sessions already exists")){ + WARNX("SQL CREATE error %d: %s", rc, zErrMsg); + sqlite3_free(zErrMsg); + goto ret; + } + } + // get session information + if(sqprep(sessionDB->db, "SELECT sockID, atime, username, data FROM sessions WHERE sessID=?", + &sessionDB->getsid)) goto ret; + if(sqprep(sessionDB->db, "SELECT sessID, atime, username, data FROM sessions WHERE sockID=?", + &sessionDB->getsockid)) goto ret; + // add or modify session + if(sqprep(sessionDB->db, "INSERT OR REPLACE INTO sessions (sessID, sockID, atime, username, data) VALUES (?, ?, ?, ?, ?)", + &sessionDB->add)) goto ret; + // delete session + if(sqprep(sessionDB->db, "DELETE FROM sessions WHERE sessID=?", + &sessionDB->del)) goto ret; + if(sqprep(sessionDB->db, "DELETE FROM sessions WHERE atimedelold)) goto ret; + return 0; +ret: + closeSQLite(); + return -1; +} + +/** + * @brief getUserData - find all records in authDB for given username + * @param username - user name + * @return userinfo structure allocated here or NULL if user not found + */ +userinfo *getUserData(const char *username){ + if(!username) return NULL; + userinfo *U = NULL; + if(!authDB){ + WARNX("User database not initialized"); + return NULL; + } + pthread_mutex_lock(&authDB->mutex); + sqlite3_reset(authDB->get); + if(addtext(authDB->get, 1, username)) return NULL; + if(SQLITE_ROW != sqlite3_step(authDB->get)){ + WARNX("User %s not found", username); + goto ret; + } + const char *pass = (const char *) sqlite3_column_text(authDB->get, 0); + int levl = sqlite3_column_int(authDB->get, 1); + const char *comment = (const char *) sqlite3_column_text(authDB->get, 2); + U = (userinfo *) malloc(sizeof(userinfo)); + if(!U) goto ret; + U->username = strdup(username); + U->password = strdup(pass); + U->level = levl; + U->comment = strdup(comment); +ret: + pthread_mutex_unlock(&authDB->mutex); + return U; +} + +/** + * @brief freeUserInfo - free memory for struct userinfo + * @param x - pointer to userinfo pointer + */ +void freeUserInfo(userinfo **x){ + if(!x || !*x) return; + userinfo *U = *x; + free(U->username); + free(U->password); + free(U->comment); + free(U); + *x = NULL; +} + +// callback for showAllUsers() +static int selallcb(_U_ void *notused, int argc, char **argv, _U_ char **no){ + if(argc != 4){ + fprintf(stderr, "Wrong argc: %d\n", argc); + return 1; + } + printf("%s\t%s\t%s\t%s\n", argv[0], argv[2], argv[1], argv[3]); + return 0; +} +/** + * @brief showAllUsers - printout user database + */ +void showAllUsers(){ + char *zErrMsg = NULL; + green("\nUSER\tLevel\tPassHash\t\t\tComment\n"); + pthread_mutex_lock(&authDB->mutex); + int rc = sqlite3_exec(authDB->db, "SELECT * FROM passwd GROUP BY user", selallcb, NULL, &zErrMsg); + if(rc != SQLITE_OK){ + WARNX("SQL CREATE error %d: %s", rc, zErrMsg); + sqlite3_free(zErrMsg); + } + pthread_mutex_unlock(&authDB->mutex); +} + +/** + * @brief deleteUser - delete user record from database + * @param username - user name + * @return 0 if all OK + */ +int deleteUser(const char *username){ + if(!username) return 1; + userinfo *u = getUserData(username); + if(!u) return 1; + FREE(u); + pthread_mutex_lock(&authDB->mutex); + sqlite3_reset(authDB->del); + if(addtext(authDB->del, 1, username)) goto reterr; + if(SQLITE_DONE != sqlite3_step(authDB->del)) goto reterr; + else{ + printf("User %s deleted\n", username); + pthread_mutex_unlock(&authDB->mutex); + return 0; + } +reterr: + pthread_mutex_unlock(&authDB->mutex); + WARNX("Can't delete user %s", username); + return 2; +} + +/** + * @brief addUser - add user to database + * @param User - struct with user data + * @return 0 if OK + */ +int addUser(userinfo *User){ + if(!User) return 1; + if(strlen(User->username) < 1){ + WARNX("No username"); + return 1; + } + pthread_mutex_lock(&authDB->mutex); + sqlite3_reset(authDB->add); + if(addtext(authDB->add, 1, User->username)) goto reterr; + if(addtext(authDB->add, 2, User->password)) goto reterr; + if(SQLITE_OK != sqlite3_bind_int(authDB->add, 3, User->level)) goto reterr; + addtext(authDB->add, 4, User->comment); + if(SQLITE_DONE == sqlite3_step(authDB->add)){ + green("Add to database:\n"); + printf("\tuser %s, passHash %s, level %d, comment '%s'\n", + User->username, User->password, User->level, User->comment); + pthread_mutex_unlock(&authDB->mutex); + return 0; + } +reterr: + pthread_mutex_unlock(&authDB->mutex); + WARNX("Can't insert user %s", User->username); + return 1; +} + +/** + * @brief getSession - find session by session ID or socket ID + * @param ID - session or socket ID + * @return NULL if not found or session structure (allocated here) + */ +sessinfo *getSession(const char *ID){ + if(!ID) return NULL; + sqlite3_stmt *stmt = sessionDB->getsid; + sessinfo *s = NULL; + pthread_mutex_lock(&sessionDB->mutex); + sqlite3_reset(stmt); + if(addtext(stmt, 1, ID)) goto ret; + const char *sessID = NULL, *sockID = NULL; + if(SQLITE_ROW != sqlite3_step(stmt)){ + stmt = sessionDB->getsockid; + sqlite3_reset(stmt); + if(addtext(stmt, 1, ID)) goto ret; + if(SQLITE_ROW != sqlite3_step(stmt)){ + WARNX("Session %s not found\n", ID); + goto ret; + }else{ + sockID = ID; + sessID = (const char *) sqlite3_column_text(stmt, 0); + } + }else{ + sessID = ID; + sockID = (const char *) sqlite3_column_text(stmt, 0); + } + // 0-ID, 1-atime, 2-username, 3-data + int64_t atime = sqlite3_column_int64(stmt, 1); + const char *username = (const char *)sqlite3_column_text(stmt, 2); + const char *data = (const char *)sqlite3_column_text(stmt, 3); + s = (sessinfo*) malloc(sizeof(sessinfo)); + if(!s) goto ret; + s->sessID = strdup(sessID); + s->sockID = strdup(sockID); + s->atime = atime; + s->username = strdup(username); + s->data = strdup(data); +ret: + pthread_mutex_unlock(&sessionDB->mutex); + return s; +} + +/** + * @brief addSession - add new session or modify old + * @param s - structure with session information + * @param modify != 0 to modify existant session + * @return 0 if all OK + */ +int addSession(sessinfo *s, int modify){ + if(!s) return 1; + sessinfo *byID = getSession(s->sessID); + sessinfo *bysID = getSession(s->sockID); + 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; + } + }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; + } + } + pthread_mutex_lock(&sessionDB->mutex); + // 1-sessID, 2-sockID, 3-atime, 4-username, 5-data + sqlite3_reset(sessionDB->add); + if(addtext(sessionDB->add, 1, s->sessID)) goto reterr; + if(addtext(sessionDB->add, 2, s->sockID)) goto reterr; + if(SQLITE_OK != sqlite3_bind_int64(sessionDB->add, 3, s->atime)) goto reterr; + if(addtext(sessionDB->add, 4, s->username)) goto reterr; + if(addtext(sessionDB->add, 5, s->data)) goto reterr; + if(SQLITE_DONE == sqlite3_step(sessionDB->add)){ + pthread_mutex_unlock(&sessionDB->mutex); + return 0; + } +reterr: + pthread_mutex_unlock(&sessionDB->mutex); + WARNX("Can't insert session %s\n", s->sessID); + return 1; +} + +/** + * @brief deleteSession - delete session by SessID + * @param sessID - session ID + * @return 0 if all OK + */ +int deleteSession(const char *sessID){ + if(!sessID) return 1; + pthread_mutex_lock(&sessionDB->mutex); + sqlite3_reset(sessionDB->del); + if(addtext(sessionDB->del, 1, sessID)) goto reterr; + if(SQLITE_DONE != sqlite3_step(sessionDB->del)) goto reterr; + else{ + printf("Session with id %s deleted\n", sessID); + pthread_mutex_unlock(&sessionDB->mutex); + return 0; + } +reterr: + pthread_mutex_unlock(&sessionDB->mutex); + WARNX("Can't delete session with ID %s\n", sessID); + return 1; +} + +/** + * @brief deleteSession - delete session by SessID + * @param minatime - minimal access time (all sessions with atimemutex); + sqlite3_reset(sessionDB->delold); + if(SQLITE_OK != sqlite3_bind_int64(sessionDB->delold, 1, minatime)) goto reterr; + if(SQLITE_DONE != sqlite3_step(sessionDB->delold)) goto reterr; + else{ + pthread_mutex_unlock(&sessionDB->mutex); + return 0; + } +reterr: + pthread_mutex_unlock(&sessionDB->mutex); + WARNX("Can't delete sessions with atime < %" PRId64 " \n", minatime); + return 1; +} + +// callback for showAllSessions +static int selallscb(void _U_ *notused, int argc, char **argv, char _U_ **no){ + if(argc != 5){ + fprintf(stderr, "Wrong argc: %d\n", argc); + return 1; + } + printf("%s\t%s\t%s\t%s\t%s\n", argv[3], argv[2], argv[0], argv[1], argv[4]); + return 0; +} +void showAllSessions(){ + char *zErrMsg = NULL; + green("Username\tAtime\tSession ID\t\tSocket ID\t\tData\n"); + int rc = sqlite3_exec(sessionDB->db, "SELECT * FROM sessions ORDER BY username", selallscb, NULL, &zErrMsg); + if(rc != SQLITE_OK){ + WARNX("SQL CREATE error %d: %s\n", rc, zErrMsg); + sqlite3_free(zErrMsg); + } +} + +/** + * @brief freeSessInfo - free session info structure + * @param si - address of pointer to SI + */ +void freeSessInfo(sessinfo **si){ + if(!si || !*si) return; + sessinfo *s = *si; + free(s->data); + free(s->sessID); + free(s->sockID); + free(s->username); + free(*si); + *si = NULL; +} + +/** + * @brief vacuum - rebuild databases to free all deleted records + */ +void vacuumSQLite(){ + char *zErrMsg = NULL; + int rc = sqlite3_exec(authDB->db, "vacuum", NULL, NULL, &zErrMsg); + if(rc != SQLITE_OK){ + WARNX("Error in vacuum %s (%d): %s\n", authDB->name, rc, zErrMsg); + sqlite3_free(zErrMsg); + } + rc = sqlite3_exec(sessionDB->db, "vacuum", NULL, NULL, &zErrMsg); + if(rc != SQLITE_OK){ + WARNX("Error in vacuum %s (%d): %s\n", sessionDB->name, rc, zErrMsg); + sqlite3_free(zErrMsg); + } + green("Both databases are rebuilt\n"); +} + diff --git a/auth.h b/auth.h new file mode 100644 index 0000000..00e6b8e --- /dev/null +++ b/auth.h @@ -0,0 +1,64 @@ +/* + * This file is part of the Onion_test project. + * Copyright 2020 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#ifndef AUTH_H__ +#define AUTH_H__ + +#include + +#define SESSION_COOKIE_NAME "Acookie" + +typedef struct{ + char *username; // user name + char *password; // password hash (SHA512) + int level; // user access level + char *comment; // optional comment +} userinfo; + +typedef struct{ + char *sessID; // session ID + char *sockID; // websoket ID (cleared when disconnected or after 1 minute after `atime`) + int64_t atime; // last access time (UNIX time) - all ID data cleared after 1yr of inactivity + char *username; // username + char *data; // JSON data with UA, IP etc +} sessinfo; + +const char *getQdata(onion_request *req, const char *key); +onion_connection_status auth(onion_handler *h, onion_request *req, onion_response *res); +void addWSkey(onion_response *res, sessinfo *session); + +int initSQLite(const char *auth_filename, const char *sess_filename); +void closeSQLite(); +void vacuumSQLite(); + +userinfo *getUserData(const char *username); +void freeUserInfo(userinfo **x); +void showAllUsers(); +int deleteUser(const char *username); +int addUser(userinfo *User); + +sessinfo *getSession(const char *ID); +sessinfo *qookieSession(onion_request *req); +int addSession(sessinfo *s, int modify); +int deleteSession(const char *sessID); +int deleteOldSessions(int64_t minatime); +void showAllSessions(); +void freeSessInfo(sessinfo **si); + +#endif // AUTH_H__ diff --git a/cert.key b/cert.key new file mode 100644 index 0000000..20c7b37 --- /dev/null +++ b/cert.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC68f94gnFpcepC ++kpD4bxY7sQAXYbyp5ahAWun4scbLJPjJe56/CE47yvTpdU8KrVfHLYmP8KcF4pM +w0ZUNMCEenkQkb+eWjIviw3wzgesqLSssM+GZzlg92V2xkYlS/hEolUh0Z+Gl1Se +zl9f1S/EhL6RTfQx5ZuGlppHknqtbP0g4QNkxPXGj/qXqmaWLOxb8fX30eNf/6SF +vl1o45xwFE1SSGQnyh8KKZUWAPH2aZWpwwgB8gv4XwaVWo7DirbqOlUhhkylhhl0 +cA7dHFgODTLluRstYr9M5u3WdoJzTGZTvg2ZV1vmfpQMVbat49TVJYZI3iLKoBHQ +IzeSU/lvAgMBAAECggEAUlc40RmTXoBgUHPxtgh9byZrikWnpMWQIQaBJodKb3uo +/8m7Ssw2zd76jNRkIYYmMOhyilJXI21y6vCvz3MUwMU5AcVQgyzzIeG7mC8HTlNY +kR+nqGla6ozNUg1u5AqcJY7itGyiOSP6j6ASfiFmUsatMU8GmduqLxOyjIfGJRA1 +hnNAqYsVv2LbhbUQJTBozYMz2XYfiq/GbzL2NC1tv/xWoSL4zELjDuiGyZcT8oyV +u+dOf0x/VXOLvHqDrA8HuFbUrBjK5z0QW9iYReti/oxuhGrlp2rJLuzivKr4l+v8 +qA0momNxhFFgyyx2Mb5zxp9TwA2x7q5Cg3vOCje3MQKBgQDe6MTywxuO4MVpwDSl +yot8vzUlI7Bv8BtdW18JUc5y1SIyGHlW1cuaQnZW6hbor6UxAoWi7oIjaFX+JQVD +3NLEnjuUF64/Y+k8wtvK0fFeZ2nvx2UNHF50JV0tQZtHo8znwKI/L8ZBSGJcBwh2 +BGXtJtK9AY6C5xv5U03xRukw/QKBgQDWsn0qd6mV6iGqLth6sjDOJt/RFrvcikXb +lMhkCyGPuR2lQsUDbgWMcb/td3CqR5iZAksDbXrPqQBZVm9LO7DfDsL9GxtfgMrt +0dWk0Baod/n4NQBn8A6qGpbheFZwOhNGayYI17IqtRJmfLJuR7gRvm/IfGKso24X +TTA3kpSl2wKBgEBv52cJ8bB213p/fniiuXnhSDqpO3rQXQi6vhlSlaxqYk069/Cb +MxUvu0faua6f/8/QG9OCwQn9QkaKayA3+JGv8CcaRVu7xRO0fJb/45dXq68N4+9L +UR6gInRPr9SgzD3+WKiNZfE/PHe/7Lk5AkHw5CCRD6JVrqd/ZlumFQj9AoGBAKae +fM7xcRYcTyYRFwYZthC3UKmnOAJO+SoRTHd/v/sXUe+IYvdncjztpmK3eCNeTwoo +Imk1lMMGSHQMxXCgkYJ6pU7is5qpjFOGroQqzfrOqZs8HuWLAwZ2fjPbPVH5cC4N +R8ZDB01nmzEYgy1c0XhLz9rK1ZVffDfvOoVWZ7BTAoGAdQUkTWTTHU+Xwc9HshJy +qDFAFu5SvvO2dn3OvbGVUVc+I3M1P+HMeKS3HnomFLtRRDUGEbqZtPfrxBRnduW9 +cEtgO+smyq86vs1p/ohmUESB2ablkvOgwXTXus+NsmUt3uVDq+0lFTfog/DX49+O +EiTvSXB1U92Opv/kh8ZhQak= +-----END PRIVATE KEY----- diff --git a/cert.pem b/cert.pem new file mode 100644 index 0000000..0c39286 --- /dev/null +++ b/cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUU3mUrQDJVG5A00d+WvVFfqScrqIwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDA3MTMxNjE0MTlaFw0yMTA3 +MTMxNjE0MTlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC68f94gnFpcepC+kpD4bxY7sQAXYbyp5ahAWun4scb +LJPjJe56/CE47yvTpdU8KrVfHLYmP8KcF4pMw0ZUNMCEenkQkb+eWjIviw3wzges +qLSssM+GZzlg92V2xkYlS/hEolUh0Z+Gl1Sezl9f1S/EhL6RTfQx5ZuGlppHknqt +bP0g4QNkxPXGj/qXqmaWLOxb8fX30eNf/6SFvl1o45xwFE1SSGQnyh8KKZUWAPH2 +aZWpwwgB8gv4XwaVWo7DirbqOlUhhkylhhl0cA7dHFgODTLluRstYr9M5u3WdoJz +TGZTvg2ZV1vmfpQMVbat49TVJYZI3iLKoBHQIzeSU/lvAgMBAAGjUzBRMB0GA1Ud +DgQWBBQiHARml0EswF90cHiLYLKxpgj3DjAfBgNVHSMEGDAWgBQiHARml0EswF90 +cHiLYLKxpgj3DjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB4 +mMeSGGA4GAfJnxIwRJWGhj2KoweOh9UOVUtvrEJm3LoURbElo1pV3Q3vzt79wLn5 +JPdXyN/MexYhtq7cwKQps3E4FElmIGUbR4peO/M1z3wJLaXSbaZqeo7ogSW6vzZL +xIHuxLtchNOi4rkSmOhzJqJ23cLULIF7Z94nwxXRxqKaIW8hXB7mlV9cDztpn2sz +K58M9VYmlfZetoHDzmsymM2DTmmoqR28Ykz+wK+G4U6W1+s7sY3Q+mG91lwvpzTf +yvz6dEtqi8ktBdu86E0yHfX04oDyKtl40d0LDzQT5JWhKQFiC25W5tE2p8QLbtHz +D5vg23HjTgRHZ80+3e4J +-----END CERTIFICATE----- diff --git a/cmdlnopts.c b/cmdlnopts.c new file mode 100644 index 0000000..158a97c --- /dev/null +++ b/cmdlnopts.c @@ -0,0 +1,147 @@ +/* + * This file is part of the Onion_test project. + * Copyright 2020 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + + +#include "cmdlnopts.h" + +/* + * here are global parameters initialisation + */ +static int help; +// global parameters (init with default): +glob_pars G = { + .port = "8080", + .certfile = "cert.pem", + .keyfile = "cert.key", +}; + +/* + * Define command line options by filling structure: + * name has_arg flag val type argptr help +*/ +static myoption cmdlnopts[] = { +// common options + {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")}, + {"vacuum", NO_ARGS, NULL, 'v', arg_int, APTR(&G.vacuum), _("make \"vacuum\" operation with databases")}, + {"dumpusers", NO_ARGS, NULL, 'U', arg_int, APTR(&G.dumpUserDB),_("dump users database")}, + {"dumpsess", NO_ARGS, NULL, 'S', arg_int, APTR(&G.dumpSessDB),_("dump session database")}, + {"server", NO_ARGS, NULL, 'r', arg_int, APTR(&G.runServer), _("run server process")}, + {"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), _("port to listen")}, + {"certfile",NEED_ARG, NULL, 'c', arg_string, APTR(&G.certfile), _("file with SSL certificate")}, + {"keyfile", NEED_ARG, NULL, 'k', arg_string, APTR(&G.keyfile), _("file with SSL key")}, + {"usersdb", NEED_ARG, NULL, 'u', arg_string, APTR(&G.usersdb), _("users database filename")}, + {"sessiondb",NEED_ARG, NULL, 's', arg_string, APTR(&G.sessiondb), _("session database filename")}, + {"userdel", MULT_PAR, NULL, 'd', arg_string, APTR(&G.userdel), _("user to delete (maybe several)")}, + {"useradd", NO_ARGS, NULL, 'a', arg_int, APTR(&G.useradd), _("add user[s] interactively")}, + {"sdatime", NEED_ARG, NULL, 'A', arg_longlong,APTR(&G.delatime), _("minimal atime to delete sessions from DB (-1 for >year)")}, + {"sessdel", NEED_ARG, NULL, 'l', arg_string, APTR(&G.delsession),_("delete session by sessID or sockID")}, + end_option +}; + +/** + * Parse command line options and return dynamically allocated structure + * to global parameters + * @param argc - copy of argc from main + * @param argv - copy of argv from main + * @return allocated structure with global parameters + */ +glob_pars *parse_args(int argc, char **argv){ + int i; + size_t hlen = 1024; + char helpstring[1024], *hptr = helpstring; + snprintf(hptr, hlen, "Usage: %%s [args]\n\n\tWhere args are:\n"); + // format of help: "Usage: progname [args]\n" + change_helpstring(helpstring); + // parse arguments + parseargs(&argc, &argv, cmdlnopts); + if(help) showhelp(-1, cmdlnopts); + if(argc > 0){ + G.rest_pars_num = argc; + G.rest_pars = MALLOC(char *, argc); + for (i = 0; i < argc; i++) + G.rest_pars[i] = strdup(argv[i]); + } + return &G; +} + +// array with all opened logs - for error/warning messages +static Cl_log *errlogs = NULL; +static int errlogsnum = 0; + +/** + * @brief Cl_createlog - create log file: init mutex, test file open ability + * @param log - log structure + * @return 0 if all OK + */ +int Cl_createlog(Cl_log *log){ + if(!log || !log->logpath || log->loglevel >= LOGLEVEL_NONE) return 1; + FILE *logfd = fopen(log->logpath, "a"); + if(!logfd){ + WARN("Can't open log file"); + return 2; + } + fclose(logfd); + if(pthread_mutex_init(&log->mutex, NULL)){ + WARN("Can't init log mutes"); + return 3; + } + errlogs = realloc(errlogs, (++errlogsnum) *sizeof(Cl_log)); + if(!errlogs) errlogsnum = 0; + else memcpy(&errlogs[errlogsnum-1], log, sizeof(Cl_log)); + return 0; +} + +/** + * @brief Cl_putlog - put message to log file with/without timestamp + * @param timest - ==1 to put timestamp + * @param log - pointer to log structure + * @param lvl - message loglevel (if lvl > loglevel, message won't be printed) + * @param fmt - format and the rest part of message + * @return amount of symbols saved in file + */ +int Cl_putlogt(int timest, Cl_log *log, Cl_loglevel lvl, const char *fmt, ...){ + if(!log || !log->logpath || log->loglevel >= LOGLEVEL_NONE) return 0; + if(lvl > log->loglevel) return 0; + if(pthread_mutex_lock(&log->mutex)){ + WARN("Can't lock log mutex"); + return 0; + } + int i = 0; + FILE *logfd = fopen(log->logpath, "a"); + if(!logfd) goto rtn; + if(timest){ + char strtm[128]; + time_t t = time(NULL); + struct tm *curtm = localtime(&t); + strftime(strtm, 128, "%Y/%m/%d-%H:%M", curtm); + i = fprintf(logfd, "%s\t", strtm); + } + va_list ar; + va_start(ar, fmt); + i += vfprintf(logfd, fmt, ar); + va_end(ar); + fclose(logfd); +rtn: + pthread_mutex_unlock(&log->mutex); + return i; +} diff --git a/cmdlnopts.h b/cmdlnopts.h new file mode 100644 index 0000000..3b5d0c3 --- /dev/null +++ b/cmdlnopts.h @@ -0,0 +1,67 @@ +/* + * This file is part of the Onion_test project. + * Copyright 2020 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#ifndef CMDLNOPTS_H__ +#define CMDLNOPTS_H__ + +#include + +/* + * here are some typedef's for global data + */ +typedef struct{ + int vacuum; // make "vacuum" operation + int dumpUserDB; // dump users database + int dumpSessDB; // dump session database + int runServer; // run as server + int useradd; // add user[s] + char *port; // port to listen + char *certfile; // file with SSL certificate + char *keyfile; // file with SSL key + char *usersdb; // users database name + char *sessiondb; // session database name + char **userdel; // user names to delete + long long delatime; // minimal atime to delete sessions from DB + char *delsession; // delete session by sessID or sockID + int rest_pars_num; // number of rest parameters + char** rest_pars; // the rest parameters: array of char* +} glob_pars; + +extern glob_pars G; + +glob_pars *parse_args(int argc, char **argv); + +typedef enum{ + LOGLEVEL_DBG, // all messages + LOGLEVEL_MSG, // all without debug + LOGLEVEL_WARN, // only warnings and errors + LOGLEVEL_ERR, // only errors + LOGLEVEL_NONE // no logs +} Cl_loglevel; + +typedef struct{ + char *logpath; // full path to logfile + Cl_loglevel loglevel; // loglevel + pthread_mutex_t mutex; // log mutex +} Cl_log; + +int Cl_createlog(Cl_log *log); +int Cl_putlogt(int timest, Cl_log *log, Cl_loglevel lvl, const char *fmt, ...); + +#endif // CMDLNOPTS_H__ diff --git a/main.c b/main.c new file mode 100644 index 0000000..4bdbe2c --- /dev/null +++ b/main.c @@ -0,0 +1,184 @@ +/* + * This file is part of the Onion_test project. + * Copyright 2020 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "auth.h" +#include "cmdlnopts.h" + +onion_connection_status get(_U_ onion_handler *h, onion_request *req, onion_response *res){ + sessinfo *session = qookieSession(req); + if(!session){ + onion_response_write0(res, "NeedAuth"); + ONION_WARNING("try to run command without auth"); + return OCS_FORBIDDEN; + } + const char *path = onion_request_get_path(req); + if(path && *path == 0) path = NULL; + else printf("GET ask path %s\n", path); + const char *param = getQdata(req, "param"); + if(param) printf("User set param=%s\n", param); + if(getQdata(req, "getWSkey")){ + addWSkey(res, session); + } + freeSessInfo(&session); + return OCS_CLOSE_CONNECTION; +} + +static onion *o = NULL; +void signals(int signo){ + if(o) onion_free(o); + closeSQLite(); + exit(signo); +} + +static void runServer(){ + o = onion_new(O_POOL); + if(!(onion_flags(o) & O_SSL_AVAILABLE)){ + ONION_ERROR("SSL support is not available"); + signals(1); + } + int error = onion_set_certificate(o, 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(o, G.port); + onion_url *url = onion_root_url(o); + 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); + signal(SIGTERM, signals); + error = onion_listen(o); + if(error){ + ONION_ERROR("Cant create the server: %s", strerror(errno)); + } + onion_free(o); +} + +static char *getl(char *msg, int noempty){ + static char *inpstr = NULL; + do{ + FREE(inpstr); + printf("Enter %s: ", msg); + size_t n = 0; + if(getline(&inpstr, &n, stdin) < 0) FREE(inpstr); + else{ + char *nl = strchr(inpstr, '\n'); + if(nl) *nl = 0; + if(strlen(inpstr) < 1 && noempty){ + WARNX("Enter non-empty string"); + }else break; + } + }while(1); + return inpstr; +} + +static void adduser(){ + userinfo *User = MALLOC(userinfo, 1); + char *str; + do{ + str = getl("username", 1); + printf("Username: %s\n", str); + User->username = strdup(str); + do{ + str = getl("password", 1); + if(strlen(str) < 4){ + WARNX("Bad password: not less than 4 symbols"); + }else break; + }while(1); + User->password = strdup(crypt(str, "$6$")); + printf("PassHash: %s\n", User->password); + str = getl("access level", 1); + User->level = atoi(str); + printf("Level: %d\n", User->level); + str = getl("comment", 0); + if(strlen(str)){ + User->comment = strdup(str); + printf("Comment: %s", User->comment); + } + if(addUser(User)) continue; + if(!(str = getl("next? (y/n)", 0)) || 0 != strcmp(str, "y")) break; + }while(1); + freeUserInfo(&User); +} + +extern char *onion_sessions_generate_id(); + +int main(int argc, char **argv){ + initial_setup(); + parse_args(argc, argv); + if(!G.usersdb) ERRX("You should point users database file name"); + if(!G.sessiondb) ERRX("You should point session database file name"); + if(initSQLite(G.usersdb, G.sessiondb)) return 1; + if(G.vacuum){ + printf("VAAAA\n"); + vacuumSQLite(); + } + if(G.dumpUserDB) showAllUsers(); + if(G.userdel){ + for(int i = 0; G.userdel[i]; ++i){ + if(!deleteUser(G.userdel[i])) green("User %s deleted\n", G.userdel[i]); + } + } + 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)) + green("Session with ID %s deleted\n", G.delsession); + } + if(G.delatime){ + if(G.delatime < 0) G.delatime = time(NULL) - 31556926LL; + if(!deleteOldSessions((int64_t)G.delatime)) + green("All sessions with atime<%lld deleted\n", G.delatime); + } + if(G.runServer) runServer(); + closeSQLite(); + return 0; +} diff --git a/static/admin.html b/static/admin.html new file mode 100644 index 0000000..75c6670 --- /dev/null +++ b/static/admin.html @@ -0,0 +1,156 @@ + + + + + +Управление пользователями + + +
+
Вход
+
Новый пользователь
+
+ +
+ + diff --git a/static/auth.js b/static/auth.js new file mode 100644 index 0000000..e161c9e --- /dev/null +++ b/static/auth.js @@ -0,0 +1,71 @@ +auth = function(){ + var wsKey = ""; + function _ilogin(){ + $("inout").innerHTML = "Log in"; + $("inout").onclick = auth.login; + } + function _ilogout(){ + $("shadow").style.display = "none"; + $("inout").innerHTML = "Log out"; + $("inout").onclick = auth.logout; + } + function _wsk(request){ + var wsKey = request.responseText; + if(wsKey) console.log("Web key received: " + wsKey); + } + function reqAuth(request){ + var txt = request.responseText; + dbg("received " + txt); + if(txt == "AuthOK"){ // cookies received + sendrequest("get/?getWSkey", _wsk); + _ilogout(); + }else if(txt == "NeedAuth"){ + _ilogin(); + }else{ + parseErr(txt); + } + } + function init1(){ + sendrequest("auth/?check=1", reqAuth); + var l = document.createElement('a'); + l.id = "inout"; + l.href = "#"; + var s1 = document.createElement('style'); + s1.type = 'text/css'; + s1.innerHTML = ".inout{position:absolute;top:0;right:0;background-color:green;" + document.body.appendChild(s1); + l.className = "inout"; + document.body.appendChild(l); + var d = document.createElement('div'); + d.id = "shadow"; + var s = document.createElement('style'); + s.type = 'text/css'; + s.innerHTML = '.shadow{position:absolute;text-align:center;vertical-align:center;top:0;display:none;left:0;width:100%;height:100%;background-color:lightGrey;opacity:0.9;}'; + document.body.appendChild(s); + d.innerHTML = "
Login:
Password:
"; + d.className = "shadow"; + document.body.appendChild(d); + } + function login1(){ + $("shadow").style.display = "block"; + } + function logout1(){ + sendrequest("auth/?LogOut=1", _ilogin); + } + function sendlogpass(){ + $("shadow").style.display = "none"; + var l = $("login").value, p = $("passwd").value; + if(!l || !p){ + parseErr("give login and password"); + return; + } + var str = "auth/?login=" + l + "&passwd=" + p; + sendrequest(str, reqAuth); + } + return{ + init: init1, + login: login1, + logout: logout1, + send: sendlogpass + }; +}(); diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..6f3dc2c --- /dev/null +++ b/static/index.html @@ -0,0 +1,13 @@ + + + Index + + + + +

Text +

More text +

+

+ + diff --git a/static/pass.html b/static/pass.html new file mode 100644 index 0000000..801a152 --- /dev/null +++ b/static/pass.html @@ -0,0 +1,92 @@ + + +Авторизация + + + +
+

Пожалуйста введите регистрационную информацию для получения доступа к сервисам

+
+
Имя:
+
Пароль:

+
+
+
+
+ + diff --git a/static/pass.js b/static/pass.js new file mode 100644 index 0000000..6a592e6 --- /dev/null +++ b/static/pass.js @@ -0,0 +1,56 @@ +// move this file to the root html directory +// change const's EXURL & PASSURL +var KEY; +const PASSURL="https://ishtar.sao.ru/pass"; +const EXURL = "https://ishtar.sao.ru/cgi-bin/auth"; +function $(id){ + return document.getElementById(id); +} +function checkcookie(){ + var txt = document.cookie; + if(txt.length==0 || txt.indexOf('KEY')<0){ + $("inout").innerHTML = "Войти"; + return 0; + } + else{ + $("inout").innerHTML = "Выйти"; + return 1; + } +} +function getcookie(){ +/* без аргументов - для текущей страницы, + каждый аргумент - доп. "печенька" +*/ + var i, newurl = PASSURL+"?URL="+document.location.href; + for(i = 0; i < getcookie.arguments.length; i++) + newurl += "&URL=" + getcookie.arguments[i]; + if(!checkcookie()) + document.location.href = newurl; +} +function onEX(){ + var d = new Date(); + d.setTime(d.getTime() - 1000); + var str = "KEY=; expires="+d.toGMTString()+"; path="+document.location.pathname; + document.cookie = str; + window.location.reload(); +} +function exit(){ + var request = new XMLHttpRequest(); + request.open("POST", EXURL, true); + request.setRequestHeader("Accept-Charset", "koi8-r"); + request.setRequestHeader("Cookie", document.cookie); + request.overrideMimeType("multipart/form-data; charset=koi8-r"); + request.onreadystatechange=function(){ + if (request.readyState == 4){ + if (request.status == 200){ + onEX(); + } + else alert("Ошибка соединения"); + } + } + request.send("") +} +function inout(){ + if(checkcookie()) exit(); + else getcookie(); +} diff --git a/static/requests.js b/static/requests.js new file mode 100644 index 0000000..d42c0ad --- /dev/null +++ b/static/requests.js @@ -0,0 +1,43 @@ +const Debug = true; + +var elementsCache = {}; +function $(id) { + if (elementsCache[id] === undefined) + elementsCache[id] = document.getElementById(id); + return elementsCache[id]; +} + +function dbg(text){ + if(Debug) console.log("Debug message: " + text); +} + +function sendrequest(req_STR, onOK, postdata){ + var request = new XMLHttpRequest(), timeout_id; + dbg("send request " + req_STR); + var method = postdata ? "POST" : "GET"; + request.open(method, req_STR, true); + //request.setRequestHeader("Accept-Charset", "koi8-r"); + //request.overrideMimeType("multipart/form-data; charset=koi8-r"); + request.onreadystatechange=function(){ + if(request.readyState == 4){ + if(request.status == 200){ + clearTimeout(timeout_id); + if(onOK) onOK(request); + } + else{ + clearTimeout(timeout_id); + parseErr("request sending error"); + } + } + } + request.send(postdata); + timeout_id = setTimeout(function(){request.onreadystatechange=null; request.abort(); parseErr("request timeout");}, 5000); +} + +function parseErr(txt){ + console.log("Error: " + txt); + var msgDiv = $('errmsg'); + if(!msgDiv) return; + msgDiv.innerHTML = "Error: " + txt; + setTimeout(function(){msgDiv.innerHTML = "";}, 3500); +}