commit 29f158cb2ec3e50f1895908984399253c2d22f49 Author: Andrew Pamment Date: Tue Mar 22 11:48:59 2016 +1000 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9378e53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +*.sq3 +*.core diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1f09d84 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +CC=cc +CFLAGS=-I/usr/local/include +DEPS = bbs.h +OBJ = inih/ini.o bbs.o main.o users.o main_menu.o +%.o: %.c $(DEPS) + $(CC) -c -o $@ $< $(CFLAGS) + +magicka: $(OBJ) + $(CC) -o magicka -o $@ $^ $(CFLAGS) -L/usr/local/lib -lsqlite3 + +.PHONY: clean + +clean: + rm -f $(OBJ) magicka diff --git a/ansis/issue.ans b/ansis/issue.ans new file mode 100644 index 0000000..8a7df07 --- /dev/null +++ b/ansis/issue.ans @@ -0,0 +1,17 @@ +[?7h + + +°°°° + ±±°°ßßÛÛÛÛßßÛÛÛÛ ±±°°ßß ±±°°ßßÛÛÛÛ °° ±±°°ßßÛÛÛÛ ±±°°±±°°ßß  + ±±±±°°ÛÛ °°ÛÛ ±±±±°° ±±±±°°ÛÛ ßßßß ±±±±°°ÛÛ ±±±±°°ÛÛ ±±±±°°  + ±±±±°°°°°°°°±±±±°°°°±±±±°°°°±±°°±±±±±±±±°°°°±±±±°°°° + ²²±±±±°°±±°°²²±±±±°°²²±±±±°°±±±±²²±±²²±±±±°°²²±±±±°° + ²²²²±±±±±±±±²²²²±±±±²²²²±±±±²²±±²²²²±±±±²²²²±±±±²²²²±±±± + ²²²²±±±±±±±±²²²²Üܱ±±±²²²²±±±±²²²²²²²²±±±±²²²²±±±±²²²²Üܱ±±± + ²²²²²²±±²²±±²²²²²²±±²²²²Üܲ²²°²²²²²²²²Üܲ²±±²²²²ßß²²ÜÜ ²²²²²²±± + úßßßß ßßßß ²ßß ²ßßÜ ²²²²²²²²²ßß ²ßß + ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ ÜÛÛÛÛܲ²²Ý ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ ²²²²ÍÍÍÍÍÍÍÍÍÍ + Welcome To...Ûß ßÛ²²ß +Another Fine Magicka BBS + ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ + diff --git a/ansis/mainmenu.ans b/ansis/mainmenu.ans new file mode 100644 index 0000000..9f57caa --- /dev/null +++ b/ansis/mainmenu.ans @@ -0,0 +1,15 @@ +[?7h +ÜÜÜÜÜÜÜÜÜÜÜ ÜÜÜÜÜÜ ÜÜ ÜÜÜÜÜÜ ÜÜÜÜÜÜÜÜÜÜÜ ÜÜÜÜÜÜÜ ÜÜÜÜÜÜ ÜÜ Ü ÜÜ +²ß°Ü ²ß°Ü ²Û²ß° ²ÛÛÛ²ß°Ü ²Û²ß°Ü ²ß°Ü ²Û²ßÜÜ ßß ²ß°Ü ²Û²ßÛ Û² +°² ² °² ² ±ß°²ßß߲߱² °² ² ±ß°² ² °² ² ±ß±²Ü° ²ß°² ² ±ß°²Üß ²  +ßß ß ßß ß °Û ßß ß °Û ßß ßß ß °Û ßß ß ßß ß °Û ßßßßßß ßß ß °Û ßßßßßß + ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ + ³ M. Message Areas³ L. BBS List³ + ³ T. File Areas³ C. Chat System³ + ³ B. Bulletins³ U. User List³ + ³ O. Online Games³ 1. Last 10 Callers³ + ³³³ + ³³³ + ³³ G. Goodbye (Log Off)³ + ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ + diff --git a/ansis/newuser.ans b/ansis/newuser.ans new file mode 100644 index 0000000..e0edd3c --- /dev/null +++ b/ansis/newuser.ans @@ -0,0 +1,19 @@ +[?7hÜÜÜÜÜÜ ÜÜÜÜÜÜÜ ÜÜ ÜÜ ÛÛ ÜÜ Ü ÜÜ ÜÜÜÜÜÜÜ ÜÜÜÜÜÜÜ ÜÜÜÜÜÜ +²ß°Ü ²Û²ßÜÜ ßß ÛÛ°Û ²Û² ÛÛ²ßÛ Û²²ÛÜÜÜÜÜ ²ßÜÜ ßß ÛÛ°Ü ²Û +°² ² ±ß±²Ü° ²ß²² ßܱßßܲ² °²Üß ² ÜÜ ²ß±²Ü° ²ß²² ² °ß +ßß ß °Û ßßßßßß ßßßßßßÜßßßß ßßßßßß ßßßßßß° ßßßßßß ßß° +ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ + +This is where you would put the information you want to give +people before they sign up. + +For Example: + +Please use real information, else I will delete your +account. + +Do not hack the BBS, or I will send my flying monkeys +after you. + +Be nice. + diff --git a/bbs.c b/bbs.c new file mode 100644 index 0000000..0924e6a --- /dev/null +++ b/bbs.c @@ -0,0 +1,333 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "inih/ini.h" +#include "bbs.h" + +int mynode; +struct bbs_config conf; + +struct user_record *gUser; +int gSocket; + +int usertimeout; + +void timer_handler(int signum) { + if (signum == SIGALRM) { + if (gUser != NULL) { + gUser->timeleft--; + + if (gUser->timeleft <= 0) { + s_putstring(gSocket, "\r\n\r\nSorry, you're out of time today..\r\n"); + disconnect(gSocket); + } + + + } + usertimeout--; + if (usertimeout <= 0) { + s_putstring(gSocket, "\r\n\r\nTimeout waiting for input..\r\n"); + disconnect(gSocket); + } + } +} + +static int handler(void* user, const char* section, const char* name, + const char* value) +{ + struct bbs_config *conf = (struct bbs_config *)user; + + if (strcasecmp(section, "main") == 0) { + if (strcasecmp(name, "bbs name") == 0) { + conf->bbs_name = strdup(value); + } else if (strcasecmp(name, "sysop name") == 0) { + conf->sysop_name = strdup(value); + } else if (strcasecmp(name, "nodes") == 0) { + conf->nodes = atoi(value); + } else if (strcasecmp(name, "new user level") == 0) { + conf->newuserlvl = atoi(value); + } + } else if (strcasecmp(section, "paths") == 0){ + if (strcasecmp(name, "ansi path") == 0) { + conf->ansi_path = strdup(value); + } else if (strcasecmp(name, "bbs path") == 0) { + conf->bbs_path = strdup(value); + } + } + + return 1; +} + +void s_putchar(int socket, char c) { + write(socket, &c, 1); +} + +void s_putstring(int socket, char *c) { + write(socket, c, strlen(c)); +} + +void s_displayansi(int socket, char *file) { + FILE *fptr; + char c; + + char buffer[256]; + + sprintf(buffer, "%s/%s.ans", conf.ansi_path, file); + + fptr = fopen(buffer, "r"); + if (!fptr) { + return; + } + c = fgetc(fptr); + while (!feof(fptr)) { + s_putchar(socket, c); + c = fgetc(fptr); + } + fclose(fptr); +} + +char s_getchar(int socket) { + unsigned char c; + int len; + + len = read(socket, &c, 1); + + if (len == 0) { + disconnect(socket); + } + + while (c == 255) { + len = read(socket, &c, 1); + if (len == 0) { + disconnect(socket); + } + len = read(socket, &c, 1); + if (len == 0) { + disconnect(socket); + } + len = read(socket, &c, 1); + if (len == 0) { + disconnect(socket); + } + } + usertimeout = 10; + return (char)c; +} + +char s_getc(int socket) { + char c = s_getchar(socket); + + s_putchar(socket, c); + return (char)c; +} + +void s_readstring(int socket, char *buffer, int max) { + int i; + char c; + + for (i=0;i 0) { + buffer[i-1] = '\0'; + i -= 2; + s_putstring(socket, "\e[D \e[D"); + continue; + } + + if (c == '\n' || c == '\r') { + c = s_getchar(socket); + return; + } + s_putchar(socket, c); + buffer[i] = c; + buffer[i+1] = '\0'; + } +} + +void s_readpass(int socket, char *buffer, int max) { + int i; + char c; + + for (i=0;i 0) { + buffer[i-1] = '\0'; + i-=2; + s_putstring(socket, "\e[D \e[D"); + continue; + } + + if (c == '\n' || c == '\r') { + c = s_getchar(socket); + return; + } + s_putchar(socket, '*'); + buffer[i] = c; + buffer[i+1] = '\0'; + } +} + +void disconnect(int socket) { + char buffer[256]; + if (gUser != NULL) { + save_user(gUser); + } + sprintf(buffer, "%s/nodeinuse.%d", conf.bbs_path, mynode); + remove(buffer); + close(socket); + exit(0); +} + +void runbbs(int socket, char *config_path) { + char buffer[256]; + char password[17]; + + struct stat s; + FILE *nodefile; + int i; + char iac_echo[] = {255, 251, 1, '\0'}; + char iac_sga[] = {255, 251, 3, '\0'}; + struct user_record *user; + struct tm thetime; + struct tm oldtime; + time_t now; + struct itimerval itime; + struct sigaction sa; + + + write(socket, iac_echo, 3); + write(socket, iac_sga, 3); + + + + sprintf(buffer, "Magicka BBS v%d.%d (%s) Loading...\r\n", VERSION_MAJOR, VERSION_MINOR, VERSION_STR); + s_putstring(socket, buffer); + + + // Load BBS data + if (ini_parse(config_path, handler, &conf) <0) { + printf("Unable to load configuration ini (%s)!\n", config_path); + exit(-1); + } + + + + // find out which node we are + mynode = 0; + for (i=1;i<=conf.nodes;i++) { + sprintf(buffer, "%s/nodeinuse.%d", conf.bbs_path, i); + if (stat(buffer, &s) != 0) { + mynode = i; + nodefile = fopen(buffer, "w"); + if (!nodefile) { + printf("Error opening nodefile!\n"); + close(socket); + exit(1); + } + + fputs("UNKNOWN", nodefile); + fclose(nodefile); + + break; + } + } + + if (mynode == 0) { + s_putstring(socket, "Sorry, all nodes are in use. Please try later\r\n"); + close(socket); + exit(1); + } + gUser = NULL; + gSocket = socket; + usertimeout = 10; + + memset (&sa, 0, sizeof (sa)); + sa.sa_handler = &timer_handler; + sa.sa_flags = SA_RESTART; + sigaction (SIGALRM, &sa, 0); + + itime.it_interval.tv_sec = 60; + itime.it_interval.tv_usec = 0; + itime.it_value.tv_sec = 60; + itime.it_value.tv_usec = 0; + + setitimer (ITIMER_REAL, &itime, 0); + + s_displayansi(socket, "issue"); + + + + s_putstring(socket, "Enter your Login Name or NEW to create an account\r\n"); + s_putstring(socket, "Login:> "); + + s_readstring(socket, buffer, 25); + + if (strcasecmp(buffer, "new") == 0) { + user = new_user(socket); + } else { + s_putstring(socket, "\r\nPassword:> "); + s_readpass(socket, password, 16); + user = check_user_pass(socket, buffer, password); + if (user == NULL) { + s_putstring(socket, "\r\nIncorrect Login.\r\n"); + disconnect(socket); + } + + for (i=1;i<=conf.nodes;i++) { + sprintf(buffer, "%s/nodeinuse.%d", conf.bbs_path, i); + if (stat(buffer, &s) == 0) { + nodefile = fopen(buffer, "r"); + if (!nodefile) { + printf("Error opening nodefile!\n"); + disconnect(socket); + } + fgets(buffer, 256, nodefile); + + buffer[strlen(buffer) - 1] = '\0'; + + if (strcasecmp(user->loginname, buffer) == 0) { + fclose(nodefile); + s_putstring(socket, "You are already logged in.\r\n"); + disconnect(socket); + } + fclose(nodefile); + } + } + } + + sprintf(buffer, "%s/nodeinuse.%d", conf.bbs_path, mynode); + nodefile = fopen(buffer, "w"); + if (!nodefile) { + printf("Error opening nodefile!\n"); + close(socket); + exit(1); + } + + fputs(user->loginname, nodefile); + fclose(nodefile); + + // do post-login + // check time left + now = time(NULL); + localtime_r(&now, &thetime); + localtime_r(&user->laston, &oldtime); + + if (thetime.tm_mday != oldtime.tm_mday || thetime.tm_mon != oldtime.tm_mon || thetime.tm_year != oldtime.tm_year) { + user->timeleft = user->sec_info->timeperday; + user->laston = now; + save_user(user); + } + gUser = user; + + + // main menu + main_menu(socket, user); + disconnect(socket); +} diff --git a/bbs.h b/bbs.h new file mode 100644 index 0000000..fc7ca00 --- /dev/null +++ b/bbs.h @@ -0,0 +1,57 @@ +#ifndef __BBS_H__ +#define __BBS_H__ + +#include + +#define VERSION_MAJOR 0 +#define VERSION_MINOR 1 +#define VERSION_STR "dev" + +struct bbs_config { + char *bbs_name; + char *sysop_name; + + char *ansi_path; + char *bbs_path; + int nodes; + int newuserlvl; + +}; + +struct sec_level_t { + int timeperday; +}; + +struct user_record { + int id; + char *loginname; + char *password; + char *firstname; + char *lastname; + char *email; + char *location; + int sec_level; + struct sec_level_t *sec_info; + time_t laston; + int timeleft; +}; + + +extern void runbbs(int sock, char *config); + +extern void s_putchar(int socket, char c); +extern void s_putstring(int socket, char *c); +extern void s_displayansi(int socket, char *file); +extern char s_getchar(int socket); +extern void s_readstring(int socket, char *buffer, int max); +extern char s_getc(int socket); +extern void disconnect(int socket); + +extern int save_user(struct user_record *user); +extern int check_user(char *loginname); +extern struct user_record *new_user(int socket); +extern struct user_record *check_user_pass(int socket, char *loginname, char *password); + + +extern void main_menu(int socket, struct user_record *user); +#endif diff --git a/bbs.ini b/bbs.ini new file mode 100644 index 0000000..b113429 --- /dev/null +++ b/bbs.ini @@ -0,0 +1,9 @@ +[main] +BBS Name = Enigma BBS +Sysop Name = Andrew Pamment +nodes = 4 +New User Level = 10 + +[paths] +ANSI Path = /home/andrew/MagickaBBS/ansis +BBS Path = /home/andrew/MagickaBBS diff --git a/inih/LICENSE.txt b/inih/LICENSE.txt new file mode 100644 index 0000000..cb7ee2d --- /dev/null +++ b/inih/LICENSE.txt @@ -0,0 +1,27 @@ + +The "inih" library is distributed under the New BSD license: + +Copyright (c) 2009, Ben Hoyt +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Ben Hoyt nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/inih/ini.c b/inih/ini.c new file mode 100644 index 0000000..27ca85b --- /dev/null +++ b/inih/ini.c @@ -0,0 +1,194 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#include +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to null at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + strncpy(dest, src, size); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; +#else + char* line; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)malloc(INI_MAX_LINE); + if (!line) { + return -2; + } +#endif + + /* Scan through stream line by line */ + while (reader(line, INI_MAX_LINE, stream) != NULL) { + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (*start == ';' || *start == '#') { + /* Per Python configparser, allow both ; and # comments at the + start of a line */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!handler(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = lskip(end + 1); +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!handler(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} diff --git a/inih/ini.h b/inih/ini.h new file mode 100644 index 0000000..eaa554d --- /dev/null +++ b/inih/ini.h @@ -0,0 +1,93 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Typedef for prototype of handler function. */ +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See http://code.google.com/p/inih/issues/detail?id=21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Maximum line length for any line in INI file. */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __INI_H__ */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..eabc9f4 --- /dev/null +++ b/main.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include +#include +#include +#include "bbs.h" + +int main(int argc, char **argv) { + int socket_desc, client_sock, c, *new_sock; + int pid; + struct sockaddr_in server, client; + + if (argc < 2) { + printf("Usage ./magicka bbs.ini\n"); + exit(1); + } + + socket_desc = socket(AF_INET, SOCK_STREAM, 0); + if (socket_desc == -1) { + printf("Couldn't create socket..\n"); + return 1; + } + + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + server.sin_port = htons(2300); + + if (bind(socket_desc, (struct sockaddr *)&server, sizeof(server)) < 0) { + perror("Bind Failed, Error\n"); + return 1; + } + + listen(socket_desc, 3); + + c = sizeof(struct sockaddr_in); + + while ((client_sock = accept(socket_desc, (struct sockaddr *)&client, (socklen_t *)&c))) { + pid = fork(); + + if (pid < 0) { + perror("Error on fork\n"); + return 1; + } + + if (pid == 0) { + close(socket_desc); + + runbbs(client_sock, argv[1]); + + exit(0); + } else { + close(client_sock); + } + } +} diff --git a/main_menu.c b/main_menu.c new file mode 100644 index 0000000..d33563f --- /dev/null +++ b/main_menu.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include "bbs.h" + +extern struct bbs_config conf; + +void main_menu(int socket, struct user_record *user) { + int doquit = 0; + char c; + char prompt[128]; + + while (!doquit) { + s_displayansi(socket, "mainmenu"); + + + sprintf(prompt, "TL: %dm :> ", user->timeleft); + s_putstring(socket, prompt); + + c = s_getc(socket); + + switch(tolower(c)) { + case 'g': + { + s_putstring(socket, "\r\nAre you sure you want to log off? (Y/N)"); + c = s_getc(socket); + if (tolower(c) == 'y') { + doquit = 1; + } + } + break; + + } + } +} diff --git a/s10.ini b/s10.ini new file mode 100644 index 0000000..4bb117e --- /dev/null +++ b/s10.ini @@ -0,0 +1,2 @@ +[main] +time per day = 240 diff --git a/users.c b/users.c new file mode 100644 index 0000000..6453c2c --- /dev/null +++ b/users.c @@ -0,0 +1,374 @@ +#include +#include +#include +#include +#include +#include "bbs.h" +#include "inih/ini.h" + +extern struct bbs_config conf; + +static int secLevel(void* user, const char* section, const char* name, + const char* value) +{ + struct sec_level_t *conf = (struct sec_level_t *)user; + + if (strcasecmp(section, "main") == 0) { + if (strcasecmp(name, "time per day") == 0) { + conf->timeperday = atoi(value); + } + } + return 1; +} + +int save_user(struct user_record *user) { + char buffer[256]; + sqlite3 *db; + sqlite3_stmt *res; + int rc; + + char *update_sql = "UPDATE users SET password=?, firstname=?," + "lastname=?, email=?, location=?, sec_level=?, last_on=?, time_left=? where loginname LIKE ?"; + char *err_msg = 0; + + sprintf(buffer, "%s/users.sq3", conf.bbs_path); + + rc = sqlite3_open(buffer, &db); + + if (rc != SQLITE_OK) { + fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + + exit(1); + } + + rc = sqlite3_prepare_v2(db, update_sql, -1, &res, 0); + + if (rc == SQLITE_OK) { + sqlite3_bind_text(res, 1, user->password, -1, 0); + sqlite3_bind_text(res, 2, user->firstname, -1, 0); + sqlite3_bind_text(res, 3, user->lastname, -1, 0); + sqlite3_bind_text(res, 4, user->email, -1, 0); + sqlite3_bind_text(res, 5, user->location, -1, 0); + sqlite3_bind_int(res, 6, user->sec_level); + sqlite3_bind_int(res, 7, user->laston); + sqlite3_bind_int(res, 8, user->timeleft); + sqlite3_bind_text(res, 9, user->loginname, -1, 0); + } else { + fprintf(stderr, "Failed to execute statement: %s\n", sqlite3_errmsg(db)); + } + + + rc = sqlite3_step(res); + + if (rc != SQLITE_DONE) { + + printf("execution failed: %s", sqlite3_errmsg(db)); + sqlite3_close(db); + exit(1); + } + sqlite3_close(db); + return 1; + +} + +int inst_user(struct user_record *user) { + char buffer[256]; + sqlite3 *db; + sqlite3_stmt *res; + int rc; + char *create_sql = "CREATE TABLE IF NOT EXISTS users (" + "Id INTEGER PRIMARY KEY," + "loginname TEXT," + "password TEXT," + "firstname TEXT," + "lastname TEXT," + "email TEXT," + "location TEXT," + "sec_level INTEGER," + "last_on INTEGER," + "time_left INTEGER);"; + + char *insert_sql = "INSERT INTO users (loginname, password, firstname," + "lastname, email, location, sec_level, last_on, time_left) VALUES(?,?, ?, ?, ?, ?, ?, ?, ?)"; + char *err_msg = 0; + + sprintf(buffer, "%s/users.sq3", conf.bbs_path); + + rc = sqlite3_open(buffer, &db); + + if (rc != SQLITE_OK) { + fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + + exit(1); + } + + rc = sqlite3_exec(db, create_sql, 0, 0, &err_msg); + if (rc != SQLITE_OK ) { + + fprintf(stderr, "SQL error: %s\n", err_msg); + + sqlite3_free(err_msg); + sqlite3_close(db); + + return 1; + } + + rc = sqlite3_prepare_v2(db, insert_sql, -1, &res, 0); + + if (rc == SQLITE_OK) { + sqlite3_bind_text(res, 1, user->loginname, -1, 0); + sqlite3_bind_text(res, 2, user->password, -1, 0); + sqlite3_bind_text(res, 3, user->firstname, -1, 0); + sqlite3_bind_text(res, 4, user->lastname, -1, 0); + sqlite3_bind_text(res, 5, user->email, -1, 0); + sqlite3_bind_text(res, 6, user->location, -1, 0); + sqlite3_bind_int(res, 7, user->sec_level); + sqlite3_bind_int(res, 8, user->laston); + sqlite3_bind_int(res, 9, user->timeleft); + } else { + fprintf(stderr, "Failed to execute statement: %s\n", sqlite3_errmsg(db)); + } + + + rc = sqlite3_step(res); + + if (rc != SQLITE_DONE) { + + printf("execution failed: %s", sqlite3_errmsg(db)); + sqlite3_close(db); + exit(1); + } + sqlite3_close(db); + return 1; +} + +struct user_record *check_user_pass(int socket, char *loginname, char *password) { + struct user_record *user; + char buffer[256]; + sqlite3 *db; + sqlite3_stmt *res; + int rc; + char *sql = "SELECT * FROM users WHERE loginname LIKE ?"; + + sprintf(buffer, "%s/users.sq3", conf.bbs_path); + + rc = sqlite3_open(buffer, &db); + if (rc != SQLITE_OK) { + fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + + exit(1); + } + rc = sqlite3_prepare_v2(db, sql, -1, &res, 0); + + if (rc == SQLITE_OK) { + sqlite3_bind_text(res, 1, loginname, -1, 0); + } else { + fprintf(stderr, "Failed to execute statement: %s\n", sqlite3_errmsg(db)); + sqlite3_finalize(res); + sqlite3_close(db); + return NULL; + } + + int step = sqlite3_step(res); + + if (step == SQLITE_ROW) { + user = (struct user_record *)malloc(sizeof(struct user_record)); + user->id = sqlite3_column_int(res, 0); + user->loginname = strdup((char *)sqlite3_column_text(res, 1)); + user->password = strdup((char *)sqlite3_column_text(res, 2)); + user->firstname = strdup((char *)sqlite3_column_text(res, 3)); + user->lastname = strdup((char *)sqlite3_column_text(res, 4)); + user->email = strdup((char *)sqlite3_column_text(res, 5)); + user->location = strdup((char *)sqlite3_column_text(res, 6)); + user->sec_level = sqlite3_column_int(res, 7); + user->laston = (time_t)sqlite3_column_int(res, 8); + user->timeleft = sqlite3_column_int(res, 9); + + if (strcmp(password, user->password) != 0) { + free(user); + sqlite3_finalize(res); + sqlite3_close(db); + return NULL; + } + } else { + sqlite3_finalize(res); + sqlite3_close(db); + return NULL; + } + + sqlite3_finalize(res); + sqlite3_close(db); + + user->sec_info = (struct sec_level_t *)malloc(sizeof(struct sec_level_t)); + + sprintf(buffer, "%s/s%d.ini", conf.bbs_path, user->sec_level); + if (ini_parse(buffer, secLevel, user->sec_info) <0) { + printf("Unable to load sec Level ini (%s)!\n", buffer); + exit(-1); + } + + return user; +} + +int check_user(char *loginname) { + char buffer[256]; + sqlite3 *db; + sqlite3_stmt *res; + int rc; + char *sql = "SELECT * FROM users WHERE loginname = ?"; + + sprintf(buffer, "%s/users.sq3", conf.bbs_path); + + rc = sqlite3_open(buffer, &db); + + if (rc != SQLITE_OK) { + fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + + exit(1); + } + rc = sqlite3_prepare_v2(db, sql, -1, &res, 0); + + if (rc == SQLITE_OK) { + sqlite3_bind_text(res, 1, loginname, -1, 0); + } else { + fprintf(stderr, "Failed to execute statement: %s\n", sqlite3_errmsg(db)); + } + + int step = sqlite3_step(res); + + if (step == SQLITE_ROW) { + sqlite3_finalize(res); + sqlite3_close(db); + return 0; + } + + sqlite3_finalize(res); + sqlite3_close(db); + return 1; +} + +struct user_record *new_user(int socket) { + char buffer[256]; + struct user_record *user; + int done = 0; + char c; + int nameok = 0; + int passok = 0; + + user = (struct user_record *)malloc(sizeof(struct user_record)); + + s_displayansi(socket, "newuser"); + + do { + passok = 0; + nameok = 0; + do { + s_putstring(socket, "\r\nWhat is your login name: "); + s_readstring(socket, buffer, 16); + s_putstring(socket, "\r\n"); + if (strlen(buffer) < 3) { + s_putstring(socket, "Sorry, that name is too short.\r\n"); + continue; + } + + if (strchr(buffer, '%') != NULL) { + s_putstring(socket, "Sorry, invalid character.\r\n"); + continue; + } + + if (strcasecmp(buffer, "unknown") == 0) { + s_putstring(socket, "Sorry, that name is reserved.\r\n"); + continue; + } + + user->loginname = strdup(buffer); + nameok = check_user(user->loginname); + if (!nameok) { + s_putstring(socket, "Sorry, that name is in use.\r\n"); + free(user->loginname); + memset(buffer, 0, 256); + } + } while (!nameok); + s_putstring(socket, "What is your first name: "); + memset(buffer, 0, 256); + s_readstring(socket, buffer, 32); + s_putstring(socket, "\r\n"); + user->firstname = strdup(buffer); + + s_putstring(socket, "What is your last name: "); + memset(buffer, 0, 256); + s_readstring(socket, buffer, 32); + s_putstring(socket, "\r\n"); + user->lastname = strdup(buffer); + + s_putstring(socket, "What is your e-mail address: "); + memset(buffer, 0, 256); + s_readstring(socket, buffer, 64); + s_putstring(socket, "\r\n"); + user->email = strdup(buffer); + + s_putstring(socket, "Where are you located: "); + memset(buffer, 0, 256); + s_readstring(socket, buffer, 32); + s_putstring(socket, "\r\n"); + user->location = strdup(buffer); + + do { + s_putstring(socket, "What password would you like (at least 8 characters): "); + memset(buffer, 0, 256); + s_readstring(socket, buffer, 16); + s_putstring(socket, "\r\n"); + if (strlen(buffer) >= 8) { + passok = 1; + } else { + s_putstring(socket, "Password too short!\r\n"); + } + } while (!passok); + user->password = strdup(buffer); + + s_putstring(socket, "You Entered:\r\n"); + s_putstring(socket, "-------------------------------------\r\n"); + s_putstring(socket, "Login Name: "); + s_putstring(socket, user->loginname); + s_putstring(socket, "\r\nFirst Name: "); + s_putstring(socket, user->firstname); + s_putstring(socket, "\r\nLast Name: "); + s_putstring(socket, user->lastname); + s_putstring(socket, "\r\nE-mail: "); + s_putstring(socket, user->email); + s_putstring(socket, "\r\nLocation: "); + s_putstring(socket, user->location); + s_putstring(socket, "\r\nPassword: "); + s_putstring(socket, user->password); + s_putstring(socket, "\r\n-------------------------------------\r\n"); + s_putstring(socket, "Is this Correct? (Y/N)"); + c = s_getchar(socket); + while (tolower(c) != 'y' && tolower(c) != 'n') { + c = s_getchar(socket); + } + + if (tolower(c) == 'y') { + done = 1; + } + } while (!done); + user->sec_level = conf.newuserlvl; + + user->sec_info = (struct sec_level_t *)malloc(sizeof(struct sec_level_t)); + + sprintf(buffer, "%s/s%d.ini", conf.bbs_path, user->sec_level); + + if (ini_parse(buffer, secLevel, user->sec_info) <0) { + printf("Unable to load sec Level ini (%s)!\n", buffer); + exit(-1); + } + + user->laston = time(NULL); + user->timeleft = user->sec_info->timeperday; + inst_user(user); + + return user; +}