From d4e063b55ca7c7ffbea4d8601cae008e64a28521 Mon Sep 17 00:00:00 2001 From: piaip Date: Tue, 9 Jun 2009 14:17:57 +0000 Subject: * 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 --- daemon/logind/Makefile | 28 ++ daemon/logind/loginc.c | 81 ++++ daemon/logind/logind.c | 1077 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1186 insertions(+) create mode 100644 daemon/logind/Makefile create mode 100644 daemon/logind/loginc.c create mode 100644 daemon/logind/logind.c (limited to 'daemon/logind') 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 +#include +#include + +#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 +// Contributors: wens, kcwu +// Initial Date: 2009/06/01 +// +// Copyright (C) 2009, Hung-Te Lin +// 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 +#include +#include +#include +#include +#include +#include + +#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 -- cgit v1.2.3