summaryrefslogblamecommitdiffstats
path: root/mbbsd/mbbsd.c
blob: 496ea00fc21181dc9c17145ba862582bac59ae61 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
                                                       















                       
                         

























                                                                          
     
                      
      










































                                                                             

                   


                       
      










                                                                    
               


                       
      


































































































                                                                                     
    













                                                               




                                                                         





























                                                 



                                                                           




























                                                                          
                     





































                                                                               
                                 


                                                           
                                                                               





















                                                                             
                             
                                                       
 
                                                

                                                                  











                                                    
                                    
                 
                                                       
                                          



                                                       

                          
                  





                                                  
         








                                                                              



                                      
                                          
     
        






                                                                     
     


             
    

                       
              
 




                                                           

                                             
                    



                                                    

                                                                              

                                                


                                                                  
             

                                       
         
     
         












































                                                                    


     
































                                                                          

                                                                        













































                                                                             
                       



















                                                                   
                                         












                                                                
                                                                        











































































































































































































































                                                                               













































                                                                                      
                                       
      


                                    



                                        
                                                                          



























































                                                                             


















                                                                  
                                                                      



















                                       





                                 





























                                                            



                                         
                              














































                                                                              
     





                               
      


























































































































































































































































                                                                              
                                                                                        










                                      



              



                         
      




















































                                                                             
/* $Id: mbbsd.c,v 1.24 2002/04/28 19:35:29 in2 Exp $ */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <netdb.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/telnet.h>
#include "config.h"
#include "pttstruct.h"
#include "common.h"
#include "perm.h"
#include "modes.h"
#include "proto.h"
#ifdef FreeBSD
  #include <machine/limits.h>
#else
  #include <limits.h>
#endif

#define SOCKET_QLEN 4
#define TH_LOW 100
#define TH_HIGH 120

extern int t_lines, t_columns;  /* Screen size / width */
extern int b_lines;     /* Screen bottom line number: t_lines-1 */
extern userinfo_t *currutmp;
extern int curr_idle_timeout;

static void do_aloha (char *hello);

#if 0
static jmp_buf byebye;
#endif

int talkrequest = NA;

static char remoteusername[40] = "?";

extern struct fromcache_t *fcache;
extern struct utmpfile_t *utmpshm;
extern int fcache_semid;

static unsigned char enter_uflag;
static int use_shell_login_mode = 0;

char fromhost[STRLEN] = "\0";

static struct sockaddr_in xsin;

/* set signal handler, which won't be reset once signal comes */
static void
signal_restart (int signum, void (*handler) (int))
{
    struct sigaction act;
    act.sa_handler = handler;
    memset (&(act.sa_mask), 0, sizeof (sigset_t));
    act.sa_flags = 0;
    sigaction (signum, &act, NULL);
}

static void
start_daemon ()
{
    int n;
    char buf[80];
    
    /*
     * More idiot speed-hacking --- the first time conversion makes the C
     * library open the files containing the locale definition and time zone.
     * If this hasn't happened in the parent process, it happens in the
     * children, once per connection --- and it does add up.
     */
    time_t dummy = time (NULL);
    struct tm *dummy_time = localtime (&dummy);
    
    strftime (buf, 80, "%d/%b/%Y:%H:%M:%S", dummy_time);

#ifndef NO_FORK    
    if ((n = fork ())){
    exit (0);
    }
#endif
    
    /* rocker.011018: it's a good idea to close all unexcept fd!! */
    n = getdtablesize ();
    while (n)
    close (--n);
    /* rocker.011018: we don't need to remember original tty, 
       so request a new session id */
    setsid ();
    
    /* rocker.011018: after new session, 
       we should insure the process is clean daemon */
#ifndef NO_FORK
    if ((n = fork ())){
    exit (0);
    }
#endif
}

static void
reapchild (int sig)
{
    int state, pid;
    
    while ((pid = waitpid (-1, &state, WNOHANG | WUNTRACED)) > 0)
    ;
}

#define BANNER \
"【" BBSNAME "】◎ 台大流行網 ◎(" MYHOSTNAME ") 調幅(" MYIP ") "
/*
#define BANNER \
"【" BBSNAME "】◎ 台大流行網 ◎(" MYHOSTNAME ")\r\n"\
"  調幅(" MYIP ") "
*/
/* check load and print approriate banner string in buf */
static int
chkload (char *buf)
{
    char cpu_load[30];
    int i;
    
    i = cpuload (cpu_load);
    
    sprintf (buf, BANNER" 系統負荷\r\n %s %s \r\n", cpu_load,
         (i > MAX_CPULOAD ? ",高負荷量,請稍後再來 "
          "(請利用port 3000~3010連線)" : ""));
#ifdef INSCREEN
    strcpy(buf, (i > MAX_CPULOAD ? BANNER
         "高負荷量,請稍後再來(請利用port 3000~3010連線)" : ""));
#else
    sprintf(buf, BANNER "%s\r\n",
        (i > MAX_CPULOAD ? "高負荷量,請稍後再來(請利用port 3000~3010連線)":""));
#endif
    if (i > MAX_CPULOAD)
    return 1;
    else if (i > MAX_CPULOAD / 2)
    curr_idle_timeout = 10 * 60;
    else
    curr_idle_timeout = 30 * 60;
    
    return 0;
}

extern userec_t cuser;

void
log_user (char *msg)
{
    char filename[200];
    
    sprintf (filename, BBSHOME "/home/%c/%s/USERLOG",
         cuser.userid[0], cuser.userid);
    log_file (filename, msg);
}

