summaryrefslogblamecommitdiffstats
path: root/util/xchatd.c
blob: 8e2373b5e5b2b860fddeba1cb417bd7399aa8bf3 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
          
                


                    






                                                                  























                                                                           























































                                                                               





                                                                               
                                                    







































                                                                                

















                                                                     
                                   

           






                                                       
                                       
                  
                                                           

 





                                                           
                                               






                                                           
                                      

   
                                               

             
                        
 
                                  












                                                           
                           
 
                


                  

                              
                                                                    































                                                                   
                                                                                 
















                              
                                                                                  















                                                           











                                               
                                               
 
                                 














                                                           
                                               




                                                               



























                                                           
                             




                                           







                                          
                             




                                           







                                          
                                   


                                  
                




                                           



                                             


                       

         
                                  




                                                
                   
    
                                              

                                         









                                                           
                         




                  


                         





                   
                                         


                   






                                                        



          
                                          



                           


                                            
                       









                                                              
                                       


                













                                                            
                                           
 
                                                        






                                                                           


                       
 

                                                     
                                                                                   






                              

                                     
                                                                              









                              
                                                               



                       
                      









                                                                                      

















                                                                                            
                                                                          
                
                                                                  
 
                                         

            
                                     




           
                                                               







                                                          
                          







                              
                                                                          
                
                                                                  
                                          

            
                                      




           
                                                            




                          
                              





                                                           
                            



               
                                                                                                               



                                                       
                          



               
                                                                                                              





                                                       
                                              


                   

                          
 


                                
 








                              





                                                             












                                                                   


                                                          
 

                                                       

         


                                                       
 

               
 



                                                                        
 

                                                           
 



                                      
 
                   














                                                           
                     
 
                                     
                                        
 
                                                                              





                                                                      
                                   







                                    
                                                                             
                                                                               

                                                  
                                                                                         




                                                                 












                                                      
         


        
                                                                




                                                  
                                   







                                               
                                  
 
                    
 
                         





                                                         
                                   
















                                                          
                 
                        
                                             

















                                                                         
                                     





                                                                        
                                  




















                                                                      
                                                                                         










                                                                 
                                                           




                                        
                                           
 
                 











                                                                                        

                                                                                    


                                      
                                                                                                                 



                                                              
                                                                                                          









                                                             
     






                                                    
                                                             




















                                                                                    


























                                                                                 
                                                                                                                      




                                       
                                                                                                                                   














                                                                                         
                                          










                                                           
                                                                                   















                                                                                
                                        




                                         
                                      





                                                               
                                                                                




                            







                                                                                                                 
                          

                                                       





                                                                           




                                                       




                                                      






                                                  
                                                  





                                   
                                     
























































                                                                                                            
                               




































































                                                                        
                                  













                                                                 
                                                                         





                                                  
                                     























                                                                        





                                                                             





                                                                     
















                                                                                  
                                   














                                                                         
                                            



                                                                   
                                                                                                                   






























                                                                
                                                   













                                                
                                                     






                                                                       
                                          

                                              
                                                                                   




































                                                                                           
                            



                           



                       








                                  


















                                           






                                  




                
                                  





                                          
                                                    











                                 
     














                                                                                
                                   


              
                 







                            
              


















                                                                          
                 



                                                                         

                       















                                                                          

                                                    
     









                                                                 



                                                      


                           
                                          
      
                  




                                                     















                                                                         










                                                                              















                                                                             
                                                                             

                                                    
                                                    





                                                                                                   
                                             

                                                                    

        
                                         




















                                                 
                                 









                                                                 
                                    


















                                                       
                                                          

























































                                                                                
                                      



















                                                                
                                  

















                                                                          
                                  













































                                                              
                                    













































                                                          
                                                    




           
                                    




















                                                     
                    
                 


















                                                                                
                                       



















                                                                         
                                     





















                                                   
                                                               



















































































                                                                                          
                                                  





                                                     




                                  
         




















                                                                    
             


                                                          


                
                                      
             
         



                                                                   











                                                   




                                          
                                  




                                                     
                                             

















                                             



          
                                                





                                                     





                                                                 











                                                   





                                                      
                                             












                                                                                                     
                                                               


                                                    


                                                             

                                                  


                             
                                                                                    

      






                                                                   
                                                 




                                                                            
                                                            









                                                                                    
                                           


                                                              



          
                                         





                                                         




                                                                     
                     






















                                                                                 
                                       








                                                                                 
                                   

































                                                                     
                                                                                













                                                                                                 







                                                                                  
                                                         





                                                                       
                             


                                           




                               
                                                                











                                                                                     



                                                                     












































                                                                               
                             


                    
              

                   





                                                                        


                                         
         




                                         

     

                      
 

                                                    
     
                     
 



                                                                     























                                                 












                                                                             




































                                                                    
                         


                                
                 


























                                                    

                   

                    
                                     


                       
                            
                      





                                        

                                       

















































                                                                                 
                

                
      




                   
                
                 
      



























                                                                   
                                             











                                        
                            

























                                                                             
                                                  










                                                               
                     




                            
                        


                                                   





                          
                                                                                 






























                                                           
                 













                                                                                   
                          




















                                                        


     











































































































































































                                                                                     

   
                            

                                    
                                   


                                 
                              
 






                                 






                           

                               

                             
 

                                  

                    
                                  














                                             




                            












                                                                             


                  

         







                                               

                                                     
                                                                  
                      
                     

                                   
                     
                     

                                   

                                              
 


















                                                           

             
                                          
      

                 

         

                                                                 
                                                                                       
                               
                             
                                        
                                

                                               

                                        
                       
             



                              
             
 
/* $Id$ */
#include "bbs.h"
#include "xchatd.h"

#define SERVER_USAGE
#undef  MONITOR                 /* 監督 chatroom 活動以解決糾紛 */
#undef  DEBUG                   /* 程式除錯之用 */

#ifdef  DEBUG
#define MONITOR
#endif

/* self-test:
 * random test, 隨機產生各種 client input, 目的為找到讓 server
 * crash 的狀況, 因此 client 並未檢驗 server 傳過來的 data.
 * server 也不會讀取 bbs 實際的檔案或 SHM.
 * 根據 gcov(1) 此 self-test coverage 約 90%
 *
 * 如何測試:
 * define SELFTEST & SELFTESTER, 執行時隨意加參數(argc>1)就會跑 test child.
 * test server 僅進行 100 秒.
 *
 * Hint:
 * 配合 valgrind 尋找 memory related bug.
 */
//#define SELFTEST
//#define SELFTESTER

#ifdef SELFTEST
// 另開 port
#undef NEW_CHATPORT
#define NEW_CHATPORT 12333
// only test 100 secs
#undef CHAT_INTERVAL
#define CHAT_INTERVAL 100
#endif

#define CHAT_PIDFILE    "log/chat.pid"
#define CHAT_LOGFILE    "log/chat.log"
#define CHAT_INTERVAL   (60 * 30)
#define SOCK_QLEN       1


/* name of the main room (always exists) */


#define MAIN_NAME       "main"
#define MAIN_TOPIC      "烹茶可貢西天佛"


#define ROOM_LOCKED     1
#define ROOM_SECRET     2
#define ROOM_OPENTOPIC  4
#define ROOM_HANDUP     8
#define ROOM_ALL        (NULL)


#define LOCKED(room)    (room->rflag & ROOM_LOCKED)
#define SECRET(room)    (room->rflag & ROOM_SECRET)
#define OPENTOPIC(room) (room->rflag & ROOM_OPENTOPIC)
#define RHANDUP(room)    (room->rflag & ROOM_HANDUP)

#define RESTRICTED(usr) (usr->uflag == 0)       /* guest */
#define CHATSYSOP(usr)  (usr->uflag & ( PERM_SYSOP | PERM_CHATROOM))
/* Thor: SYSOP 與 CHATROOM都是 chat總管 */
#define PERM_ROOMOP     PERM_CHAT       /* Thor: 借 PERM_CHAT為 PERM_ROOMOP */
#define PERM_HANDUP     PERM_BM     /* 借 PERM_BM 為有沒有舉手過 */
#define PERM_SAY        PERM_NOTOP  /* 借 PERM_NOTOP 為有沒有發表權 */

/* 進入時需清空              */
/* Thor: ROOMOP為房間管理員 */
#define ROOMOP(usr)  (usr->uflag & ( PERM_ROOMOP | PERM_SYSOP | PERM_CHATROOM))
#define CLOAK(usr)      (usr->uflag & PERM_CLOAK)
#define HANDUP(usr)  (usr->uflag & PERM_HANDUP) 
#define SAY(usr)      (usr->uflag & PERM_SAY)
/* Thor: 聊天室隱身術 */


/* ----------------------------------------------------- */
/* ChatRoom data structure                               */
/* ----------------------------------------------------- */

typedef struct ChatRoom ChatRoom;
typedef struct ChatUser ChatUser;
typedef struct UserList UserList;
typedef struct ChatCmd ChatCmd;
typedef struct ChatAction ChatAction;

struct ChatUser
{
    struct ChatUser *unext;
    int sock;                     /* user socket */
    ChatRoom *room;
    UserList *ignore;
    int userno;
    int uflag;
    int clitype;                  /* Xshadow: client type. 1 for common client,
                   * 0 for bbs only client */
    time4_t uptime;               /* Thor: unused */
    char userid[IDLEN + 1];       /* real userid */
    char chatid[9];               /* chat id */
    char lasthost[30];            /* host address */
    char ibuf[80];                /* buffer for non-blocking receiving */
    int isize;                    /* current size of ibuf */
};


struct ChatRoom
{
    struct ChatRoom *next, *prev;
    char name[IDLEN];
    char topic[48];               /* Let the room op to define room topic */
    int rflag;                    /* ROOM_LOCKED, ROOM_SECRET, ROOM_OPENTOPIC */
    int occupants;                /* number of users in room */
    UserList *invite;
};


struct UserList
{
    struct UserList *next;
    int userno;
    char userid[IDLEN + 1];
};


struct ChatCmd
{
    char *cmdstr;
    void (*cmdfunc) ();
    int exact;
};


static ChatRoom mainroom;
static ChatUser *mainuser;
static fd_set mainfds;
static int maxfds;              /* number of sockets to select on */
static int totaluser;           /* current number of connections */
static char chatbuf[256];       /* general purpose buffer */
static int common_client_command;

static char msg_not_op[] = "◆ 您不是這間聊天室的 Op";
static char msg_no_such_id[] = "◆ 目前沒有人使用 [%s] 這個聊天代號";
static char msg_not_here[] = "◆ [%s] 不在這間聊天室";


#define FUZZY_USER      ((ChatUser *) -1)


typedef struct userec_t ACCT;

/* ----------------------------------------------------- */
/* acct_load for check acct                              */
/* ----------------------------------------------------- */

int
acct_load(ACCT *acct, char *userid)
{
    int id;
#ifdef SELFTEST
    memset(acct, 0, sizeof(ACCT));
    acct->userlevel |= PERM_BASIC|PERM_CHAT;
    if(random()%4==0) acct->userlevel |= PERM_CHATROOM;
    if(random()%8==0) acct->userlevel |= PERM_SYSOP;
    return atoi(userid);
#endif
    if((id=searchuser(userid, NULL))<0)
    return -1;
    return get_record(FN_PASSWD, acct, sizeof(ACCT), id);  
}

/* ----------------------------------------------------- */
/* usr_fpath for check acct                              */
/* ----------------------------------------------------- */
char *str_home_file = "home/%c/%s/%s";

void
usr_fpath(char *buf, char *userid, char *fname)
{
    sprintf(buf, str_home_file, userid[0], userid, fname);
}

/* ----------------------------------------------------- */
/* chkpasswd for check passwd                            */
/* ----------------------------------------------------- */
char *crypt(const char*, const char*);

int
chkpasswd(const char *passwd, const char *test)
{
    char *pw;
    char pwbuf[PASSLEN];

    strlcpy(pwbuf, test, PASSLEN);
    pw = crypt(pwbuf, passwd);
    return (!strncmp(pw, passwd, PASSLEN));
}

