summaryrefslogblamecommitdiffstats
path: root/mbbsd/mbbsd.c
blob: d487eca38c80593119892adbff98de6ec07d0765 (plain) (tree)
1
2
3
4
5
          
                
                  
                   
 








                           
 
                     
 
                                        
                                                                  
 



                                                                  

                                             









                                
                                 
                       





                       
 

                            
                            
                            
                                        


                                   
                                 

                            

                               
                                   
  
 








                                              

                                 




                                      
      
 



                                               

                                                                
                                                 


                             
                                                
                     
                                  


           
                                          
 
                
                          
      
 





                                                                             
                                       
                               
                            
 

                                                                 
 



                            
     
 
                                                                    
                
                        
             
                   
 
                                                                                           


                    
      
 

                               

                                   
 









                                                                          



                            



           
                             
 


                                                                 

 
    
                                             
 
                     
              
                                       
                                              
                                                  
                                                                     
        
                                      

                                                        
 
                    
                                  
                           

 
 
           
                             

              
                            
        
                             

 
    
                        
 



                                                 
 
                                             


             
                                                                   



                                                             


                   
      



                                        
      
 


                                                                         
                                                      

                                              
                                          
                                    

                                          
                  






                                                                        
                                                        

                                                

                                      
                                   
                         
                          

 
    
                             
 






                               
                 
                          
            

 



                                                  

                                                                    
           
                        
 
             
                    
 







                                                 
                                                 
     
















                                
                                

                                            
                                                                                       

                                    
                                                    



                                                                
                                                    
 



                                               
 

             





                                                                       
                                          
                                                                               








                                                                        
                 
 
                
                                                                      
      

                                                               
                                                                            

                                       
                              
      
 
            



                   
             
 
                                                                        

 
    
                                
 
                              


                             
                  

                   
                                                                                       
                                                                       

                                                                                



                                                 
                                   
 

                                
                              
                    

                                 
                                 


     
    
                                 
 
                             

                                                         
                                                                                                  


                                                                                  
                                                                                              
                                                                               



                                    
                           
                                                           
                                             
         
                          
                                                                    



         
          
                                                    
 
                                                   

                                                    
 

                              
 



                    
                                 
 

                                            
                       
                                                      
                                          

                                                       





                                                                   
                      
                     
                          
                  
                                                  
                                


                                      
         

                        
                        


                                                                              
                            
                                      

                               

                                      
                                          
     


                                                                     



                                               
     


             
    
                      
 

                                
                               
                      
                                                            
               

                                
              
                       
                               
















                                                                             
                       
                                             
                          
             







                                                                       

                                                      
                            
         
            
                                  
                                           
                                   





                                        




                                                                     

                                    
                           
 
                      
                                 
      











                                                                       
                             
                 
             
 


                                     

                   

                                            

                      

                                   
     

                                
                                    
      

 
                  
                      

                   


                                                                  
 


                                                         






                                     
 


              
           
                      

                            

                              
                                
                                                        
 

                                             
                          
                                         
                              
                      
                   
 






                                                                       

                                   
                                         
                               





                                                    

                                                   
                              
                     



                                                                       
                                                         
                                              
                
                                            

                                           
                                                
             
         
            
                                       
                                                        
                     
                                                                   
                    



         

                             
 
                         





                                                       



































































                                                                         
           
                       
 
              
                                  

                                   
     
                                   
      



                                     

                      




                                                          
                                                        
      





                                          
                     

                    
                                     
 
            

                                             
      
 
                                                                        



                                              
                     
         
                                       
 
              


                                                   

                                         
                        


                                                 
                             
                        
         


                                  
 
                                   
 
                          
 
                

                                                                   

                                              
      












                                                       

                  

                    


                             

                                                      

                              


                                     
 


                                   
                                                         
                                                      
 
                                                
                                                                               
                         
                                 
 
                    
 
                                                     
                                                                          


                                                    

                      

         

               








                                                                             


    
                                                 
 







                                     
                                                 


                                             

                   



                                                            

                                               
                                      
                      

                                                          
         








                                                              

             

                      
                               



     
                                                                    
 







                                     
                                                 


                                             

                   



                                                            

                                               







                                              

                                                          
         




                                                              
             

                      
                               


     
                                     
          
                       
 
          
                                      
 

                     
                                         
                                                                                    
                     
         
     
             



           
              
 
                  
                      
 
                                



                                                                                 

 
           
                    
 
                                                                        



                                         
 
                                      


                                    


                                                                                 
                                                                  
                                                                    

                                                                


                                                                
                                     
                                      



                                     
                                                    


                                    
                                          

                                                
                                                              

                                                       
 

                                 
 
                          




                                                                      
                            
                          



                                                                                    
                             

                                                               
                                                              
 







                                                  


                                                                                
                      


                                                                            


                 




                                                                                  



                 
                                      

                                                                  
 
            

 

                                    







                                                                              
                                                                          


                  

                                        
                                
                                     

                                 
                                               

                                                                          
                                                                



                           
                                                                                     

                                         
                                    


                                       


     
                                                        
 
                                                              

 

                                            






















                                                               
                                                    
                                          

                                               


                                                                  
                                    

 
                       
                                         

                                                                                     
                                                                            
         

                                                                                    





                                                       
 
           
                
 
                                    
                                             
 


                                                                     

                             
 
                                  
                      
                              








                                                
                                              
 
                
 



                                                                
                            

                                                        


                                                                                                 















                                                                   

                                                       
                            
     
 
                                                                    
                             

                                              
                                             
     
 
                           
                                               
                                      
            

                                
 
                                               
                             
                   
                      
                         
 
                      
                                                    
                                                                        
                                    
         
                          
                              
                         
                         
                                  
                         


                                                                  
                          
                      

                                      
                      
      
            





                                                     
                              
     
 
                                       
                                     
 
                               














                                                                          

                               
                                           



                                           


     
                       
                      
      
 
                                   
 

                                    



                                          
                                                                                     



                           
     
 
                                     
     
                                       

                                                       

                      










                                                                      


           
                           
 





                                    

                                       

                                                                        
                                                                                   
             
         
                   



           
                                                   
 

                







                                                             
                                  
                        

 

                                          
 
               
                        
                                     


                                 
 
                        
                     

                                                         
                 
 










                                     
                                     



                                           
                               
               
 

                                                               








                                                              
                                                             
                 
                                              
 
                             
             

 
           
                                                         
 
                              
                                                           

 

                            
 


                                         
                                                                        
                      
                                                                       



                                                                         
                                                                               

      


                                                               
 



                                                                            
 






                      
 
                                                                       
                             
 

             
 




                            
 


                                                     
 

                                             

                                                                  
                                                                 
                
     
                                        
                                                                   
                







                                                         


                                                                         
                                                                         

                                                                         
 
                      
 

                            
                       
                             
 
                                                 

                             
 
                                                  









                                                   

              
                   
      
    








                                                                     
                                                                 






























                                                                              
                                                                    
                     



                                                          




















































































                                                                                                        





                                                                            
















                                                                             
                         
                                 









                                                                          
                 
 



                                                
        

                                                
                   
                                    

                 
 

                                                     

                                
 

                       

 
          
                                                      
 
           
 
                             
                                                        



                   


                                    
 
                                                     
             
                                                     



                                                                                           

               







                                                        


                                        
     


                                 
             

 
          






















































                                                                                   

                                      





















                                                                      

                                      




                                          
                   



                                                                                    



             
                                                       
 
                                                                         
                       
                                                      
                             

                                              
                                 
      

                              
 
                          
                         
                                       
 




                                             
                                          
                                                        
                                                           


                                                                 

                      



                                      
                                      
     
 



                                                        
 
                   
                
                                                                 


                                          
               
                            
                                                            




                                             

      
 
                              
                                                                
                                 
                                           

                   
 
                   
               
                                        

                                                                

                               

                     
 
                                                        


                                                            
                  
                                                     

                                                                         








                                        
                         
      

                     
 





                                
                  
         
     
                                    
 
                
                                                 
      


                   
               
 
                                                                
 
                                


                  
                   
             

 




                                                                           
                                                        
 
                       
                                      



                                                                             





                                                                      
                                          

      
                                       















                                                              

                               
 


                                





                                                                 
                                           


                                        
 
                           

                  

             
 
                                  
 
                                      



                                                            

             
/* $Id$ */
#include "bbs.h"
#include "banip.h"
#include "logind.h"

