/* $Id$ */ #include "bbs.h" #include "xchatd.h" #define SERVER_USAGE #define WATCH_DOG #undef MONITOR /* 監督 chatroom 活動以解決糾紛 */ #undef DEBUG /* 程式除錯之用 */ #ifdef DEBUG #define MONITOR #endif static int gline; #ifdef WATCH_DOG #define MYDOG gline = __LINE__ #else #define MYDOG /* NOOP */ #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 */ int talksock; /* talk socket */ ChatRoom *room; UserList *ignore; int userno; int uflag; int clitype; /* Xshadow: client type. 1 for common client, * 0 for bbs only client */ time_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 struct timeval zerotv; /* timeval for selecting */ 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, userid) ACCT *acct; char *userid; { int id; if((id=searchuser(userid))<0) { return -1; } else { return get_record(FN_PASSWD, acct, sizeof(ACCT), id); } } /* * str_ncpy() - similar to strncpy(3) but terminates string always with '\0' * if n != 0, and doesn't do padding */ void str_ncpy(dst, src, n) char *dst; char *src; int n; { char *end; end = dst + n; do { n = (dst == end) ? 0 : *src++; *dst++ = n; } while (n); } /* ----------------------------------------------------- */ /* usr_fpath for check acct */ /* ----------------------------------------------------- */ char *str_home_file = "home/%c/%s/%s"; void usr_fpath(buf, userid, fname) char *buf, *userid, *fname; { sprintf(buf, str_home_file, userid[0], userid, fname); } /* ----------------------------------------------------- */ /* chkpasswd for check passwd */ /* ----------------------------------------------------- */ char *crypt(); static char pwbuf[PASSLEN]; int chkpasswd(passwd, test) char *passwd, *test; { char *pw; str_ncpy(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(key, msg) char *key; char *msg; { time_t now; struct tm *p; char buf[512]; time(&now); p = localtime(&now); sprintf(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) { sprintf(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 { sprintf(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 */ MYDOG; if(ch == '/' || ch == '*' || ch == ':') return 0; if(++len > 8) return 0; } return len; } /* Case Independent strcmp : 1 ==> euqal */ static int str_equal(s1, s2) register unsigned char *s1, *s2; /* Thor: 加上 unsigned, * 避免中文的問題 */ { register int c1, c2; for (;;) { /* Thor: check for endless */ MYDOG; c1 = *s1; if (c1 >= 'A' && c1 <= 'Z') c1 |= 32; c2 = *s2; if (c2 >= 'A' && c2 <= 'Z') c2 |= 32; if (c1 != c2) return 0; if (!c1) return 1; s1++; s2++; } } /* ----------------------------------------------------- */ /* 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(s1, s2) register unsigned char *s1, *s2; /* Thor: 加上 unsigned, * 避免中文的問題 */ { register int c1, c2; for (;;) { /* Thor: check for endless */ MYDOG; 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(userid) char *userid; { register ChatUser *cu; for (cu = mainuser; cu; cu = cu->unext) { MYDOG; if (str_equal(userid, cu->userid)) break; } return cu; } static ChatUser * cuser_by_chatid(chatid) char *chatid; { register ChatUser *cu; for (cu = mainuser; cu; cu = cu->unext) { MYDOG; if (str_equal(chatid, cu->chatid)) break; } return cu; } static ChatUser * fuzzy_cuser_by_chatid(chatid) char *chatid; { register ChatUser *cu, *xuser; int mode; xuser = NULL; for (cu = mainuser; cu; cu = cu->unext) { MYDOG; mode = str_match(chatid, cu->chatid); if (mode == 0) return cu; if (mode > 0) { if (xuser == NULL) xuser = cu; else return FUZZY_USER; /* 符合者大於 2 人 */ } } return xuser; } static ChatRoom *croom_by_roomid(char *roomid) { register ChatRoom *room; room = &mainroom; do { MYDOG; if(str_equal(roomid, room->name)) break; } while((room = room->next)); return room; } /* ----------------------------------------------------- */ /* UserList routines */ /* ----------------------------------------------------- */ static void list_free(list) UserList *list; { UserList *tmp; while (list) { MYDOG; tmp = list->next; free(list); MYDOG; list = tmp; } } static void list_add(list, user) UserList **list; ChatUser *user; { UserList *node; MYDOG; if((node = (UserList *) malloc(sizeof(UserList)))) { /* Thor: 防止空間不夠 */ strcpy(node->userid, user->userid); node->userno = user->userno; node->next = *list; *list = node; } MYDOG; } static int list_delete(list, userid) UserList **list; char *userid; { UserList *node; while((node = *list)) { MYDOG; if (str_equal(node->userid, userid)) { *list = node->next; MYDOG; free(node); MYDOG; return 1; } list = &node->next; /* Thor: list要跟著前進 */ } return 0; } static int list_belong(list, userno) UserList *list; int userno; { while (list) { MYDOG; if (userno == list->userno) return 1; list = list->next; } return 0; } /* ------------------------------------------------------ */ /* non-blocking socket routines : send message to users */ /* ------------------------------------------------------ */ static void Xdo_send(nfds, wset, msg, number) int nfds; fd_set *wset; char *msg; int number; { int sr; /* Thor: for future reservation bug */ zerotv.tv_sec = 0; zerotv.tv_usec = 16384; /* Ptt: 改成16384 避免不按時for loop吃cpu time 16384 約每秒64次 */ MYDOG; sr = select(nfds + 1, NULL, wset, NULL, &zerotv); MYDOG; if (sr > 0) { register int len; len = strlen(msg) + 1; while (nfds >= 0) { MYDOG; if (FD_ISSET(nfds, wset)) { MYDOG; send(nfds, msg, len, 0);/* Thor: 如果buffer滿了, 仍會 block */ MYDOG; if (--sr <= 0) return; } nfds--; } } } static void send_to_room(room, msg, userno, number) ChatRoom *room; char *msg; int userno; int number; { ChatUser *cu; fd_set wset, *wptr; int sock, max; static 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) { MYDOG; 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)) sprintf(sendbuf, "%3d %s", number, msg); else sprintf(sendbuf, "%3d", number); Xdo_send(max, wptr, sendbuf); } else Xdo_send(max, wptr, msg); } } static void send_to_user(user, msg, userno, number) 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; static char sendbuf[256]; sock = user->sock; FD_ZERO(wptr = &wset); FD_SET(sock, wptr); if (user->clitype) { if (strlen(msg)) sprintf(sendbuf, "%3d %s", number, msg); else sprintf(sendbuf, "%3d", number); Xdo_send(sock, wptr, sendbuf); } else Xdo_send(sock, wptr, msg); } } #if 0 static void send_to_sock(sock, msg) /* Thor: unused */ int sock; char *msg; { fd_set wset, *wptr; FD_ZERO(wptr = &wset); FD_SET(sock, wptr); Xdo_send(sock, wptr, msg); } #endif /* ----------------------------------------------------- */ static void room_changed(room) ChatRoom *room; { if (!room) return; sprintf(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(cu) ChatUser *cu; { if (!cu) return; sprintf(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(user, mode, msg) ChatUser *user; int mode; char *msg; { ChatRoom *room; if((room = user->room)) { user->room = NULL; user->uflag &= ~PERM_ROOMOP; if (--room->occupants > 0) { char *chatid; chatid = user->chatid; switch (mode) { case EXIT_LOGOUT: sprintf(chatbuf, "◆ %s 離開了 ...", chatid); if (msg && *msg) { strcat(chatbuf, ": "); msg[79] = 0; /* Thor:防止太長 */ 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); sprintf(chatbuf, "- %s", user->userid); send_to_room(room, chatbuf, 0, MSG_USERNOTIFY); room_changed(room); return; } else if (room != &mainroom) { /* Thor: 人數為0時,不是mainroom才free */ register ChatRoom *next; #ifdef DEBUG debug_room(); #endif 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); MYDOG; free(room); MYDOG; #ifdef DEBUG debug_room(); #endif } } } /* ----------------------------------------------------- */ /* chat commands */ /* ----------------------------------------------------- */ /* ----------------------------------------------------- */ /* (.ACCT) 使用者帳號 (account) subroutines */ /* ----------------------------------------------------- */ static char datemsg[32]; char * Ctime(clock) time_t *clock; { struct tm *t = localtime(clock); static char week[] = "日一二三四五六"; sprintf(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(cu, msg) ChatUser *cu; char *msg; { char str[256]; int i; ACCT xuser; FILE *fp; if (acct_load(&xuser, msg) >= 0) { sprintf(chatbuf, "%s(%s) 共上站 %d 次,文章 %d 篇", xuser.userid, xuser.username, xuser.numlogins, xuser.numposts); send_to_user(cu, chatbuf, 0, MSG_MESSAGE); sprintf(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"); i = 0; while (fp && fgets(str, 256, fp)) { if (!strlen(str)) continue; str[strlen(str) - 1] = 0; send_to_user(cu, str, 0, MSG_MESSAGE); if (++i >= MAX_QUERYLINES) break; } fclose(fp); } else { sprintf(chatbuf, msg_no_such_id, msg); send_to_user(cu, chatbuf, 0, MSG_MESSAGE); } } static void chat_clear(cu, msg) 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(cu, msg) ChatUser *cu; char *msg; { time_t thetime; time(&thetime); sprintf(chatbuf, "◆ 標準時間: %s", Ctime(&thetime)); send_to_user(cu, chatbuf, 0, MSG_MESSAGE); } static void chat_topic(cu, msg) 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; topic = room->topic; /* Thor: room 有可能 NULL嗎?? */ strncpy(topic, msg, 47); topic[47] = '\0'; 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(cu, msg) 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(cu, msg) 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; sprintf(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 { sprintf(chatbuf, "/n%s", chatid); send_to_user(cu, chatbuf, 0, 0); } } static void chat_list_rooms(cuser, msg) ChatUser *cuser; char *msg; { ChatRoom *cr, *room; 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); room = cuser->room; cr = &mainroom; do { MYDOG; if (!SECRET(cr) || CHATSYSOP(cuser) || (cr == room && ROOMOP(cuser))) { if (common_client_command) { sprintf(chatbuf, "%s %d %d %s", cr->name, cr->occupants, cr->rflag, cr->topic); send_to_user(cuser, chatbuf, 0, MSG_ROOMLIST); } else { sprintf(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); } } } while((cr = cr->next)); if (common_client_command) send_to_user(cuser, "", 0, MSG_ROOMLISTEND); } static void chat_do_user_list(cu, msg, theroom) 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) { MYDOG; 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: 還沒進入任何房間的就不列出 */ sprintf(chatbuf, "%s %s %s %s", user->chatid, user->userid, room->name, user->lasthost); if (ROOMOP(user)) strcat(chatbuf, " Op"); } else { sprintf(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(cu, msg) 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) { sprintf(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(cu, msg) ChatUser *cu; char *msg; { chat_do_user_list(cu, msg, ROOM_ALL); } static void chat_chatroom(cu, msg) ChatUser *cu; char *msg; { if (common_client_command) send_to_user(cu, "批踢踢茶藝館 4 21", 0, MSG_CHATROOM); } static void chat_map_chatids(cu, whichroom) ChatUser *cu; /* Thor: 還沒有作不同間的 */ ChatRoom *whichroom; { int c; ChatRoom *myroom, *room; ChatUser *user; /* myroom = cu->room; */ myroom = whichroom; send_to_user(cu, " 聊天代號 使用者代號 │ 聊天代號 使用者代號 │ 聊天代號 使用者代號 ", 0, MSG_MESSAGE); c = 0; for (user = mainuser; user; user = user->unext) { MYDOG; room = user->room; MYDOG; if (whichroom != ROOM_ALL && whichroom != room) continue; MYDOG; if (myroom != room) { if (RESTRICTED(cu) || /* Thor: 要先check room 是不是空的 */ (room && SECRET(room) && !CHATSYSOP(cu))) continue; } MYDOG; if (CLOAK(user)) /* Thor:隱身術 */ continue; sprintf(chatbuf + (c * 24), " %-8s%c%-12s%s", user->chatid, ROOMOP(user) ? '*' : ' ', user->userid, (c < 2 ? "│" : " ")); MYDOG; if (++c == 3) { send_to_user(cu, chatbuf, 0, MSG_MESSAGE); c = 0; } MYDOG; } if (c > 0) send_to_user(cu, chatbuf, 0, MSG_MESSAGE); } static void chat_map_chatids_thisroom(cu, msg) ChatUser *cu; char *msg; { chat_map_chatids(cu, cu->room); } static void chat_setroom(cu, msg) 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_OPENTOPIC; 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 ", "做一個動作", "[/b]ye [msg]", "道別", "[/c]lear [/d]ate", "清除螢幕 目前時間", /* "[/d]ate", "目前時間", *//* Thor: 指令太多 */ #if 0 "[/f]ire ", "發送熱訊", /* Thor.0727: 和 flag 衝key */ #endif "[/i]gnore [user]", "忽略使用者", "[/j]oin ", "建立或加入談天室", "[/l]ist [start [stop]]", "列出談天室使用者", "[/m]sg ", "跟 說悄悄話", "[/n]ick ", "將談天代號換成 ", "[/p]ager", "切換呼叫器", "[/q]uery ", "查詢網友", "[/r]oom", "列出一般談天室", "[/t]ape", "開關錄音機", "[/u]nignore ", "取消忽略", #if 0 "[/u]sers", "列出站上使用者", #endif "[/w]ho", "列出本談天室使用者", "[/w]hoin ", "列出談天室 的使用者", NULL }; static char *room_msg[] = { "[/f]lag [+-][lsth]", "設定鎖定、秘密、開放話題、舉手發言", "[/i]nvite ", "邀請 加入談天室", "[/kick] ", "將 踢出談天室", "[/o]p ", "將 Op 的權力轉移給 ", "[/topic] ", "換個話題", "[/w]all", "廣播 (站長專用)", NULL }; static void chat_help(cu, msg) 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++)) { sprintf(chatbuf, " %-20s- %s", str, *table++); send_to_user(cu, chatbuf, 0, MSG_MESSAGE); } } static void chat_private(cu, msg) 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); msg[79] = 0; /* Thor:防止太長 */ 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); msg[79] = 0; strncat(chatbuf, msg, 80); send_to_user(xuser, chatbuf, userno, MSG_PRIVMSG); } if (cu->clitype) { sprintf(chatbuf, "%s %s ", xuser->userid, xuser->chatid); msg[79] = 0; 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(cu, msg) 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(cuser, room) ChatUser *cuser; ChatRoom *room; { char *rname; /* Xshadow: 不必送給自己, 反正換房間就會重新 build user list */ sprintf(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(cuser, rname, msg) 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 MYDOG; room = (ChatRoom *) malloc(sizeof(ChatRoom)); MYDOG; if (room == NULL) { send_to_user(cuser, "※ 無法再新闢包廂了", 0, MSG_MESSAGE); return 0; } memset(room, 0, sizeof(ChatRoom)); memcpy(room->name, rname, IDLEN - 1); strcpy(room->topic, "這是一個新天地"); sprintf(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(cuser) ChatUser *cuser; { int sock; ChatUser *xuser, *prev; #ifdef DEBUG logit("before", "logout"); debug_user(); #endif sock = cuser->sock; shutdown(sock, 2); close(sock); MYDOG; FD_CLR(sock, &mainfds); #if 0 /* Thor: 也許不差這一個 */ if (sock >= maxfds) maxfds = sock - 1; #endif list_free(cuser->ignore); #ifdef DEBUG debug_user(); #endif 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); } MYDOG; #ifdef DEBUG sprintf(chatbuf, "%p", cuser); logit("free cuser", chatbuf); #endif free(cuser); #ifdef DEBUG logit("after", "logout"); debug_user(); #endif #if 0 next = cuser->next; prev = cuser->prev; prev->next = next; if (next) next->prev = prev; if (cuser) free(cuser); MYDOG; #endif totaluser--; } static void print_user_counts(cuser) ChatUser *cuser; { ChatRoom *room; int num, userc, suserc, roomc, number; userc = suserc = roomc = 0; room = &mainroom; do { MYDOG; num = room->occupants; if (SECRET(room)) { suserc += num; if (CHATSYSOP(cuser)) roomc++; } else { userc += num; roomc++; } } while((room = room->next)); 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(cu, msg) ChatUser *cu; char *msg; { int utent; char *level; char *userid; char *chatid; struct sockaddr_in from; int fromlen; struct hostent *hp; ACCT acct; char buf[20]; /* * 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 */ level = msg; /* Thor.0813: 跳過一空格即可, 因為反正如果chatid有空格, 密碼也不對 */ /* 就算密碼對, 也不會怎麼樣:p */ /* 可是如果密碼第一個字是空格, 那跳太多空格會進不來... */ if (*level == ' ') level++; /* 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(level, acct.passwd, PASSLEN) && !chkpasswd(acct.passwd, level)) { #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; } else { /* Thor.0729: if ok, read level. */ sprintf(buf, "%d", acct.userlevel); level = buf; /* Thor.0819: read userno for client/server bbs */ utent = searchuser(acct.userid); } /* Thor.0819: for client/server bbs */ /* for (xuser = mainuser; xuser; xuser = xuser->unext) { MYDOG; 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; } #ifdef DEBUG debug_user(); #endif 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 = atoi(level) & ~(PERM_ROOMOP | PERM_CLOAK | PERM_HANDUP | PERM_SAY); /* Thor: 進來先清空ROOMOP(同PERM_CHAT), CLOAK */ strcpy(cu->userid, userid); memcpy(cu->chatid, chatid, 8); cu->chatid[8] = '\0'; /* 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(cu, msg) 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(cu, msg) 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); #if 0 sprintf(chatbuf, "◆ 談天室現在沒有 [%s] 這號人物", ignoree); #endif } 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(cu, msg) 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(cu, msg) 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(cu, msg) 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(cu, msg) 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, MSG_MESSAGE); } static void chat_invite(cu, msg) 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; /* Thor: 是否要 check room 是否 NULL ? */ 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(cu, msg) 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(cu, msg) 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", "阿魯巴", "把", "架上柱子阿魯巴!!"}, {"aodre", "景仰", "對", "的景仰有如滔滔江水,連綿不絕……"}, {"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 chicken_action(cu, cmd, party) ChatUser *cu; char *cmd; char *party; { return 0; } static int party_action(cu, cmd, party) ChatUser *cu; char *cmd; char *party; { ChatAction *cap; char *verb; for (cap = party_data; (verb = cap->verb); cap++) { MYDOG; if (str_equal(cmd, verb)) { 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; /* Thor: cu->room 是否為 NULL? */ } } 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(cu, cmd, msg) ChatUser *cu; char *cmd; char *msg; { ChatAction *cap; char *verb; for (cap = speak_data; (verb = cap->verb); cap++) { MYDOG; if (str_equal(cmd, verb)) { sprintf(chatbuf, "%s %s: %s", cu->chatid, cap->part1_msg, msg); send_to_room(cu->room, chatbuf, cu->userno, MSG_MESSAGE); return 0; /* Thor: cu->room 是否為 NULL? */ } } return 1; } /* -------------------------------------------- */ /* MUD-like social commands : condition */ /* -------------------------------------------- */ static ChatAction condition_data[] = { { "applaud", "拍手", "啪啪啪啪啪啪啪....", NULL }, { "ayo", "唉呦喂", "唉呦喂~~~", NULL }, { "back", "坐回來", "回來坐正繼續奮戰", NULL }, { "blood", "在血中", "倒在血泊之中", NULL }, { "blush", "臉紅", "臉都紅了", NULL }, { "broke", "心碎", "的心破碎成一片一片的", NULL }, /* Thor.0731:應觀眾要求 */ /* {"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-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(cu, cmd) ChatUser *cu; char *cmd; { ChatAction *cap; char *verb; for (cap = condition_data; (verb = cap->verb); cap++) { MYDOG; 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; /* Thor: cu->room 是否為 NULL? */ } } 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(cu, msg) 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(cu, msg) 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(cu, cmd) /* Thor.0726: 新加動詞分類顯示 */ register ChatUser *cu; char cmd; { 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); MYDOG; 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 { i = cmd - '1'; send_to_user(cu, dscrb[i], 0, MSG_MESSAGE); expn = chatbuf + 100; /* Thor.0726: 應該不會overlap吧? */ *data = '\0'; *expn = '\0'; cap = catbl[i]; for (i = 0; (p = cap[i].verb); i++) { MYDOG; q = cap[i].chinese; strcat(data, p); strcat(expn, q); if (((i + 1) % VERB_NO) == 0) { 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)); } } if (i % VERB_NO) { send_to_user(cu, data, 0, MSG_MESSAGE); send_to_user(cu, expn, 0, MSG_MESSAGE); /* Thor.0726: 顯示中文註解 */ } } /* send_to_user(cu, " ",0); *//* Thor.0726: 換行, 需要 " " 嗎? */ } void view_chicken_help(cu) /* Ptt: 鬥雞程式 的help */ register ChatUser *cu; { } /* ----------------------------------------------------- */ /* 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(cu) ChatUser *cu; { char *cmd, *msg; ChatCmd *cmdrec; int match, ch; msg = cu->ibuf; match = *msg; /* Validation routine */ if (cu->room == NULL) { /* MUST give special /! or /-! command if not in the room yet */ if (match == '/' && ((ch = msg[1]) == '!' || (ch == '-' && msg[2] == '!'))) { cu->clitype = (ch == '-') ? 1 : 0; return (login_user(cu, msg + 2 + cu->clitype)); } else return -1; } /* If not a /-command, it goes to the room. */ if (match != '/') { if (match) { 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); /* Thor: 要 check cu->room NULL嗎? */ } 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 if(*cmd == '.') { cmd++; if (!*cmd || str_equal(cmd, "help")) { view_chicken_help(cu); match = 1; } else match = chicken_action(cu, cmd, msg); } 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++) { MYDOG; 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(cu) ChatUser *cu; { register int ch, len, isize; register char *str, *cmd; static 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 + isize; while (len--) { MYDOG; ch = *str++; if (ch == '\r' || !ch) continue; if (ch == '\n') { *cmd = '\0'; isize = 0; cmd = cu->ibuf; if (command_execute(cu) < 0) return -1; continue; } if (isize < 79) { *cmd++ = ch; isize++; } } 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); if (fork()) exit(0); chdir(BBSHOME); setsid(); attach_SHM(); /* --------------------------------------------------- */ /* 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", 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_ANY); if (bind(fd, (struct sockaddr *) & fsin, sizeof(fsin)) < 0) exit(1); listen(fd, SOCK_QLEN); return fd; } static void free_resource(fd) int fd; { static int loop = 0; register ChatUser *user; register int sock, num; num = 0; for (user = mainuser; user; user = user->unext) { MYDOG; num++; sock = user->sock; if (fd < sock) fd = sock; } sprintf(chatbuf, "%d, %d user (%d -> %d)", ++loop, num, maxfds, fd); 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" "gline: %d\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, gline); 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) { MYDOG; } } int main() { register int msock, csock, nfds; register ChatUser *cu; register fd_set *rptr, *xptr; fd_set rset, xset; struct timeval tv; time_t uptime, tmaintain; 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 */ /* ----------------------------------- */ #if 0 /* Thor: 在listen 後才回client, 每次進來就會成功 */ if (fork()) exit(0); #endif 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); } MYDOG; 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; MYDOG; nfds = select(maxfds, rptr, NULL, xptr, &tv); MYDOG; /* free idle user & chatroom's resource when no traffic */ if (nfds == 0) { continue; } /* check error condition */ if (nfds < 0) { csock = errno; continue; } /* accept new connection */ if (FD_ISSET(msock, rptr)) { for (;;) { MYDOG; /* Thor: check for endless */ csock = accept(msock, NULL, NULL); if (csock >= 0) { MYDOG; if((cu = (ChatUser *) malloc(sizeof(ChatUser)))) { memset(cu, 0, sizeof(ChatUser)); cu->sock = csock; cu->unext = mainuser; mainuser = cu; #if 0 if (mainuser.next) mainuser.next->prev = cu; cu->next = mainuser.next; mainuser.next = cu; cu->prev = &mainuser; #endif totaluser++; FD_SET(csock, &mainfds); if (csock >= maxfds) maxfds = csock + 1; #ifdef DEBUG logit("accept", "OK"); #endif } else { close(csock); logit("accept", "malloc fail"); } MYDOG; break; } csock = errno; if (csock != EINTR) { break; } } FD_CLR(msock, rptr); if (--nfds <= 0) continue; } for (cu = mainuser; cu; cu = cu->unext) { MYDOG; csock = cu->sock; if (FD_ISSET(csock, xptr)) { logout_user(cu); FD_CLR(csock, xptr); } else if (FD_ISSET(csock, rptr)) { if (cuser_serve(cu) < 0) logout_user(cu); } else { continue; } FD_CLR(csock, rptr); if (--nfds <= 0) break; } /* end of main loop */ } }