/* ----------------------------------------------------- */
/* operation log and debug information                   */
/* ----------------------------------------------------- */


static int flog;                /* log file descriptor */


static void
logit(char *key, char *msg)
{
    time4_t now;
    struct tm *p;
    char buf[512];

    now = (time4_t)time(NULL);
    p = localtime4(&now);
    snprintf(buf, sizeof(buf), "%02d/%02d %02d:%02d:%02d %-13s%s\n",
        p->tm_mon + 1, p->tm_mday,
        p->tm_hour, p->tm_min, p->tm_sec, key, msg);
    write(flog, buf, strlen(buf));
}


static void
log_init()
{
    flog = open(CHAT_LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0644);
    logit("START", "chat daemon");
}


static void
log_close()
{
    close(flog);
}


#ifdef  DEBUG
static void
debug_user()
{
    register ChatUser *user;
    int i;
    char buf[80];

    i = 0;
    for (user = mainuser; user; user = user->unext)
    {
    snprintf(buf, sizeof(buf), "%d) %s %s", ++i, user->userid, user->chatid);
    logit("DEBUG_U", buf);
    }
}


static void
debug_room()
{
    register ChatRoom *room;
    int i;
    char buf[80];

    i = 0;
    room = &mainroom;

    do
    {
    snprintf(buf, sizeof(buf), "%d) %s %d", ++i, room->name, room->occupants);
    logit("DEBUG_R", buf);
    } while (room = room->next);
}
#endif                          /* DEBUG */


/* ----------------------------------------------------- */
/* string routines                                       */
/* ----------------------------------------------------- */


static int valid_chatid(register char *id) {
    register int ch, len;
    
    for(len = 0; (ch = *id); id++) {
    /* Thor: check for endless */
    if(ch == '/' || ch == '*' || ch == ':')
        return 0;
    if(++len > 8)
        return 0;
    }
    return len;
}

/* Case Independent strcmp : 1 ==> euqal */


static int
str_equal(unsigned char *s1, unsigned char *s2)
{
    return strcasecmp(s1, s2)==0;
}


/* ----------------------------------------------------- */
/* match strings' similarity case-insensitively          */
/* ----------------------------------------------------- */
/* str_match(keyword, string)                            */
/* ----------------------------------------------------- */
/* 0 : equal            ("foo", "foo")                   */
/* -1 : mismatch        ("abc", "xyz")                   */
/* ow : similar         ("goo", "good")                  */
/* ----------------------------------------------------- */


static int
str_match(unsigned char *s1, unsigned char *s2)
{
    register int c1, c2;

    for (;;)
    {                             /* Thor: check for endless */
    c2 = *s2;
    c1 = *s1;
    if (!c1)
    {
        return c2;
    }

    if (c1 >= 'A' && c1 <= 'Z')
        c1 |= 32;

    if (c2 >= 'A' && c2 <= 'Z')
        c2 |= 32;

    if (c1 != c2)
        return -1;

    s1++;
    s2++;
    }
}


/* ----------------------------------------------------- */
/* search user/room by its ID                            */
/* ----------------------------------------------------- */


static ChatUser *
cuser_by_userid(char *userid)
{
    register ChatUser *cu;

    for (cu = mainuser; cu; cu = cu->unext)
    {
    if (str_equal(userid, cu->userid))
        break;
    }
    return cu;
}


static ChatUser *
cuser_by_chatid(char *chatid)
{
    register ChatUser *cu;

    for (cu = mainuser; cu; cu = cu->unext)
    {
    if (str_equal(chatid, cu->chatid))
        break;
    }
    return cu;
}


static ChatUser *
fuzzy_cuser_by_chatid(char *chatid)
{
    register ChatUser *cu, *xuser;
    int mode;
    int count=0;

    xuser = NULL;

    for (cu = mainuser; cu; cu = cu->unext)
    {
    mode = str_match(chatid, cu->chatid);
    if (mode == 0)
        return cu;

    if (mode > 0) {
        xuser = cu;
        count++;
    }
    }
    if(count>1) return FUZZY_USER;
    return xuser;
}


static ChatRoom *croom_by_roomid(char *roomid) {
    ChatRoom *room;
    
    for(room=&mainroom; room; room=room->next)
    if(str_equal(roomid, room->name))
        break;
    return room;
}


/* ----------------------------------------------------- */
/* UserList routines                                     */
/* ----------------------------------------------------- */


static void
list_free(UserList *list)
{
    UserList *tmp;

    while (list)
    {
    tmp = list->next;

    free(list);
    list = tmp;
    }
}


static void
list_add(UserList **list, ChatUser *user)
{
    UserList *node;

    if((node = (UserList *) malloc(sizeof(UserList)))) {
    /* Thor: 防止空間不夠 */
    strcpy(node->userid, user->userid);
    node->userno = user->userno;
    node->next = *list;
    *list = node;
    }
}


static int
list_delete(UserList **list, char *userid)
{
    UserList *node;

    while((node = *list)) {
    if (str_equal(node->userid, userid))
    {
        *list = node->next;
        free(node);
        return 1;
    }
    list = &node->next;         /* Thor: list要跟著前進 */
    }

    return 0;
}


static int
list_belong(UserList *list, int userno)
{
    while (list)
    {
    if (userno == list->userno)
        return 1;
    list = list->next;
    }
    return 0;
}


/* ------------------------------------------------------ */
/* non-blocking socket routines : send message to users   */
/* ------------------------------------------------------ */


static void
Xdo_send(int nfds, fd_set *wset, char *msg)
{
    struct timeval zerotv;   /* timeval for selecting */
    int sr;

    /* Thor: for future reservation bug */

    zerotv.tv_sec = 0;
    zerotv.tv_usec = 16384;  /* Ptt: 改成16384 避免不按時for loop吃cpu time
                16384 約每秒64次 */
#ifdef SELFTEST
    zerotv.tv_usec = 0;
#endif

    sr = select(nfds + 1, NULL, wset, NULL, &zerotv);

    /* FIXME 若 select() timeout, 或有的 write ready 有的沒有. 則可能會漏接 msg? */
    if (sr > 0)
    {
    register int len;

    len = strlen(msg) + 1;
    while (nfds >= 0)
    {
        if (FD_ISSET(nfds, wset))
        {
        send(nfds, msg, len, 0);/* Thor: 如果buffer滿了, 仍會 block */
        if (--sr <= 0)
            return;
        }
        nfds--;
    }
    }
}


static void
send_to_room(ChatRoom *room, char *msg, int userno, int number)
{
    ChatUser *cu;
    fd_set wset, *wptr;
    int sock, max;
    char sendbuf[256];
    int clitype;                  /* 分為 bbs client 及 common client 兩次處理 */

    for (clitype = (number == MSG_MESSAGE || !number) ? 0 : 1; clitype < 2; clitype++)
    {

    FD_ZERO(wptr = &wset);
    max = -1;

    for (cu = mainuser; cu; cu = cu->unext)
    {
        if (room == cu->room || room == ROOM_ALL)
        {
        if (cu->clitype == clitype && (!userno || !list_belong(cu->ignore, userno)))
        {
            sock = cu->sock;
            FD_SET(sock, wptr);
            if (max < sock)
            max = sock;
        }
        }
    }

    if (max < 0)
        continue;

    if (clitype)
    {
        if (strlen(msg))
        snprintf(sendbuf, sizeof(sendbuf), "%3d %s", number, msg);
        else
        snprintf(sendbuf, sizeof(sendbuf), "%3d", number);

        Xdo_send(max, wptr, sendbuf);
    }
    else
        Xdo_send(max, wptr, msg);
    }
}


static void
send_to_user(ChatUser *user, char *msg, int userno, int number)
{
    if (!user->clitype && number && number != MSG_MESSAGE)
    return;

    if (!userno || !list_belong(user->ignore, userno))
    {
    fd_set wset, *wptr;
    int sock;
    char sendbuf[256];

    sock = user->sock;
    FD_ZERO(wptr = &wset);
    FD_SET(sock, wptr);

    if (user->clitype)
    {
        if (strlen(msg))
        snprintf(sendbuf, sizeof(sendbuf), "%3d %s", number, msg);
        else
        snprintf(sendbuf, sizeof(sendbuf), "%3d", number);
        Xdo_send(sock, wptr, sendbuf);
    }
    else
        Xdo_send(sock, wptr, msg);
    }
}

#if 0
static void
send_to_sock(int sock, char *msg)         /* Thor: unused */
{
    fd_set wset, *wptr;

    FD_ZERO(wptr = &wset);
    FD_SET(sock, wptr);
    Xdo_send(sock, wptr, msg);
}
#endif

/* ----------------------------------------------------- */

static void
room_changed(ChatRoom *room)
{
    if (!room)
    return;

    snprintf(chatbuf, sizeof(chatbuf), "= %s %d %d %s", room->name, room->occupants, room->rflag, room->topic);
    send_to_room(ROOM_ALL, chatbuf, 0, MSG_ROOMNOTIFY);
}

static void
user_changed(ChatUser *cu)
{
    if (!cu)
    return;

    snprintf(chatbuf, sizeof(chatbuf), "= %s %s %s %s", cu->userid, cu->chatid, cu->room->name, cu->lasthost);
    if (ROOMOP(cu))
    strcat(chatbuf, " Op");
    send_to_room(cu->room, chatbuf, 0, MSG_USERNOTIFY);
}

static void
exit_room(ChatUser *user, int mode, char *msg)
{
    ChatRoom *room;

    if(user->room == NULL)
    return;

    room = user->room;
    user->room = NULL;
    user->uflag &= ~PERM_ROOMOP;

    room->occupants--;

    if (room->occupants > 0)
    {
    char *chatid;

    chatid = user->chatid;
    switch (mode)
    {
        case EXIT_LOGOUT:

        sprintf(chatbuf, "◆ %s 離開了 ...", chatid);
        if (msg && *msg)
        {
            strcat(chatbuf, ": ");
            strncat(chatbuf, msg, 80);
        }
        break;

        case EXIT_LOSTCONN:

        sprintf(chatbuf, "◆ %s 成了斷線的風箏囉", chatid);
        break;

        case EXIT_KICK:

        sprintf(chatbuf, "◆ 哈哈!%s 被踢出去了", chatid);
        break;
    }
    if (!CLOAK(user))         /* Thor: 聊天室隱身術 */
        send_to_room(room, chatbuf, 0, MSG_MESSAGE);

    if (list_belong(room->invite, user->userno)) {
        list_delete(&(room->invite), user->userid);
    }

    sprintf(chatbuf, "- %s", user->userid);
    send_to_room(room, chatbuf, 0, MSG_USERNOTIFY);
    room_changed(room);

    return;
    }

    /* Now, room->occupants==0 */
    if (room != &mainroom)
    {                           /* Thor: 人數為0時,不是mainroom才free */
    register ChatRoom *next;

    sprintf(chatbuf, "- %s", room->name);
    send_to_room(ROOM_ALL, chatbuf, 0, MSG_ROOMNOTIFY);

    room->prev->next = room->next;
    if((next = room->next))
        next->prev = room->prev;
    list_free(room->invite);

    free(room);
    }
}


/* ----------------------------------------------------- */
/* chat commands                                         */
/* ----------------------------------------------------- */

/* ----------------------------------------------------- */
/* (.ACCT) 使用者帳號 (account) subroutines              */
/* ----------------------------------------------------- */

static char datemsg[32];

char *
Ctime(time4_t *clock)
{
    struct tm *t = localtime4(clock);
    const char *week = "日一二三四五六";

    snprintf(datemsg, sizeof(datemsg), "%d年%2d月%2d日%3d:%02d:%02d 星期%.2s",
        t->tm_year - 11, t->tm_mon + 1, t->tm_mday,
        t->tm_hour, t->tm_min, t->tm_sec, &week[t->tm_wday << 1]);
    return (datemsg);
}