extern time_t login_start_time;

void
log_usies (char *mode, char *mesg)
{
    char genbuf[200];
    time_t now = time (0);
    
    if (!mesg)
    sprintf (genbuf, cuser.userid[0] ? "%s %s %-12s Stay:%d (%s)" :
         "%s %s %s Stay:%d (%s)",
         Cdate (&now), mode, cuser.userid,
         (int)(now - login_start_time) / 60, cuser.username);
    else
    sprintf (genbuf, cuser.userid[0] ? "%s %s %-12s %s" : "%s %s %s%s",
         Cdate (&now), mode, cuser.userid, mesg);
    log_file (FN_USIES, genbuf);
    
    /* 追蹤使用者 */
    if (HAS_PERM (PERM_LOGUSER))
    log_user (genbuf);
}

static void
setflags (int mask, int value)
{
    if (value)
    cuser.uflag |= mask;
    else
    cuser.uflag &= ~mask;
}

extern int usernum;
extern int currmode;

void
u_exit (char *mode)
{
    //userec_t xuser;
    int diff = (time (0) - login_start_time) / 60;
    
    reload_money(); 
    auto_backup ();
    save_brdbuf();
    setflags (PAGER_FLAG, currutmp->pager != 1);
    setflags (CLOAK_FLAG, currutmp->invisible);
    
    cuser.invisible = currutmp->invisible;
    cuser.pager = currutmp->pager;
    cuser.mind  = currutmp->mind; 
    if (!(HAS_PERM (PERM_SYSOP) && HAS_PERM (PERM_DENYPOST)) &&
    !currutmp->invisible )
    do_aloha ("<<下站通知>> -- 我走囉!");
    
    purge_utmp (currutmp);
    if ((cuser.uflag != enter_uflag) || (currmode & MODE_DIRTY) || diff){
    if (!diff && cuser.numlogins)
        cuser.numlogins = --cuser.numlogins;
    /* Leeym 上站停留時間限制式 */
    }
    passwd_update (usernum, &cuser);
    log_usies (mode, NULL);
}

static void
system_abort ()
{
    if (currmode)
    u_exit ("ABORT");
    
    clear ();
    refresh ();
    fprintf (stdout, "謝謝光臨, 記得常來喔 !\n");
    exit (0);
}

void
abort_bbs (int sig)
{
    if (currmode)
    u_exit ("AXXED");
    exit (0);
}

static void
abort_bbs_debug (int sig)
{
    static int reentrant = 0;
    
    if (!reentrant){
    int     i;
    /* close all file descriptors (including the network connection) */
    for( i = 0 ; i < 256 ; ++i )
        close(i);
    reentrant = 1;
    if (currmode)
        u_exit ("AXXED");
    setproctitle("debug me!(%d)",sig);
    sleep(3600); /* wait 60 mins for debug */
    }
    exit (0);
}

/* 登錄 BBS 程式 */
static void
mysrand ()
{
    srand (time (NULL) + currutmp->pid);  /* 時間跟 pid 當 rand 的 seed */
}

extern userec_t xuser;

int
dosearchuser (char *userid)
{
    if ((usernum = getuser (userid)))
    memcpy (&cuser, &xuser, sizeof (cuser));
    else
    memset (&cuser, 0, sizeof (cuser));
    return usernum;
}

static void
talk_request(int sig)
{
    bell ();
    bell ();
    if (currutmp->msgcount){
    char buf[200];
    time_t now = time (0);
    
    sprintf (buf, "\033[33;41m★%s\033[34;47m [%s] %s \033[0m",
         utmpshm->uinfo[currutmp->destuip].userid, my_ctime (&now),
         (currutmp->sig == 2)? "重要消息廣播!(請Ctrl-U,l查看熱訊記錄)"
         : "呼叫、呼叫,聽到請回答");
    move (0, 0);
    clrtoeol ();
    outs (buf);
    refresh ();
    }
    else{
    unsigned char mode0 = currutmp->mode;
    char c0 = currutmp->chatid[0];
    screenline_t *screen0 = calloc (t_lines, sizeof (screenline_t));
    extern screenline_t *big_picture;
    
    currutmp->mode = 0;
    currutmp->chatid[0] = 1;
    memcpy (screen0, big_picture, t_lines * sizeof (screenline_t));
    talkreply ();
    currutmp->mode = mode0;
    currutmp->chatid[0] = c0;
    memcpy (big_picture, screen0, t_lines * sizeof (screenline_t));
    free (screen0);
    redoscr ();
    }
}

extern char *fn_writelog;
FILE *fp_writelog = NULL;

void
show_call_in(int save, int which)
{
    char buf[200];
    sprintf (buf, "\033[1;33;46m★%s\033[37;45m %s \033[m",
         currutmp->msgs[which].userid, currutmp->msgs[which].last_call_in);
    move (b_lines, 0);
    clrtoeol ();
    refresh ();
    outmsg (buf);
    
    if (save){
    char genbuf[200];
    time_t now;
    if (!fp_writelog){
        sethomefile (genbuf, cuser.userid, fn_writelog);
        fp_writelog = fopen (genbuf, "a");
    }
    if (fp_writelog){
        time (&now);
        fprintf (fp_writelog, "%s \033[0m[%s]\n", buf, Cdatelite (&now));
    }
    }
}