#ifdef __linux__
#    ifdef CRITICAL_MEMORY
#        include <malloc.h>
#    endif
#    ifdef DEBUG
#        include <mcheck.h>
#    endif
#endif


#define SOCKET_QLEN 4

static void do_aloha(const char *hello);
static void getremotename(const struct in_addr from, char *rhost);

//////////////////////////////////////////////////////////////////
// Site Optimization
// override these macro if you need more optimization, 
// based on OS/lib/package...
#ifndef OPTIMIZE_SOCKET
#define OPTIMIZE_SOCKET(sock) do {} while (0)
#endif

#ifndef XAUTH_HOST
#define XAUTH_HOST(x) x
#endif

#ifndef XAUTH_GETREMOTENAME
#define XAUTH_GETREMOTENAME(x) x
#endif

static unsigned char enter_uflag;
static int listen_port;

#define MAX_BINDPORT 20
enum TermMode {
    TermMode_TELNET,
    TermMode_TTY,
};

struct ProgramOption {
    bool    daemon_mode;
    bool    tunnel_mode;
    enum TermMode term_mode;
    int     term_width, term_height;
    int     nport;
    int     port[MAX_BINDPORT];
    int     flag_listenfd;
    char*   flag_tunnel_path;

    bool    flag_bypass;
    bool    flag_fork;
    bool    flag_checkload;
    char    flag_user[IDLEN+1];
};

static void 
free_program_option(struct ProgramOption *opt)
{
    if (!opt)
    return;
    free(opt->flag_tunnel_path);
    free(opt);
}

#ifdef DETECT_CLIENT
Fnv32_t client_code=FNV1_32_INIT;

void UpdateClientCode(unsigned char c)
{
    FNV1A_CHAR(c, client_code);
}
#endif

#ifdef USE_RFORK
#define fork() rfork(RFFDG | RFPROC | RFNOWAIT)
#endif

/* 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(struct ProgramOption *option)
{
#ifndef VALGRIND
    int             n, fd;
#endif

    /*
     * 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;
    char            buf[32];

    localtime_r(&dummy, &dummy_time);
    strftime(buf, sizeof(buf), "%d/%b/%Y:%H:%M:%S", &dummy_time);

    if (option->flag_fork) {
    if (fork()) {
        exit(0);
    }
    }

    /* rocker.011018: it's a good idea to close all unexcept fd!! */
#ifndef VALGRIND
    n = getdtablesize();
    while (n)
    close(--n);

    if( ((fd = open("log/stderr", O_WRONLY | O_CREAT | O_APPEND, 0644)) >= 0) && fd != 2 ){
    dup2(fd, 2);
    close(fd);
    }
#endif

    if(getenv("SSH_CLIENT"))
    unsetenv("SSH_CLIENT");
    if(getenv("SSH_CONNECTION"))
    unsetenv("SSH_CONNECTION");

    /*
     * 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
     */
    if (option->flag_fork) {
    if (fork()) {
        exit(0);
    }
    }
}

static void
reapchild(int sig GCC_UNUSED)
{
    int             state, pid;

    while ((pid = waitpid(-1, &state, WNOHANG | WUNTRACED)) > 0);
}

void
log_usies(const char *mode, const char *mesg)
{
    now = time(NULL);
    if (!mesg)
        log_filef(FN_USIES, LOG_CREAT, 
                 "%s %s %-12s Stay:%d (%s)\n",
                 Cdate(&now), mode, cuser.userid ,
                 (int)(now - login_start_time) / 60, cuser.nickname);
    else
        log_filef(FN_USIES, LOG_CREAT,
                 "%s %s %-12s %s\n",
                 Cdate(&now), mode, cuser.userid, mesg);

    /* 追蹤使用者 */
    if (HasUserPerm(PERM_LOGUSER))
        log_user("logout");
}


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

void
u_exit(const char *mode)
{
    int diff = (time(0) - login_start_time) / 60;
    int dirty = currmode & MODE_DIRTY;

    currmode = 0;

    /* close fd 0 & 1 to terminate network */
    close(0);
    close(1);

    // verify if utmp is valid. only flush data if utmp is correct.
    assert(strncmp(currutmp->userid,cuser.userid, IDLEN)==0);
    if(strncmp(currutmp->userid,cuser.userid, IDLEN)!=0)
    return;

    auto_backup();
    save_brdbuf();
    brc_finalize();
    /*
    cuser.goodpost = currutmp->goodpost;
    cuser.badpost = currutmp->badpost;
    cuser.goodsale = currutmp->goodsale;
    cuser.badsale = currutmp->badsale;
    */

    // no need because in later passwd_update will reload money from SHM.
    // reload_money();

    setflags(PAGER_FLAG, currutmp->pager != PAGER_ON);
    setflags(CLOAK_FLAG, currutmp->invisible);

    cuser.invisible = currutmp->invisible;
    cuser.withme = currutmp->withme;
    cuser.pager = currutmp->pager;
    memcpy(cuser.mind, currutmp->mind, 4);
    setutmpbid(0);

    if (!SHM->GV2.e.shutdown) {
    if (!(HasUserPerm(PERM_SYSOP) && HasUserPerm(PERM_SYSOPHIDE)) &&
        !currutmp->invisible)
        do_aloha("<<下站通知>> -- 我走囉!");
    }

    if ((cuser.uflag != enter_uflag) || dirty || diff) {
    if (!diff && cuser.numlogins)
        cuser.numlogins = --cuser.numlogins;
    /* Leeym 上站停留時間限制式 */
    }
    passwd_update(usernum, &cuser);
    purge_utmp(currutmp);
    log_usies(mode, NULL);
}

void
abort_bbs(int sig GCC_UNUSED)
{
    /* ignore normal signals */
    Signal(SIGALRM, SIG_IGN);
    Signal(SIGUSR1, SIG_IGN);
    Signal(SIGUSR2, SIG_IGN);
    Signal(SIGHUP, SIG_IGN);
    Signal(SIGTERM, SIG_IGN);
    Signal(SIGPIPE, SIG_IGN);
    if (currmode)
    u_exit("ABORTED");
    exit(0);
}

#ifdef GCC_NORETURN
static void abort_bbs_debug(int sig) GCC_NORETURN;
#endif

/* NOTE: It's better to use signal-safe functions. Avoid to call
 * functions with global/static variable -- data may be corrupted */
static void
abort_bbs_debug(int sig)
{
    int    i;
    sigset_t sigset;

    switch(sig) {
      case SIGINT: STATINC(STAT_SIGINT); break;
      case SIGQUIT: STATINC(STAT_SIGQUIT); break;
      case SIGILL: STATINC(STAT_SIGILL); break;
      case SIGABRT: STATINC(STAT_SIGABRT); break;
      case SIGFPE: STATINC(STAT_SIGFPE); break;
      case SIGBUS: STATINC(STAT_SIGBUS); break;
      case SIGSEGV: STATINC(STAT_SIGSEGV); break;
      case SIGXCPU: STATINC(STAT_SIGXCPU); break;
    }
    /* ignore normal signals */
    Signal(SIGALRM, SIG_IGN);
    Signal(SIGUSR1, SIG_IGN);
    Signal(SIGUSR2, SIG_IGN);
    Signal(SIGHUP, SIG_IGN);
    Signal(SIGTERM, SIG_IGN);
    Signal(SIGPIPE, SIG_IGN);

    /* unblock */
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGINT);
    sigaddset(&sigset, SIGQUIT);
    sigaddset(&sigset, SIGILL);
    sigaddset(&sigset, SIGABRT);
    sigaddset(&sigset, SIGFPE);
    sigaddset(&sigset, SIGBUS);
    sigaddset(&sigset, SIGSEGV);
    sigaddset(&sigset, SIGXCPU);
    sigprocmask(SIG_UNBLOCK, &sigset, NULL);

    fprintf(stderr, "%d %d %d %.12s\n", (int)time4(NULL), getpid(), sig, cuser.userid);