static void
chat_query(ChatUser *cu, char *msg)
{
    char str[256];
    int i;
    ACCT xuser;
    FILE *fp;

    if (acct_load(&xuser, msg) >= 0)
    {
    snprintf(chatbuf, sizeof(chatbuf), "%s(%s) 共上站 %d 次,文章 %d 篇",
        xuser.userid, xuser.nickname, xuser.numlogins, xuser.numposts);
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);

    snprintf(chatbuf, sizeof(chatbuf), "最近(%s)從(%s)上站", Ctime(&xuser.lastlogin),
        (xuser.lasthost[0] ? xuser.lasthost : "外太空"));
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);

    usr_fpath(chatbuf, xuser.userid, "plans");
    fp = fopen(chatbuf, "rt");
    if(fp) {
        i = 0;
        while (fgets(str, sizeof(str), fp)) {
        int len=strlen(str);
        if (len==0)
            continue;

        str[len - 1] = 0;
        send_to_user(cu, str, 0, MSG_MESSAGE);
        if (++i >= MAX_QUERYLINES)
            break;
        }
        fclose(fp);
    }
    }
    else
    {
    snprintf(chatbuf, sizeof(chatbuf), msg_no_such_id, msg);
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
    }
}

static void
chat_clear(ChatUser *cu, char *msg)
{
    if (cu->clitype)
    send_to_user(cu, "", 0, MSG_CLRSCR);
    else
    send_to_user(cu, "/c", 0, MSG_MESSAGE);
}

static void
chat_date(ChatUser *cu, char *msg)
{
    time4_t thetime;

    thetime = time(NULL);
    sprintf(chatbuf, "◆ 標準時間: %s", Ctime(&thetime));
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
}


static void
chat_topic(ChatUser *cu, char *msg)
{
    ChatRoom *room;
    char *topic;

    if (!ROOMOP(cu) && !OPENTOPIC(cu->room))
    {
    send_to_user(cu, msg_not_op, 0, MSG_MESSAGE);
    return;
    }

    if (*msg == '\0')
    {
    send_to_user(cu, "※ 請指定話題", 0, MSG_MESSAGE);
    return;
    }

    room = cu->room;
    assert(room);
    topic = room->topic;
    strlcpy(topic, msg, sizeof(room->topic));

    if (cu->clitype)
    send_to_room(room, topic, 0, MSG_TOPIC);
    else
    {
    sprintf(chatbuf, "/t%s", topic);
    send_to_room(room, chatbuf, 0, 0);
    }

    room_changed(room);

    sprintf(chatbuf, "◆ %s 將話題改為 %s", cu->chatid, topic);
    if (!CLOAK(cu))               /* Thor: 聊天室隱身術 */
    send_to_room(room, chatbuf, 0, MSG_MESSAGE);
}


static void
chat_version(ChatUser *cu, char *msg)
{
    sprintf(chatbuf, "%d %d", XCHAT_VERSION_MAJOR, XCHAT_VERSION_MINOR);
    send_to_user(cu, chatbuf, 0, MSG_VERSION);
}

static void
chat_nick(ChatUser *cu, char *msg)
{
    char *chatid, *str;
    ChatUser *xuser;

    chatid = nextword(&msg);
    chatid[8] = '\0';
    if (!valid_chatid(chatid))
    {
    send_to_user(cu, "※ 這個聊天代號是不正確的", 0, MSG_MESSAGE);
    return;
    }

    xuser = cuser_by_chatid(chatid);
    if (xuser != NULL && xuser != cu)
    {
    send_to_user(cu, "※ 已經有人捷足先登囉", 0, MSG_MESSAGE);
    return;
    }

    str = cu->chatid;

    snprintf(chatbuf, sizeof(chatbuf), "※ %s 將聊天代號改為 %s", str, chatid);
    if (!CLOAK(cu))               /* Thor: 聊天室隱身術 */
    send_to_room(cu->room, chatbuf, cu->userno, MSG_MESSAGE);

    strcpy(str, chatid);

    user_changed(cu);

    if (cu->clitype)
    send_to_user(cu, chatid, 0, MSG_NICK);
    else
    {
    snprintf(chatbuf, sizeof(chatbuf), "/n%s", chatid);
    send_to_user(cu, chatbuf, 0, 0);
    }
}

static void
chat_list_rooms(ChatUser *cuser, char *msg)
{
    ChatRoom *cr;

    if (RESTRICTED(cuser))
    {
    send_to_user(cuser, "※ 您沒有權限列出現有的聊天室", 0, MSG_MESSAGE);
    return;
    }

    if (common_client_command)
    send_to_user(cuser, "", 0, MSG_ROOMLISTSTART);
    else
    send_to_user(cuser, " 談天室名稱  │人數│話題        ", 0, MSG_MESSAGE);

    for(cr = &mainroom; cr; cr = cr->next) {
    if (!SECRET(cr) || CHATSYSOP(cuser) || (cr == cuser->room && ROOMOP(cuser)))
    {
        if (common_client_command)
        {
        snprintf(chatbuf, sizeof(chatbuf), "%s %d %d %s", cr->name, cr->occupants, cr->rflag, cr->topic);
        send_to_user(cuser, chatbuf, 0, MSG_ROOMLIST);
        }
        else
        {
        snprintf(chatbuf, sizeof(chatbuf), " %-12s│%4d│%s", cr->name, cr->occupants, cr->topic);
        if (LOCKED(cr))
            strcat(chatbuf, " [鎖住]");
        if (SECRET(cr))
            strcat(chatbuf, " [秘密]");
        if (OPENTOPIC(cr))
            strcat(chatbuf, " [話題]");
        send_to_user(cuser, chatbuf, 0, MSG_MESSAGE);
        }

    }
    }

    if (common_client_command)
    send_to_user(cuser, "", 0, MSG_ROOMLISTEND);
}


static void
chat_do_user_list(ChatUser *cu, char *msg, ChatRoom *theroom)
{
    ChatRoom *myroom, *room;
    ChatUser *user;

    int start, stop, curr = 0;
    start = atoi(nextword(&msg));
    stop = atoi(nextword(&msg));

    myroom = cu->room;

#ifdef DEBUG
    logit(cu->chatid, "do user list");
#endif

    if (common_client_command)
    send_to_user(cu, "", 0, MSG_USERLISTSTART);
    else
    send_to_user(cu, " 聊天代號│使用者代號  │聊天室 ", 0, MSG_MESSAGE);

    for (user = mainuser; user; user = user->unext)
    {

    room = user->room;
    if ((theroom != ROOM_ALL) && (theroom != room))
        continue;

    if (myroom != room)
    {
        if (RESTRICTED(cu) ||
        (room && SECRET(room) && !CHATSYSOP(cu)))
        continue;
    }

    if (CLOAK(user))            /* Thor: 隱身術 */
        continue;


    curr++;
    if (start && curr < start)
        continue;
    else if (stop && (curr > stop))
        break;

    if (common_client_command)
    {
        if (!room)
        continue;               /* Xshadow: 還沒進入任何房間的就不列出 */

        snprintf(chatbuf, sizeof(chatbuf), "%s %s %s %s", user->chatid, user->userid, room->name, user->lasthost);
        if (ROOMOP(user))
        strcat(chatbuf, " Op");
    }
    else
    {
        snprintf(chatbuf, sizeof(chatbuf), " %-8s│%-12s│%s", user->chatid, user->userid, room ? room->name : "[在門口徘徊]");
        if (ROOMOP(user))
        strcat(chatbuf, " [Op]");
    }

#ifdef  DEBUG
    logit("list_U", chatbuf);
#endif

    send_to_user(cu, chatbuf, 0, common_client_command ? MSG_USERLIST : MSG_MESSAGE);
    }
    if (common_client_command)
    send_to_user(cu, "", 0, MSG_USERLISTEND);
}

static void
chat_list_by_room(ChatUser *cu, char *msg)
{
    ChatRoom *whichroom;
    char *roomstr;

    roomstr = nextword(&msg);
    if (*roomstr == '\0')
    whichroom = cu->room;
    else
    {
    if ((whichroom = croom_by_roomid(roomstr)) == NULL)
    {
        snprintf(chatbuf, sizeof(chatbuf), "※ 沒有 [%s] 這個聊天室", roomstr);
        send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
        return;
    }

    if (whichroom != cu->room && SECRET(whichroom) && !CHATSYSOP(cu))
    {                           /* Thor: 要不要測同一room雖SECRET但可以列?
                     * Xshadow: 我改成同一 room 就可以列 */
        send_to_user(cu, "※ 無法列出在秘密聊天室的使用者", 0, MSG_MESSAGE);
        return;
    }
    }
    chat_do_user_list(cu, msg, whichroom);
}


static void
chat_list_users(ChatUser *cu, char *msg)
{
    chat_do_user_list(cu, msg, ROOM_ALL);
}

static void
chat_chatroom(ChatUser *cu, char *msg)
{
    if (common_client_command)
    send_to_user(cu, "批踢踢茶藝館 4 21", 0, MSG_CHATROOM);
}

static void
chat_map_chatids(ChatUser *cu, ChatRoom *whichroom) /* Thor: 還沒有作不同間的 */
{
    int c;
    ChatRoom *myroom, *room;
    ChatUser *user;

    myroom = whichroom;
    send_to_user(cu,
         " 聊天代號 使用者代號  │ 聊天代號 使用者代號  │ 聊天代號 使用者代號 ", 0, MSG_MESSAGE);

    c = 0;

    for (user = mainuser; user; user = user->unext)
    {
    room = user->room;
    if (whichroom != ROOM_ALL && whichroom != room)
        continue;
    if (myroom != room)
    {
        if (RESTRICTED(cu) ||     /* Thor: 要先check room 是不是空的 */
        (room && SECRET(room) && !CHATSYSOP(cu)))
        continue;
    }
    if (CLOAK(user))            /* Thor:隱身術 */
        continue;
    sprintf(chatbuf + (c * 24), " %-8s%c%-12s%s",
        user->chatid, ROOMOP(user) ? '*' : ' ',
        user->userid, (c < 2 ? "│" : "  "));
    if (++c == 3)
    {
        send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
        c = 0;
    }
    }
    if (c > 0)
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
}


static void
chat_map_chatids_thisroom(ChatUser *cu, char *msg)
{
    chat_map_chatids(cu, cu->room);
}


static void
chat_setroom(ChatUser *cu, char *msg)
{
    char *modestr;
    ChatRoom *room;
    char *chatid;
    int sign;
    int flag;
    char *fstr = NULL;

    if (!ROOMOP(cu))
    {
    send_to_user(cu, msg_not_op, 0, MSG_MESSAGE);
    return;
    }

    modestr = nextword(&msg);
    sign = 1;
    if (*modestr == '+')
    modestr++;
    else if (*modestr == '-')
    {
    modestr++;
    sign = 0;
    }
    if (*modestr == '\0')
    {
    send_to_user(cu,
             "※ 請指定狀態: {[+(設定)][-(取消)]}{[l(鎖住)][s(秘密)][t(開放話題)}", 0, MSG_MESSAGE);
    return;
    }

    room = cu->room;
    chatid = cu->chatid;

    while (*modestr)
    {
    flag = 0;
    switch (*modestr)
    {
    case 'l':
    case 'L':
        flag = ROOM_LOCKED;
        fstr = "鎖住";
        break;

    case 's':
    case 'S':
        flag = ROOM_SECRET;
        fstr = "秘密";
        break;

    case 't':
    case 'T':
        flag = ROOM_OPENTOPIC;
        fstr = "開放話題";
        break;
    case 'h':
    case 'H':
        flag = ROOM_HANDUP;
        fstr = "舉手發言";
        break;

    default:
        sprintf(chatbuf, "※ 狀態錯誤:[%c]", *modestr);
        send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
    }

    /* Thor: check room 是不是空的, 應該不是空的 */
    if (flag && (room->rflag & flag) != sign * flag)
    {
        room->rflag ^= flag;
        sprintf(chatbuf, "※ 本聊天室被 %s %s [%s] 狀態",
            chatid, sign ? "設定為" : "取消", fstr);
        if (!CLOAK(cu))           /* Thor: 聊天室隱身術 */
        send_to_room(room, chatbuf, 0, MSG_MESSAGE);
    }
    modestr++;
    }
    room_changed(room);
}

