summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpiaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2009-06-09 22:17:57 +0800
committerpiaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2009-06-09 22:17:57 +0800
commitd4e063b55ca7c7ffbea4d8601cae008e64a28521 (patch)
tree38f2f97acb8277899cb8d2beddf92a928f48187b
parent4370dd5a94a80d21a9bcb7d5e464d4653f021dff (diff)
downloadpttbbs-d4e063b55ca7c7ffbea4d8601cae008e64a28521.tar
pttbbs-d4e063b55ca7c7ffbea4d8601cae008e64a28521.tar.gz
pttbbs-d4e063b55ca7c7ffbea4d8601cae008e64a28521.tar.bz2
pttbbs-d4e063b55ca7c7ffbea4d8601cae008e64a28521.tar.lz
pttbbs-d4e063b55ca7c7ffbea4d8601cae008e64a28521.tar.xz
pttbbs-d4e063b55ca7c7ffbea4d8601cae008e64a28521.tar.zst
pttbbs-d4e063b55ca7c7ffbea4d8601cae008e64a28521.zip
* the long-waited high performance login daemon
* works only with tunnel mode mbbsd git-svn-id: http://opensvn.csie.org/pttbbs/trunk/pttbbs@4527 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
-rw-r--r--daemon/logind/Makefile28
-rw-r--r--daemon/logind/loginc.c81
-rw-r--r--daemon/logind/logind.c1077
3 files changed, 1186 insertions, 0 deletions
diff --git a/daemon/logind/Makefile b/daemon/logind/Makefile
new file mode 100644
index 00000000..c984239c
--- /dev/null
+++ b/daemon/logind/Makefile
@@ -0,0 +1,28 @@
+# $Id: Makefile 4384 2008-06-28 15:50:14Z wens $
+SRCROOT= ../..
+.include "$(SRCROOT)/pttbbs.mk"
+
+PROGRAMS= logind loginc
+UTILDIR= $(SRCROOT)/util
+UTILOBJ= $(UTILDIR)/util_var.o $(UTILDIR)/util_cache.o $(UTILDIR)/util_passwd.o
+
+LDLIBS+=$(SRCROOT)/common/bbs/libcmbbs.a \
+ $(SRCROOT)/common/sys/libcmsys.a \
+ $(SRCROOT)/common/osdep/libosdep.a
+
+all: ${PROGRAMS}
+
+.SUFFIXES: .c .cpp .o
+.c.o:
+ $(CC) $(CFLAGS) -c $*.c
+.cpp.o:
+ $(CXX) $(CXXFLAGS) -c $*.cpp
+
+logind: logind.o
+ ${CC} ${CFLAGS} ${LDFLAGS} -levent -o $@ $> $(UTILOBJ) $(LDLIBS)
+
+loginc: loginc.o
+ ${CC} ${CFLAGS} ${LDFLAGS} -levent -o $@ $> $(LDLIBS)
+
+clean:
+ rm -f *~ ${PROGRAMS} logind.o loginc.o
diff --git a/daemon/logind/loginc.c b/daemon/logind/loginc.c
new file mode 100644
index 00000000..21a4d434
--- /dev/null
+++ b/daemon/logind/loginc.c
@@ -0,0 +1,81 @@
+// $Id$
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "bbs.h"
+#include "logind.h"
+
+// standalone client to test logind
+
+int main(int argc, char *argv[])
+{
+ int fd;
+ char buf[64];
+
+ memset(buf, 0, sizeof(buf));
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s tunnel_path\n", argv[0]);
+ return 0;
+ }
+
+ if ( (fd = toconnect(argv[1])) < 0 ) {
+ perror("toconnect");
+ return 1;
+ }
+
+ puts("start waiting!\n");
+ while (1)
+ {
+ int xfd = 0, ok = 1, i;
+ const char *encoding = "";
+ login_data dat = {0};
+
+ if ((xfd = recv_remote_fd(fd)) < 0)
+ {
+ fprintf(stderr, "recv_remote_fd error. abort.\r\n");
+ break;
+ }
+ puts("got recv_remote_fd");
+ if (read(fd, &dat, sizeof(dat)) <= 0)
+ {
+ fprintf(stderr, "recv error. abort.\r\n");
+ break;
+ }
+#ifdef CONVERT
+ switch (dat.encoding)
+ {
+ case CONV_GB:
+ encoding = "[GB] ";
+ break;
+ case CONV_UTF8:
+ encoding = "[UTF-8] ";
+ break;
+ }
+#endif
+ fprintf(stderr, "got login data: userid=%s, (%dx%d) %sfrom: %s\r\n",
+ dat.userid, dat.t_cols, dat.t_lines,
+ encoding, dat.hostip);
+ write(fd, &ok, sizeof(ok));
+
+ dup2(xfd, 0);
+ dup2(xfd, 1);
+
+ // write something to user!
+ printf("\r\nwelcome, %s from %s! greetings from loginc!\r\n", dat.userid, dat.hostip);
+ printf("please give me 3 key hits to test i/o!\r\n");
+
+ for (i = 0; i < 3; i++)
+ {
+ char c;
+ read(0, &c, sizeof(c));
+ printf("you hit %02X\r\n", c);
+ }
+ printf("\r\ntest complete. connection closed.\r\n");
+ close(xfd);
+ }
+ return 0;
+}
+
+
diff --git a/daemon/logind/logind.c b/daemon/logind/logind.c
new file mode 100644
index 00000000..355f07bd
--- /dev/null
+++ b/daemon/logind/logind.c
@@ -0,0 +1,1077 @@
+// The High Performance Login Daemon (works with tunnel mode)
+// $Id: logind.c,v 1.1 2009/06/09 13:13:42 piaip Exp piaip $
+//
+// Create: Hung-Te Lin <piaip@csie.ntu.edu.tw>
+// Contributors: wens, kcwu
+// Initial Date: 2009/06/01
+//
+// Copyright (C) 2009, Hung-Te Lin <piaip@csie.ntu.edu.tw>
+// All rights reserved
+
+// TODO:
+// 1. cache guest's usernum and check if too many guests online
+// 2. change close connection to 'wait until user hit then close' (drop?)
+// 3. make original port information in margs(mbbsd)?
+
+#include <stdio.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <event.h>
+
+#define _BBS_UTIL_C_
+#include "bbs.h"
+#include "banip.h"
+#include "logind.h"
+
+#ifndef OPTIMIZE_SOCKET
+#define OPTIMIZE_SOCKET(sock) do {} while(0)
+#endif
+
+#ifndef SOCKET_QLEN
+#define SOCKET_QLEN (10)
+#endif
+
+#ifndef AUTHFAIL_SLEEP_SEC
+#define AUTHFAIL_SLEEP_SEC (15)
+#endif
+
+#ifndef OVERLOAD_SLEEP_SEC
+#define OVERLOAD_SLEEP_SEC (60)
+#endif
+
+#ifndef BAN_SLEEP_SEC
+#define BAN_SLEEP_SEC (60)
+#endif
+
+#define MAX_TEXT_SCREEN_LINES (24)
+
+///////////////////////////////////////////////////////////////////////
+// global variables
+int g_tunnel; // tunnel for service daemon
+int g_reload_data = 1; // request to reload data
+
+// server status
+int g_overload = 0;
+int g_banned = 0;
+
+///////////////////////////////////////////////////////////////////////
+// login context, constants and states
+
+enum {
+ LOGIN_STATE_START = 1,
+ LOGIN_STATE_USERID,
+ LOGIN_STATE_PASSWD,
+ LOGIN_STATE_AUTH,
+
+ LOGIN_HANDLE_WAIT = 1,
+ LOGIN_HANDLE_BEEP,
+ LOGIN_HANDLE_OUTC,
+ LOGIN_HANDLE_BS,
+ LOGIN_HANDLE_PROMPT_PASSWD,
+ LOGIN_HANDLE_START_AUTH,
+
+ AUTH_RESULT_STOP = -2,
+ AUTH_RESULT_FREEID = -1,
+ AUTH_RESULT_FAIL = 0,
+ AUTH_RESULT_RETRY = AUTH_RESULT_FAIL,
+ AUTH_RESULT_OK = 1,
+};
+
+#ifdef CONVERT
+#define IDBOXLEN (IDLEN+2) // one extra char for encoding
+#else
+#define IDBOXLEN (IDLEN+1)
+#endif
+
+typedef struct {
+ int state;
+ int retry;
+ int encoding;
+ int t_lines;
+ int t_cols;
+ Fnv32_t client_code;
+ char userid [IDBOXLEN];
+ char passwd [PASSLEN+1];
+ char hostip [IPV4LEN+1];
+} login_ctx;
+
+typedef struct {
+ struct bufferevent *bufev;
+ struct event ev;
+ TelnetCtx telnet;
+ login_ctx ctx;
+} login_conn_ctx;
+
+void
+login_ctx_init(login_ctx *ctx)
+{
+ assert(ctx);
+ memset(ctx, 0, sizeof(login_ctx));
+ ctx->client_code = FNV1_32_INIT;
+ ctx->state = LOGIN_STATE_START;
+}
+
+int
+login_ctx_retry(login_ctx *ctx)
+{
+ assert(ctx);
+ ctx->state = LOGIN_STATE_START;
+ ctx->encoding = 0;
+ memset(ctx->userid, 0, sizeof(ctx->userid));
+ memset(ctx->passwd, 0, sizeof(ctx->passwd));
+ // do not touch hostip, client code, t_*
+ ctx->retry ++;
+ return ctx->retry;
+}
+
+int
+login_ctx_handle(login_ctx *ctx, int c)
+{
+ int l;
+
+ assert(ctx);
+ switch(ctx->state)
+ {
+ case LOGIN_STATE_START:
+ case LOGIN_STATE_USERID:
+ l = strlen(ctx->userid);
+
+ if (c == KEY_ENTER)
+ {
+ ctx->state = LOGIN_STATE_PASSWD;
+ return LOGIN_HANDLE_PROMPT_PASSWD;
+ }
+ if (c == KEY_BS)
+ {
+ if (!l)
+ return LOGIN_HANDLE_BEEP;
+ ctx->userid[l-1] = 0;
+ return LOGIN_HANDLE_BS;
+ }
+
+ if (!isascii(c) || !isprint(c) ||
+ l+1 >= sizeof(ctx->userid))
+ return LOGIN_HANDLE_BEEP;
+
+ ctx->userid[l] = c;
+
+ return LOGIN_HANDLE_OUTC;
+
+ case LOGIN_STATE_PASSWD:
+ l = strlen(ctx->passwd);
+
+ if (c == KEY_ENTER)
+ {
+ // no matter what, apply the passwd
+ ctx->state = LOGIN_STATE_AUTH;
+ return LOGIN_HANDLE_START_AUTH;
+ }
+ if (c == KEY_BS)
+ {
+ if (!l)
+ return LOGIN_HANDLE_BEEP;
+ ctx->passwd[l-1] = 0;
+ return LOGIN_HANDLE_WAIT;
+ }
+
+ if (!isascii(c) || !isprint(c) ||
+ l+1 >= sizeof(ctx->passwd))
+ return LOGIN_HANDLE_BEEP;
+
+ ctx->passwd[l] = c;
+
+ return LOGIN_HANDLE_WAIT;
+
+ default:
+ break;
+ }
+ return LOGIN_HANDLE_BEEP;
+}
+
+///////////////////////////////////////////////////////////////////////
+// I/O
+
+static ssize_t
+_buff_write(login_conn_ctx *conn, const void *buf, size_t nbytes)
+{
+ return bufferevent_write(conn->bufev, buf, nbytes);
+}
+
+///////////////////////////////////////////////////////////////////////
+// Mini Terminal
+
+static void
+_mt_bell(login_conn_ctx *conn)
+{
+ static const char b = Ctrl('G');
+ _buff_write(conn, &b, 1);
+}
+
+static void
+_mt_bs(login_conn_ctx *conn)
+{
+ static const char cmd[] = "\b \b";
+ _buff_write(conn, cmd, sizeof(cmd)-1);
+}
+
+static void
+_mt_home(login_conn_ctx *conn)
+{
+ static const char cmd[] = ESC_STR "[H";
+ _buff_write(conn, cmd, sizeof(cmd)-1);
+}
+
+static void
+_mt_clrtoeol(login_conn_ctx *conn)
+{
+ static const char cmd[] = ESC_STR "[K";
+ _buff_write(conn, cmd, sizeof(cmd)-1);
+}
+
+static void
+_mt_clear(login_conn_ctx *conn)
+{
+ static const char cmd[] = ESC_STR "[2J";
+ _mt_home(conn);
+ _buff_write(conn, cmd, sizeof(cmd)-1);
+}
+
+static void
+_mt_move_yx(login_conn_ctx *conn, const char *mcmd)
+{
+ static const char cmd1[] = ESC_STR "[",
+ cmd2[] = "H";
+ _buff_write(conn, cmd1, sizeof(cmd1)-1);
+ _buff_write(conn, mcmd, strlen(mcmd));
+ _buff_write(conn, cmd2, sizeof(cmd2)-1);
+}
+
+///////////////////////////////////////////////////////////////////////
+// Telnet Protocol
+
+static void
+_telnet_resize_term_cb(void *resize_arg, int w, int h)
+{
+ login_ctx *ctx = (login_ctx*) resize_arg;
+ assert(ctx);
+ ctx->t_lines = h;
+ ctx->t_cols = w;
+}
+
+static void
+_telnet_update_cc_cb(void *cc_arg, unsigned char c)
+{
+ login_ctx *ctx = (login_ctx*) cc_arg;
+ assert(ctx);
+ // fprintf(stderr, "update cc: %08lX", (unsigned long)ctx->client_code);
+ FNV1A_CHAR(c, ctx->client_code);
+ // fprintf(stderr, "-> %08lX\r\n", (unsigned long)ctx->client_code);
+}
+
+static void
+_telnet_write_data_cb(void *write_arg, int fd, const void *buf, size_t nbytes)
+{
+ login_conn_ctx *conn = (login_conn_ctx *)write_arg;
+ _buff_write(conn, buf, nbytes);
+}
+
+const static struct TelnetCallback
+telnet_callback = {
+ _telnet_write_data_cb,
+ _telnet_resize_term_cb,
+#ifdef DETECT_CLIENT
+ _telnet_update_cc_cb,
+#else
+ NULL,
+#endif
+};
+
+///////////////////////////////////////////////////////////////////////
+// Socket Option
+
+static int
+_set_connection_opt(int sock)
+{
+ const int szrecv = 1024, szsend=4096;
+ const struct linger lin = {0};
+
+ // keep alive: server will check target connection. (around 2hr)
+ const int on = 1;
+ setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on));
+
+ // fast close
+ setsockopt(sock, SOL_SOCKET, SO_LINGER, &lin, sizeof(lin));
+ // adjust transmission window
+ setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&szrecv, sizeof(szrecv));
+ setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&szsend, sizeof(szsend));
+ OPTIMIZE_SOCKET(sock);
+
+ return 0;
+}
+
+static int
+_set_bind_opt(int sock)
+{
+ const int on = 1;
+
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on));
+ _set_connection_opt(sock);
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////
+// Draw Screen
+
+#ifdef STR_GUEST
+# define MSG_GUEST ",或以[" STR_GUEST "]參觀"
+#else
+# define MSG_GUEST ""
+#endif
+
+#ifdef STR_REGNEW
+# define MSG_REGNEW ",或以[new]註冊"
+#else
+# define MSG_REGNEW
+#endif
+
+#define BOTTOM_YX "24;1"
+#define LOGIN_PROMPT_MSG ANSI_RESET "請輸入代號" MSG_GUEST MSG_REGNEW ": " ANSI_REVERSE
+#define LOGIN_PROMPT_YX "21;1"
+#define PASSWD_PROMPT_MSG ANSI_RESET MSG_PASSWD
+#define PASSWD_PROMPT_YX "22;1"
+#define PASSWD_CHECK_MSG ANSI_RESET "正在檢查帳號與密碼..."
+#define PASSWD_CHECK_YX PASSWD_PROMPT_YX
+#define AUTH_SUCCESS_MSG ANSI_RESET "密碼正確! 開始登入系統...\r\n"
+#define AUTH_SUCCESS_YX PASSWD_PROMPT_YX
+#define FREEAUTH_SUCCESS_MSG ANSI_RESET "開始登入系統...\r\n"
+#define FREEAUTH_SUCCESS_YX AUTH_SUCCESS_YX
+#define AUTH_FAIL_MSG ANSI_RESET ERR_PASSWD
+#define AUTH_FAIL_YX PASSWD_PROMPT_YX
+#define USERID_EMPTY_MSG ANSI_RESET "請重新輸入。"
+#define USERID_EMPTY_YX PASSWD_PROMPT_YX
+#define SERVICE_FAIL_MSG ANSI_COLOR(0;1;31) "抱歉,系統目前無法使用,請稍候再試。" ANSI_RESET "\r\n"
+#define SERVICE_FAIL_YX BOTTOM_YX
+#define OVERLOAD_CPU_MSG "系統過載, 請稍後再來...\r\n"
+#define OVERLOAD_CPU_YX BOTTOM_YX
+#define OVERLOAD_USER_MSG "由於人數過多,請您稍後再來...\r\n"
+#define OVERLOAD_USER_YX BOTTOM_YX
+
+#define FN_WELCOME BBSHOME "/etc/Welcome"
+#define FN_GOODBYE BBSHOME "/etc/goodbye"
+#define FN_BAN BBSHOME "/" BAN_FILE
+
+static char *welcome_screen, *goodbye_screen, *ban_screen;
+
+static void
+load_text_screen_file(const char *filename, char **pptr)
+{
+ FILE *fp;
+ off_t sz, wsz, psz;
+ char *p, *s = NULL;
+ int max_lines = MAX_TEXT_SCREEN_LINES;
+
+ sz = dashs(filename);
+ if (sz < 1)
+ {
+ free(*pptr);
+ *pptr = NULL;
+ return;
+ }
+ wsz = sz*2 +1; // *2 for cr+lf, extra one byte for safe strchr().
+
+ // XXX TODO use realloc?
+ assert(pptr);
+ s = *pptr;
+ s = realloc(s, wsz);
+ *pptr = s;
+ if (!s)
+ return;
+
+ memset(s, 0, wsz);
+ p = s;
+ psz = wsz;
+
+ fp = fopen(filename, "rt");
+ if (!fp)
+ {
+ free(s);
+ return;
+ }
+ while ( max_lines-- > 0 &&
+ fgets(p, psz, fp))
+ {
+ psz -= strlen(p);
+ p += strlen(p);
+ *p ++ = '\r';
+ }
+ fclose(fp);
+ *pptr = s;
+}
+
+static void regular_check();
+
+static void
+reload_data()
+{
+ regular_check();
+
+ if (!g_reload_data)
+ return;
+
+ fprintf(stderr, "start reloading data.\r\n");
+ g_reload_data = 0;
+ load_text_screen_file(FN_WELCOME, &welcome_screen);
+ load_text_screen_file(FN_GOODBYE, &goodbye_screen);
+ load_text_screen_file(FN_BAN, &ban_screen);
+}
+
+static void
+draw_text_screen(login_conn_ctx *conn, const char *scr)
+{
+ const char *ps, *pe;
+ char buf[64];
+ time4_t now;
+
+ _mt_clear(conn);
+ if (!scr || !*scr)
+ return;
+
+ // draw the screen from text file
+ // XXX Because the text file may contain a very small subset of escape sequence
+ // *t[Cdate] and *u[SHM->UTMPnumber], we implement a tiny version of
+ // expand_esc_star here.
+
+ ps = pe = scr;
+ while(pe && *pe)
+ {
+ // find a safe range between (ps, pe) to print
+ pe = strchr(pe, ESC_CHR);
+ if (!pe)
+ {
+ // no more escapes, print all.
+ _buff_write(conn, ps, strlen(ps));
+ break;
+ }
+
+ // let's look ahead
+ pe++;
+
+ // if not esc_star, search for next.
+ if (*pe != '*')
+ continue;
+
+ // flush previous data
+ _buff_write(conn, ps, pe - ps - 1);
+
+ buf[0] = 0; pe++;
+
+ // expand the star
+ switch(*pe)
+ {
+ case 't': // current time
+ // strcpy(buf, "[date]");
+ now = time(0);
+ strlcpy(buf, Cdate(&now), sizeof(buf));
+ break;
+
+ case 'u': // current online users
+ // strcpy(buf, "[SHM->UTMPnumber]");
+ snprintf(buf, sizeof(buf), "%d", SHM->UTMPnumber);
+ break;
+ }
+
+ if(buf[0])
+ _buff_write(conn, buf, strlen(buf));
+ pe ++;
+ ps = pe;
+ }
+}
+
+static void
+draw_goodbye(login_conn_ctx *conn)
+{
+ draw_text_screen(conn, goodbye_screen);
+}
+
+static void
+draw_userid_prompt(login_conn_ctx *conn)
+{
+ char box[IDBOXLEN];
+
+ _mt_move_yx(conn, LOGIN_PROMPT_YX); _mt_clrtoeol(conn);
+ _buff_write(conn, LOGIN_PROMPT_MSG, sizeof(LOGIN_PROMPT_MSG)-1);
+ // draw input box
+ memset(box, ' ', sizeof(box));
+ _buff_write (conn, box, sizeof(box));
+ memset(box, '\b',sizeof(box));
+ _buff_write (conn, box, sizeof(box));
+}
+
+static void
+draw_passwd_prompt(login_conn_ctx *conn)
+{
+ _mt_move_yx(conn, PASSWD_PROMPT_YX); _mt_clrtoeol(conn);
+ _buff_write(conn, PASSWD_PROMPT_MSG, sizeof(PASSWD_PROMPT_MSG)-1);
+}
+
+static void
+draw_empty_userid_warn(login_conn_ctx *conn)
+{
+ _mt_move_yx(conn, USERID_EMPTY_YX); _mt_clrtoeol(conn);
+ _buff_write(conn, USERID_EMPTY_MSG, sizeof(USERID_EMPTY_MSG)-1);
+}
+
+static void
+draw_check_passwd(login_conn_ctx *conn)
+{
+ _mt_move_yx(conn, PASSWD_CHECK_YX); _mt_clrtoeol(conn);
+ _buff_write(conn, PASSWD_CHECK_MSG, sizeof(PASSWD_CHECK_MSG)-1);
+}
+
+static void
+draw_auth_success(login_conn_ctx *conn, int free)
+{
+ if (free)
+ {
+ _mt_move_yx(conn, FREEAUTH_SUCCESS_YX); _mt_clrtoeol(conn);
+ _buff_write(conn, FREEAUTH_SUCCESS_MSG, sizeof(FREEAUTH_SUCCESS_MSG)-1);
+ } else {
+ _mt_move_yx(conn, AUTH_SUCCESS_YX); _mt_clrtoeol(conn);
+ _buff_write(conn, AUTH_SUCCESS_MSG, sizeof(AUTH_SUCCESS_MSG)-1);
+ }
+}
+
+static void
+draw_auth_fail(login_conn_ctx *conn)
+{
+ _mt_move_yx(conn, AUTH_FAIL_YX); _mt_clrtoeol(conn);
+ _buff_write(conn, AUTH_FAIL_MSG, sizeof(AUTH_FAIL_MSG)-1);
+}
+
+static void
+draw_service_failure(login_conn_ctx *conn)
+{
+ _mt_move_yx(conn, SERVICE_FAIL_YX); _mt_clrtoeol(conn);
+ _buff_write(conn, SERVICE_FAIL_MSG, sizeof(SERVICE_FAIL_MSG)-1);
+}
+
+static void
+draw_overload(login_conn_ctx *conn, int type)
+{
+ if (type == 1)
+ {
+ _mt_move_yx(conn, OVERLOAD_CPU_MSG); _mt_clrtoeol(conn);
+ _buff_write(conn, OVERLOAD_CPU_MSG, sizeof(OVERLOAD_CPU_MSG)-1);
+ }
+ else if (type == 2)
+ {
+ _mt_move_yx(conn, OVERLOAD_USER_MSG); _mt_clrtoeol(conn);
+ _buff_write(conn, OVERLOAD_USER_MSG, sizeof(OVERLOAD_USER_MSG)-1);
+ }
+ else {
+ assert(false);
+ _mt_move_yx(conn, OVERLOAD_CPU_MSG); _mt_clrtoeol(conn);
+ _buff_write(conn, OVERLOAD_CPU_MSG, sizeof(OVERLOAD_CPU_MSG)-1);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////
+// BBS Logic
+
+#define REGULAR_CHECK_DURATION (5)
+
+static void
+regular_check()
+{
+ // cache results
+ static time_t last_check_time = 0;
+
+ time_t now = time(0);
+
+ if ( now - last_check_time < REGULAR_CHECK_DURATION)
+ return;
+
+ last_check_time = now;
+ g_overload = 0;
+ g_banned = 0;
+
+ if (cpuload(NULL) > MAX_CPULOAD)
+ {
+ g_overload = 1;
+ }
+ else if (SHM->UTMPnumber >= MAX_ACTIVE
+#ifdef DYMAX_ACTIVE
+ || (SHM->GV2.e.dymaxactive > 2000 &&
+ SHM->UTMPnumber >= SHM->GV2.e.dymaxactive)
+#endif
+ )
+ {
+ ++SHM->GV2.e.toomanyusers;
+ g_overload = 2;
+ }
+
+ if (dashf(FN_BAN))
+ {
+ g_banned = 1;
+ load_text_screen_file(FN_BAN, &ban_screen);
+ }
+}
+
+static int
+check_banip(char *host)
+{
+ uint32_t thisip = ipstr2int(host);
+ return uintbsearch(thisip, &banip[1], banip[0]) ? 1 : 0;
+}
+
+static const char *
+auth_is_free_userid(const char *userid)
+{
+#if defined(STR_GUEST) && !defined(NO_GUEST_ACCOUNT_REG)
+ if (strcasecmp(userid, STR_GUEST) == 0)
+ return STR_GUEST;
+#endif
+
+#ifdef STR_REGNEW
+ if (strcasecmp(userid, STR_REGNEW) == 0)
+ return STR_REGNEW;
+#endif
+
+ return NULL;
+}
+
+// NOTE ctx->passwd will be destroyed (must > PASSLEN+1)
+// NOTE ctx->userid may be changed (must > IDLEN+1)
+static int
+auth_user_challenge(login_ctx *ctx)
+{
+ char *uid = ctx->userid,
+ *passbuf = ctx->passwd;
+
+ const char *free_uid = auth_is_free_userid(uid);
+
+ if (free_uid)
+ {
+ strlcpy(ctx->userid, free_uid, sizeof(ctx->userid));
+ return AUTH_RESULT_FREEID;
+ }
+
+ // reuse cuser
+ memset(&cuser, 0, sizeof(cuser));
+ if( initcuser(uid) < 1 || !cuser.userid[0] ||
+ !checkpasswd(cuser.passwd, passbuf) )
+ {
+ if (cuser.userid[0])
+ strcpy(uid, cuser.userid);
+ return AUTH_RESULT_FAIL;
+ }
+
+ // normalize user id
+ strcpy(uid, cuser.userid);
+ return AUTH_RESULT_OK;
+}
+
+static int
+start_service(int fd, login_ctx *ctx)
+{
+ login_data ld = {0};
+ int ack = 0;
+
+ if (!g_tunnel)
+ return 0;
+
+ ld.cb = sizeof(ld);
+ strlcpy(ld.userid, ctx->userid, sizeof(ld.userid));
+ strlcpy(ld.hostip, ctx->hostip, sizeof(ld.hostip));
+ ld.encoding = ctx->encoding;
+ ld.client_code = ctx->client_code;
+ ld.t_lines = 25;
+ ld.t_cols = 80;
+ if (ctx->t_lines > ld.t_lines)
+ ld.t_lines = ctx->t_lines;
+ if (ctx->t_cols > ld.t_cols)
+ ld.t_cols = ctx->t_cols;
+
+ fprintf(stderr, "start new service!\r\n");
+
+ // deliver the fd to hosting service
+ if (send_remote_fd(g_tunnel, fd) < 0)
+ return ack;
+
+ // deliver the login data to hosting servier
+ if (towrite(g_tunnel, &ld, sizeof(ld)) <= 0)
+ return ack;
+
+ // wait service to response
+ read(g_tunnel, &ack, sizeof(ack));
+ return ack;
+}
+
+static int
+auth_start(int fd, login_conn_ctx *conn)
+{
+ login_ctx *ctx = &conn->ctx;
+ int isfree = 1, was_valid_uid = 0;
+ draw_check_passwd(conn);
+
+ if (is_validuserid(ctx->userid))
+ {
+ // ctx content may be changed.
+ was_valid_uid = 1;
+ switch (auth_user_challenge(ctx))
+ {
+ case AUTH_RESULT_FAIL:
+ logattempt(ctx->userid , '-', time(0), ctx->hostip);
+ break;
+
+ case AUTH_RESULT_OK:
+ isfree = 0;
+ logattempt(ctx->userid , ' ', time(0), ctx->hostip);
+ // share FREEID case, no break here!
+ case AUTH_RESULT_FREEID:
+ draw_auth_success(conn, isfree);
+
+ if (!start_service(fd, ctx))
+ {
+ // too bad, we can't start service.
+ draw_service_failure(conn);
+ return AUTH_RESULT_STOP;
+ }
+ return AUTH_RESULT_OK;
+
+ default:
+ assert(false);
+ break;
+ }
+
+ }
+
+ // auth fail.
+ _mt_bell(conn);
+
+ // if fail, restart
+ if (login_ctx_retry(ctx) >= LOGINATTEMPTS)
+ {
+ // end retry.
+ draw_goodbye(conn);
+ return AUTH_RESULT_STOP;
+
+ }
+
+ // prompt for retry
+ if (was_valid_uid)
+ draw_auth_fail(conn);
+ else
+ draw_empty_userid_warn(conn);
+
+ ctx->state = LOGIN_STATE_USERID;
+ draw_userid_prompt(conn);
+ return AUTH_RESULT_RETRY;
+}
+
+
+
+///////////////////////////////////////////////////////////////////////
+// Event callbacks
+
+static struct event ev_listen, ev_sighup, ev_tunnel;
+
+static void
+sighup_cb(int signal, short event, void *arg)
+{
+ fprintf(stderr, "caught sighup (request to reload)\r\n");
+ g_reload_data = 1;
+}
+
+static void
+endconn_cb(int fd, short event, void *arg)
+{
+ login_conn_ctx *conn = (login_conn_ctx*) arg;
+ fprintf(stderr, "login_conn_remove: removed connection #%d...", fd);
+ event_del(&conn->ev);
+ bufferevent_free(conn->bufev);
+ close(fd);
+ free(conn);
+ fprintf(stderr, " done.\r\n");
+}
+
+static void
+login_conn_remove(login_conn_ctx *conn, int fd, int sleep_sec)
+{
+ if (!sleep_sec)
+ {
+ endconn_cb(fd, EV_TIMEOUT, (void*) conn);
+ } else {
+ struct timeval tv = { sleep_sec, 0};
+ event_del(&conn->ev);
+ event_set(&conn->ev, fd, EV_PERSIST, endconn_cb, conn);
+ event_add(&conn->ev, &tv);
+ fprintf(stderr,
+ "login_conn_remove: stop conn #%d in %d seconds later.\r\n",
+ fd, sleep_sec);
+ }
+}
+
+static void
+client_cb(int fd, short event, void *arg)
+{
+ login_conn_ctx *conn = (login_conn_ctx*) arg;
+ int i, len, r;
+ unsigned char buf[32], ch;
+
+ // ignore clients that timeout
+ if (event & EV_TIMEOUT)
+ return;
+
+ // XXX will this happen?
+ if (!(event & EV_READ))
+ return;
+
+ if ( (len = read(fd, buf, sizeof(buf) - 1)) <= 0)
+ {
+ if (errno == EINTR || errno == EAGAIN)
+ return;
+
+ // close connection
+ login_conn_remove(conn, fd, 0);
+ return;
+ }
+
+ len = telnet_process(&conn->telnet, buf, len);
+
+ for (i = 0; i < len; i++)
+ {
+ int c = (unsigned char)buf[i];
+
+ // quick convert
+ if (c == KEY_CR)
+ c = KEY_ENTER;
+ else if (c == KEY_LF)
+ continue; // ignore LF
+ else if (c == '\b' || c == Ctrl('H') || c == 127)
+ c = KEY_BS;
+
+ // deal with context
+ switch ( login_ctx_handle(&conn->ctx, c) )
+ {
+ case LOGIN_HANDLE_WAIT:
+ break;
+
+ case LOGIN_HANDLE_BEEP:
+ _mt_bell(conn);
+ break;
+
+ case LOGIN_HANDLE_BS:
+ _mt_bs(conn);
+ break;
+
+ case LOGIN_HANDLE_OUTC:
+ ch = c;
+ _buff_write(conn, &ch, 1);
+ break;
+
+ case LOGIN_HANDLE_PROMPT_PASSWD:
+#ifdef DETECT_CLIENT
+ // stop detection
+ conn->telnet.cc_arg = NULL;
+#endif
+ if (conn->ctx.userid[0])
+ {
+ char *uid = conn->ctx.userid;
+ char *uid_lastc = uid + strlen(uid)-1;
+
+ draw_passwd_prompt(conn);
+#ifdef CONVERT
+ // convert encoding if required
+ switch(*uid_lastc)
+ {
+ case '.': // GB mode
+ conn->ctx.encoding = CONV_GB;
+ *uid_lastc = 0;
+ break;
+ case ',': // UTF-8 mode
+ conn->ctx.encoding = CONV_UTF8;
+ *uid_lastc = 0;
+ break;
+ }
+#endif
+ // accounts except free_auth [guest / new]
+ // require passwd.
+ if (!auth_is_free_userid(uid))
+ break;
+ }
+
+ // force changing state
+ conn->ctx.state = LOGIN_STATE_AUTH;
+ // XXX share start auth, no break here.
+ case LOGIN_HANDLE_START_AUTH:
+ if ((r = auth_start(fd, conn)) != AUTH_RESULT_RETRY)
+ {
+ login_conn_remove(conn, fd,
+ (r == AUTH_RESULT_OK) ? 0 : AUTHFAIL_SLEEP_SEC);
+ return;
+ }
+ break;
+ }
+ }
+}
+
+static void
+listen_cb(int fd, short event, void *arg)
+{
+ int cfd;
+ struct sockaddr_in xsin = {0};
+ socklen_t szxsin = sizeof(xsin);
+ login_conn_ctx *conn;
+
+ if ((cfd = accept(fd, (struct sockaddr *)&xsin, &szxsin)) < 0 )
+ return;
+
+ if ((conn = malloc(sizeof(login_conn_ctx))) == NULL) {
+ close(cfd);
+ return;
+ }
+ memset(conn, 0, sizeof(login_conn_ctx));
+
+ if ((conn->bufev = bufferevent_new(cfd, NULL, NULL, NULL, NULL)) == NULL) {
+ free(conn);
+ close(cfd);
+ return;
+ }
+
+ reload_data();
+ login_ctx_init(&conn->ctx);
+
+ // initialize telnet protocol
+ telnet_ctx_init(&conn->telnet, &telnet_callback, cfd);
+ telnet_ctx_set_write_arg (&conn->telnet, (void*) conn); // use conn for buffered events
+ telnet_ctx_set_resize_arg(&conn->telnet, (void*) &conn->ctx);
+#ifdef DETECT_CLIENT
+ telnet_ctx_set_cc_arg(&conn->telnet, (void*) &conn->ctx);
+#endif
+ // better send after all parameters were set
+ telnet_ctx_send_init_cmds(&conn->telnet);
+
+ // getremotename
+ inet_ntop(AF_INET, &xsin.sin_addr, conn->ctx.hostip, sizeof(conn->ctx.hostip));
+
+ // set events
+ event_set(&conn->ev, cfd, EV_READ|EV_PERSIST, client_cb, conn);
+ event_add(&conn->ev, NULL);
+
+ // check ban here? XXX can we directly use xsin.sin_addr instead of ASCII form?
+ if (g_banned || check_banip(conn->ctx.hostip) )
+ {
+ // draw ban screen, if available. (for banip, this is empty).
+ draw_text_screen(conn, ban_screen);
+ login_conn_remove(conn, fd, BAN_SLEEP_SEC);
+ return;
+ }
+
+ // draw banner
+#ifdef INSCREEN
+ _buff_write(conn, INSCREEN, sizeof(INSCREEN));
+#endif
+
+ // XXX check system load here.
+ if (g_overload)
+ {
+ draw_overload(conn, g_overload);
+ login_conn_remove(conn, fd, OVERLOAD_SLEEP_SEC);
+ return;
+
+ } else {
+ draw_text_screen(conn, welcome_screen);
+ draw_userid_prompt(conn);
+ }
+}
+
+static void
+tunnel_cb(int fd, short event, void *arg)
+{
+ int cfd;
+ if ((cfd = accept(fd, NULL, NULL)) < 0 )
+ return;
+
+ _set_connection_opt(cfd);
+
+ // got new tunnel
+ if (g_tunnel)
+ close(g_tunnel);
+ g_tunnel = cfd;
+}
+
+///////////////////////////////////////////////////////////////////////
+// Main
+
+#ifndef LOGIND_ADDR
+#define LOGIND_ADDR "*:9999"
+#endif
+
+#ifndef LOGIND_TUNNEL_PATH
+#define LOGIND_TUNNEL_PATH BBSHOME "/run/logind.tunnel"
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ int ch, sfd, tfd;
+ char *iface_ip = LOGIND_ADDR;
+ char *tunnel_path = LOGIND_TUNNEL_PATH;
+
+ Signal(SIGPIPE, SIG_IGN);
+
+ while ( (ch = getopt(argc, argv, "i:h")) != -1 )
+ {
+ switch( ch ){
+ case 'i':
+ iface_ip = optarg;
+ break;
+ case 't':
+ tunnel_path = optarg;
+ case 'h':
+ default:
+ fprintf(stderr, "usage: %s [-i *:port] [-t tunnel_path]\r\n", argv[0]);
+ return 1;
+ }
+ }
+
+ chdir(BBSHOME);
+ attach_SHM();
+
+ reload_data();
+
+ if ( (sfd = tobindex(iface_ip, SOCKET_QLEN, _set_bind_opt, 1)) < 0 )
+ {
+ fprintf(stderr, "cannot bind to port: %s. abort.\r\n", iface_ip);
+ return 2;
+ }
+ if ( (tfd = tobindex(tunnel_path, 1, _set_bind_opt, 1)) < 0)
+ {
+ fprintf(stderr, "cannot create tunnel: %s. abort.\r\n", tunnel_path);
+ return 2;
+ }
+
+ fprintf(stderr, "start daemonize\r\n");
+ daemonize(BBSHOME "/run/logind.pid", NULL);
+
+ event_init();
+ event_set(&ev_listen, sfd, EV_READ | EV_PERSIST, listen_cb, &ev_listen);
+ event_add(&ev_listen, NULL);
+ event_set(&ev_tunnel, tfd, EV_READ | EV_PERSIST, tunnel_cb, &ev_tunnel);
+ event_add(&ev_tunnel, NULL);
+
+ signal_set(&ev_sighup, SIGHUP, sighup_cb, &ev_sighup);
+ signal_add(&ev_sighup, NULL);
+
+ fprintf(stderr, "start event dispatch.\r\n");
+ event_dispatch();
+
+ return 0;
+}
+
+// vim:et