// 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 LOGIN_PROMPT_END ANSI_RESET #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_userid_prompt_end(login_conn_ctx *conn) { _buff_write(conn, LOGIN_PROMPT_END, sizeof(LOGIN_PROMPT_END)-1); } 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_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: // because prompt would reverse attribute, reset here. draw_userid_prompt_end(conn); #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_TUNNEL_PATH #define LOGIND_TUNNEL_PATH BBSHOME "/run/logind.tunnel" #endif #ifndef FN_BINDLIST #define FN_BINDLIST BBSHOME "/etc/logind_ports" // a file with list of ports to bind. #endif static int bind_port(int port) { char buf[STRLEN]; int sfd; struct event *pev_listen = NULL; snprintf(buf, sizeof(buf), "*:%d", port); fprintf(stderr,"binding to port: %d...", port); if ( (sfd = tobindex(buf, SOCKET_QLEN, _set_bind_opt, 1)) < 0 ) { fprintf(stderr, "cannot bind to port: %d. abort.\r\n", port); return -1; } pev_listen = malloc (sizeof(struct event)); assert(pev_listen); event_set(pev_listen, sfd, EV_READ | EV_PERSIST, listen_cb, pev_listen); event_add(pev_listen, NULL); fprintf(stderr,"ok. \r\n"); return 0; } int main(int argc, char *argv[]) { int ch, port = 0, bound_ports = 0, tfd, as_daemon = 1; FILE *fp; const char *tunnel_path = LOGIND_TUNNEL_PATH; const char *config_file = FN_BINDLIST; Signal(SIGPIPE, SIG_IGN); while ( (ch = getopt(argc, argv, "f:p:t:hD")) != -1 ) { switch( ch ){ case 'f': config_file = optarg; break; case 'p': if (optarg) port = atoi(optarg); break; case 't': tunnel_path = optarg; break; case 'D': as_daemon = 0; break; case 'h': default: fprintf(stderr, "usage: %s [-D] [-f bindlist_file] [-p port] [-t tunnel_path]\r\n", argv[0]); return 1; } } chdir(BBSHOME); attach_SHM(); reload_data(); if (as_daemon) { fprintf(stderr, "start daemonize\r\n"); daemonize(BBSHOME "/run/logind.pid", NULL); } event_init(); signal_set(&ev_sighup, SIGHUP, sighup_cb, &ev_sighup); signal_add(&ev_sighup, NULL); // bind ports if (port && bind_port(port) < 0) { fprintf(stderr, "cannot bind to port: %d. abort.\r\n", port); return 3; } if (port) bound_ports++; // bind from port list file if( NULL != (fp = fopen(config_file, "rt")) ) { char buf [STRLEN]; while (fgets(buf, sizeof(buf), fp)) { port = atoi(strtok(buf, " #\r\n")); if (!port) continue; if (bind_port(port) < 0) { fprintf(stderr, "cannot bind to port: %d. abort.\r\n", port); return 3; } bound_ports++; } fclose(fp); } if (!bound_ports) { fprintf(stderr, "error: no ports to bind. abort.\r\n"); return 4; } /* Give up root privileges: no way back from here */ setgid(BBSGID); setuid(BBSUID); // create tunnel if ( (tfd = tobindex(tunnel_path, 1, _set_bind_opt, 1)) < 0) { fprintf(stderr, "cannot create tunnel: %s. abort.\r\n", tunnel_path); return 2; } event_set(&ev_tunnel, tfd, EV_READ | EV_PERSIST, tunnel_cb, &ev_tunnel); event_add(&ev_tunnel, NULL); fprintf(stderr, "start event dispatch.\r\n"); event_dispatch(); return 0; } // vim:et