static char *chat_msg[] =
{
    "[//]help", "MUD-like 社交動詞",
    "[/h]elp op", "談天室管理員專用指令",
    "[/a]ct <msg>", "做一個動作",
    "[/b]ye [msg]", "道別",
    "[/c]lear  [/d]ate", "清除螢幕  目前時間",
    /* "[/d]ate", "目前時間", *//* Thor: 指令太多 */

#if 0
    "[/f]ire <user> <msg>", "發送熱訊",   /* Thor.0727: 和 flag 衝key */
#endif

    "[/i]gnore [user]", "忽略使用者",
    "[/j]oin <room>", "建立或加入談天室",
    "[/l]ist [start [stop]]", "列出談天室使用者",
    "[/m]sg <id|user> <msg>", "跟 <id> 說悄悄話",
    "[/n]ick <id>", "將談天代號換成 <id>",
    "[/p]ager", "切換呼叫器",
    "[/q]uery <user>", "查詢網友",
    "[/r]oom", "列出一般談天室",
    "[/t]ape", "開關錄音機",
    "[/u]nignore <user>", "取消忽略",

#if 0
    "[/u]sers", "列出站上使用者",
#endif

    "[/w]ho", "列出本談天室使用者",
    "[/w]hoin <room>", "列出談天室<room> 的使用者",
    NULL
};


static char *room_msg[] =
{
    "[/f]lag [+-][lsth]", "設定鎖定、秘密、開放話題、舉手發言",
    "[/i]nvite <id>", "邀請 <id> 加入談天室",
    "[/kick] <id>", "將 <id> 踢出談天室",
    "[/o]p <id>", "將 Op 的權力轉移給 <id>",
    "[/topic] <text>", "換個話題",
    "[/w]all", "廣播 (站長專用)",
    NULL
};


static void
chat_help(ChatUser *cu, char *msg)
{
    char **table, *str;

    if (str_equal(nextword(&msg), "op"))
    {
    send_to_user(cu, "談天室管理員專用指令", 0, MSG_MESSAGE);
    table = room_msg;
    }
    else
    {
    table = chat_msg;
    }

    while((str = *table++)) {
    snprintf(chatbuf, sizeof(chatbuf), "  %-20s- %s", str, *table++);
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
    }
}


static void
chat_private(ChatUser *cu, char *msg)
{
    char *recipient;
    ChatUser *xuser;
    int userno;

    userno = 0;
    recipient = nextword(&msg);
    xuser = (ChatUser *) fuzzy_cuser_by_chatid(recipient);
    if (xuser == NULL)
    {                             /* Thor.0724: 用 userid也可傳悄悄話 */
    xuser = cuser_by_userid(recipient);
    }
    if (xuser == NULL)
    {
    sprintf(chatbuf, msg_no_such_id, recipient);
    }
    else if (xuser == FUZZY_USER)
    {                             /* ambiguous */
    strcpy(chatbuf, "※ 請指明聊天代號");
    }
    else if (*msg)
    {
    userno = cu->userno;
    sprintf(chatbuf, "*%s* ", cu->chatid);
    strncat(chatbuf, msg, 80);
    send_to_user(xuser, chatbuf, userno, MSG_MESSAGE);

    if (xuser->clitype)
    {                           /* Xshadow: 如果對方是用 client 上來的 */
        sprintf(chatbuf, "%s %s ", cu->userid, cu->chatid);
        strncat(chatbuf, msg, 80);
        send_to_user(xuser, chatbuf, userno, MSG_PRIVMSG);
    }
    if (cu->clitype)
    {
        sprintf(chatbuf, "%s %s ", xuser->userid, xuser->chatid);
        strncat(chatbuf, msg, 80);
        send_to_user(cu, chatbuf, 0, MSG_MYPRIVMSG);
    }

    sprintf(chatbuf, "%s> ", xuser->chatid);
    strncat(chatbuf, msg, 80);
    }
    else
    {
    sprintf(chatbuf, "※ 您想對 %s 說什麼話呢?", xuser->chatid);
    }
    send_to_user(cu, chatbuf, userno, MSG_MESSAGE);       /* Thor: userno 要改成 0
                               * 嗎? */
}


static void
chat_cloak(ChatUser *cu, char *msg)
{
    if (CHATSYSOP(cu))
    {
    cu->uflag ^= PERM_CLOAK;
    sprintf(chatbuf, "◆ %s", CLOAK(cu) ? MSG_CLOAKED : MSG_UNCLOAK);
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
    }
}



/* ----------------------------------------------------- */


static void
arrive_room(ChatUser *cuser, ChatRoom *room)
{
    char *rname;

    /* Xshadow: 不必送給自己, 反正換房間就會重新 build user list */
    snprintf(chatbuf, sizeof(chatbuf), "+ %s %s %s %s", cuser->userid, cuser->chatid, room->name, cuser->lasthost);
    if (ROOMOP(cuser))
    strcat(chatbuf, " Op");
    send_to_room(room, chatbuf, 0, MSG_USERNOTIFY);

    cuser->room = room;
    room->occupants++;
    rname = room->name;

    room_changed(room);

    if (cuser->clitype)
    {
    send_to_user(cuser, rname, 0, MSG_ROOM);
    send_to_user(cuser, room->topic, 0, MSG_TOPIC);
    }
    else
    {
    sprintf(chatbuf, "/r%s", rname);
    send_to_user(cuser, chatbuf, 0, 0);
    sprintf(chatbuf, "/t%s", room->topic);
    send_to_user(cuser, chatbuf, 0, 0);
    }

    sprintf(chatbuf, "※ %s 進入 [%s] 包廂",
        cuser->chatid, rname);
    if (!CLOAK(cuser))            /* Thor: 聊天室隱身術 */
    send_to_room(room, chatbuf, cuser->userno, MSG_MESSAGE);
}


static int
enter_room(ChatUser *cuser, char *rname, char *msg)
{
    ChatRoom *room;
    int create;

    create = 0;
    room = croom_by_roomid(rname);
    if (room == NULL)
    {
    /* new room */

#ifdef  MONITOR
    logit(cuser->userid, "create new room");
#endif

    room = (ChatRoom *) malloc(sizeof(ChatRoom));
    if (room == NULL)
    {
        send_to_user(cuser, "※ 無法再新闢包廂了", 0, MSG_MESSAGE);
        return 0;
    }

    memset(room, 0, sizeof(ChatRoom));
    strlcpy(room->name, rname, IDLEN);
    strcpy(room->topic, "這是一個新天地");

    snprintf(chatbuf, sizeof(chatbuf), "+ %s 1 0 %s", room->name, room->topic);
    send_to_room(ROOM_ALL, chatbuf, 0, MSG_ROOMNOTIFY);

    if (mainroom.next != NULL)
        mainroom.next->prev = room;
    room->next = mainroom.next;
    mainroom.next = room;
    room->prev = &mainroom;

    create = 1;
    }
    else
    {
    if (cuser->room == room)
    {
        sprintf(chatbuf, "※ 您本來就在 [%s] 聊天室囉 :)", rname);
        send_to_user(cuser, chatbuf, 0, MSG_MESSAGE);
        return 0;
    }

    if (!CHATSYSOP(cuser) && LOCKED(room) && !list_belong(room->invite, cuser->userno))
    {
        send_to_user(cuser, "※ 內有惡犬,非請莫入", 0, MSG_MESSAGE);
        return 0;
    }
    }

    exit_room(cuser, EXIT_LOGOUT, msg);
    arrive_room(cuser, room);

    if (create)
    cuser->uflag |= PERM_ROOMOP;

    return 0;
}


static void
logout_user(ChatUser *cuser)
{
    int sock;
    ChatUser *xuser, *prev;

    sock = cuser->sock;
    shutdown(sock, 2);
    close(sock);

    FD_CLR(sock, &mainfds);

#if 0   /* Thor: 也許不差這一個 */
    if (sock >= maxfds)
    maxfds = sock - 1;
#endif

    list_free(cuser->ignore);

    xuser = mainuser;
    if (xuser == cuser)
    {
    mainuser = cuser->unext;
    }
    else
    {
    do
    {
        prev = xuser;
        xuser = xuser->unext;
        if (xuser == cuser)
        {
        prev->unext = cuser->unext;
        break;
        }
    } while (xuser);
    }

#ifdef DEBUG
    sprintf(chatbuf, "%p", cuser);
    logit("free cuser", chatbuf);
#endif

    free(cuser);

    totaluser--;
}


static void
print_user_counts(ChatUser *cuser)
{
    ChatRoom *room;
    int num, userc, suserc, roomc, number;

    userc = suserc = roomc = 0;

    for(room = &mainroom; room; room = room->next) {
    num = room->occupants;
    if (SECRET(room))
    {
        suserc += num;
        if (CHATSYSOP(cuser))
        roomc++;
    }
    else
    {
        userc += num;
        roomc++;
    }
    }

    number = (cuser->clitype) ? MSG_MOTD : MSG_MESSAGE;

    sprintf(chatbuf,
        "⊙ 歡迎光臨【批踢踢茶藝館】,目前開了 %d 間包廂", roomc);
    send_to_user(cuser, chatbuf, 0, number);

    sprintf(chatbuf, "⊙ 共有 %d 人來擺\龍門陣", userc);
    if (suserc)
    sprintf(chatbuf + strlen(chatbuf), " [%d 人在秘密聊天室]", suserc);
    send_to_user(cuser, chatbuf, 0, number);
}


static int
login_user(ChatUser *cu, char *msg)
{
    int utent;

    char *passwd;
    char *userid;
    char *chatid;
    struct sockaddr_in from;
    int fromlen;
    struct hostent *hp;


    ACCT acct;
    int level;

    /*
     * Thor.0819: SECURED_CHATROOM : /! userid chatid passwd , userno
     * el 在check完passwd後取得
     */
    /* Xshadow.0915: common client support : /-! userid chatid password */

    /* 傳參數:userlevel, userid, chatid */

    /* client/server 版本依據 userid 抓 .PASSWDS 判斷 userlevel */

    userid = nextword(&msg);
    chatid = nextword(&msg);


#ifdef  DEBUG
    logit("ENTER", userid);
#endif
    /* Thor.0730: parse space before passwd */
    passwd = msg;

    /* Thor.0813: 跳過一空格即可, 因為反正如果chatid有空格, 密碼也不對 */
    /* 就算密碼對, 也不會怎麼樣:p */
    /* 可是如果密碼第一個字是空格, 那跳太多空格會進不來... */
    if (*passwd == ' ')
    passwd++;

    /* Thor.0729: load acct */
    if (!*userid || (acct_load(&acct, userid) < 0))
    {

#ifdef  DEBUG
    logit("noexist", chatid);
#endif

    if (cu->clitype)
        send_to_user(cu, "錯誤的使用者代號", 0, ERR_LOGIN_NOSUCHUSER);
    else
        send_to_user(cu, CHAT_LOGIN_INVALID, 0, 0);

    return -1;
    }
    else if(strncmp(passwd, acct.passwd, PASSLEN) &&
        !chkpasswd(acct.passwd, passwd))
    {
#ifdef  DEBUG
    logit("fake", chatid);
#endif

    if (cu->clitype)
        send_to_user(cu, "密碼錯誤", 0, ERR_LOGIN_PASSERROR);
    else
        send_to_user(cu, CHAT_LOGIN_INVALID, 0, 0);
    return -1;
    }

    /* Thor.0729: if ok, read level.  */
    level = acct.userlevel;
    /* Thor.0819: read userno for client/server bbs */
#ifdef SELFTEST
    utent = atoi(userid)+1;
#else
    utent = searchuser(acct.userid, NULL);
#endif
    assert(utent);

    /* Thor.0819: for client/server bbs */
/*
  for (xuser = mainuser; xuser; xuser = xuser->unext)
  {
  if (xuser->userno == utent)
  {

  #ifdef  DEBUG
  logit("enter", "bogus");
  #endif
  if (cu->clitype)
  send_to_user(cu, "請勿派遣分身進入聊天室 !!", 0, ERR_LOGIN_USERONLINE);
  else
  send_to_user(cu, CHAT_LOGIN_BOGUS, 0, 0);
  return -1;    
  }
  }
*/
    if (!valid_chatid(chatid))
    {
#ifdef  DEBUG
    logit("enter", chatid);
#endif

    if (cu->clitype)
        send_to_user(cu, "不合法的聊天室代號 !!", 0, ERR_LOGIN_NICKERROR);
    else
        send_to_user(cu, CHAT_LOGIN_INVALID, 0, 0);
    return 0;
    }

    if (cuser_by_chatid(chatid) != NULL)
    {
    /* chatid in use */

#ifdef  DEBUG
    logit("enter", "duplicate");
#endif

    if (cu->clitype)
        send_to_user(cu, "這個代號已經有人使用", 0, ERR_LOGIN_NICKINUSE);
    else
        send_to_user(cu, CHAT_LOGIN_EXISTS, 0, 0);
    return 0;
    }

    cu->userno = utent;
    cu->uflag = level & ~(PERM_ROOMOP | PERM_CLOAK | PERM_HANDUP | PERM_SAY);
    /* Thor: 進來先清空ROOMOP(同PERM_CHAT), CLOAK */
    strcpy(cu->userid, userid);
    strlcpy(cu->chatid, chatid, sizeof(cu->chatid));

    /* Xshadow: 取得 client 的來源 */
    fromlen = sizeof(from);
    if (!getpeername(cu->sock, (struct sockaddr *) & from, &fromlen))
    {
    if ((hp = gethostbyaddr((char *) &from.sin_addr, sizeof(struct in_addr), from.sin_family)))
        strcpy(cu->lasthost, hp->h_name);
    else
        strcpy(cu->lasthost, (char *) inet_ntoa(from.sin_addr));
    }
    else
    strcpy(cu->lasthost, "[外太空]");

    if (cu->clitype)
    send_to_user(cu, "順利", 0, MSG_LOGINOK);
    else
    send_to_user(cu, CHAT_LOGIN_OK, 0, 0);

    arrive_room(cu, &mainroom);

    send_to_user(cu, "", 0, MSG_MOTDSTART);
    print_user_counts(cu);
    send_to_user(cu, "", 0, MSG_MOTDEND);

#ifdef  DEBUG
    logit("enter", "OK");
#endif

    return 0;
}