extern  unsigned int currstat;
water_t water[6], *swater[6], *water_which=&water[0];
char    water_usies=0;
extern  int watermode, wmofo;
static int add_history_water(water_t *w, msgque_t *msg)
{
    // mode: 1: all data(including userid, pid);
    //       0: only last_call_in Ptt:先改回來 省不多 進階會有問題
    memcpy(&w->msg[w->top], msg, sizeof(msgque_t));
    w->top++;
    w->top %= WATERMODE(WATER_OFO) ? 5 : MAX_REVIEW;
    
    if (w->count < MAX_REVIEW)
    w->count++;
    
    return w->count;
}

static int
add_history(msgque_t *msg)
{
    int     i = 0, j, waterinit = 0;
    water_t *tmp;
    if( WATERMODE(WATER_ORIG) || WATERMODE(WATER_NEW) )
    add_history_water(&water[0], msg);
    if( WATERMODE(WATER_NEW) || WATERMODE(WATER_OFO) ){
    for(i = 0 ; i < 5 && swater[i] ; i++ )
        if( swater[i]->pid == msg->pid )
        break;
    if( i == 5 ){
        waterinit = 1;
        i = 4;
        memset(swater[4], 0, sizeof(water_t));
    }
    else if( !swater[i] ){
        water_usies = i + 1;
        swater[i] = &water[i + 1];
        waterinit = 1;
    }
    tmp = swater[i];

    if( waterinit ){
        memcpy(swater[i]->userid, msg->userid, sizeof(swater[i]->userid));
        swater[i]->pid = msg->pid;
    }

    if( !swater[i]->uin )
        swater[i]->uin = currutmp;
    
    for( j = i ; j > 0 ; j-- )
        swater[j] = swater[j - 1];
    swater[0] = tmp;
    add_history_water(swater[0], msg);
    }
    
    if(WATERMODE(WATER_ORIG) || WATERMODE(WATER_NEW) ){
    if( watermode > 0 &&
        (water_which == swater[0] || water_which == &water[0]) ){
        if (watermode < water_which->count)
        watermode++;
        t_display_new();
    }
    }
    return i;
}

void
write_request (int sig)
{
    int     i;

    if( WATERMODE(WATER_OFO) ){
    /* sig = SIGUSR2 waterball come in
             0       flush to water[]  (by my_write2())
    */
    if( sig != 0 ){
        if( wmofo == 0 ) /* 正在回水球 */
        wmofo = 1;
        bell ();
        show_call_in(1, currutmp->msgcount - 1);
        refresh ();
    }

    if( sig == 0 ||      /* 回水球的時候又有水球進來, 回完後一次寫回去  */
        wmofo == -1 ){   /* 不在回水球模式                              */
        do{
        add_history(&currutmp->msgs[0]);
        if( currutmp->msgcount-- )
            for (i = 0; i < currutmp->msgcount; i++)
            currutmp->msgs[i] = currutmp->msgs[i + 1];
        }
        while (currutmp->msgcount);
        currutmp->msgcount = 0;
    }
    }
    else{
    if (currutmp->mode != 0 &&
        currutmp->pager != 0 &&
        cuser.userlevel != 0 &&
        currutmp->msgcount != 0 &&
        currutmp->mode != TALK &&
        currutmp->mode != EDITING &&
        currutmp->mode != CHATING &&
        currutmp->mode != PAGE &&
        currutmp->mode != IDLE &&
        currutmp->mode != MAILALL && currutmp->mode != MONITOR){
        char c0 = currutmp->chatid[0];
        int currstat0 = currstat;
        unsigned char mode0 = currutmp->mode;
        
        currutmp->mode = 0;
        currutmp->chatid[0] = 2;
        currstat = XMODE;
        
        do{
        bell ();
        show_call_in(1, 0);
        igetch ();
        currutmp->msgcount--;
        if (currutmp->msgcount >= MAX_MSGS){
            /* this causes chaos... jochang */
            raise (SIGFPE);
        }
        
        add_history(&currutmp->msgs[0]);
        for (i = 0; i < currutmp->msgcount; i++)
            currutmp->msgs[i] = currutmp->msgs[i + 1];
        }
        while (currutmp->msgcount);
        currutmp->chatid[0] = c0;
        currutmp->mode = mode0;
        currstat = currstat0;
    }
    else{
        bell ();
        show_call_in(1, 0);
        add_history(&currutmp->msgs[0]);
        
        refresh ();
        currutmp->msgcount = 0;
    }
    }
}

static void
multi_user_check ()
{
    register userinfo_t *ui;
    register pid_t pid;
    char genbuf[3];
    
    if (HAS_PERM (PERM_SYSOP))
    return;         /* don't check sysops */
    
    if (cuser.userlevel){
    if (!(ui = (userinfo_t *) search_ulist (usernum)))
        return;         /* user isn't logged in */
    
    pid = ui->pid;
    if (!pid /*|| (kill(pid, 0) == -1) */ )
        return;         /* stale entry in utmp file */
    
    getdata (b_lines - 1, 0, "您想刪除其他重複的 login (Y/N)嗎?[Y] ",
         genbuf, 3, LCECHO);
    
    if (genbuf[0] != 'n'){
        if (pid > 0)
        kill (pid, SIGHUP);
        log_usies ("KICK ", cuser.username);
    }
    else{
        if (search_ulistn(usernum, 3)!=NULL)
        system_abort ();    /* Goodbye(); */
    }
    }
    else{
    /* allow multiple guest user */
    if (search_ulistn(usernum, 100)!=NULL){
        outs ("\n抱歉,目前已有太多 guest 在站上, 請用new註冊。\n");
        pressanykey ();
        oflush ();
        exit (1);
    }
    }
}

/* bad login */
static char str_badlogin[] = "logins.bad";