#define CRASH_MSG ANSI_COLOR(0) \
    "\r\n程式異常, 立刻斷線. \r\n" \
    "請洽 " BN_BUGREPORT " 板詳述問題發生經過。\r\n"

#define XCPU_MSG ANSI_COLOR(0) \
    "\r\n程式耗用過多計算資源, 立刻斷線。\r\n" \
    "可能是 (a)執行太多耗用資源的動作 或 (b)程式掉入無窮迴圈. "\
    "請洽 " BN_BUGREPORT " 板詳述問題發生經過。\r\n"

    if(sig==SIGXCPU)
    write(1, XCPU_MSG, sizeof(XCPU_MSG));
    else
    write(1, CRASH_MSG, sizeof(CRASH_MSG));

    sleep(5);

    /* close all file descriptors (including the network connection) */
    for (i = 0; i < 256; ++i)
    close(i);

    /* log */
    /* assume vsnprintf() in log_file() is signal-safe, is it? */
    log_filef("log/crash.log", LOG_CREAT, 
        "%d %d %d %.12s\n", (int)time4(NULL), getpid(), sig, cuser.userid);

    /* try logout... not a good idea, maybe crash again. now disabled */
    /*
    if (currmode) {
    currmode = 0;
    u_exit("AXXED");
    }
    */

#ifdef DEBUGSLEEP

#ifndef VALGRIND
    setproctitle("debug me!(%d)(%s,%d)", sig, cuser.userid, currstat);
#endif
    /* do this manually to prevent broken stuff */
    /* will broken currutmp cause problems here? hope not... */
    if(currutmp && strncmp(cuser.userid, currutmp->userid, IDLEN) == EQUSTR)
    currutmp->mode = DEBUGSLEEPING;

    sleep(DEBUGSLEEP_SECONDS);
#endif

    exit(0);
}

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

void
talk_request(int sig GCC_UNUSED)
{
    STATINC(STAT_TALKREQUEST);
    bell();
    bell();
    if (currutmp->msgcount) {
    syncnow();
    move(0, 0);
    clrtoeol();
    prints(ANSI_COLOR(33;41) "★%s" ANSI_COLOR(34;47) " [%s] %s " ANSI_COLOR(0) "",
         SHM->uinfo[currutmp->destuip].userid, Cdatelite(&now),
         (currutmp->sig == 2) ? "重要消息廣播!(請Ctrl-U,l查看熱訊記錄)"
         : "呼叫、呼叫,聽到請回答");
    refresh();
    } else {
    unsigned char   mode0 = currutmp->mode;
    char            c0 = currutmp->chatid[0];
    screen_backup_t old_screen;

    currutmp->mode = 0;
    currutmp->chatid[0] = 1;
    scr_dump(&old_screen);
    talkreply();
    currutmp->mode = mode0;
    currutmp->chatid[0] = c0;
    scr_restore(&old_screen);
    }
}

void
show_call_in(int save, int which)
{
    char            buf[200];
#ifdef PLAY_ANGEL
    if (currutmp->msgs[which].msgmode == MSGMODE_TOANGEL)
    snprintf(buf, sizeof(buf), ANSI_COLOR(1;37;46) "★%s" ANSI_COLOR(37;45) " %s " ANSI_RESET,
        currutmp->msgs[which].userid, currutmp->msgs[which].last_call_in);
    else
#endif
    snprintf(buf, sizeof(buf), ANSI_COLOR(1;33;46) "★%s" ANSI_COLOR(37;45) " %s " ANSI_RESET,
         currutmp->msgs[which].userid, currutmp->msgs[which].last_call_in);
    outmsg(buf);

    if (save) {
    char            genbuf[200];
    if (!fp_writelog) {
        sethomefile(genbuf, cuser.userid, fn_writelog);
        fp_writelog = fopen(genbuf, "a");
    }
    if (fp_writelog) {
        fprintf(fp_writelog, "%s [%s]\n", buf, Cdatelite(&now));
    }
    }
}

static int
add_history_water(water_t * w, const msgque_t * msg)
{
    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(const msgque_t * msg)
{
    int             i = 0, j, waterinit = 0;
    water_t        *tmp;
    check_water_init();
    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
#ifdef PLAY_ANGEL
            && swater[i]->msg[0].msgmode == msg->msgmode
            /* When throwing waterball to angel directly */
#endif
            )
        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, msgcount;

    STATINC(STAT_WRITEREQUEST);
#ifdef NOKILLWATERBALL
    if( reentrant_write_request ) /* kill again by shmctl */
    return;
    reentrant_write_request = 1;
#endif
    syncnow();
    check_water_init();
    if (WATERMODE(WATER_OFO)) {
    /* 如果目前正在回水球模式的話, 就不能進行 add_history() ,
       因為會改寫 water[], 而使回水球目的爛掉, 所以分成幾種情況考慮.
       sig != 0表真的有水球進來, 故顯示.
       sig == 0表示沒有水球進來, 不過之前尚有水球還沒寫到 water[].
    */
    static  int     alreadyshow = 0;

    if( sig ){ /* 真的有水球進來 */

        /* 若原來正在 REPLYING , 則改成 RECVINREPLYING,
           這樣在回水球結束後, 會再呼叫一次 write_request(0) */
        if( wmofo == REPLYING )
        wmofo = RECVINREPLYING;

        /* 顯示 */
        for( ; alreadyshow < currutmp->msgcount && alreadyshow < MAX_MSGS
             ; ++alreadyshow ){
        bell();
        show_call_in(1, alreadyshow);
        refresh();
        }
    }

    /* 看看是不是要把 currutmp->msg 拿回 water[] (by add_history())
       須要是不在回水球中 (NOTREPLYING) */
    if( wmofo == NOTREPLYING &&
        (msgcount = currutmp->msgcount) > 0 ){
        for( i = 0 ; i < msgcount ; ++i )
        add_history(&currutmp->msgs[i]);
        if( (currutmp->msgcount -= msgcount) < 0 )
        currutmp->msgcount = 0;
        alreadyshow = 0;
    }
    } else {
    if (currutmp->mode != 0 &&
        currutmp->pager != PAGER_OFF &&
        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 = HIT;

#ifdef NOKILLWATERBALL
        currutmp->wbtime = 0;
#endif
        if( (msgcount = currutmp->msgcount) > 0 ){
        for( i = 0 ; i < msgcount ; ++i ){
            bell();
            show_call_in(1, 0);
            add_history(&currutmp->msgs[0]);

            if( (--currutmp->msgcount) < 0 )
            i = msgcount; /* force to exit for() */
            else if( currutmp->msgcount > 0 )
            memmove(&currutmp->msgs[0],
                &currutmp->msgs[1],
                sizeof(msgque_t) * currutmp->msgcount);
            igetch();
        }
        }

        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;
    }
    }
#ifdef NOKILLWATERBALL
    reentrant_write_request = 0;
    currutmp->wbtime = 0; /* race */
#endif
}

static userinfo_t*
getotherlogin(int num)
{
    userinfo_t *ui;
    do {
    if (!(ui = (userinfo_t *) search_ulistn(usernum, num)))
        return NULL;        /* user isn't logged in */

    /* skip sleeping process, this is slow if lots */
    if(ui->mode == DEBUGSLEEPING)
        num++;
    else if(ui->pid <= 0)
        num++;
    else if(kill(ui->pid, 0) < 0)
        num++;
    else
        break;
    } while (1);

    return ui;
}