static void
chat_act(ChatUser *cu, char *msg)
{
    if (*msg && (!RHANDUP(cu->room) || SAY(cu) || ROOMOP(cu)))
    {
    sprintf(chatbuf, "%s %s", cu->chatid, msg);
    send_to_room(cu->room, chatbuf, cu->userno, MSG_MESSAGE);
    }
}


static void
chat_ignore(ChatUser *cu, char *msg)
{

    if (RESTRICTED(cu))
    {
    strcpy(chatbuf, "※ 您沒有 ignore 別人的權利");
    }
    else
    {
    char *ignoree;

    ignoree = nextword(&msg);
    if (*ignoree)
    {
        ChatUser *xuser;

        xuser = cuser_by_userid(ignoree);

        if (xuser == NULL)
        {
        sprintf(chatbuf, msg_no_such_id, ignoree);
        }
        else if (xuser == cu || CHATSYSOP(xuser) ||
             (ROOMOP(xuser) && (xuser->room == cu->room)))
        {
        sprintf(chatbuf, "◆ 不可以 ignore [%s]", ignoree);
        }
        else
        {

        if (list_belong(cu->ignore, xuser->userno))
        {
            sprintf(chatbuf, "※ %s 已經被凍結了", xuser->chatid);
        }
        else
        {
            list_add(&(cu->ignore), xuser);
            sprintf(chatbuf, "◆ 將 [%s] 打入冷宮了 :p", xuser->chatid);
        }
        }
    }
    else
    {
        UserList *list;

        if((list = cu->ignore))
        {
        int len;
        char buf[16];

        send_to_user(cu, "◆ 這些人被打入冷宮了:", 0, MSG_MESSAGE);
        len = 0;
        do
        {
            sprintf(buf, "%-13s", list->userid);
            strcpy(chatbuf + len, buf);
            len += 13;
            if (len >= 78)
            {
            send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
            len = 0;
            }
        } while((list = list->next));

        if (len == 0)
            return;
        }
        else
        {
        strcpy(chatbuf, "◆ 您目前並沒有 ignore 任何人");
        }
    }
    }

    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
}


static void
chat_unignore(ChatUser *cu, char *msg)
{
    char *ignoree;

    ignoree = nextword(&msg);

    if (*ignoree)
    {
    sprintf(chatbuf, (list_delete(&(cu->ignore), ignoree)) ?
        "◆ [%s] 不再被你冷落了" :
        "◆ 您並未 ignore [%s] 這號人物", ignoree);
    }
    else
    {
    strcpy(chatbuf, "◆ 請指明 user ID");
    }
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
}


static void
chat_join(ChatUser *cu, char *msg)
{
    if (RESTRICTED(cu))
    {
    send_to_user(cu, "※ 您沒有加入其他聊天室的權限", 0, MSG_MESSAGE);
    }
    else
    {
    char *roomid = nextword(&msg);

    if (*roomid)
        enter_room(cu, roomid, msg);
    else
        send_to_user(cu, "※ 請指定聊天室的名字", 0, MSG_MESSAGE);
    }
}


static void
chat_kick(ChatUser *cu, char *msg)
{
    char *twit;
    ChatUser *xuser;
    ChatRoom *room;

    if (!ROOMOP(cu))
    {
    send_to_user(cu, msg_not_op, 0, MSG_MESSAGE);
    return;
    }

    twit = nextword(&msg);
    xuser = cuser_by_chatid(twit);

    if (xuser == NULL)
    {
    sprintf(chatbuf, msg_no_such_id, twit);
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
    return;
    }

    room = cu->room;
    if (room != xuser->room || CLOAK(xuser))
    {                             /* Thor: 聊天室隱身術 */
    sprintf(chatbuf, msg_not_here, twit);
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
    return;
    }

    if (CHATSYSOP(xuser))
    {                             /* Thor: 踢不走 CHATSYSOP */
    sprintf(chatbuf, "◆ 不可以 kick [%s]", twit);
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
    return;
    }

    exit_room(xuser, EXIT_KICK, (char *) NULL);

    if (room == &mainroom)
    logout_user(xuser);
    else
    enter_room(xuser, MAIN_NAME, (char *) NULL);
}


static void
chat_makeop(ChatUser *cu, char *msg)
{
    char *newop;
    ChatUser *xuser;
    ChatRoom *room;

    if (!ROOMOP(cu))
    {
    send_to_user(cu, msg_not_op, 0, MSG_MESSAGE);
    return;
    }

    newop = nextword(&msg);
    xuser = cuser_by_chatid(newop);

    if (xuser == NULL)
    {
    sprintf(chatbuf, msg_no_such_id, newop);
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
    return;
    }

    if (cu == xuser)
    {
    sprintf(chatbuf, "※ 您早就已經是 Op 了啊");
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
    return;
    }

    room = cu->room;

    if (room != xuser->room || CLOAK(xuser))
    {                             /* Thor: 聊天室隱身術 */
    sprintf(chatbuf, msg_not_here, xuser->chatid);
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
    return;
    }

    cu->uflag &= ~PERM_ROOMOP;
    xuser->uflag |= PERM_ROOMOP;

    user_changed(cu);
    user_changed(xuser);

    sprintf(chatbuf, "※ %s 將 Op 權力轉移給 %s",
        cu->chatid, xuser->chatid);
    if (!CLOAK(cu))               /* Thor: 聊天室隱身術 */
    send_to_room(room, chatbuf, 0, MSG_MESSAGE);
}



static void
chat_invite(ChatUser *cu, char *msg)
{
    char *invitee;
    ChatUser *xuser;
    ChatRoom *room;
    UserList **list;

    if (!ROOMOP(cu))
    {
    send_to_user(cu, msg_not_op, 0, MSG_MESSAGE);
    return;
    }

    invitee = nextword(&msg);
    xuser = cuser_by_chatid(invitee);
    if (xuser == NULL)
    {
    sprintf(chatbuf, msg_no_such_id, invitee);
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
    return;
    }

    room = cu->room;
    assert(room);
    list = &(room->invite);

    if (list_belong(*list, xuser->userno))
    {
    sprintf(chatbuf, "※ %s 已經接受過邀請了", xuser->chatid);
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
    return;
    }
    list_add(list, xuser);

    sprintf(chatbuf, "※ %s 邀請您到 [%s] 聊天室",
        cu->chatid, room->name);
    send_to_user(xuser, chatbuf, 0, MSG_MESSAGE); /* Thor: 要不要可以 ignore? */
    sprintf(chatbuf, "※ %s 收到您的邀請了", xuser->chatid);
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
}


static void
chat_broadcast(ChatUser *cu, char *msg)
{
    if (!CHATSYSOP(cu))
    {
    send_to_user(cu, "※ 您沒有在聊天室廣播的權力!", 0, MSG_MESSAGE);
    return;
    }
    if (*msg == '\0')
    {
    send_to_user(cu, "※ 請指定廣播內容", 0, MSG_MESSAGE);
    return;
    }
    sprintf(chatbuf, "※ " BBSNAME "談天室廣播中 [%s].....",
        cu->chatid);
    send_to_room(ROOM_ALL, chatbuf, 0, MSG_MESSAGE);
    sprintf(chatbuf, "◆ %s", msg);
    send_to_room(ROOM_ALL, chatbuf, 0, MSG_MESSAGE);
}


static void
chat_goodbye(ChatUser *cu, char *msg)
{
    exit_room(cu, EXIT_LOGOUT, msg);
    /* Thor: 要不要加 logout_user(cu) ? */
}


/* --------------------------------------------- */
/* MUD-like social commands : action             */
/* --------------------------------------------- */

struct ChatAction
{
    char *verb;                   /* 動詞 */
    char *chinese;                /* 中文翻譯 */
    char *part1_msg;              /* 介詞 */
    char *part2_msg;              /* 動作 */
};