static void
logattempt (char *uid, char type)
{
    char fname[40];
    int fd, len;
    char genbuf[200];
    
    sprintf (genbuf, "%c%-12s[%s] %s@%s\n", type, uid,
         Cdate (&login_start_time), remoteusername, fromhost);
    len = strlen (genbuf);
    if ((fd = open (str_badlogin, O_WRONLY | O_CREAT | O_APPEND, 0644)) > 0){
    write (fd, genbuf, len);
    close (fd);
    }
    if (type == '-'){
    sprintf (genbuf, "[%s] %s\n", Cdate (&login_start_time), fromhost);
    len = strlen (genbuf);
    sethomefile (fname, uid, str_badlogin);
    if ((fd = open (fname, O_WRONLY | O_CREAT | O_APPEND, 0644)) > 0){
        write (fd, genbuf, len);
        close (fd);
    }
    }
}

extern char *str_new;
extern char *err_uid;

static void
login_query ()
{
    char uid[IDLEN + 1], passbuf[PASSLEN];
    int attempts;
    char genbuf[200];
    extern struct utmpfile_t *utmpshm;
    resolve_utmp ();
    resolve_garbage ();
    attach_uhash ();
    attempts = utmpshm->number;
    show_file ("etc/Welcome", 1, -1, NO_RELOAD);
    output ("1", 1);
    if (attempts >= MAX_ACTIVE){
    outs ("由於人數太多,請您稍後再來。\n");
    refresh ();
    exit (1);
    }
    
  /* hint */

  attempts = 0;
  while (1){
      if (attempts++ >= LOGINATTEMPTS){
      more ("etc/goodbye", NA);
      pressanykey ();
      exit (1);
      }
      getdata (20, 0, "請輸入代號,或以[guest]參觀,以[new]註冊:",
           uid, sizeof(uid), DOECHO);
      if (strcasecmp (uid, str_new) == 0){
#ifdef LOGINASNEW
      new_register ();
      break;
#else
      outs ("本系統目前無法以 new 註冊, 請用 guest 進入\n");
      continue;
#endif
      }
      else if (uid[0] == '\0' || !dosearchuser (uid)){
      outs (err_uid);
      }
      else if (strcmp (uid, STR_GUEST)){
      getdata (21, 0, MSG_PASSWD, passbuf, sizeof(passbuf), NOECHO);
      passbuf[8] = '\0';
      
      if (!checkpasswd (cuser.passwd, passbuf)
          /* || (HAS_PERM(PERM_SYSOP) && !use_shell_login_mode) */ ){
          logattempt (cuser.userid, '-');
          outs (ERR_PASSWD);
      }
      else{
          logattempt (cuser.userid, ' ');
          if (strcasecmp ("SYSOP", cuser.userid) == 0)
          cuser.userlevel = PERM_BASIC | PERM_CHAT | PERM_PAGE |
              PERM_POST | PERM_LOGINOK | PERM_MAILLIMIT |
              PERM_CLOAK | PERM_SEECLOAK | PERM_XEMPT |
              PERM_DENYPOST | PERM_BM | PERM_ACCOUNTS |
              PERM_CHATROOM | PERM_BOARD | PERM_SYSOP | PERM_BBSADM;
          break;
      }
      }
      else{ /* guest */
      cuser.userlevel = 0;
      cuser.uflag = COLOR_FLAG | PAGER_FLAG | BRDSORT_FLAG | MOVIE_FLAG;
      break;
      }
  }
  multi_user_check ();
  sethomepath (genbuf, cuser.userid);
  mkdir (genbuf, 0755);
}

void
add_distinct (char *fname, char *line)
{
    FILE *fp;
    int n = 0;
    
    if ((fp = fopen (fname, "a+"))){
    char buffer[80];
    char tmpname[100];
    FILE *fptmp;
    
    strcpy (tmpname, fname);
    strcat (tmpname, "_tmp");
    if (!(fptmp = fopen (tmpname, "w"))){
        fclose (fp);
        return;
    }
    rewind (fp);
    while (fgets (buffer, 80, fp)){
        char *p = buffer + strlen (buffer) - 1;
        
        if (p[-1] == '\n' || p[-1] == '\r')
        p[-1] = 0;
        if (!strcmp (buffer, line))
        break;
        sscanf (buffer + strlen (buffer) + 2, "%d", &n);
        fprintf (fptmp, "%s%c#%d\n", buffer, 0, n);
    }
    
    if (feof (fp))
        fprintf (fptmp, "%s%c#1\n", line, 0);
    else{
        sscanf (buffer + strlen (buffer) + 2, "%d", &n);
        fprintf (fptmp, "%s%c#%d\n", buffer, 0, n + 1);
        while (fgets (buffer, 80, fp)){
        sscanf (buffer + strlen (buffer) + 2, "%d", &n);
        fprintf (fptmp, "%s%c#%d\n", buffer, 0, n);
        }
    }
    fclose (fp);
    fclose (fptmp);
    unlink (fname);
    rename (tmpname, fname);
    }
}