static void
multi_user_check(void)
{
    register userinfo_t *ui;
    char            genbuf[3];

    if (HasUserPerm(PERM_SYSOP))
    return;         /* don't check sysops */

    srandom(getpid());
    // race condition here, sleep may help..?
    if (cuser.userlevel) {
    usleep(random()%1000000); // 0~1s
    ui = getotherlogin(1);
    if(ui == NULL)
        return;

    move(b_lines-6, 0); clrtobot();
    outs("\n" ANSI_COLOR(1) 
        "注意: 您有其它連線已登入此帳號。\n"
        " 同時多次登入同個帳號可能導致文章數或金錢異常,\n"
        " 且本站不受理因多重登入造成的損失。" ANSI_RESET);

    getdata(b_lines - 1, 0, "您想刪除其他重複登入的連線嗎?[Y/n] ",
        genbuf, 3, LCECHO);

    usleep(random()%5000000); // 0~5s
    if (genbuf[0] != 'n') {
        do {
        // scan again, old ui may be invalid
        ui = getotherlogin(1);
        if(ui==NULL)
            return;
        if (ui->pid > 0) {
            if(kill(ui->pid, SIGHUP)<0) {
            perror("kill SIGHUP fail");
            break;
            }
            log_usies("KICK ", cuser.nickname);
        }  else {
            fprintf(stderr, "id=%s ui->pid=0\n", cuser.userid);
        }
        usleep(random()%2000000+1000000); // 1~3s
        } while(getotherlogin(3) != NULL);
    } else {
        /* deny login if still have 3 */
        if (getotherlogin(3) != NULL) {
        sleep(1);
        abort_bbs(0);   /* Goodbye(); */
        }
    }
    } else {
    /* allow multiple guest user */
    if (search_ulistn(usernum, MAX_GUEST) != NULL) {
        sleep(1);
        vmsg("抱歉,目前已有太多 guest 在站上, 請用new註冊。");
        exit(1);
    }
    }
}

void 
mkuserdir(const char *userid)
{
    char genbuf[PATHLEN];
    sethomepath(genbuf, userid);
    // assume it is a dir, so just check if it is exist
    if (access(genbuf, F_OK) != 0)
    mkdir(genbuf, 0755);
}

static int
load_current_user(const char *uid)
{
    // userid should be already normalized.

    // ----------------------------------------------------- NEW ACCOUNT 

#ifdef STR_REGNEW
    if (strcasecmp(uid, STR_REGNEW) == 0) 
    {

# ifndef LOGINASNEW
    assert(false);
    exit(0);
# endif // !LOGINASNEW

    new_register();
    mkuserdir(cuser.userid);
    reginit_fav();
    } else 
#endif

    // --------------------------------------------------- GUEST ACCOUNT 
    
#ifdef STR_GUEST
    if (strcasecmp(uid, STR_GUEST) == 0)
    {
    if (initcuser(STR_GUEST)< 1) exit (0) ;
    cuser.userlevel = 0;
    cuser.uflag = PAGER_FLAG | BRDSORT_FLAG | MOVIE_FLAG;
    cuser.uflag2= 0; // we don't need FAVNEW_FLAG or anything else.

# ifdef GUEST_DEFAULT_DBCS_NOINTRESC
    cuser.uflag |= DBCS_NOINTRESC;
# endif
    // can we prevent mkuserdir() here?
    mkuserdir(cuser.userid);
    } else 
#endif

    // ---------------------------------------------------- USER ACCOUNT 
    {
    if (!cuser.userid[0] && initcuser(uid) < 1) exit(0);

#ifdef LOCAL_LOGIN_MOD
    LOCAL_LOGIN_MOD();
#endif
    if (strcasecmp(str_sysop, cuser.userid) == 0){
#ifdef NO_SYSOP_ACCOUNT
        exit(0);
#else /* 自動加上各個主要權限 */
        // TODO only allow in local connection?
        cuser.userlevel = PERM_BASIC | PERM_CHAT | PERM_PAGE |
        PERM_POST | PERM_LOGINOK | PERM_MAILLIMIT |
        PERM_CLOAK | PERM_SEECLOAK | PERM_XEMPT |
        PERM_SYSOPHIDE | PERM_BM | PERM_ACCOUNTS |
        PERM_CHATROOM | PERM_BOARD | PERM_SYSOP | PERM_BBSADM;
#endif
    }
    /* 早該有 home 了, 不知道為何有的帳號會沒有, 被砍掉了? */
    mkuserdir(cuser.userid);
    }

    // check multi user
    multi_user_check();
    return 1;
}

static void
login_query(char *ruid)
{
#ifdef CONVERT
    /* uid 加一位, for gb login */
    char            uid[IDLEN + 2];
    int         len;
#else
    char            uid[IDLEN + 1];
#endif

    char        passbuf[PASSLEN];
    int             attempts;

    resolve_garbage();
    now = time(0);

#ifdef DEBUG
    move(1, 0);
    prints("debugging mode\ncurrent pid: %d\n", getpid());
#else
    show_file("etc/Welcome", 1, -1, SHOWFILE_ALLOW_ALL);
#endif

    attempts = 0;
    while (1) {
    if (attempts++ >= LOGINATTEMPTS) {
        more("etc/goodbye", NA);
        pressanykey();
        sleep(3);
        exit(1);
    }
    bzero(&cuser, sizeof(cuser));

#ifdef DEBUG
    move(19, 0);
    prints("current pid: %d ", getpid());
#endif

    if (getdata(20, 0, "請輸入代號,或以[guest]參觀,以[new]註冊: ",
        uid, sizeof(uid), DOECHO) < 1)
    {
        // got nothing 
        outs("請重新輸入。\n");
        continue;
    }
    telnet_turnoff_client_detect();

#ifdef CONVERT
    /* switch to gb mode if uid end with '.' */
    len = strlen(uid);
    if (uid[0] && uid[len - 1] == '.') {
        set_converting_type(CONV_GB);
        uid[len - 1] = 0;
        redrawwin();
    }
    else if (uid[0] && uid[len - 1] == ',') {
        set_converting_type(CONV_UTF8);
        uid[len - 1] = 0;
        redrawwin();
    }
    else if (len >= IDLEN + 1)
        uid[IDLEN] = 0;
#endif

    if (!is_validuserid(uid)) {

        outs(err_uid);

#ifdef STR_GUEST
    } else if (strcasecmp(uid, STR_GUEST) == 0) {   /* guest */

        strlcpy(ruid, STR_GUEST, IDLEN+1);
        break;
#endif

#ifdef STR_REGNEW
    } else if (strcasecmp(uid, STR_REGNEW) == 0) {
# ifndef LOGINASNEW
        outs("本系統目前無法以 " STR_REGNEW " 註冊"
#  ifdef STR_GUEST
         ", 請用 " STR_GUEST " 進入"
#  endif  // STR_GUEST
         "\n");
        continue;
# endif // !LOGINASNEW

        strlcpy(ruid, STR_REGNEW, IDLEN+1);
        break;

#endif // STR_REGNEW

    } else {

        /* normal user */
        getdata(21, 0, MSG_PASSWD,
            passbuf, sizeof(passbuf), NOECHO);
        passbuf[8] = '\0';

        move (22, 0); clrtoeol();
        outs("正在檢查密碼...");
        move(22, 0); refresh(); 

        /* prepare for later */
        clrtoeol();

        if( initcuser(uid) < 1 || !cuser.userid[0] ||
        !checkpasswd(cuser.passwd, passbuf) ){

        if(is_validuserid(cuser.userid))
            logattempt(cuser.userid , '-', login_start_time, fromhost);
        sleep(1);
        outs(ERR_PASSWD);

        } else {

        strlcpy(ruid, cuser.userid, IDLEN+1);
        logattempt(cuser.userid, ' ', login_start_time, fromhost);
        outs("密碼正確! 開始登入系統..."); 
        move(22, 0); refresh();
        clrtoeol();
        break;
        }
    }
    }

    // auth ok.
#ifdef DETECT_CLIENT
    {
    int fd = open("log/client_code",O_WRONLY | O_CREAT | O_APPEND, 0644);
    if(fd>=0) {
        write(fd, &client_code, sizeof(client_code));
        close(fd);
    }
    }
#endif
}

void
add_distinct(const char *fname, const char *line)
{
    FILE           *fp;
    int             n = 0;

    if ((fp = fopen(fname, "a+"))) {
    char            buffer[80];
    char            tmpname[100];
    FILE           *fptmp;

    strlcpy(tmpname, fname, sizeof(tmpname));
    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);
    rename(tmpname, fname);
    }
}