static ChatAction party_data[] =
{
    {"aluba", "阿魯巴", "把", "架上柱子阿魯巴!!"},
    {"adore", "景仰", "對", "的景仰有如滔滔江水,連綿不絕……"},
    {"bearhug", "熱擁", "熱情的擁抱", ""},
    {"blade", "一刀", "一刀啟程把", "送上西天"},
    {"bless", "祝福", "祝福", "心想事成"},
    {"board", "主機板", "把", "抓去跪主機板"},
    {"bokan", "氣功\", "雙掌微合,蓄勢待發……突然間,電光乍現,對", "使出了Bo--Kan!"},
  {"bow", "鞠躬", "畢躬畢敬的向", "鞠躬"},
  {"box", "幕之內", "開始輪擺\式移位,對", "作肝臟攻擊"},
  {"boy", "平底鍋", "從背後拿出了平底鍋,把", "敲昏了"},
  {"bye", "掰掰", "", "說掰掰!!"},
  {"call", "呼喚", "大聲的呼喚,~~", "~~~你在哪裡啊啊啊啊~~~~"},
  {"caress", "輕撫", "輕輕的撫摸著", ""},
  {"clap", "鼓掌", "", "熱烈鼓掌"},
  {"claw", "抓抓", "從貓咪樂園借了隻貓爪,把", "抓得死去活來"},
  {"comfort", "安慰", "溫言安慰", ""},
  {"cong", "恭喜", "從背後拿出了拉炮,呯!呯!恭喜", ""},
  {"cpr", "口對口", "對著", "做口對口人工呼吸"},
  {"cringe", "乞憐", "", "卑躬屈膝,搖尾乞憐"},
  {"cry", "大哭", "", "嚎啕大哭"},
  {"dance", "跳舞", "拉了", "的手翩翩起舞"  },
  {"destroy", "毀滅", "祭起了『極大毀滅咒文』,轟向", ""},
  {"dogleg", "狗腿", "", "狗腿"},
  {"drivel", "流口水", "對著", "流口水"},
  {"envy", "羨慕", "", "流露出羨慕的眼光"},
  {"eye", "送秋波", "", "頻送秋波"},
  {"fire", "銬問", "拿著火紅的鐵棒走向", ""},
  {"forgive", "原諒", "接受道歉,原諒了", ""},
  {"french", "法式吻", "把舌頭伸到", "喉嚨裡~~~哇!一個浪漫的法國氏深吻"},
  {"giggle", "傻笑", "對著", "傻傻的呆笑"},
  {"glue", "補心", "用三秒膠,把", "的心黏了起來"},
  {"goodbye", "告別", "淚\眼汪汪的向", "告別"},
  {"grin", "奸笑", "", "露出邪惡的笑容"},
  {"growl", "咆哮", "", "咆哮不已"},
  {"hand", "握手", "", "握手"},
  {"hide", "", "躲在", "背後"},
  {"hospitl", "送醫院", "", "送進醫院"},
  {"hug", "擁抱", "輕輕地擁抱", ""},
  {"hrk", "昇龍拳", "沉穩了身形,匯聚了內勁,對", "使出了一記Ho--Ryu--Kan!!!"},
  {"jab", "戳人", "溫柔的戳著", ""},
  {"judo", "過肩摔", "抓住了", "的衣襟,轉身……啊,是一記過肩摔!"},
  {"kickout", "", "用大腳把", "踢到山下去了"},
  {"kick", "踢人", "", "踢的死去活來"},
  {"kiss", "輕吻", "輕吻", "的臉頰"},
  {"laugh", "嘲笑", "大聲嘲笑", ""},
  {"levis", "給我", "說:給我", "!其餘免談!"},
  {"lick", "", "狂舔", ""},
  {"lobster", "壓制", "施展逆蝦形固定,把", "壓制在地板上"},
  {"love", "表白", "", "深情的表白"},
  {"marry", "求婚", "捧著九百九十九朵玫瑰向", "求婚"},
  {"no", "不要啊", "拼命對著", "搖頭~~~~不要啊~~~~"},
  {"nod", "點頭", "", "點頭稱是"},
  {"nudge", "頂肚子", "用手肘頂", "的肥肚子"},
  {"pad", "拍肩膀", "輕拍", "的肩膀"},
  {"pettish", "撒嬌", "", "嗲聲嗲氣地撒嬌"},
  {"pili", "霹靂", "使出 君子風 天地根 般若懺 三式合一打向", "~~~~~~"},
  {"pinch", "擰人", "用力的把", "擰的黑青"},
  {"roll", "打滾", "放出多爾袞的音樂,", "在地上滾來滾去"},
  {"protect", "保護", "保護著", ""},
  {"pull", "", "死命地拉住", "不放"},
  {"punch", "揍人", "狠狠揍了", "一頓"},
  {"rascal", "耍賴", "", "耍賴"},
  {"recline", "入懷", "鑽到", "的懷裡睡著了……"},
  {"respond", "負責", "安慰", "說:『不要哭,我會負責的……』"},
  {"shrug", "聳肩", "無奈地向", "聳了聳肩膀"},
  {"sigh", "歎氣", "", "歎了一口氣"},
  {"slap", "打耳光", "啪啪的巴了", "一頓耳光"},
  {"smooch", "擁吻", "擁吻著", ""},
  {"snicker", "竊笑", "嘿嘿嘿..的對", "竊笑"},
  {"sniff", "不屑", "", "嗤之以鼻"},
  {"spank", "打屁屁", "用巴掌打", "的臀部"},
  {"squeeze", "緊擁", "緊緊地擁抱著", ""},
  {"sysop", "召喚", "叫出了批踢踢,把", "踩扁了!"},
  {"thank", "感謝", "", "感謝得五體投地"},
  {"tickle", "搔癢", "咕嘰!咕嘰!", "的癢"},
  {"wake", "搖醒", "輕輕地把", "搖醒"},
  {"wave", "揮手", "對著", "拼命的搖手"},
  {"welcome", "歡迎", "歡迎", "進來八卦一下"},
  {"what", "什麼", "說:『", "哩公瞎密哇隴聽某???"},
  {"whip", "鞭子", "手上拿著蠟燭,用鞭子痛打", ""},
  {"wink", "眨眼", "", "神秘的眨眨眼睛"},
  {"zap", "猛攻", "", "瘋狂的攻擊"},
  {NULL, NULL, NULL, NULL}
};

static int
party_action(ChatUser *cu, char *cmd, char *party)
{
    ChatAction *cap;
    char *verb;

    for (cap = party_data; (verb = cap->verb); cap++)
    {
    if (!str_equal(cmd, verb))
        continue;
    if (*party == '\0')
        party = "大家";
    else
    {
        ChatUser *xuser;

        xuser = fuzzy_cuser_by_chatid(party);
        if (xuser == NULL)
        {                       /* Thor.0724: 用 userid也嘛通 */
        xuser = cuser_by_userid(party);
        }

        if (xuser == NULL)
        {
        sprintf(chatbuf, msg_no_such_id, party);
        send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
        return 0;
        }
        else if (xuser == FUZZY_USER)
        {
        sprintf(chatbuf, "※ 請指明聊天代號");
        send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
        return 0;
        }
        else if (cu->room != xuser->room || CLOAK(xuser))
        {
        sprintf(chatbuf, msg_not_here, party);
        send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
        return 0;
        }
        else
        {
        party = xuser->chatid;
        }
    }
    sprintf(chatbuf, "%s %s %s %s",
        cu->chatid, cap->part1_msg, party, cap->part2_msg);
    send_to_room(cu->room, chatbuf, cu->userno, MSG_MESSAGE);
    return 0;
    }
    return 1;
}


/* --------------------------------------------- */
/* MUD-like social commands : speak              */
/* --------------------------------------------- */


static ChatAction speak_data[] =
{
    { "ask", "詢問", "", NULL },
    { "chant", "歌頌", "高聲歌頌", NULL },
    { "cheer", "喝采", "喝采", NULL },
    { "chuckle", "輕笑", "輕笑", NULL },
    { "curse", "暗幹", "暗幹", NULL },
    /* {"curse", "咒罵", NULL}, */
    { "demand", "要求", "要求", NULL },
    { "frown", "皺眉頭", "蹙眉", NULL },
    { "groan", "呻吟", "呻吟", NULL },
    { "grumble", "發牢騷", "發牢騷", NULL },
    { "guitar", "彈唱", "邊彈著吉他,邊唱著", NULL },
    /* {"helpme", "呼救","大聲呼救",NULL}, */
    { "hum", "喃喃", "喃喃自語", NULL },
    { "moan", "怨嘆", "怨嘆", NULL },
    { "notice", "強調", "強調", NULL },
    { "order", "命令", "命令", NULL },
    { "ponder", "沈思", "沈思", NULL },
    { "pout", "噘嘴", "噘著嘴說", NULL },
    { "pray", "祈禱", "祈禱", NULL },
    { "request", "懇求", "懇求", NULL },
    { "shout", "大罵", "大罵", NULL },
    { "sing", "唱歌", "唱歌", NULL },
    { "smile", "微笑", "微笑", NULL },
    { "smirk", "假笑", "假笑", NULL },
    { "swear", "發誓", "發誓", NULL },
    { "tease", "嘲笑", "嘲笑", NULL },
    { "whimper", "嗚咽", "嗚咽的說", NULL },
    { "yawn", "哈欠", "邊打哈欠邊說", NULL },
    { "yell", "大喊", "大喊", NULL },
    { NULL, NULL, NULL, NULL }
};


static int
speak_action(ChatUser *cu, char *cmd, char *msg)
{
    ChatAction *cap;
    char *verb;

    for (cap = speak_data; (verb = cap->verb); cap++)
    {
    if (!str_equal(cmd, verb))
        continue;
    sprintf(chatbuf, "%s %s: %s",
        cu->chatid, cap->part1_msg, msg);
    send_to_room(cu->room, chatbuf, cu->userno, MSG_MESSAGE);
    return 0;
    }
    return 1;
}


/* -------------------------------------------- */
/* MUD-like social commands : condition          */
/* -------------------------------------------- */


static ChatAction condition_data[] =
{
    { "applaud", "拍手", "啪啪啪啪啪啪啪....", NULL },
    { "ayo", "唉呦喂", "唉呦喂~~~", NULL },
    { "back", "坐回來", "回來坐正繼續奮戰", NULL },
    { "blood", "在血中", "倒在血泊之中", NULL },
    { "blush", "臉紅", "臉都紅了", NULL },
    { "broke", "心碎", "的心破碎成一片一片的", NULL },
    /* {"bokan", "Bo Kan! Bo Kan!", NULL}, */
    { "careles", "沒人理", "嗚~~都沒有人理我 :~~~~", NULL },
    { "chew", "嗑瓜子", "很悠閒的嗑起瓜子來了", NULL },
    { "climb", "爬山", "自己慢慢爬上山來……", NULL },
    { "cold", "感冒了", "感冒了,媽媽不讓我出去玩 :~~~(", NULL },
    { "cough", "咳嗽", "咳了幾聲", NULL },
    { "die", "暴斃", "當場暴斃", NULL },
    { "faint", "昏倒", "當場昏倒", NULL },
    { "flop", "香蕉皮", "踩到香蕉皮... 滑倒!", NULL },
    { "fly", "飄飄然", "飄飄然", NULL },
    { "frown", "蹙眉", "蹙眉", NULL },
    { "gold", "拿金牌", "唱著:『金ㄍㄠˊ金ㄍㄠˊ  出國比賽! 得冠軍,拿金牌,光榮倒鄧來!』", NULL },
    { "gulu", "肚子餓", "的肚子發出咕嚕~~~咕嚕~~~的聲音", NULL },
    { "haha", "哇哈哈", "哇哈哈哈.....^o^", NULL },
    /* {"haha", "大笑","哇哈哈哈哈哈哈哈哈~~~~!!!!!", NULL}, */
    { "helpme", "求救", "大喊~~~救命啊~~~~", NULL },
    { "hoho", "呵呵笑", "呵呵呵笑個不停", NULL },
    { "happy", "高興", "高興得在地上打滾", NULL },
    /* {"happy", "高興", "YA! *^_^*", NULL}, */
    /* {"happy", "", "r-o-O-m....聽了真爽!", NULL}, */
    /* {"hurricane", "Ho---Ryu--Kan!!!", NULL}, */
    { "idle", "呆住了", "呆住了", NULL },
    { "jacky", "晃晃", "痞子般的晃來晃去", NULL },

#if 0
    /* Thor.0729: 不知其意 */
    { "lag", "網路慢", "lllllllaaaaaaaaaaaagggggggggggggg.................", NULL },
#endif

    { "luck", "幸運", "哇!福氣啦!", NULL },
    { "macarn", "一種舞", "開始跳起了MaCaReNa~~~~", NULL },
    { "miou", "喵喵", "喵喵口苗口苗~~~~~", NULL },
    { "mouth", "扁嘴", "扁嘴中!!", NULL },
    { "nani", "怎麼會", ":奈ㄝ啊捏??", NULL },
    { "nose", "流鼻血", "流鼻血", NULL },
    { "puke", "嘔吐", "嘔吐中", NULL },
    /* {"puke", "真噁心,我聽了都想吐", NULL}, */
    { "rest", "休息", "休息中,請勿打擾", NULL },
    { "reverse", "翻肚", "翻肚", NULL },
    { "room", "開房間", "r-o-O-m-r-O--Mmm-rRR........", NULL },
    { "shake", "搖頭", "搖了搖頭", NULL },
    { "sleep", "睡著", "趴在鍵盤上睡著了,口水流進鍵盤,造成當機!", NULL },
    /* {"sleep", "Zzzzzzzzzz,真無聊,都快睡著了", NULL}, */
    { "so", "就醬子", "就醬子!!", NULL },
    { "sorry", "道歉", "嗚啊!!我對不起大家,我對不起國家社會~~~~~~嗚啊~~~~~", NULL },
    { "story", "講古", "開始講古了", NULL },
    { "strut", "搖擺\走", "大搖大擺\地走", NULL },
    { "suicide", "自殺", "自殺", NULL },
    { "tea", "泡茶", "泡了壺好茶", NULL },
    { "think", "思考", "歪著頭想了一下", NULL },
    { "tongue", "吐舌", "吐了吐舌頭", NULL },
    { "wall", "撞牆", "跑去撞牆", NULL },
    { "wawa", "哇哇", "哇哇哇~~~~~!!!!!  ~~~>_<~~~", NULL },
    /* {"wawa","哇哇哇......>_<",NULL},  */
    { "www", "汪汪", "汪汪汪!!!", NULL },
    { "zzz", "打呼", "呼嚕~~~~ZZzZzzZZZzzZzzzZZ...", NULL },
    { NULL, NULL, NULL, NULL }
};


static int
condition_action(ChatUser *cu, char *cmd)
{
    ChatAction *cap;
    char *verb;

    for (cap = condition_data; (verb = cap->verb); cap++)
    {
    if (str_equal(cmd, verb))
    {
        sprintf(chatbuf, "%s %s",
            cu->chatid, cap->part1_msg);
        send_to_room(cu->room, chatbuf, cu->userno, MSG_MESSAGE);
        return 1;
    }
    }
    return 0;
}


/* --------------------------------------------- */
/* MUD-like social commands : help               */
/* --------------------------------------------- */


static char *dscrb[] =
{
    "【 Verb + Nick:   動詞 + 對方名字 】   例://kick piggy",
    "【 Verb + Message:動詞 + 要說的話 】   例://sing 天天天藍",
    "【 Verb:動詞 】    ↑↓:舊話重提", NULL
};
ChatAction *catbl[] =
{
    party_data, speak_data, condition_data, NULL
};

static void
chat_partyinfo(ChatUser *cu, char *msg)
{
    if (!common_client_command)
    return;                     /* only allow common client to retrieve it */

    sprintf(chatbuf, "3 動作  交談  狀態");
    send_to_user(cu, chatbuf, 0, MSG_PARTYINFO);
}

static void
chat_party(ChatUser *cu, char *msg)
{
    int kind, i;
    ChatAction *cap;

    if (!common_client_command)
    return;

    kind = atoi(nextword(&msg));
    if (kind < 0 || kind > 2)
    return;

    sprintf(chatbuf, "%d  %s", kind, kind == 2 ? "I" : "");

    /* Xshadow: 只有 condition 才是 immediate mode */
    send_to_user(cu, chatbuf, 0, MSG_PARTYLISTSTART);

    cap = catbl[kind];
    for (i = 0; cap[i].verb; i++)
    {
    sprintf(chatbuf, "%-10s %-20s", cap[i].verb, cap[i].chinese);
    /* for (j=0;j<1000000;j++); */
    send_to_user(cu, chatbuf, 0, MSG_PARTYLIST);
    }

    sprintf(chatbuf, "%d", kind);
    send_to_user(cu, chatbuf, 0, MSG_PARTYLISTEND);
}


#define SCREEN_WIDTH    80
#define MAX_VERB_LEN    8
#define VERB_NO         10

static void
view_action_verb(ChatUser *cu, char cmd)       /* Thor.0726: 新加動詞分類顯示 */
{
    register int i;
    register char *p, *q, *data, *expn;
    register ChatAction *cap;

    send_to_user(cu, "/c", 0, MSG_CLRSCR);

    data = chatbuf;

    if (cmd < '1' || cmd > '3')
    {                             /* Thor.0726: 寫得不好, 想辦法改進... */
    for (i = 0; (p = dscrb[i]); i++)
    {
        sprintf(data, "  [//]help %d          - MUD-like 社交動詞   第 %d 類", i + 1, i + 1);
        send_to_user(cu, data, 0, MSG_MESSAGE);
        send_to_user(cu, p, 0, MSG_MESSAGE);
        send_to_user(cu, " ", 0, MSG_MESSAGE);    /* Thor.0726: 換行, 需要 " "
                               * 嗎? */
    }
    }
    else
    {
    send_to_user(cu, dscrb[cmd-'1'], 0, MSG_MESSAGE);

    expn = chatbuf + 100;       /* Thor.0726: 應該不會overlap吧? */

    *data = '\0';
    *expn = '\0';

    cap = catbl[cmd-'1'];

    for (i = 0; (p = cap[i].verb); i++)
    {
        q = cap[i].chinese;

        strcat(data, p);
        strcat(expn, q);

        if (((i + 1) % VERB_NO) == 0 || cap[i+1].verb==NULL)
        {
        send_to_user(cu, data, 0, MSG_MESSAGE);
        send_to_user(cu, expn, 0, MSG_MESSAGE); /* Thor.0726: 顯示中文註解 */
        *data = '\0';
        *expn = '\0';
        }
        else
        {
        strncat(data, "        ", MAX_VERB_LEN - strlen(p));
        strncat(expn, "        ", MAX_VERB_LEN - strlen(q));
        }
    }
    }
    /* send_to_user(cu, " ",0); *//* Thor.0726: 換行, 需要 " " 嗎? */
}

/* ----------------------------------------------------- */
/* chat user service routines                            */
/* ----------------------------------------------------- */


static ChatCmd chatcmdlist[] =
{
    {"act", chat_act, 0},
    {"bye", chat_goodbye, 0},
    {"chatroom", chat_chatroom, 1}, /* Xshadow: for common client */
    {"clear", chat_clear, 0},
    {"cloak", chat_cloak, 2},
    {"date", chat_date, 0},
    {"flags", chat_setroom, 0},
    {"help", chat_help, 0},
    {"ignore", chat_ignore, 1},
    {"invite", chat_invite, 0},
    {"join", chat_join, 0},
    {"kick", chat_kick, 1},
    {"msg", chat_private, 0},
    {"nick", chat_nick, 0},
    {"operator", chat_makeop, 0},
    {"party", chat_party, 1},       /* Xshadow: party data for common client */
    {"partyinfo", chat_partyinfo, 1},       /* Xshadow: party info for common
                       * client */

    {"query", chat_query, 0},

    {"room", chat_list_rooms, 0},
    {"unignore", chat_unignore, 1},
    {"whoin", chat_list_by_room, 1},
    {"wall", chat_broadcast, 2},

    {"who", chat_map_chatids_thisroom, 0},
    {"list", chat_list_users, 0},
    {"topic", chat_topic, 1},
    {"version", chat_version, 1},

    {NULL, NULL, 0}
};

/* Thor: 0 不用 exact, 1 要 exactly equal, 2 秘密指令 */


static int
command_execute(ChatUser *cu)
{
    char *cmd, *msg;
    ChatCmd *cmdrec;
    int match;

    msg = cu->ibuf;

    /* Validation routine */

    if (cu->room == NULL)
    {
    /* MUST give special /! or /-! command if not in the room yet */
    if(strncmp(msg, "/!", 2)==0) {
        cu->clitype = 0;
        return login_user(cu, msg+2);
    }
    if(strncmp(msg, "/-!", 3)==0) {
        cu->clitype = 1;
        return login_user(cu, msg+3);
    }
    return -1;
    }

    if(msg[0] == '\0')
    return 0;

    /* If not a "/"-command, it goes to the room. */
    if (msg[0] != '/')
    {
    char buf[16];

    sprintf(buf, "%s:", cu->chatid);
    sprintf(chatbuf, "%-10s%s", buf, msg);
    if (!CLOAK(cu))           /* Thor: 聊天室隱身術 */
        send_to_room(cu->room, chatbuf, cu->userno, MSG_MESSAGE);
    return 0;
    }

    msg++;
    cmd = nextword(&msg);
    match = 0;

    if (*cmd == '/')
    {
    cmd++;
    if (!*cmd || str_equal(cmd, "help"))
    {
        /* Thor.0726: 動詞分類 */
        cmd = nextword(&msg);
        view_action_verb(cu, *cmd);
        match = 1;
    }
    else if (party_action(cu, cmd, msg) == 0)
        match = 1;
    else if (speak_action(cu, cmd, msg) == 0)
        match = 1;
    else
        match = condition_action(cu, cmd);
    }
    else
    {
    char *str;

    common_client_command = 0;
    if((*cmd == '-')) {
        if(cu->clitype) {
        cmd++;                  /* Xshadow: 指令從下一個字元才開始 */
        common_client_command = 1;
        }
    }
    for(cmdrec = chatcmdlist; (str = cmdrec->cmdstr); cmdrec++)
    {
        switch (cmdrec->exact)
        {
        case 1:                   /* exactly equal */
            match = str_equal(cmd, str);
            break;
        case 2:                   /* Thor: secret command */
            if (CHATSYSOP(cu))
            match = str_equal(cmd, str);
            break;
        default:                  /* not necessary equal */
            match = str_match(cmd, str) >= 0;
            break;
        }

        if (match)
        {
            cmdrec->cmdfunc(cu, msg);
            break;
        }
        }
    }

    if (!match)
    {
    sprintf(chatbuf, "◆ 指令錯誤:/%s", cmd);
    send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
    }
    return 0;
}


/* ----------------------------------------------------- */
/* serve chat_user's connection                          */
/* ----------------------------------------------------- */


static int
cuser_serve(ChatUser *cu)
{
    register int ch, len, isize;
    register char *str, *cmd;
    char buf[80];

    str = buf;
    len = recv(cu->sock, str, sizeof(buf) - 1, 0);
    if (len <= 0)
    {
    /* disconnected */

    exit_room(cu, EXIT_LOSTCONN, (char *) NULL);
    return -1;
    }

#if 0
    /* Xshadow: 將送達的資料忠實紀錄下來 */
    memcpy(logbuf, buf, sizeof(buf));
    for (ch = 0; ch < sizeof(buf); ch++)
    if (!logbuf[ch])
        logbuf[ch] = '$';

    logbuf[len + 1] = '\0';
    logit("recv: ", logbuf);
#endif

#if 0
    logit(cu->userid, str);
#endif

    isize = cu->isize;
    cmd = cu->ibuf;
    while (len--) {
    ch = *str++;

    if (ch == '\r' || ch == '\0')
        continue;
    if (ch == '\n')
    {
        cmd[isize]='\0';
        isize = 0;

        if (command_execute(cu) < 0)
        return -1;

        continue;
    }
    if (isize < sizeof(cu->ibuf)-1)
        cmd[isize++] = ch;
    }
    cu->isize = isize;
    return 0;
}


/* ----------------------------------------------------- */
/* chatroom server core routines                         */
/* ----------------------------------------------------- */

static int
start_daemon()
{
    int fd, value;
    char buf[80];
    struct sockaddr_in fsin;
    struct linger ld;
    struct rlimit limit;
    time_t dummy;
    struct tm *dummy_time;

    /*
     * 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(&dummy);
    dummy_time = gmtime(&dummy);
    dummy_time = localtime(&dummy);
    strftime(buf, 80, "%d/%b/%Y:%H:%M:%S", dummy_time);

    /* --------------------------------------------------- */
    /* speed-hacking DNS resolve                           */
    /* --------------------------------------------------- */

    gethostname(buf, sizeof(buf));

    /* Thor: 萬一server尚未接受connection, 就回去的話, client 第一次會進入失敗 */
    /* 所以移至 listen 後 */

    /* --------------------------------------------------- */
    /* detach daemon process                               */
    /* --------------------------------------------------- */

    close(0);
    close(1);
    close(2);

#ifndef SELFTEST
    if (fork())
    exit(0);
#endif

    chdir(BBSHOME);

    setsid();

#ifndef SELFTEST
    attach_SHM();
#endif
    /* --------------------------------------------------- */
    /* adjust the resource limit                           */
    /* --------------------------------------------------- */

    getrlimit(RLIMIT_NOFILE, &limit);
    limit.rlim_cur = limit.rlim_max;
    setrlimit(RLIMIT_NOFILE, &limit);

#if 0
    while (fd)
    {
    close(--fd);
    }

    value = getpid();
    setpgrp(0, value);

    if ((fd = open("/dev/tty", O_RDWR)) >= 0)
    {
    ioctl(fd, TIOCNOTTY, 0);    /* Thor : 為什麼還要用  tty? */
    close(fd);
    }
#endif

    fd = open(CHAT_PIDFILE, O_WRONLY | O_CREAT | O_TRUNC, 0600);
    if (fd >= 0)
    {
    /* sprintf(buf, "%5d\n", value); */
    sprintf(buf, "%5d\n", (int)getpid());
    write(fd, buf, 6);
    close(fd);
    }

#if 0
    /* ------------------------------ */
    /* trap signals                   */
    /* ------------------------------ */

    for (fd = 1; fd < NSIG; fd++)
    {

    Signal(fd, SIG_IGN);
    }
#endif

    fd = socket(PF_INET, SOCK_STREAM, 0);

#if 0
    value = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, value | O_NDELAY);
#endif

    value = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &value, sizeof(value));