void
del_distinct (char *fname, char *line)
{
    FILE *fp;
    int n = 0;
    
    if ((fp = fopen (fname, "r"))){
    char buffer[80];
    char tmpname[100];
    FILE *fptmp;
    
    strcpy (tmpname, fname);
    strcat (tmpname, "_tmp");
    if (!(fptmp = fopen (tmpname, "w"))){
        fclose (fp);
        return;
    }
    rewind (fp);
    while (fgets (buffer, 80, fp)){
        char *p = buffer + strlen (buffer) - 1;
        
        if (p[-1] == '\n' || p[-1] == '\r')
        p[-1] = 0;
        if (!strcmp (buffer, line))
        break;
        sscanf (buffer + strlen (buffer) + 2, "%d", &n);
        fprintf (fptmp, "%s%c#%d\n", buffer, 0, n);
    }
    
    if (!feof (fp))
        while (fgets (buffer, 80, fp)){
        sscanf (buffer + strlen (buffer) + 2, "%d", &n);
        fprintf (fptmp, "%s%c#%d\n", buffer, 0, n);
        }
    fclose (fp);
    fclose (fptmp);
    unlink (fname);
    rename (tmpname, fname);
    }
}

#ifdef WHERE
static int
where (char *from)
{
    register int i = 0, count = 0, j;
    
    for (j = 0; j < fcache->top; j++){
    char *token = strtok (fcache->domain[j], "&");
    
    i = 0;
    count = 0;
    while (token){
        if (strstr (from, token))
        count++;
        token = strtok (NULL, "&");
        i++;
    }
    if (i == count)
        break;
    }
    if (i != count)
    return 0;
    return j;
}
#endif

static void
check_BM ()
{
    int i;
    boardheader_t *bhdr;
    extern boardheader_t *bcache;
    extern int numboards;
    
    cuser.userlevel &= ~PERM_BM;
    for (i = 0, bhdr = bcache; i < numboards && !is_BM (bhdr->BM); i++, bhdr++)
    ;
}

extern pid_t currpid;
extern crosspost_t postrecord;

static void
setup_utmp (int mode)
{
    userinfo_t uinfo;
    /*
      char buf[80];
      char remotebuf[1024];
      time_t now = time(NULL);
    */
    memset (&uinfo, 0, sizeof (uinfo));
    uinfo.pid = currpid = getpid ();
    uinfo.uid = usernum;
    uinfo.mode = currstat = mode;
    uinfo.msgcount = 0;
    uinfo.mailalert = load_mailalert (cuser.userid);
    if (!(cuser.numlogins % 20) && cuser.userlevel & PERM_BM)
    check_BM ();        /* Ptt 自動取下離職板主權力 */
    
    uinfo.userlevel = cuser.userlevel;
    uinfo.sex = cuser.sex % 8;
    uinfo.lastact = time (NULL);
    
    postrecord.times = 0;       /* 計算crosspost數 */
    
    strcpy (uinfo.userid, cuser.userid);
    strcpy (uinfo.realname, cuser.realname);
    strcpy (uinfo.username, cuser.username);
    strncpy (uinfo.from, fromhost, 23);
    
    uinfo.five_win = cuser.five_win;
    uinfo.five_lose = cuser.five_lose;
    uinfo.five_tie = cuser.five_tie;
    
    uinfo.invisible = cuser.invisible % 2;
    uinfo.pager = cuser.pager%5;
    uinfo.mind  = cuser.mind; 
    uinfo.brc_id = 0;
#ifdef WHERE
    uinfo.from_alias = where (fromhost);
#else
    uinfo.from_alias = 0;
#endif
#ifndef FAST_LOGIN
    setuserfile (buf, "remoteuser");
    
    strcpy (remotebuf, fromhost);
    strcat (remotebuf, ctime (&now));
    remotebuf[strlen (remotebuf) - 1] = 0;
    add_distinct (buf, remotebuf);
#endif
    if (enter_uflag & CLOAK_FLAG)
    uinfo.invisible = YEA;
    getnewutmpent (&uinfo);
#ifndef _BBS_UTIL_C_
    friend_load ();
#endif
}

extern char margs[];
extern char *str_sysop;
extern char *loginview_file[NUMVIEWFILE][2];