void
del_distinct(const char *fname, const char *line, int casesensitive)
{
    FILE           *fp;
    int             n = 0;

    if ((fp = fopen(fname, "r"))) {
    char            buffer[80];
    char            tmpname[100];
    FILE           *fptmp;

    strlcpy(tmpname, fname, sizeof(tmpname));
    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(casesensitive)
        {
        if (!strcmp(buffer, line))
            break;
        } else {
        if (!strcasecmp(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);
    rename(tmpname, fname);
    }
}

#if defined(WHERE) && !defined(FROMD)
static int
where(const char *from)
{
    int i;
    uint32_t ipaddr = ipstr2int(from);

    resolve_fcache();

    for (i = 0; i < SHM->home_num; i++) {
    if ((SHM->home_ip[i] & SHM->home_mask[i]) == (ipaddr & SHM->home_mask[i])) {
        return i;
    }
    }
    return 0;
}
#endif

static void
check_BM(void)
{
    /* XXX: -_- */
    int             i;

    cuser.userlevel &= ~PERM_BM;
    for( i = 0 ; i < numboards ; ++i )
    if( is_BM_cache(i + 1) ) /* XXXbid */
        return;
    //for (i = 0, bhdr = bcache; i < numboards && !is_BM(bhdr->BM); i++, bhdr++);
}

static void
setup_utmp(int mode)
{
    /* NOTE, 在 getnewutmpent 之前不應該有任何 slow/blocking function */
    userinfo_t      uinfo = {0};
    uinfo.pid       = currpid = getpid();
    uinfo.uid       = usernum;
    uinfo.mode      = currstat = mode;

    uinfo.userlevel = cuser.userlevel;
    uinfo.sex       = cuser.sex % 8;
    uinfo.lastact   = time(NULL);

    // only enable this after you've really changed talk.c to NOT use from_alias.
    uinfo.from_ip   = inet_addr(fromhost);

    strlcpy(uinfo.userid,   cuser.userid,   sizeof(uinfo.userid));
    strlcpy(uinfo.nickname, cuser.nickname, sizeof(uinfo.nickname));
    strlcpy(uinfo.from,     fromhost,       sizeof(uinfo.from));

    // 當初設計的人把 mind 設計成非 NULL terminated 的...
    memcpy(uinfo.mind,      cuser.mind,     sizeof(cuser.mind));

    uinfo.five_win  = cuser.five_win;
    uinfo.five_lose = cuser.five_lose;
    uinfo.five_tie  = cuser.five_tie;
    uinfo.chc_win   = cuser.chc_win;
    uinfo.chc_lose  = cuser.chc_lose;
    uinfo.chc_tie   = cuser.chc_tie;
    uinfo.chess_elo_rating = cuser.chess_elo_rating;
    uinfo.go_win    = cuser.go_win;
    uinfo.go_lose   = cuser.go_lose;
    uinfo.go_tie    = cuser.go_tie;
    uinfo.invisible = cuser.invisible % 2;
    uinfo.pager     = cuser.pager % PAGER_MODES;

    if(cuser.withme & (cuser.withme<<1) & (WITHME_ALLFLAG<<1))
    cuser.withme = 0; /* unset all if contradict */
    uinfo.withme = cuser.withme & ~WITHME_ALLFLAG;

    if (enter_uflag & CLOAK_FLAG)
    uinfo.invisible = YEA;

    getnewutmpent(&uinfo);

    //////////////////////////////////////////////////////////////////
    // 以下可以進行比較花時間的運算。
    //////////////////////////////////////////////////////////////////

    currmode = MODE_STARTED;
    SHM->UTMPneedsort = 1;

    strip_nonebig5((unsigned char *)currutmp->nickname, sizeof(currutmp->nickname));
    strip_nonebig5((unsigned char *)currutmp->mind, sizeof(currutmp->mind));

    // XXX 不用每 20 才檢查吧
    // XXX 這個 check 花不少時間,有點間隔比較好
    if ((cuser.userlevel & PERM_BM) && !(cuser.numlogins % 20))
    check_BM();     /* Ptt 自動取下離職板主權力 */

    // resolve fromhost
#if defined(WHERE)

# ifdef FROMD
    {
    int fd;
    if ( (fd = toconnect(FROMD_ADDR)) >= 0 ) {
        write(fd, fromhost, strlen(fromhost));
        // zero and reuse uinfo.from to check real data
        memset(uinfo.from, 0, sizeof(uinfo.from));
        read(fd, uinfo.from,  sizeof(uinfo.from) - 1); // keep trailing zero
        close(fd);
        // copy back to currutmp
        if (uinfo.from[0])
        strlcpy(currutmp->from, uinfo.from, sizeof(currutmp->from));
    }
    }
# else  // !FROMD
    {
    int desc = where(fromhost);
    if (desc > 0)
        strlcpy(currutmp->from, SHM->home_desc[desc], sizeof(currutmp->from));
    }
# endif // !FROMD

#endif // WHERE

    /* Very, very slow friend_load. */
    if( strcmp(cuser.userid, STR_GUEST) != 0 ) // guest 不處理好友
    friend_load(0);

    nice(3);
}

inline static void welcome_msg(void)
{
    prints(ANSI_RESET "      歡迎您第 " 
        ANSI_COLOR(1;33) "%d" ANSI_COLOR(0;37) " 度拜訪本站,上次您是從 " 
        ANSI_COLOR(1;33) "%s" ANSI_COLOR(0;37) " 連往本站," 
        ANSI_CLRTOEND "\n"
        "     我記得那天是 " ANSI_COLOR(1;33) "%s" ANSI_COLOR(0;37) "。"
        ANSI_CLRTOEND "\n"
        ANSI_CLRTOEND "\n"
        ,
        ++cuser.numlogins, cuser.lasthost, Cdate(&(cuser.lastlogin)));
    pressanykey();
}

inline static void check_bad_login(void)
{
    char            genbuf[200];
    setuserfile(genbuf, FN_BADLOGIN);
    if (more(genbuf, NA) != -1) {
    move(b_lines - 3, 0);
    outs("通常並沒有辦法知道該ip是誰所有, "
        "以及其意圖(是不小心按錯或有意測您密碼)\n"
        "若您有帳號被盜用疑慮, 請經常更改您的密碼或使用加密連線");
    if (vans("您要刪除以上錯誤嘗試的記錄嗎? [y/N] ") == 'y')
        unlink(genbuf);
    }
}

inline static void birthday_make_a_wish(const struct tm *ptime, const struct tm *tmp)
{
    if (tmp->tm_mday != ptime->tm_mday) {
    more("etc/birth.post", YEA);
    if (enter_board("WhoAmI")==0) {
        do_post();
    }
    }
}

inline static void record_lasthost(const char *fromhost)
{
    strlcpy(cuser.lasthost, fromhost, sizeof(cuser.lasthost));
}

inline static void check_mailbox_quota(void)
{
    if (chkmailbox())
    m_read();
}

static void init_guest_info(void)
{
    int i;
    char           *nick[13] = {
    "椰子", "貝殼", "內衣", "寶特瓶", "翻車魚",
    "樹葉", "浮萍", "鞋子", "潛水艇", "魔王",
    "鐵罐", "考卷", "大美女"
    };
    char           *name[13] = {
    "大王椰子", "鸚鵡螺", "比基尼", "可口可樂", "仰泳的魚",
    "憶", "高岡屋", "AIR Jordon", "紅色十月號", "批踢踢",
    "SASAYA椰奶", "鴨蛋", "布魯克鱈魚香絲"
    };
    char           *addr[13] = {
    "天堂樂園", "大海", "綠島小夜曲", "美國", "綠色珊瑚礁",
    "遠方", "原本海", "NIKE", "蘇聯", "男八618室",
    "愛之味", "天上", "藍色珊瑚礁"
    };
    i = login_start_time % 13;
    snprintf(cuser.nickname, sizeof(cuser.nickname),
        "海邊漂來的%s", nick[(int)i]);
    strlcpy(currutmp->nickname, cuser.nickname,
        sizeof(currutmp->nickname));
    strlcpy(cuser.realname, name[(int)i], sizeof(cuser.realname));
    strlcpy(cuser.address, addr[(int)i], sizeof(cuser.address));
    cuser.sex = i % 8;
    currutmp->pager = PAGER_DISABLE;
}

#if FOREIGN_REG_DAY > 0
inline static void foreign_warning(void){
    if ((cuser.uflag2 & FOREIGN) && !(cuser.uflag2 & LIVERIGHT)){
    if (login_start_time - cuser.firstlogin > (FOREIGN_REG_DAY - 5) * 24 * 3600){
        mail_muser(cuser, "[出入境管理局]", "etc/foreign_expired_warn");
    }
    else if (login_start_time - cuser.firstlogin > FOREIGN_REG_DAY * 24 * 3600){
        cuser.userlevel &= ~(PERM_LOGINOK | PERM_POST);
        vmsg("警告:請至出入境管理局申請永久居留");
    }
    }
}
#endif


static void
user_login(void)
{
    struct tm       ptime, lasttime;
    int             nowusers, ifbirth = 0, i;

    /* NOTE! 在 setup_utmp 之前, 不應該有任何 blocking/slow function,
     * 否則可藉機 race condition 達到 multi-login */

    /* resolve_boards(); */
    numboards = SHM->Bnumber;

    /* 初始化 uinfo、flag、mode */
    setup_utmp(LOGIN);
    enter_uflag = cuser.uflag;

    /* log usies */
    log_usies("ENTER", fromhost);
#ifndef VALGRIND
    setproctitle("%s: %s", margs, cuser.userid);
#endif

    /* get local time */
    localtime4_r(&now, &ptime);
    localtime4_r(&cuser.lastlogin, &lasttime);

    redrawwin();

    /* mask fromhost a.b.c.d to a.b.c.* */
    strlcpy(fromhost_masked, fromhost, sizeof(fromhost_masked));
    obfuscate_ipstr(fromhost_masked);

    /* show welcome_login */
    if( (ifbirth = (ptime.tm_mday == cuser.day &&
            ptime.tm_mon + 1 == cuser.month)) ){
    char buf[PATHLEN];
    snprintf(buf, sizeof(buf), "etc/Welcome_birth.%d", getHoroscope(cuser.month, cuser.day));
    more(buf, NA);
    }
    else {
#ifndef MULTI_WELCOME_LOGIN
    more("etc/Welcome_login", NA);
#else
    if( SHM->GV2.e.nWelcomes ){
        char            buf[80];
        snprintf(buf, sizeof(buf), "etc/Welcome_login.%d",
             (int)login_start_time % SHM->GV2.e.nWelcomes);
        more(buf, NA);
    }
#endif
    }
    refresh();
    currutmp->alerts |= load_mailalert(cuser.userid);

    if ((nowusers = SHM->UTMPnumber) > SHM->max_user) {
    SHM->max_user = nowusers;
    SHM->max_time = now;
    }

    if (!(HasUserPerm(PERM_SYSOP) && HasUserPerm(PERM_SYSOPHIDE)) &&
    !currutmp->invisible)
    {
    /* do_aloha is costly. do it later? */
    do_aloha("<<上站通知>> -- 我來啦!");
    }

    if (SHM->loginmsg.pid){
        if(search_ulist_pid(SHM->loginmsg.pid))
        getmessage(SHM->loginmsg);
        else
        SHM->loginmsg.pid=0;
    }

    if (cuser.userlevel) {  /* not guest */
    move(t_lines - 4, 0);
    clrtobot();
    welcome_msg();
    resolve_over18();

    if( ifbirth ){
        birthday_make_a_wish(&ptime, &lasttime);
        if( vans("是否要顯示「壽星」於使用者名單上?(y/N)") == 'y' )
        currutmp->birth = 1;
    }
    check_bad_login();
    check_mailbox_quota();
    check_birthday();
    check_register();
    record_lasthost(fromhost);
    restore_backup();

    } else if (strcmp(cuser.userid, STR_GUEST) == 0) { /* guest */

    init_guest_info();
#if 0 // def DBCSAWARE
    u_detectDBCSAwareEvilClient();
#else
    pressanykey();
#endif
    } else {
    // XXX no userlevel, no guest - what is this?
    // clear();
    // outs("此帳號停權中");
    // pressanykey();
    // exit(1);

    check_mailbox_quota();
    }

    if(ptime.tm_yday!=lasttime.tm_yday)
    STATINC(STAT_TODAYLOGIN_MAX);

    if (!PERM_HIDE(currutmp)) {
    /* If you wanna do incremental upgrade
     * (like, added a function/flag that wants user to confirm againe)
     * put it here.
     */

#if defined(DBCSAWARE) && defined(DBCSAWARE_UPGRADE_STARTTIME)
    // define the real time you upgraded in your pttbbs.conf
    if(cuser.lastlogin < DBCSAWARE_UPGRADE_STARTTIME)
    {
        if (u_detectDBCSAwareEvilClient())
        cuser.uflag &= ~DBCSAWARE_FLAG;
        else
        cuser.uflag |= DBCSAWARE_FLAG;
    }
#endif
    /* login time update */

    if(ptime.tm_yday!=lasttime.tm_yday)
        STATINC(STAT_TODAYLOGIN_MIN);


    cuser.lastlogin = login_start_time;

    }

#if FOREIGN_REG_DAY > 0
    foreign_warning();
#endif

    passwd_update(usernum, &cuser);

    if(cuser.uflag2 & FAVNEW_FLAG) {
    fav_load();
    if (get_fav_root() != NULL) {
        int num;
        num = updatenewfav(1);
        if (num > NEW_FAV_THRESHOLD &&
        vansf("找到 %d 個新看板,確定要加入我的最愛嗎?[Y/n]", num) == 'n') {
        fav_free();
        fav_load();
        }
    }
    }

    for (i = 0; i < NUMVIEWFILE; i++)
    {
    if ((cuser.loginview >> i) & 1)
    {
        const char *fn = loginview_file[(int)i][0];
        if (!fn)
        break;
        if (*fn == '@') // special
        {
        // since only one special now, let's write directly...
        if (strcmp(fn, "@calendar") == 0)
            calendar();
        } else {
        // use NA+pause or YEA?
        more(fn, YEA);
        }
    }
    }
}

static void
do_aloha(const char *hello)
{
    FILE           *fp;
    char            userid[80];
    char            genbuf[200];

    setuserfile(genbuf, "aloha");
    if ((fp = fopen(genbuf, "r"))) {
    while (fgets(userid, 80, fp)) {
        userinfo_t     *uentp;
        if ((uentp = (userinfo_t *) search_ulist_userid(userid)) && 
            isvisible(uentp, currutmp)) {
        my_write(uentp->pid, hello, uentp->userid, WATERBALL_ALOHA, uentp);
        }
    }
    fclose(fp);
    }
}

static void
do_term_init(enum TermMode term_mode, int w, int h)
{
    term_init();
    initscr();

    // if the terminal was already determined, resize for it.
    if ((w && (w != t_columns)) || 
    (h && (h != t_lines  )) )
    {
    term_resize(w, h);
    }

    if (term_mode == TermMode_TTY)
    raise(SIGWINCH);
}

static int
start_client(struct ProgramOption *option)
{
#ifdef CPULIMIT
    struct rlimit   rml;
    rml.rlim_cur = CPULIMIT * 60 - 5;
    rml.rlim_max = CPULIMIT * 60;
    setrlimit(RLIMIT_CPU, &rml);
#endif

    STATINC(STAT_LOGIN);
    /* 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(SIGXCPU, abort_bbs_debug);

    signal_restart(SIGUSR1, talk_request);
    signal_restart(SIGUSR2, write_request);

    Signal(SIGALRM, abort_bbs);
    alarm(600);

    mysrand(); /* 初始化: random number 增加user跟時間的差異 */

    // if flag_user contains an uid, it is already authorized.
    if (!option->flag_user[0])
    {
    // query user
    login_query(option->flag_user);
    }
    // process new, register, and load user data
    load_current_user(option->flag_user);

    m_init();           /* init the user mail path */
    user_login();
    auto_close_polls();     /* 自動開票 */

    Signal(SIGALRM, SIG_IGN);
    return 0;
}

static void
getremotename(const struct in_addr fromaddr, char *rhost)
{
    /* get remote host name */
    XAUTH_HOST(strcpy(rhost, (char *)inet_ntoa(fromaddr)));
}

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 (default 2 hours)
    const int on = 1; 
    setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on));