#if 0
    setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *) &value, sizeof(value));

    value = 81920;
    setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *) &value, sizeof(value));
#endif

    ld.l_onoff = ld.l_linger = 0;
    setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &ld, sizeof(ld));

    memset((char *) &fsin, 0, sizeof(fsin));
    fsin.sin_family = AF_INET;
    fsin.sin_port = htons(NEW_CHATPORT);
    fsin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

    if (bind(fd, (struct sockaddr *) & fsin, sizeof(fsin)) < 0)
    exit(1);

    listen(fd, SOCK_QLEN);

    return fd;
}


static void
free_resource(int fd)
{
    static int loop = 0;
    register ChatUser *user;
    register int sock, num;

    /* 重新計算 maxfd */
    num = 0;
    for (user = mainuser; user; user = user->unext)
    {
    num++;
    sock = user->sock;
    if (fd < sock)
        fd = sock;
    }

    sprintf(chatbuf, "%d, %d user (maxfds %d -> %d)", ++loop, num, maxfds, fd+1);
    logit("LOOP", chatbuf);

    maxfds = fd + 1;
}


#ifdef  SERVER_USAGE
static void
server_usage()
{
    struct rusage ru;
    char buf[2048];

    if (getrusage(RUSAGE_SELF, &ru))
    return;

    sprintf(buf, "\n[Server Usage]\n\n"
        "user time: %.6f\n"
        "system time: %.6f\n"
        "maximum resident set size: %lu P\n"
        "integral resident set size: %lu\n"
        "page faults not requiring physical I/O: %ld\n"
        "page faults requiring physical I/O: %ld\n"
        "swaps: %ld\n"
        "block input operations: %ld\n"
        "block output operations: %ld\n"
        "messages sent: %ld\n"
        "messages received: %ld\n"
        "signals received: %ld\n"
        "voluntary context switches: %ld\n"
        "involuntary context switches: %ld\n"
        "\n",

        (double) ru.ru_utime.tv_sec + (double) ru.ru_utime.tv_usec / 1000000.0,
        (double) ru.ru_stime.tv_sec + (double) ru.ru_stime.tv_usec / 1000000.0,
        ru.ru_maxrss,
        ru.ru_idrss,
        ru.ru_minflt,
        ru.ru_majflt,
        ru.ru_nswap,
        ru.ru_inblock,
        ru.ru_oublock,
        ru.ru_msgsnd,
        ru.ru_msgrcv,
        ru.ru_nsignals,
        ru.ru_nvcsw,
        ru.ru_nivcsw);

    write(flog, buf, strlen(buf));
}
#endif