static void
user_login ()
{
    char ans[4], i;
    char genbuf[200];
    struct tm *ptime, *tmp;
    time_t now;
    int a;
    /*** Heat:廣告詞
     char *ADV[] = {
     "7/17 @LIVE 亂彈, 何欣穗 的 入場卷要送給 ptt 的愛用者!",
     "欲知詳情請看 PttAct 板!!",
     }; ***/
    
    log_usies ("ENTER", fromhost);
    setproctitle ("%s: %s", margs, cuser.userid);
    resolve_fcache ();
    resolve_boards ();  
    memset( &water[0],0,sizeof(water_t) * 6);
    strcpy(water[0].userid, " 全部 ");
    /* 初始化 uinfo、flag、mode */
    setup_utmp (LOGIN);
    mysrand ();     /* 初始化: random number 增加user跟時間的差異 */
    currmode = MODE_STARTED;
    enter_uflag = cuser.uflag;
    
    /* get local time */
    time (&now);
    ptime = localtime (&now);
    tmp = localtime (&cuser.lastlogin);
    if ((a = utmpshm->number) > fcache->max_user){
    fcache->max_user = a;
    fcache->max_time = now;
    }
    init_brdbuf(); 
    brc_initial (DEFAULT_BOARD);
    set_board ();
    /* 畫面處理開始 */
    if (!(HAS_PERM (PERM_SYSOP) && HAS_PERM (PERM_DENYPOST)) && !currutmp->invisible )
    do_aloha ("<<上站通知>> -- 我來啦!");
    if (ptime->tm_mday == cuser.day && ptime->tm_mon + 1 == cuser.month){
    more ("etc/Welcome_birth", NA);
    currutmp->birth = 1;
    }
    else{
#ifdef MULTI_WELCOME_LOGIN
    char    buf[80];
    int     nScreens;
    for( nScreens = 0 ; nScreens < 10 ; ++nScreens ){
        sprintf(buf, "etc/Welcome_login.%d", nScreens);
        if( access(buf, 0) < 0 )
        break;
    }
    printf("%d\n", nScreens);
    if( nScreens ==  0 ){ // multi screen error?
        more ("etc/Welcome_login", NA);
    }
    else{
        sprintf(buf, "etc/Welcome_login.%d", (int)login_start_time % nScreens);
        more (buf, NA);
    }
#else
    more ("etc/Welcome_login", NA);
#endif
    //pressanykey();
    //more("etc/CSIE_Week", NA);
    currutmp->birth = 0;
    }
    
    if (cuser.userlevel){/* not guest */
    move (t_lines - 4, 0);
    prints ("\033[m      歡迎您第 \033[1;33m%d\033[0;37m 度拜訪本站,"
        "上次您是從 \033[1;33m%s\033[0;37m 連往本站,\n"
        "     我記得那天是 \033[1;33m%s\033[0;37m。\n",
        ++cuser.numlogins, cuser.lasthost, Cdate (&cuser.lastlogin));
    pressanykey ();
    
    if (currutmp->birth && tmp->tm_mday != ptime->tm_mday){
        more ("etc/birth.post", YEA);
        brc_initial ("WhoAmI");
        set_board ();
        do_post ();
    }
    setuserfile (genbuf, str_badlogin);
    if (more (genbuf, NA) != -1){
        getdata (b_lines - 1, 0, "您要刪除以上錯誤嘗試的記錄嗎(Y/N)?[Y]",
             ans, 3, LCECHO);
        if (*ans != 'n')
        unlink (genbuf);
    }
    check_register ();
    strncpy (cuser.lasthost, fromhost, 16);
    cuser.lasthost[15] = '\0';
    restore_backup ();
    }
    else if (!strcmp (cuser.userid, STR_GUEST)){
    char *nick[13] = {
        "椰子", "貝殼", "內衣", "寶特瓶", "翻車魚",
        "樹葉", "浮萍", "鞋子", "潛水艇", "魔王",
        "鐵罐", "考卷", "大美女"
    };
    char *name[13] = {
        "大王椰子", "鸚鵡螺", "比基尼", "可口可樂", "仰泳的魚",
        "憶", "高岡屋", "AIR Jordon", "紅色十月號", "批踢踢",
        "SASAYA椰奶", "鴨蛋", "布魯克鱈魚香絲"
    };
    char *addr[13] = {
        "天堂樂園", "大海", "綠島小夜曲", "美國", "綠色珊瑚礁",
        "遠方", "原本海", "NIKE", "蘇聯", "男八618室",
        "愛之味", "天上", "藍色珊瑚礁"
    };
    i = login_start_time % 13;
    sprintf (cuser.username, "海邊漂來的%s", nick[(int) i]);
    sprintf (currutmp->username, cuser.username);
    sprintf (cuser.realname, name[(int) i]);
    sprintf (currutmp->realname, cuser.realname);
    sprintf (cuser.address, addr[(int) i]);
    cuser.sex = i % 8;
    currutmp->pager = 2;
    pressanykey ();
    }
    else
    pressanykey ();
    
    if (!PERM_HIDE (currutmp))
    cuser.lastlogin = login_start_time;
    
    passwd_update (usernum, &cuser);
    
    for (i = 0; i < NUMVIEWFILE; i++)
    if ((cuser.loginview >> i) & 1)
        more (loginview_file[(int) i][0], YEA);
}

static void
do_aloha (char *hello)
{
    FILE *fp;
    char userid[80];
    char genbuf[200];
    
    setuserfile (genbuf, "aloha");
    if ((fp = fopen (genbuf, "r"))){
    sprintf (genbuf, hello);
    while (fgets (userid, 80, fp)){
        userinfo_t *uentp;
        int tuid;
        
        if ((tuid = searchuser (userid)) && tuid != usernum &&
        (uentp = (userinfo_t *) search_ulist (tuid)) &&
        isvisible(uentp, currutmp)){
        my_write (uentp->pid, genbuf, uentp->userid, 2, NULL);
        }
    }
    fclose (fp);
    }
}

static void
do_term_init ()
{
    term_init ();
    initscr ();
}

extern char *fn_register;
extern int showansi;

static void
start_client ()
{
    extern struct commands_t cmdlist[];
#ifdef CPULIMIT
    struct rlimit rml;
    rml.rlim_cur = CPULIMIT * 60;
    rml.rlim_max = CPULIMIT * 60;
    setrlimit(RLIMIT_CPU, &rml);
#endif
    
    /* system init */
    nice (2);           /*  Ptt: lower priority */
    login_start_time = time (0);
    currmode = 0;
    
    signal (SIGHUP, abort_bbs);
    signal (SIGTERM, abort_bbs);
    signal (SIGPIPE, abort_bbs);
    
    signal (SIGINT, abort_bbs_debug);
    signal (SIGQUIT, abort_bbs_debug);
    signal (SIGILL, abort_bbs_debug);
    signal (SIGABRT, abort_bbs_debug);
    signal (SIGFPE, abort_bbs_debug);
    signal (SIGBUS, abort_bbs_debug);
    signal (SIGSEGV, abort_bbs_debug);
    
    signal_restart (SIGUSR1, talk_request);
    signal_restart (SIGUSR2, write_request);
    
    dup2 (0, 1);
    
    do_term_init ();
    signal (SIGALRM, abort_bbs);
    alarm (600);
    login_query ();     /* Ptt 加上login time out */
    user_login ();
    m_init ();
    
    if (HAVE_PERM (PERM_SYSOP | PERM_BM))
    b_closepolls ();
    if (!(cuser.uflag & COLOR_FLAG))
    showansi = 0;
    signal (SIGALRM, SIG_IGN);
    if (chkmailbox ())
    m_read ();
    
    domenu (MMENU, "主功\能表", (currutmp->mailalert ? 'M' : 'C'), cmdlist);
}