#if defined(SOL_TCP) && defined(TCP_KEEPIDLE)
    {
    const int idle = 300*2; // experimental, minimal keep alive check
    setsockopt(sock, SOL_TCP,    TCP_KEEPIDLE, (void*)&idle, sizeof(idle));
    }
#endif
   
    // 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;
}

static int
bind_port(int port)
{
    int    sock;
    struct sockaddr_in xsin;

    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    set_bind_opt(sock);

    xsin.sin_family = AF_INET;
    xsin.sin_addr.s_addr = htonl(INADDR_ANY);
    xsin.sin_port = htons(port);
    if (bind(sock, (struct sockaddr *) & xsin, sizeof xsin) < 0) {
    syslog(LOG_ERR, "bbsd bind_port can't bind to %d", port);
    exit(1);
    }
    if (listen(sock, SOCKET_QLEN) < 0) {
    syslog(LOG_ERR, "bbsd bind_port can't listen to %d", port);
    exit(1);
    }
    return sock;
}


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


static int      shell_login  (char *argv0, struct ProgramOption *option);
static int      daemon_login (char *argv0, struct ProgramOption *option);
static int      tunnel_login (char *argv0, struct ProgramOption *option);
static int      check_ban_and_load(int fd, struct ProgramOption *option);
static int  start_client (             struct ProgramOption *option);
static int      check_banip  (char *host);

