From 6cccee95cad1b89e1b2fbaff6090c5855b674033 Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Fri, 31 Jul 2020 14:50:57 +0300 Subject: [PATCH] initial commit --- .gitignore | 23 ++ CMakeLists.txt | 68 +++++ HOWTO.cert | 5 + auth.c | 682 +++++++++++++++++++++++++++++++++++++++++++++ auth.h | 64 +++++ cert.key | 28 ++ cert.pem | 21 ++ cmdlnopts.c | 147 ++++++++++ cmdlnopts.h | 67 +++++ main.c | 184 ++++++++++++ static/admin.html | 156 +++++++++++ static/auth.js | 71 +++++ static/index.html | 13 + static/pass.html | 92 ++++++ static/pass.js | 56 ++++ static/requests.js | 43 +++ 16 files changed, 1720 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 HOWTO.cert create mode 100644 auth.c create mode 100644 auth.h create mode 100644 cert.key create mode 100644 cert.pem create mode 100644 cmdlnopts.c create mode 100644 cmdlnopts.h create mode 100644 main.c create mode 100644 static/admin.html create mode 100644 static/auth.js create mode 100644 static/index.html create mode 100644 static/pass.html create mode 100644 static/pass.js create mode 100644 static/requests.js 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); +}