/* FSA (finite state automata) for telnet protocol */
static void
telnet_init ()
{
    static char svr[] = {
    IAC, DO, TELOPT_TTYPE,
    IAC, SB, TELOPT_TTYPE, TELQUAL_SEND, IAC, SE,
    IAC, WILL, TELOPT_ECHO,
    IAC, WILL, TELOPT_SGA
    };
    char *cmd;
    int n, len, rset;
    struct timeval to;
    char buf[64];
    for (n = 0, cmd = svr; n < 4; n++){
    len = (n == 1 ? 6 : 3);
    write (0, cmd, len);
    cmd += len;
    to.tv_sec = 3;
    to.tv_usec = 0;
    rset=1;
    if (select (1, (fd_set *) & rset, NULL, NULL, &to) > 0)
        recv(0, buf, sizeof (buf),0);
    }
}

/* 取得 remote user name 以判定身份                */
/*
 * rfc931() speaks a common subset of the RFC 931, AUTH, TAP, IDENT and RFC
 * 1413 protocols. It queries an RFC 931 etc. compatible daemon on a remote
 * host to look up the owner of a connection. The information should not be
 * used for authentication purposes. This routine intercepts alarm signals.
 *
 * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
 */

#define STRN_CPY(d,s,l) { strncpy((d),(s),(l)); (d)[(l)-1] = 0; }
#define RFC931_TIMEOUT   10
#define RFC931_PORT     113 /* Semi-well-known port */
#define ANY_PORT        0   /* Any old port will do */

#if 0
/* timeout - handle timeouts */
static void
timeout (int sig)
{
    longjmp (byebye, sig);
}
#endif

static void
getremotename (struct sockaddr_in *from, char *rhost, char *rname)
{

    /* get remote host name */
    
#ifdef FAST_LOGIN
    strcpy (rhost, (char *) inet_ntoa (from->sin_addr));
#else
    struct sockaddr_in our_sin;
    struct sockaddr_in rmt_sin;
    unsigned rmt_port, rmt_pt;
    unsigned our_port, our_pt;
    FILE *fp;
    char buffer[512], user[80], *cp;
    int s;
    static struct hostent *hp;
    
    
    hp = NULL;
    if (setjmp (byebye) == 0){
    signal (SIGALRM, timeout);
    alarm (3);
    hp = gethostbyaddr ((char *) &from->sin_addr, sizeof (struct in_addr),
                from->sin_family);
    alarm (0);
    }
    strcpy (rhost, hp ? hp->h_name : (char *) inet_ntoa (from->sin_addr));
    
/*
 * Use one unbuffered stdio stream for writing to and for reading from the
 * RFC931 etc. server. This is done because of a bug in the SunOS 4.1.x
 * stdio library. The bug may live in other stdio implementations, too.
 * When we use a single, buffered, bidirectional stdio stream ("r+" or "w+"
 * mode) we read our own output. Such behaviour would make sense with
 * resources that support random-access operations, but not with sockets.
 */

    s = sizeof (our_sin);
    if (getsockname (0, (struct sockaddr *) &our_sin, &s) < 0)
    return;
    
    if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0)
    return;
    
    if (!(fp = fdopen (s, "r+"))){
    close (s);
    return;
    }
    /* Set up a timer so we won't get stuck while waiting for the server. */
    if (setjmp (byebye) == 0){
    signal (SIGALRM, timeout);
    alarm (RFC931_TIMEOUT);
    
/*
 * Bind the local and remote ends of the query socket to the same IP
 * addresses as the connection under investigation. We go through all
 * this trouble because the local or remote system might have more than
 * one network address. The RFC931 etc. client sends only port numbers;
 * the server takes the IP addresses from the query socket.
 */
    our_pt = ntohs (our_sin.sin_port);
    our_sin.sin_port = htons (ANY_PORT);
    
    rmt_sin = *from;
    rmt_pt = ntohs (rmt_sin.sin_port);
    rmt_sin.sin_port = htons (RFC931_PORT);
    
    setbuf (fp, (char *) 0);
    s = fileno (fp);
    
    if (bind (s, (struct sockaddr *) &our_sin, sizeof (our_sin)) >= 0 &&
        connect (s, (struct sockaddr *) &rmt_sin, sizeof (rmt_sin)) >= 0){
/*
 * Send query to server. Neglect the risk that a 13-byte write would
 * have to be fragmented by the local system and cause trouble with
 * buggy System V stdio libraries.
 */
        fprintf (fp, "%u,%u\r\n", rmt_pt, our_pt);
        fflush (fp);
/*
 * Read response from server. Use fgets()/sscanf() so we can work
 * around System V stdio libraries that incorrectly assume EOF when a
 * read from a socket returns less than requested.
 */
        if (fgets (buffer, sizeof (buffer), fp) && !ferror (fp)
        && !feof (fp)
        && sscanf (buffer, "%u , %u : USERID :%*[^:]:%79s", &rmt_port,
               &our_port, user) == 3 && rmt_pt == rmt_port
        && our_pt == our_port){

/*
 * Strip trailing carriage return. It is part of the protocol, not
 * part of the data.
 */
        if ((cp = (char *) strchr (user, '\r')))
            *cp = 0;
        strcpy (rname, user);
        }
    }
      alarm (0);
    }
    fclose (fp);