static void init(void)
{
    start_time = time(NULL);

    /* avoid SIGPIPE */
    Signal(SIGPIPE, SIG_IGN);

    /* avoid erroneous signal from other mbbsd */
    Signal(SIGUSR1, SIG_IGN);
    Signal(SIGUSR2, SIG_IGN);

#if defined(__GLIBC__) && defined(CRITICAL_MEMORY)
    #define MY__MMAP_THRESHOLD (1024 * 8)
    #define MY__MMAP_MAX (0)
    #define MY__TRIM_THRESHOLD (1024 * 8)
    #define MY__TOP_PAD (0)

    mallopt (M_MMAP_THRESHOLD, MY__MMAP_THRESHOLD);
    mallopt (M_MMAP_MAX, MY__MMAP_MAX);
    mallopt (M_TRIM_THRESHOLD, MY__TRIM_THRESHOLD);
    mallopt (M_TOP_PAD, MY__TOP_PAD);
#endif

#ifdef CONVERT
    init_convert();
#endif
    
}

static void usage(char *argv0)
{
    fprintf(stdout,
        "Usage: %s { -d | -D } [options]\n"
        "\n"
        "daemon mode\n"
        "\t-d                 use daemon mode, imply -t telnet\n"
        "\t-n tunnel          enable tunnel mode, imply -d\n"
        "\t-p port            listen port\n"
        "\t-l fd              pre-listen fd\n"
        "\n"
        "non-daemon mode\n"
        "\t-D                 use non-daemon mode, imply -t tty\n"
        "\t-h hostip          hostip (default 127.0.0.1)\n"
        "\t-b                 bypass opening and ask password directly\n"
        "\t-u user            for -b: user (default guest)\n"
        "\n"
        "flags\n"
        "\t-t type            terminal mode, telnet | tty\n"
        "\t-e encoding        encoding (default big5), big5 | gb | utf8\n"
        "\n"
        "testing flags\n"
        "\t-F                 don't fork\n"
        "\t-C                 don't check load\n"
        "\n",
        argv0
      );
}

bool parse_argv(int argc, char *argv[], struct ProgramOption *option)
{
    int ch;
    bool given_mode = false;

    // init options
    memset(option, 0, sizeof(*option));
    option->flag_listenfd = -1;
    option->flag_checkload = true;

    while ((ch = getopt(argc, argv, "dn:p:l:Dt:h:e:bu:FC")) != -1) {
    switch (ch) {
        case 'n':
        option->tunnel_mode = true;
        option->flag_tunnel_path = strdup(optarg);
        // reuse 'd' mode, no break here.
        case 'd':
        given_mode = true;
        option->daemon_mode = true;
        option->term_mode = TermMode_TELNET;
        option->flag_fork = true;
        break;
        case 'p':
        if (option->nport < MAX_BINDPORT) {
            int port = atoi(optarg);
            option->port[option->nport++] = port;
        } else {
            fprintf(stderr, "too many port (>%d)\n", MAX_BINDPORT);
            exit(1);
        }
        break;
        case 'l':
        option->flag_listenfd = atoi(optarg);
        break;
        case 'D':
        given_mode = true;
        option->daemon_mode = false;
        option->term_mode = TermMode_TTY;
        break;
        case 't':
        if (strcmp(optarg, "telnet") == 0) {
            option->term_mode = TermMode_TELNET;
        } else if (strcmp(optarg, "tty") == 0) {
            option->term_mode = TermMode_TTY;
        } else {
            fprintf(stderr, "unknown type: %s\n", optarg);
            exit(1);
        }
        break;
        case 'h':
        strlcpy(fromhost, optarg, sizeof(fromhost));
        break;
        case 'e':
#ifdef CONVERT
        if (strcmp(optarg, "big5") == 0) {
            set_converting_type(CONV_NORMAL);
        } else if (strcmp(optarg, "gb") == 0) {
            set_converting_type(CONV_GB);
        } else if (strcmp(optarg, "utf8") == 0) {
            set_converting_type(CONV_UTF8);
        } else {
            fprintf(stderr, "unknown encoding: %s\n", optarg);
            exit(1);
        }
#endif
        break;
        case 'b':
        option->flag_bypass = true;
        // TODO
        fprintf(stderr, "not yet implemented\n");
        exit(1);
        break;
        case 'u':
        strlcpy(option->flag_user, optarg, sizeof(option->flag_user));
        break;
        case 'F':
        option->flag_fork = false;
        break;
        case 'C':
        option->flag_checkload = false;
        break;
        default:
        fprintf(stderr, "unknown option -%c\n", ch);
        return false;
    }
    }

    if (!given_mode) {
    fprintf(stderr, "please specify -d or -D mode\n");
    return false;
    }

    if (option->daemon_mode) {

    if (option->flag_listenfd >= 0) {
        if (option->nport != 1) {
        fprintf(stderr, "for pre-binded fd, you should give 1 port number (for information)\n");
        return false;
        }
    }

    if ( option->tunnel_mode && option->nport != 0) {
        fprintf(stderr, "you cannot bind ports port in tunnel mode.\n");
        return false;
    }

    if (!option->tunnel_mode && option->nport == 0) {
        fprintf(stderr, "don't forget specify port number (-p)\n");
        return false;
    }

    if (option->nport > 1 && !option->flag_fork) {
        fprintf(stderr, "you can bind only 1 port with non-fork flag\n");
        return false;
    }
    }


    return true;
}

int
main(int argc, char *argv[], char *envp[])
{
    bool oklogin = false;
    struct ProgramOption *option;

    init();

    option = (struct ProgramOption*) malloc(sizeof(struct ProgramOption));
    if (!parse_argv(argc, argv, option)) {
    usage(argv[0]);
    return 1;
    }
    initsetproctitle(argc, argv, envp);

    attach_SHM();

    if (!option->daemon_mode)
    oklogin = shell_login (argv[0], option);
    else if (option->tunnel_mode)
    oklogin = tunnel_login(argv[0], option);
    else
    oklogin = daemon_login(argv[0], option);

    if (!oklogin) {
    free_program_option(option);
    return 0;
    }

    do_term_init(option->term_mode, 
        option->term_width, option->term_height);
    start_client(option);
    free_program_option(option);

    // tail recursion!
    return main_menu();
}

static int
shell_login(char *argv0, struct ProgramOption *option)
{
    int fd;

    STATINC(STAT_SHELLLOGIN);
    /* Give up root privileges: no way back from here */
    setgid(BBSGID);
    setuid(BBSUID);
    chdir(BBSHOME);

#if defined(linux) && defined(DEBUG)
//    mtrace();
#endif

    snprintf(margs, sizeof(margs), "%s ssh ", argv0);
    close(2);
    /* don't close fd 1, at least init_tty need it */
    if( ((fd = open("log/stderr", O_WRONLY | O_CREAT | O_APPEND, 0644)) >= 0) && fd != 2 ){
    dup2(fd, 2);
    close(fd);
    }

    init_tty();

    // XXX overwrite fromhost here is better?
    if(getenv("SSH_CONNECTION") != NULL){
    char frombuf[50];
    sscanf(getenv("SSH_CONNECTION"), "%s", frombuf);
    strlcpy(fromhost, frombuf, sizeof(fromhost));
    }

    if (check_ban_and_load(0, option)) {
    sleep(10);
    return 0;
    }
#ifdef DETECT_CLIENT
    FNV1A_CHAR(123, client_code);
#endif
    return 1;
}