static void
abort_server()
{
    log_close();
    exit(1);
}


static void
reaper()
{
    int state;

    while (waitpid(-1, &state, WNOHANG | WUNTRACED) > 0)
    {
    }
}

#ifdef SELFTESTER
#define MAXTESTUSER 20

int selftest_connect(void)
{
    struct sockaddr_in sin;
    struct hostent *h;
    int cfd;

    if (!(h = gethostbyname("localhost"))) {
    perror("gethostbyname");
    return -1;
    }
    memset(&sin, 0, sizeof sin);
#ifdef __FreeBSD__
    sin.sin_len = sizeof(sin);
#endif
    sin.sin_family = PF_INET;
    memcpy(&sin.sin_addr, h->h_addr, h->h_length);
    sin.sin_port = htons(NEW_CHATPORT);
    cfd = socket(sin.sin_family, SOCK_STREAM, 0);
    if (connect(cfd, (struct sockaddr *) & sin, sizeof sin) != 0) {
    perror("connect");
    return -1;
    }
    return cfd;
}

static int
selftest_send(int fd, char *buf)
{
    int             len;
    char            genbuf[200];

    snprintf(genbuf, sizeof(genbuf), "%s\n", buf);
    len = strlen(genbuf);
    return (send(fd, genbuf, len, 0) == len);
}
void selftest_testing(void)
{
    int cfd;
    char userid[IDLEN+1];
    char inbuf[1024], buf[1024];

    cfd=selftest_connect();
    if(cfd<0) exit(1);
    while(1) {
    snprintf(userid, sizeof(userid), "%ld", random()%(MAXTESTUSER*2));
    sprintf(buf, "/%s! %s %s %s", random()%4==0?"-":"",userid, userid, "passwd");
    selftest_send(cfd, buf);
    if (recv(cfd, inbuf, 3, 0) != 3) {
        close(cfd);
        return;
    }
    if (!strcmp(inbuf, CHAT_LOGIN_OK))
        break;
    }

    if(random()%4!=0) {
    sprintf(buf, "/j %d", random()%5);
    selftest_send(cfd, buf);
    }

    while(1) {
    int i;
    int r;
    int inlen;
    fd_set rset,xset;
    struct timeval zerotv;

    FD_ZERO(&rset);
    FD_SET(cfd, &rset);
    FD_ZERO(&xset);
    FD_SET(cfd, &xset);
    zerotv.tv_sec=0;
    zerotv.tv_usec=0;
    select(cfd+1, &rset, NULL, &xset, &zerotv);
    if(FD_ISSET(cfd, &rset)) {
        inlen=read(cfd, inbuf, sizeof(inbuf));
        if(inlen<0) break;
    }
    if(FD_ISSET(cfd, &xset)) {
        inlen=read(cfd, inbuf, sizeof(inbuf));
        if(inlen<0) break;
    }


    if(random()%10==0) {
        switch(random()%4) {
        case 0:
            r=random()%(sizeof(party_data)/sizeof(party_data[0])-1);
            sprintf(buf, "//%s",party_data[r].verb);
            break;
        case 1:
            r=random()%(sizeof(speak_data)/sizeof(speak_data[0])-1);
            sprintf(buf, "//%s",speak_data[r].verb);
            break;
        case 2:
            r=random()%(sizeof(condition_data)/sizeof(condition_data[0])-1);
            sprintf(buf, "//%s",condition_data[r].verb);
            break;
        case 3:
            sprintf(buf, "blah");
            break;
        }
    } else {
        r=random()%(sizeof(chatcmdlist)/sizeof(chatcmdlist[0])-1);
        sprintf(buf, "/%s",chatcmdlist[r].cmdstr);
        if(strncmp("/flag",buf,5)==0) {
            if(random()%2)
            strcat(buf," +");
            else
            strcat(buf," -");
            strcat(buf,random()%2?"l":"L");
            strcat(buf,random()%2?"h":"H");
            strcat(buf,random()%2?"s":"S");
            strcat(buf,random()%2?"t":"T");
        } else if(strncmp("/bye",buf,4)==0) {
            switch(random()%10) {
            case 0: strcpy(buf,"//"); break;
            case 1: strcpy(buf,"//1"); break;
            case 2: strcpy(buf,"//2"); break;
            case 3: strcpy(buf,"//3"); break;
            case 4: strcpy(buf,"/bye"); break;
            case 5: strcpy(buf,"/help op"); break;
            case 6: close(cfd); return; break;
            case 7: strcpy(buf,"/help"); break;
            case 8: strcpy(buf,"/help"); break;
            case 9: strcpy(buf,"/help"); break;
            }
        }
    }
    for(i=random()%3; i>0; i--) {
        char tmp[1024];
        sprintf(tmp," %ld", random()%(MAXTESTUSER*2));
        strcat(buf, tmp);
    }

    selftest_send(cfd, buf);
    }
    close(cfd);
}

void selftest_test(void)
{
    while(1) {
    fprintf(stderr,".");
    selftest_testing();
    usleep(1000);
    }
}

void selftest(void)
{
    int i;
    pid_t pid;

    pid=fork();
    if(pid<0) exit(1);
    if(pid) return;
    sleep(1);

    for(i=0; i<MAXTESTUSER; i++) {
    if(fork()==0)
        selftest_test();
    sleep(1);
    random();
    }

    exit(0);
}
#endif

int
main(int argc, char *argv[])
{
    register int msock, csock, nfds;
    register ChatUser *cu, *cunext;
    register fd_set *rptr, *xptr;
    fd_set rset, xset;
    struct timeval tv;
    time4_t uptime, tmaintain;

#ifdef SELFTESTER
    if(argc>1) {
    Signal(SIGPIPE, SIG_IGN);
      selftest();
      return 0;
    }
#endif
    msock = start_daemon();

    setgid(BBSGID);
    setuid(BBSUID);

    log_init();

//    Signal(SIGBUS, SIG_IGN);
//    Signal(SIGSEGV, SIG_IGN);
    Signal(SIGPIPE, SIG_IGN);
    Signal(SIGURG, SIG_IGN);

    Signal(SIGCHLD, reaper);
    Signal(SIGTERM, abort_server);

#ifdef  SERVER_USAGE
    Signal(SIGPROF, server_usage);
#endif

    /* ----------------------------- */
    /* init variable : rooms & users */
    /* ----------------------------- */

    mainuser = NULL;
    memset(&mainroom, 0, sizeof(mainroom));
    strcpy(mainroom.name, MAIN_NAME);
    strcpy(mainroom.topic, MAIN_TOPIC);

    /* ----------------------------------- */
    /* main loop                           */
    /* ----------------------------------- */

    FD_ZERO(&mainfds);
    FD_SET(msock, &mainfds);
    rptr = &rset;
    xptr = &xset;
    maxfds = msock + 1;
    tmaintain = time(0) + CHAT_INTERVAL;

    for (;;)
    {
    uptime = time(0);
    if (tmaintain < uptime)
    {
        tmaintain = uptime + CHAT_INTERVAL;

        /* client/server 版本利用 ping-pong 方法判斷 user 是不是還活著 */
        /* 如果 client 已經結束了,就釋放其 resource */

        free_resource(msock);
#ifdef SELFTEST
        break;
#endif
    }

    memcpy(rptr, &mainfds, sizeof(fd_set));
    memcpy(xptr, &mainfds, sizeof(fd_set));

    /* Thor: for future reservation bug */

    tv.tv_sec = CHAT_INTERVAL;
    tv.tv_usec = 0;

    nfds = select(maxfds, rptr, NULL, xptr, &tv);

    /* free idle user & chatroom's resource when no traffic */
    if (nfds == 0)
        continue;

    /* check error condition */
    if (nfds < 0)
        continue;

    /* accept new connection */
    if (FD_ISSET(msock, rptr)) {
        csock = accept(msock, NULL, NULL);

        if(csock < 0) {
        if(errno != EINTR) {
            // TODO
        }
        } else {
        cu = (ChatUser *) malloc(sizeof(ChatUser));
        if(cu == NULL) {
            close(csock);
            logit("accept", "malloc fail");
        } else {
            memset(cu, 0, sizeof(ChatUser));
            cu->sock = csock;
            cu->unext = mainuser;
            mainuser = cu;

            totaluser++;
            FD_SET(csock, &mainfds);
            if (csock >= maxfds)
            maxfds = csock + 1;

#ifdef  DEBUG
            logit("accept", "OK");
#endif
        }
        }
    }

    for (cu = mainuser; cu && nfds>0; cu = cunext) {
        /* logout_user() 會 free(cu); 先把 cu->next 記下來 */
        /* FIXME 若剛好 cu 在 main room /kick cu->next, 則 cu->next 會被 free 掉 */
        cunext = cu->unext;
        csock = cu->sock;
        if (FD_ISSET(csock, xptr)) {
        logout_user(cu);
        nfds--;
        } else if (FD_ISSET(csock, rptr)) {
        if (cuser_serve(cu) < 0)
            logout_user(cu);
        nfds--;
        }
    }

    /* end of main loop */
    }
    return 0;
}