#endif
}

static int
bind_port (int port)
{
    int sock, on;
    
    sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    on = 1;
    setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof (on));
    setsockopt (sock, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof (on));
    
    on = 0;
    setsockopt (sock, SOL_SOCKET, SO_LINGER, (char *) &on, sizeof (on));
    
    xsin.sin_port = htons (port);
    if (bind (sock, (struct sockaddr *) &xsin, sizeof xsin) < 0){
    syslog (LOG_INFO, "bbsd bind_port can't bind to %d", port);
    exit (1);
    }
    if (listen (sock, SOCKET_QLEN) < 0){
    syslog (LOG_INFO, "bbsd bind_port can't listen to %d", port);
    exit (1);
    }
    return sock;
}


/*******************************************************/


static void shell_login (int argc, char *argv[], char *envp[]);
static void daemon_login (int argc, char *argv[], char *envp[]);
static int check_ban_and_load (int fd);
#ifdef SUPPORT_GB
extern int current_font_type;
#endif

int
main (int argc, char *argv[], char *envp[])
{
    /* avoid SIGPIPE */
    signal (SIGPIPE, SIG_IGN);
    
    /* avoid erroneous signal from other mbbsd */
    signal (SIGUSR1, SIG_IGN);
    signal (SIGUSR2, SIG_IGN);
    
    /* check if invoked as "bbs" */
    if (argc == 3)
    shell_login (argc, argv, envp);
    else
    daemon_login (argc, argv, envp);
    
    return 0;
}

static void
shell_login (int argc, char *argv[], char *envp[])
{
    
    /* Give up root privileges: no way back from here */
    setgid (BBSGID);
    setuid (BBSUID);
    chdir (BBSHOME);
    
    /* mmap passwd file */
    if (passwd_mmap ())
    exit (1);
    
    use_shell_login_mode = 1;
    initsetproctitle (argc, argv, envp);
    
    /* copy fromindent: Standard input:1138: Error:Unexpected end of file
       the original "bbs" */
    if (argc > 1){
    strcpy (fromhost, argv[1]);
    if (argc > 3)
        strcpy (remoteusername, argv[3]);
    }
    
    close (2);
    /* don't close fd 1, at least init_tty need it */
    
    init_tty ();
    if (check_ban_and_load (0)){
    exit (0);
    }
    start_client ();
}

static void
daemon_login (int argc, char *argv[], char *envp[])
{
    int msock, csock;       /* socket for Master and Child */
    FILE *fp;
    int listen_port = 23;
    int len_of_sock_addr;
    char buf[256];
    
    /* setup standalone */

    start_daemon();
    
    signal_restart(SIGCHLD, reapchild);
    
    /* choose port */
    if(argc == 1)
    listen_port = 3006;
    else if(argc >= 2)
    listen_port = atoi(argv[1]);

    sprintf(margs, "%s %d ", argv[0],listen_port);

    /* port binding */
    xsin.sin_family = AF_INET;
    msock = bind_port(listen_port);
    if(msock<0) {
    syslog(LOG_INFO, "mbbsd bind_port failed.\n");
    exit(1);
    }
    

    initsetproctitle(argc, argv, envp);
    setproctitle("%s: listening ", margs);
    
    /* Give up root privileges: no way back from here */
    setgid(BBSGID);
    setuid(BBSUID);
    chdir(BBSHOME);
    
    /* mmap passwd file */
    if(passwd_mmap())
    {
    exit(1);
    }
    sprintf(buf, "run/mbbsd.%d.pid", listen_port);
    if((fp = fopen(buf, "w"))) {
    fprintf(fp, "%d\n", getpid());
    fclose(fp);
    }
    
    /* main loop */
    for(;;) {
    len_of_sock_addr = sizeof(xsin);
    csock = accept(msock, (struct sockaddr *)&xsin, (socklen_t *)&len_of_sock_addr);

    if(csock < 0) {
        if(errno!=EINTR) sleep(1);
        continue;
    }
    
    if(check_ban_and_load(csock))
    {
        close(csock);
        continue;
    }

#ifdef NO_FORK
    break;
#else
    if(fork()==0)
        break;
    else
        close(csock);
#endif

    }   
    /* here is only child running */
    
    setproctitle("%s: ...login wait... ", margs);
    close(msock);
    dup2(csock, 0);
    close(csock);

    getremotename(&xsin, fromhost, remoteusername);
    telnet_init();
    start_client();
    close(0);
    close(1);
}

/* check if we're banning login and if the load is too high.
   if login is permitted, return 0;
   else return -1;
   approriate message is output to fd.
*/
static int check_ban_and_load(int fd)
{
    FILE *fp;
    static char buf[256];
    static time_t chkload_time = 0;
    static int overload = 0;    /* overload or banned, update every 1 sec  */
    static int banned = 0;
    
    if((time(0) - chkload_time) > 1) {
    overload = chkload(buf);
    banned = !access(BBSHOME "/BAN",R_OK) &&
        (strcmp(fromhost, "localhost") != 0);
    chkload_time = time(0);
    }

    write(fd, buf, strlen(buf));

    if(banned && (fp = fopen(BBSHOME "/BAN", "r"))) {
    while(fgets(buf, 256, fp))
        write(fd, buf, strlen(buf));
    fclose(fp);
    }

    if(banned || overload)
    return -1;

#ifdef INSCREEN
    write(fd, INSCREEN, strlen(INSCREEN));
#endif

    return 0;
}