static int
tunnel_login(char *argv0, struct ProgramOption *option)
{
    int tunnel = 0, csock = 0, success = 1;
    struct login_data dat = {0};
    char buf[PATHLEN];
    FILE *fp;

    /* setup standalone */
    start_daemon(option);
    signal_restart(SIGCHLD, reapchild);

    assert( option->flag_tunnel_path &&
       *option->flag_tunnel_path);

    tunnel = toconnect(option->flag_tunnel_path);
    if (tunnel < 0)
    {
    syslog(LOG_ERR, "mbbsd tunnel connection failed: %s\n", 
        option->flag_tunnel_path);
    exit(1);
    }

    /* Give up root privileges: no way back from here */
    setgid(BBSGID);
    setuid(BBSUID);
    chdir(BBSHOME);

    /* proctitle */
#ifndef VALGRIND
    snprintf(margs, sizeof(margs), "%s tunnel=%u ", argv0, (unsigned int)getpid());
    setproctitle("%s: listening ", margs);
#endif

    // log pid
    snprintf(buf, sizeof(buf),
         "run/mbbsd.%s.%u.pid", "tunnel", (unsigned int)getpid());
    if ((fp = fopen(buf, "w"))) {
    fprintf(fp, "%d\n", (int)getpid());
    fclose(fp);
    }

    /* main loop */
    while( 1 )
    {
    csock = recv_remote_fd(tunnel);

    // XXX use continue or return herer?
    if (csock < 0)
    {
        return 0;
    }
    if (toread (tunnel, &dat, sizeof(dat)) < sizeof(dat) ||
        towrite(tunnel, &success, sizeof(success)) < sizeof(success))
        return 0;

    assert(dat.cb == sizeof(dat));

    // optimize connection
    set_connection_opt(csock);

    if (option->flag_fork) {
        if (fork() == 0)
        break;
        else
        close(csock);
    }
    }
    /* here is only child running */

#ifndef VALGRIND
    setproctitle("%s: ...login wait... ", margs);
#endif
    close(tunnel);
    dup2(csock, 0);
    close(csock);
    dup2(0, 1);

    strlcpy(fromhost, dat.hostip, sizeof(fromhost));
    strlcpy(option->flag_user, dat.userid, sizeof(option->flag_user));
    option->term_width  = dat.t_cols;
    option->term_height = dat.t_lines;
#ifdef CONVERT
    if (dat.encoding)
    set_converting_type(dat.encoding);
#endif

    telnet_init(0);
#ifdef DETECT_CLIENT
    telnet_turnoff_client_detect();
    client_code = dat.client_code;  // use the client code detected by remote daemon
#endif
    return 1;
}

static int
daemon_login(char *argv0, struct ProgramOption *option)
{
    int             msock = 0, csock;   /* socket for Master and Child */
    FILE           *fp;
    int             len_of_sock_addr, overloading = 0;
    char            buf[256];
#if OVERLOADBLOCKFDS
    int             blockfd[OVERLOADBLOCKFDS];
    int             nblocked = 0;
#endif
    struct sockaddr_in xsin;
    xsin.sin_family = AF_INET;

    /* setup standalone */
    start_daemon(option);
    signal_restart(SIGCHLD, reapchild);

    /* port binding */
    if (option->flag_listenfd < 0) {
    int i;
    assert(option->nport > 0);
    for (i = 0; i < option->nport; i++) {
        listen_port = option->port[i];
        if (i == option->nport - 1 || fork() == 0) {
        if( (msock = bind_port(listen_port)) < 0 ){
            syslog(LOG_ERR, "mbbsd bind_port failed.\n");
            exit(1);
        }
        break;
        }
    }
    } else {
    msock = option->flag_listenfd;
    assert(option->nport == 1);
    listen_port = option->port[0];
    }

    /* Give up root privileges: no way back from here */
    setgid(BBSGID);
    setuid(BBSUID);
    chdir(BBSHOME);

    /* proctitle */
#ifndef VALGRIND
    snprintf(margs, sizeof(margs), "%s %d ", argv0, listen_port);
    setproctitle("%s: listening ", margs);
#endif

#ifdef PRE_FORK
    if (option->flag_fork) {
    if( listen_port == 23 ){ // only pre-fork in port 23
        int i;
        for( i = 0 ; i < PRE_FORK ; ++i )
        if( fork() <= 0 )
            break;
    }
    }
#endif

    snprintf(buf, sizeof(buf),
         "run/mbbsd.%d.%d.pid", listen_port, (int)getpid());
    if ((fp = fopen(buf, "w"))) {
    fprintf(fp, "%d\n", (int)getpid());
    fclose(fp);
    }

    /* main loop */
    while( 1 ){
    len_of_sock_addr = sizeof(xsin);
    if ( (csock = accept(msock, (struct sockaddr *)&xsin,
            (socklen_t *)&len_of_sock_addr)) < 0 ) {
        if (errno != EINTR)
        sleep(1);
        continue;
    }

    overloading = check_ban_and_load(csock, option);
#if OVERLOADBLOCKFDS
    if( (!overloading && nblocked) ||
        (overloading && nblocked == OVERLOADBLOCKFDS) ){
        int i;
        for( i = 0 ; i < OVERLOADBLOCKFDS ; ++i )
        if( blockfd[i] != csock && blockfd[i] != msock )
            /* blockfd[i] should not be msock, but it happened */
            close(blockfd[i]);
        nblocked = 0;
    }
#endif

    if( overloading ){
#if OVERLOADBLOCKFDS
        blockfd[nblocked++] = csock;
#else
        close(csock);
#endif
        continue;
    }

    if (option->flag_fork) {
        if (fork() == 0)
        break;
        else
        close(csock);
    } else {
        break;
    }
    }
    /* here is only child running */

#ifndef VALGRIND
    setproctitle("%s: ...login wait... ", margs);
#endif
    close(msock);
    dup2(csock, 0);
    close(csock);
    dup2(0, 1);

    XAUTH_GETREMOTENAME(getremotename(xsin.sin_addr, fromhost));

    if( check_banip(fromhost) ){
    sleep(10);
    exit(0);
    }
    telnet_init(1);
    return 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, struct ProgramOption *option)
{
    FILE           *fp;
    static time4_t   chkload_time = 0;
    static int      overload = 0;   /* overload or banned, update every 1
                     * sec  */
    static int      banned = 0;

    // if you have your own banner, define as INSCREEN in pttbbs.conf
    // if you don't want anny benner, define NO_INSCREEN
#ifndef NO_INSCREEN
# ifndef   INSCREEN
#  define  INSCREEN "【" BBSNAME "】◎(" MYHOSTNAME ", " MYIP ") \r\n"
# endif
    write(fd, INSCREEN, sizeof(INSCREEN));
#endif

    if ((time(0) - chkload_time) > 1) {
    overload = 0;
    banned = 0;

    if(cpuload(NULL) > MAX_CPULOAD)
        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;
        overload = 2;
    } else if(!access(BBSHOME "/" BAN_FILE, R_OK))
        banned = 1;

    chkload_time = time(0);
    }

    if (!option->flag_checkload)
    overload = 0;

    if(overload == 1)
    write(fd, "系統過載, 請稍後再來\r\n", 22);
    else if(overload == 2)
    write(fd, "由於人數過多,請您稍後再來。", 28);
    else if (banned && (fp = fopen(BBSHOME "/" BAN_FILE, "r"))) {
    char     buf[256];
    while (fgets(buf, sizeof(buf), fp))
        write(fd, buf, strlen(buf));
    fclose(fp);
    }

    if (banned || overload)
    return -1;

    return 0;
}

static int check_banip(char *host)
{
    uint32_t thisip = ipstr2int(host);

    return uintbsearch(thisip, &banip[1], banip[0]) ? 1 : 0;
}

/* vim: sw=4 
 */