/* $Id$ */
#include "bbs.h"
#define SOCKET_QLEN 4
#define TH_LOW 100
#define TH_HIGH 120
static void do_aloha(char *hello);
static void getremotename(struct sockaddr_in * from, char *rhost, char *rname);
#if 0
static jmp_buf byebye;
#endif
static char remoteusername[40] = "?";
static unsigned char enter_uflag;
static int use_shell_login_mode = 0;
static struct sockaddr_in xsin;
#ifdef USE_RFORK
#define fork() rfork(RFFDG | RFPROC | RFNOWAIT)
#endif
/* set signal handler, which won't be reset once signal comes */
static void
signal_restart(int signum, void (*handler) (int))
{
struct sigaction act;
act.sa_handler = handler;
memset(&(act.sa_mask), 0, sizeof(sigset_t));
act.sa_flags = 0;
sigaction(signum, &act, NULL);
}
static void
start_daemon()
{
int n, fd;
/*
* More idiot speed-hacking --- the first time conversion makes the C
* library open the files containing the locale definition and time zone.
* If this hasn't happened in the parent process, it happens in the
* children, once per connection --- and it does add up.
*/
time_t dummy = time(NULL);
struct tm *dummy_time = localtime(&dummy);
char buf[32];
strftime(buf, sizeof(buf), "%d/%b/%Y:%H:%M:%S", dummy_time);
#ifndef NO_FORK
if ((n = fork())) {
exit(0);
}
#endif
/* rocker.011018: it's a good idea to close all unexcept fd!! */
#ifndef VALGRIND
n = getdtablesize();
while (n)
close(--n);
/* in2: open /dev/null to fd:2 */
if( ((fd = open("/dev/null", O_WRONLY)) >= 0) && fd != 2 ){
dup2(fd, 2);
close(fd);
}
#endif
if(getenv("SSH_CLIENT"))
unsetenv("SSH_CLIENT");
/*
* rocker.011018: we don't need to remember original tty, so request a
* new session id
*/
setsid();
/*
* rocker.011018: after new session, we should insure the process is
* clean daemon
*/
#ifndef NO_FORK
if ((n = fork())) {
exit(0);
}
#endif
}
static void
reapchild(int sig)
{
int state, pid;
while ((pid = waitpid(-1, &state, WNOHANG | WUNTRACED)) > 0);
}
void
log_usies(char *mode, char *mesg)
{
if (!mesg)
log_file(FN_USIES, LOG_CREAT | LOG_VF,
"%s %s %-12s Stay:%d (%s)\n",
Cdate(&now), mode, cuser.userid ,
(int)(now - login_start_time) / 60, cuser.username);
else
log_file(FN_USIES, LOG_CREAT | LOG_VF,
"%s %s %-12s %s\n",
Cdate(&now), mode, cuser.userid, mesg);
/* 追蹤使用者 */
if (HAS_PERM(PERM_LOGUSER))
log_user("logout");
}
static void
setflags(int mask, int value)
{
if (value)
cuser.uflag |= mask;
else
cuser.uflag &= ~mask;
}
void
u_exit(char *mode)
{
//userec_t xuser;
int diff = (time(0) - login_start_time) / 60;
int dirty = currmode & MODE_DIRTY;
currmode = 0;
/* close fd 0 & 1 to terminate network */
close(0);
close(1);
reload_money();
cuser.goodpost = currutmp->goodpost;
cuser.badpost = currutmp->badpost;
cuser.goodsale = currutmp->goodsale;
cuser.badsale = currutmp->badsale;
auto_backup();
setflags(PAGER_FLAG, currutmp->pager != 1);
setflags(CLOAK_FLAG, currutmp->invisible);
save_brdbuf();
brc_finalize();
cuser.invisible = currutmp->invisible;
cuser.withme = currutmp->withme;
cuser.pager = currutmp->pager;
memcpy(cuser.mind, currutmp->mind, 4);
setutmpbid(0);
if (!(HAS_PERM(PERM_SYSOP) && HAS_PERM(PERM_SYSOPHIDE)) &&
!currutmp->invisible)
do_aloha("<<下站通知>> -- 我走囉!");
purge_utmp(currutmp);
if ((cuser.uflag != enter_uflag) || dirty || diff) {
if (!diff && cuser.numlogins)
cuser.numlogins = --cuser.numlogins;
/* Leeym 上站停留時間限制式 */
}
passwd_update(usernum, &cuser);
log_usies(mode, NULL);
}
void
abort_bbs(int sig)
{
if (currmode)
u_exit("ABORTED");
exit(0);
}
#ifdef GCC_NORETURN
static void abort_bbs_debug(int sig) GCC_NORETURN;
#endif
static void
abort_bbs_debug(int sig)
{
#ifdef DEBUGSLEEP
static int reentrant = 0;
if (!reentrant) {
int i;
/* close all file descriptors (including the network connection) */
for (i = 0; i < 256; ++i)
close(i);
reentrant = 1;
if (currmode)
u_exit("AXXED");
#ifndef VALGRIND
setproctitle("debug me!(%d)(%s,%d)", sig, cuser.userid, currstat);
#endif
sleep(3600); /* wait 60 mins for debug */
}
#endif
exit(0);
}
/* 登錄 BBS 程式 */
static void
mysrand()
{
srandom(time(NULL) + getpid()); /* 時間跟 pid 當 rand 的 seed */
}
void
talk_request(int sig)
{
bell();
bell();
if (currutmp->msgcount) {
char timebuf[100];
time4_t now = time(0);
move(0, 0);
clrtoeol();
prints("\033[33;41m★%s\033[34;47m [%s] %s \033[0m",
SHM->uinfo[currutmp->destuip].userid, my_ctime(&now,timebuf,sizeof(timebuf)),
(currutmp->sig == 2) ? "重要消息廣播!(請Ctrl-U,l查看熱訊記錄)"
: "呼叫、呼叫,聽到請回答");
refresh();
} else {
unsigned char mode0 = currutmp->mode;
char c0 = currutmp->chatid[0];
void *screen0;
currutmp->mode = 0;
currutmp->chatid[0] = 1;
screen0=malloc(screen_backupsize(t_lines, big_picture));
screen_backup(t_lines, big_picture, screen0);
talkreply();
currutmp->mode = mode0;
currutmp->chatid[0] = c0;
screen_restore(t_lines, big_picture, screen0);
free(screen0);
redoscr();
}
}
void
show_call_in(int save, int which)
{
char buf[200];
#ifdef PLAY_ANGEL
if (currutmp->msgs[which].msgmode == MSGMODE_TOANGEL)
snprintf(buf, sizeof(buf), "\033[1;37;46m★%s\033[37;45m %s \033[m",
currutmp->msgs[which].userid, currutmp->msgs[which].last_call_in);
else
#endif
snprintf(buf, sizeof(buf), "\033[1;33;46m★%s\033[37;45m %s \033[m",
currutmp->msgs[which].userid, currutmp->msgs[which].last_call_in);
outmsg(buf);
if (save) {
char genbuf[200];
if (!fp_writelog) {
sethomefile(genbuf, cuser.userid, fn_writelog);
fp_writelog = fopen(genbuf, "a");
}
if (fp_writelog) {
fprintf(fp_writelog, "%s [%s]\n", buf, Cdatelite(&now));
}
}
}
static int
add_history_water(water_t * w, msgque_t * msg)
{
memcpy(&w->msg[w->top], msg, sizeof(msgque_t));
w->top++;
w->top %= WATERMODE(WATER_OFO) ? 5 : MAX_REVIEW;
if (w->count < MAX_REVIEW)
w->count++;
return w->count;
}
static int
add_history(msgque_t * msg)
{
int i = 0, j, waterinit = 0;
water_t *tmp;
if (WATERMODE(WATER_ORIG) || WATERMODE(WATER_NEW))
add_history_water(&water[0], msg);
if (WATERMODE(WATER_NEW) || WATERMODE(WATER_OFO)) {
for (i = 0; i < 5 && swater[i]; i++)
if (swater[i]->pid == msg->pid
#ifdef PLAY_ANGEL
&& swater[i]->msg[0].msgmode == msg->msgmode
/* When throwing waterball to angel directly */
#endif
)
break;
if (i == 5) {
waterinit = 1;
i = 4;
memset(swater[4], 0, sizeof(water_t));
} else if (!swater[i]) {
water_usies = i + 1;
swater[i] = &water[i + 1];
waterinit = 1;
}
tmp = swater[i];
if (waterinit) {
memcpy(swater[i]->userid, msg->userid, sizeof(swater[i]->userid));
swater[i]->pid = msg->pid;
}
if (!swater[i]->uin)
swater[i]->uin = currutmp;
for (j = i; j > 0; j--)
swater[j] = swater[j - 1];
swater[0] = tmp;
add_history_water(swater[0], msg);
}
if (WATERMODE(WATER_ORIG) || WATERMODE(WATER_NEW)) {
if (watermode > 0 &&
(water_which == swater[0] || water_which == &water[0])) {
if (watermode < water_which->count)
watermode++;
t_display_new();
}
}
return i;
}
void
write_request(int sig)
{
int i, msgcount;
#ifdef NOKILLWATERBALL
if( reentrant_write_request ) /* kill again by shmctl */
return;
reentrant_write_request = 1;
#endif
if (WATERMODE(WATER_OFO)) {
/* 如果目前正在回水球模式的話, 就不能進行 add_history() ,
因為會改寫 water[], 而使回水球目的爛掉, 所以分成幾種情況考慮.
sig != 0表真的有水球進來, 故顯示.
sig == 0表示沒有水球進來, 不過之前尚有水球還沒寫到 water[].
*/
static int alreadyshow = 0;
if( sig ){ /* 真的有水球進來 */
/* 若原來正在 REPLYING , 則改成 RECVINREPLYING,
這樣在回水球結束後, 會再呼叫一次 write_request(0) */
if( wmofo == REPLYING )
wmofo = RECVINREPLYING;
/* 顯示 */
for( ; alreadyshow < currutmp->msgcount && alreadyshow < MAX_MSGS
; ++alreadyshow ){
bell();
show_call_in(1, alreadyshow);
refresh();
}
}
/* 看看是不是要把 currutmp->msg 拿回 water[] (by add_history())
須要是不在回水球中 (NOTREPLYING) */
if( wmofo == NOTREPLYING &&
(msgcount = currutmp->msgcount) > 0 ){
for( i = 0 ; i < msgcount ; ++i )
add_history(&currutmp->msgs[i]);
if( (currutmp->msgcount -= msgcount) < 0 )
currutmp->msgcount = 0;
alreadyshow = 0;
}
} else {
if (currutmp->mode != 0 &&
currutmp->pager != 0 &&
cuser.userlevel != 0 &&
currutmp->msgcount != 0 &&
currutmp->mode != TALK &&
currutmp->mode != EDITING &&
currutmp->mode != CHATING &&
currutmp->mode != PAGE &&
currutmp->mode != IDLE &&
currutmp->mode != MAILALL && currutmp->mode != MONITOR) {
char c0 = currutmp->chatid[0];
int currstat0 = currstat;
unsigned char mode0 = currutmp->mode;
currutmp->mode = 0;
currutmp->chatid[0] = 2;
currstat = HIT;
#ifdef NOKILLWATERBALL
currutmp->wbtime = 0;
#endif
if( (msgcount = currutmp->msgcount) > 0 ){
for( i = 0 ; i < msgcount ; ++i ){
bell();
show_call_in(1, 0);
add_history(&currutmp->msgs[0]);
if( (--currutmp->msgcount) < 0 )
i = msgcount; /* force to exit for() */
else if( currutmp->msgcount > 0 )
memmove(&currutmp->msgs[0],
&currutmp->msgs[1],
sizeof(msgque_t) * currutmp->msgcount);
igetch();
}
}
currutmp->chatid[0] = c0;
currutmp->mode = mode0;
currstat = currstat0;
} else {
bell();
show_call_in(1, 0);
add_history(&currutmp->msgs[0]);
refresh();
currutmp->msgcount = 0;
}
}
#ifdef NOKILLWATERBALL
reentrant_write_request = 0;
currutmp->wbtime = 0; /* race */
#endif
}
static void
multi_user_check()
{
register userinfo_t *ui;
register pid_t pid;
char genbuf[3];
if (HAS_PERM(PERM_SYSOP))
return; /* don't check sysops */
if (cuser.userlevel) {
if (!(ui = (userinfo_t *) search_ulist(usernum)))
return; /* user isn't logged in */
pid = ui->pid;
if (!pid /* || (kill(pid, 0) == -1) */ )
return; /* stale entry in utmp file */
getdata(b_lines - 1, 0, "您想刪除其他重複的 login (Y/N)嗎?[Y] ",
genbuf, 3, LCECHO);
if (genbuf[0] != 'n') {
if (pid > 0)
kill(pid, SIGHUP);
log_usies("KICK ", cuser.username);
} else {
if (search_ulistn(usernum, 3) != NULL)
abort_bbs(0); /* Goodbye(); */
}
} else {
/* allow multiple guest user */
if (search_ulistn(usernum, 100) != NULL) {
vmsg("抱歉,目前已有太多 guest 在站上, 請用new註冊。");
exit(1);
}
}
}
/* bad login */
static char str_badlogin[] = "logins.bad";
static void
logattempt(char *uid, char type)
{
char fname[40];
int fd, len;
char genbuf[200];
snprintf(genbuf, sizeof(genbuf), "%c%-12s[%s] %s@%s\n", type, uid,
Cdate(&login_start_time), remoteusername, fromhost);
len = strlen(genbuf);
if ((fd = open(str_badlogin, O_WRONLY | O_CREAT | O_APPEND, 0644)) > 0) {
write(fd, genbuf, len);
close(fd);
}
if (type == '-') {
snprintf(genbuf, sizeof(genbuf),
"[%s] %s\n", Cdate(&login_start_time), fromhost);
len = strlen(genbuf);
sethomefile(fname, uid, str_badlogin);
if ((fd = open(fname, O_WRONLY | O_CREAT | O_APPEND, 0644)) > 0) {
write(fd, genbuf, len);
close(fd);
}
}
}
inline static void mkuserdir(char *userid)
{
char genbuf[200];
sethomepath(genbuf, userid);
// assume it is a dir, so just check if it is exist
if (access(genbuf, F_OK) != 0)
mkdir(genbuf, 0755);
}
static void
login_query()
{
#ifdef CONVERT
/* uid 加一位, for gb login */
char uid[IDLEN + 2], passbuf[PASSLEN];
int attempts, len;
#else
char uid[IDLEN + 1], passbuf[PASSLEN];
int attempts;
#endif
resolve_garbage();
now = time(0);
#ifdef DEBUG
move(1, 0);
prints("debugging mode\ncurrent pid: %d\n", getpid());
#else
show_file("etc/Welcome", 1, -1, NO_RELOAD);
#endif
output("1", 1);
attempts = 0;
while (1) {
if (attempts++ >= LOGINATTEMPTS) {
more("etc/goodbye", NA);
pressanykey();
exit(1);
}
#ifdef DEBUG
move(19, 0);
prints("current pid: %d ", getpid());
#endif
getdata(20, 0, "請輸入代號,或以[guest]參觀,以[new]註冊:",
uid, sizeof(uid), DOECHO);
#ifdef CONVERT
/* switch to gb mode if uid end with '.' */
len = strlen(uid);
if (uid[0] && uid[len - 1] == '.') {
set_converting_type(CONV_GB);
uid[len - 1] = 0;
}
else if (uid[0] && uid[len - 1] == ',') {
set_converting_type(CONV_UTF8);
uid[len - 1] = 0;
}
else if (len >= IDLEN + 1)
uid[IDLEN] = 0;
#endif
if (strcasecmp(uid, str_new) == 0) {
#ifdef LOGINASNEW
new_register();
mkuserdir(cuser.userid);
break;
#else
outs("本系統目前無法以 new 註冊, 請用 guest 進入\n");
continue;
#endif
} else if (uid[0] == '\0'){
outs(err_uid);
} else if (strcasecmp(uid, STR_GUEST)) {
getdata(21, 0, MSG_PASSWD,
passbuf, sizeof(passbuf), NOECHO);
passbuf[8] = '\0';
if( initcuser(uid) < 1 || !cuser.userid[0] ||
!checkpasswd(cuser.passwd, passbuf) ){
logattempt(cuser.userid , '-');
outs(ERR_PASSWD);
} else {
logattempt(cuser.userid, ' ');
if (strcasecmp("SYSOP", cuser.userid) == 0){
#ifdef NO_SYSOP_ACCOUNT
exit(0);
#else /* 自動加上各個主要權限 */
cuser.userlevel = PERM_BASIC | PERM_CHAT | PERM_PAGE |
PERM_POST | PERM_LOGINOK | PERM_MAILLIMIT |
PERM_CLOAK | PERM_SEECLOAK | PERM_XEMPT |
PERM_SYSOPHIDE | PERM_BM | PERM_ACCOUNTS |
PERM_CHATROOM | PERM_BOARD | PERM_SYSOP | PERM_BBSADM;
mkuserdir(cuser.userid);
#endif
}
break;
}
} else { /* guest */
if (initcuser(uid)< 1) exit (0) ;
cuser.userlevel = 0;
cuser.uflag = PAGER_FLAG | BRDSORT_FLAG | MOVIE_FLAG;
mkuserdir(cuser.userid);
break;
}
}
multi_user_check();
}
void
add_distinct(char *fname, char *line)
{
FILE *fp;
int n = 0;
if ((fp = fopen(fname, "a+"))) {
char buffer[80];
char tmpname[100];
FILE *fptmp;
strlcpy(tmpname, fname, sizeof(tmpname));
strcat(tmpname, "_tmp");
if (!(fptmp = fopen(tmpname, "w"))) {
fclose(fp);
return;
}
rewind(fp);
while (fgets(buffer, 80, fp)) {
char *p = buffer + strlen(buffer) - 1;
if (p[-1] == '\n' || p[-1] == '\r')
p[-1] = 0;
if (!strcmp(buffer, line))
break;
sscanf(buffer + strlen(buffer) + 2, "%d", &n);
fprintf(fptmp, "%s%c#%d\n", buffer, 0, n);
}
if (feof(fp))
fprintf(fptmp, "%s%c#1\n", line, 0);
else {
sscanf(buffer + strlen(buffer) + 2, "%d", &n);
fprintf(fptmp, "%s%c#%d\n", buffer, 0, n + 1);
while (fgets(buffer, 80, fp)) {
sscanf(buffer + strlen(buffer) + 2, "%d", &n);
fprintf(fptmp, "%s%c#%d\n", buffer, 0, n);
}
}
fclose(fp);
fclose(fptmp);
unlink(fname);
rename(tmpname, fname);
}
}
void
del_distinct(char *fname, char *line)
{
FILE *fp;
int n = 0;
if ((fp = fopen(fname, "r"))) {
char buffer[80];
char tmpname[100];
FILE *fptmp;
strlcpy(tmpname, fname, sizeof(tmpname));
strcat(tmpname, "_tmp");
if (!(fptmp = fopen(tmpname, "w"))) {
fclose(fp);
return;
}
rewind(fp);
while (fgets(buffer, 80, fp)) {
char *p = buffer + strlen(buffer) - 1;
if (p[-1] == '\n' || p[-1] == '\r')
p[-1] = 0;
if (!strcmp(buffer, line))
break;
sscanf(buffer + strlen(buffer) + 2, "%d", &n);
fprintf(fptmp, "%s%c#%d\n", buffer, 0, n);
}
if (!feof(fp))
while (fgets(buffer, 80, fp)) {
sscanf(buffer + strlen(buffer) + 2, "%d", &n);
fprintf(fptmp, "%s%c#%d\n", buffer, 0, n);
}
fclose(fp);
fclose(fptmp);
unlink(fname);
rename(tmpname, fname);
}
}
#ifdef WHERE
static int
where(char *from)
{
int i;
for (i = 0; i < SHM->home_num; i++) {
if ((SHM->home_ip[i] & SHM->home_mask[i]) == (ipstr2int(from) & SHM->home_mask[i])) {
return i;
}
}
return 0;
}
#endif
static void
check_BM(void)
{
/* XXX: -_- */
int i;
cuser.userlevel &= ~PERM_BM;
for( i = 0 ; i < numboards ; ++i )
if( is_BM_cache(i + 1) ) /* XXXbid */
return;
//for (i = 0, bhdr = bcache; i < numboards && !is_BM(bhdr->BM); i++, bhdr++);
}
static void
setup_utmp(int mode)
{
userinfo_t uinfo;
memset(&uinfo, 0, sizeof(uinfo));
uinfo.pid = currpid = getpid();
uinfo.uid = usernum;
uinfo.mode = currstat = mode;
uinfo.mailalert = load_mailalert(cuser.userid);
uinfo.userlevel = cuser.userlevel;
uinfo.sex = cuser.sex % 8;
uinfo.lastact = time(NULL);
strlcpy(uinfo.userid, cuser.userid, sizeof(uinfo.userid));
//strlcpy(uinfo.realname, cuser.realname, sizeof(uinfo.realname));
strlcpy(uinfo.username, cuser.username, sizeof(uinfo.username));
strip_nonebig5(uinfo.username, sizeof(uinfo.username));
strlcpy(uinfo.from, fromhost, sizeof(uinfo.from));
uinfo.five_win = cuser.five_win;
uinfo.five_lose = cuser.five_lose;
uinfo.five_tie = cuser.five_tie;
uinfo.chc_win = cuser.chc_win;
uinfo.chc_lose = cuser.chc_lose;
uinfo.chc_tie = cuser.chc_tie;
uinfo.chess_elo_rating = cuser.chess_elo_rating;
uinfo.invisible = cuser.invisible % 2;
uinfo.pager = cuser.pager % 5;
if(cuser.withme & (cuser.withme<<1) & (WITHME_ALLFLAG<<1))
cuser.withme=0;
uinfo.withme = cuser.withme;
memcpy(uinfo.mind, cuser.mind, 4);
strip_nonebig5(uinfo.mind, 4);
#ifdef WHERE
uinfo.from_alias = where(fromhost);
#endif
#ifndef FAST_LOGIN
setuserfile(buf, "remoteuser");
strlcpy(remotebuf, fromhost, sizeof(fromhost));
strcat(remotebuf, ctime4(&now));
chomp(remotebuf);
add_distinct(buf, remotebuf);
#endif
if (enter_uflag & CLOAK_FLAG)
uinfo.invisible = YEA;
#ifdef PLAY_ANGEL
if (REJECT_QUESTION)
uinfo.angel = 1;
uinfo.angel |= ANGEL_STATUS() << 1;
#endif
getnewutmpent(&uinfo);
SHM->UTMPneedsort = 1;
if (!(cuser.numlogins % 20) && cuser.userlevel & PERM_BM)
check_BM(); /* Ptt 自動取下離職板主權力 */
#ifndef _BBS_UTIL_C_
friend_load(0);
nice(3);
#endif
}
inline static void welcome_msg(void) {
prints("\033[m 歡迎您第 \033[1;33m%d\033[0;37m 度拜訪本站,"
"上次您是從 \033[1;33m%s\033[0;37m 連往本站,\n"
" 我記得那天是 \033[1;33m%s\033[0;37m。\n",
++cuser.numlogins, cuser.lasthost, Cdate(&(cuser.lastlogin)));
pressanykey();
}
inline static void check_bad_login(void) {
char genbuf[200];
setuserfile(genbuf, str_badlogin);
if (more(genbuf, NA) != -1) {
move(b_lines - 3, 0);
outs("通常並沒有辦法知道該ip是誰所有, "
"以及其意圖(是不小心按錯或有意測您密碼)\n"
"若您有帳號被盜用疑慮, 請經常更改您的密碼或使用加密連線");
if (getans("您要刪除以上錯誤嘗試的記錄嗎(Y/N)?[Y]") != 'n')
unlink(genbuf);
}
}
inline static void birthday_make_a_wish(struct tm *ptime, struct tm *tmp){
if (currutmp->birth && tmp->tm_mday != ptime->tm_mday) {
more("etc/birth.post", YEA);
brc_initial_board("WhoAmI");
set_board();
do_post();
}
}
inline static void record_lasthost(char *fromhost, int len){
strncpy(cuser.lasthost, fromhost, len);
cuser.lasthost[len - 1] = '\0';
}
inline static void check_mailbox_quota(void){
if (chkmailbox())
m_read();
}
static void init_guest_info(void)
{
int i;
char *nick[13] = {
"椰子", "貝殼", "內衣", "寶特瓶", "翻車魚",
"樹葉", "浮萍", "鞋子", "潛水艇", "魔王",
"鐵罐", "考卷", "大美女"
};
char *name[13] = {
"大王椰子", "鸚鵡螺", "比基尼", "可口可樂", "仰泳的魚",
"憶", "高岡屋", "AIR Jordon", "紅色十月號", "批踢踢",
"SASAYA椰奶", "鴨蛋", "布魯克鱈魚香絲"
};
char *addr[13] = {
"天堂樂園", "大海", "綠島小夜曲", "美國", "綠色珊瑚礁",
"遠方", "原本海", "NIKE", "蘇聯", "男八618室",
"愛之味", "天上", "藍色珊瑚礁"
};
i = login_start_time % 13;
snprintf(cuser.username, sizeof(cuser.username),
"海邊漂來的%s", nick[(int)i]);
strlcpy(currutmp->username, cuser.username,
sizeof(currutmp->username));
strlcpy(cuser.realname, name[(int)i], sizeof(cuser.realname));
strlcpy(cuser.address, addr[(int)i], sizeof(cuser.address));
cuser.sex = i % 8;
currutmp->pager = 2;
}
#if FOREIGN_REG_DAY > 0
inline static void foreign_warning(void){
if ((cuser.uflag2 & FOREIGN) && !(cuser.uflag2 & LIVERIGHT)){
if (login_start_time - cuser.firstlogin > (FOREIGN_REG_DAY - 5) * 24 * 3600){
mail_muser(cuser, "[出入境管理局]", "etc/foreign_expired_warn");
}
else if (login_start_time - cuser.firstlogin > FOREIGN_REG_DAY * 24 * 3600){
cuser.userlevel &= ~(PERM_LOGINOK | PERM_POST);
vmsg("警告:請至出入境管理局申請永久居留");
}
}
}
#endif
static void
user_login()
{
char i;
struct tm *ptime, *tmp;
int a, ifbirth;
/* get local time */
ptime = localtime4(&now);
/* 初始化: random number 增加user跟時間的差異 */
mysrand();
/* show welcome_login */
ifbirth = (ptime->tm_mday == cuser.day &&
ptime->tm_mon + 1 == cuser.month);
if (ifbirth)
more("etc/Welcome_birth", NA);
else {
#ifndef MULTI_WELCOME_LOGIN
more("etc/Welcome_login", NA);
#else
if( SHM->GV2.e.nWelcomes ){
char buf[80];
snprintf(buf, sizeof(buf), "etc/Welcome_login.%d",
(int)login_start_time % SHM->GV2.e.nWelcomes);
more(buf, NA);
}
#endif
}
log_usies("ENTER", fromhost);
#ifndef VALGRIND
setproctitle("%s: %s", margs, cuser.userid);
#endif
resolve_fcache();
/* resolve_boards(); */
numboards = SHM->Bnumber;
memset(&water[0], 0, sizeof(water_t) * 6);
strlcpy(water[0].userid, " 全部 ", sizeof(water[0].userid));
if(getenv("SSH_CLIENT") != NULL){
char frombuf[50];
sscanf(getenv("SSH_CLIENT"), "%s", frombuf);
xsin.sin_family = AF_INET;
xsin.sin_port = htons(23);
if (strrchr(frombuf, ':'))
inet_pton(AF_INET, strrchr(frombuf, ':') + 1, &xsin.sin_addr);
else
inet_pton(AF_INET, frombuf, &xsin.sin_addr);
getremotename(&xsin, fromhost, remoteusername); /* FC931 */
}
/* 初始化 uinfo、flag、mode */
setup_utmp(LOGIN);
currmode = MODE_STARTED;
enter_uflag = cuser.uflag;
currutmp->birth = ifbirth;
tmp = localtime4(&(cuser.lastlogin));
if ((a = SHM->UTMPnumber) > SHM->max_user) {
SHM->max_user = a;
SHM->max_time = now;
}
if (!(HAS_PERM(PERM_SYSOP) && HAS_PERM(PERM_SYSOPHIDE)) &&
!currutmp->invisible)
do_aloha("<<上站通知>> -- 我來啦!");
if(SHM->loginmsg.pid)
{
if(search_ulist_pid(SHM->loginmsg.pid))
getmessage(SHM->loginmsg);
else
SHM->loginmsg.pid=0;
}
if (cuser.userlevel) { /* not guest */
move(t_lines - 4, 0);
welcome_msg();
birthday_make_a_wish(ptime, tmp);
check_bad_login();
check_mailbox_quota();
check_register();
record_lasthost(fromhost, 16);
restore_backup();
} else if (!strcmp(cuser.userid, STR_GUEST)) {
init_guest_info();
pressanykey();
} else {
pressanykey();
check_mailbox_quota();
}
if (!PERM_HIDE(currutmp))
cuser.lastlogin = login_start_time;
#if FOREIGN_REG_DAY > 0
foreign_warning();
#endif
passwd_update(usernum, &cuser);
currutmp->goodpost = cuser.goodpost;
currutmp->badpost = cuser.badpost;
currutmp->goodsale = cuser.goodsale;
currutmp->badsale = cuser.badsale;
fav_load();
/* subscribe new fav (deprecated) */
if (get_fav_root() != NULL)
updatenewfav(1);
for (i = 0; i < NUMVIEWFILE; i++)
if ((cuser.loginview >> i) & 1)
more(loginview_file[(int)i][0], YEA);
}
static void
do_aloha(char *hello)
{
FILE *fp;
char userid[80];
char genbuf[200];
setuserfile(genbuf, "aloha");
if ((fp = fopen(genbuf, "r"))) {
snprintf(genbuf, sizeof(genbuf), hello);
while (fgets(userid, 80, fp)) {
userinfo_t *uentp;
if ((uentp = (userinfo_t *) search_ulist_userid(userid)) &&
isvisible(uentp, currutmp)) {
my_write(uentp->pid, genbuf, uentp->userid, WATERBALL_ALOHA, uentp);
}
}
fclose(fp);
}
}
static void
do_term_init()
{
term_init();
initscr();
if(use_shell_login_mode)
raise(SIGWINCH);
}
inline static void
start_client()
{
#ifdef CPULIMIT
struct rlimit rml;
rml.rlim_cur = CPULIMIT * 60;
rml.rlim_max = CPULIMIT * 60;
setrlimit(RLIMIT_CPU, &rml);
#endif
/* system init */
nice(2); /* Ptt: lower priority */
login_start_time = time(0);
currmode = 0;
Signal(SIGHUP, abort_bbs);
Signal(SIGTERM, abort_bbs);
Signal(SIGPIPE, abort_bbs);
Signal(SIGINT, abort_bbs_debug);
Signal(SIGQUIT, abort_bbs_debug);
Signal(SIGILL, abort_bbs_debug);
Signal(SIGABRT, abort_bbs_debug);
Signal(SIGFPE, abort_bbs_debug);
Signal(SIGBUS, abort_bbs_debug);
Signal(SIGSEGV, abort_bbs_debug);
signal_restart(SIGUSR1, talk_request);
signal_restart(SIGUSR2, write_request);
dup2(0, 1);
/* initialize passwd semaphores */
if (passwd_init())
exit(1);
do_term_init();
Signal(SIGALRM, abort_bbs);
alarm(600);
login_query(); /* Ptt 加上login time out */
m_init(); /* init the user mail path */
user_login();
auto_close_polls(); /* 自動開票 */
Signal(SIGALRM, SIG_IGN);
domenu(MMENU, "主功\能表", (currutmp->mailalert ? 'M' : 'C'), cmdlist);
}
/* FSA (finite state automata) for telnet protocol */
static void
telnet_init()
{
const static char svr[] = {
IAC, DO, TELOPT_TTYPE,
IAC, SB, TELOPT_TTYPE, TELQUAL_SEND, IAC, SE,
IAC, WILL, TELOPT_ECHO,
IAC, WILL, TELOPT_SGA
};
const char *cmd;
int n, len;
struct timeval to;
char buf[64];
fd_set ReadSet, r;
FD_ZERO(&ReadSet);
FD_SET(0, &ReadSet);
for (n = 0, cmd = svr; n < 4; n++) {
len = (n == 1 ? 6 : 3);
write(0, cmd, len);
cmd += len;
to.tv_sec = 3;
to.tv_usec = 0;
r = ReadSet;
if (select(1, &r, NULL, NULL, &to) > 0)
recv(0, buf, sizeof(buf), 0);
}
}
/* 取得 remote user name 以判定身份 */
/*
* rfc931() speaks a common subset of the RFC 931, AUTH, TAP, IDENT and RFC
* 1413 protocols. It queries an RFC 931 etc. compatible daemon on a remote
* host to look up the owner of a connection. The information should not be
* used for authentication purposes. This routine intercepts alarm signals.
*
* Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
*/
#define STRN_CPY(d,s,l) { strncpy((d),(s),(l)); (d)[(l)-1] = 0; }
#define RFC931_TIMEOUT 10
#define RFC931_PORT 113 /* Semi-well-known port */
#define ANY_PORT 0 /* Any old port will do */
#if 0
/* timeout - handle timeouts */
static void
timeout(int sig)
{
longjmp(byebye, sig);
}
#endif
static void
getremotename(struct sockaddr_in * from, char *rhost, char *rname)
{
/* get remote host name */
#ifdef FAST_LOGIN
strcpy(rhost, (char *)inet_ntoa(from->sin_addr));
#else
struct sockaddr_in our_sin;
struct sockaddr_in rmt_sin;
unsigned rmt_port, rmt_pt;
unsigned our_port, our_pt;
FILE *fp;
char buffer[512], user[80], *cp;
int s;
static struct hostent *hp;
hp = NULL;
if (setjmp(byebye) == 0) {
Signal(SIGALRM, timeout);
alarm(3);
hp = gethostbyaddr((char *)&from->sin_addr, sizeof(struct in_addr),
from->sin_family);
alarm(0);
}
strcpy(rhost, hp ? hp->h_name : (char *)inet_ntoa(from->sin_addr));
/*
* Use one unbuffered stdio stream for writing to and for reading from
* the RFC931 etc. server. This is done because of a bug in the SunOS
* 4.1.x stdio library. The bug may live in other stdio implementations,
* too. When we use a single, buffered, bidirectional stdio stream ("r+"
* or "w+" mode) we read our own output. Such behaviour would make sense
* with resources that support random-access operations, but not with
* sockets.
*/
s = sizeof(our_sin);
if (getsockname(0, (struct sockaddr *) & our_sin, &s) < 0)
return;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return;
if (!(fp = fdopen(s, "r+"))) {
close(s);
return;
}
/* Set up a timer so we won't get stuck while waiting for the server. */
if (setjmp(byebye) == 0) {
Signal(SIGALRM, timeout);
alarm(RFC931_TIMEOUT);
/*
* Bind the local and remote ends of the query socket to the same IP
* addresses as the connection under investigation. We go through all
* this trouble because the local or remote system might have more
* than one network address. The RFC931 etc. client sends only port
* numbers; the server takes the IP addresses from the query socket.
*/
our_pt = ntohs(our_sin.sin_port);
our_sin.sin_port = htons(ANY_PORT);
rmt_sin = *from;
rmt_pt = ntohs(rmt_sin.sin_port);
rmt_sin.sin_port = htons(RFC931_PORT);
setbuf(fp, (char *)0);
s = fileno(fp);
if (bind(s, (struct sockaddr *) & our_sin, sizeof(our_sin)) >= 0 &&
connect(s, (struct sockaddr *) & rmt_sin, sizeof(rmt_sin)) >= 0) {
/*
* Send query to server. Neglect the risk that a 13-byte write
* would have to be fragmented by the local system and cause
* trouble with buggy System V stdio libraries.
*/
fprintf(fp, "%u,%u\r\n", rmt_pt, our_pt);
fflush(fp);
/*
* Read response from server. Use fgets()/sscanf() so we can work
* around System V stdio libraries that incorrectly assume EOF
* when a read from a socket returns less than requested.
*/
if (fgets(buffer, sizeof(buffer), fp) && !ferror(fp)
&& !feof(fp)
&& sscanf(buffer, "%u , %u : USERID :%*[^:]:%79s", &rmt_port,
&our_port, user) == 3 && rmt_pt == rmt_port
&& our_pt == our_port) {
/*
* Strip trailing carriage return. It is part of the
* protocol, not part of the data.
*/
if ((cp = (char *)strchr(user, '\r')))
*cp = 0;
strlcpy(rname, user, sizeof(user));
}
}
alarm(0);
}
fclose(fp);
#endif
}
static int
bind_port(int port)
{
int sock, on, sz;
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
on = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof(on));
on = 0;
setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *)&on, sizeof(on));
sz = 1024;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&sz, sizeof(sz));
sz = 4096;
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&sz, sizeof(sz));
xsin.sin_port = htons(port);
if (bind(sock, (struct sockaddr *) & xsin, sizeof xsin) < 0) {
syslog(LOG_INFO, "bbsd bind_port can't bind to %d", port);
exit(1);
}
if (listen(sock, SOCKET_QLEN) < 0) {
syslog(LOG_INFO, "bbsd bind_port can't listen to %d", port);
exit(1);
}
return sock;
}
/*******************************************************/
static int shell_login(int argc, char *argv[], char *envp[]);
static int daemon_login(int argc, char *argv[], char *envp[]);
static int check_ban_and_load(int fd);
int
main(int argc, char *argv[], char *envp[])
{
start_time = time(NULL);
/* avoid SIGPIPE */
Signal(SIGPIPE, SIG_IGN);
/* avoid erroneous signal from other mbbsd */
Signal(SIGUSR1, SIG_IGN);
Signal(SIGUSR2, SIG_IGN);
attach_SHM();
if( (argc == 3 && shell_login(argc, argv, envp)) ||
(argc != 3 && daemon_login(argc, argv, envp)) )
start_client();
return 0;
}
static int
shell_login(int argc, char *argv[], char *envp[])
{
/* Give up root privileges: no way back from here */
setgid(BBSGID);
setuid(BBSUID);
chdir(BBSHOME);
use_shell_login_mode = 1;
initsetproctitle(argc, argv, envp);
snprintf(margs, sizeof(margs), "%s ssh ", argv[0]);
/*
* copy fromindent: Standard input:1138: Error:Unexpected end of file the
* original "bbs"
*/
if (argc > 1) {
strcpy(fromhost, argv[1]);
if (argc > 3)
strlcpy(remoteusername, argv[3], sizeof(remoteusername));
}
close(2);
/* don't close fd 1, at least init_tty need it */
init_tty();
if (check_ban_and_load(0)) {
return 0;
}
return 1;
}
static int
daemon_login(int argc, char *argv[], char *envp[])
{
int msock, csock; /* socket for Master and Child */
FILE *fp;
int listen_port = 23;
int len_of_sock_addr, overloading = 0;
char buf[256];
#if OVERLOADBLOCKFDS
int blockfd[OVERLOADBLOCKFDS];
int i, nblocked = 0;
#endif
/* setup standalone */
start_daemon();
signal_restart(SIGCHLD, reapchild);
/* choose port */
if (argc == 1)
listen_port = 3006;
else if (argc >= 2)
listen_port = atoi(argv[1]);
snprintf(margs, sizeof(margs), "%s %d ", argv[0], listen_port);
/* port binding */
xsin.sin_family = AF_INET;
msock = bind_port(listen_port);
if (msock < 0) {
syslog(LOG_INFO, "mbbsd bind_port failed.\n");
exit(1);
}
initsetproctitle(argc, argv, envp);
#ifndef VALGRIND
setproctitle("%s: listening ", margs);
#endif
/* Give up root privileges: no way back from here */
setgid(BBSGID);
setuid(BBSUID);
chdir(BBSHOME);
/* It's better to do something before fork */
#ifdef CONVERT
void big2gb_init(void*);
void gb2big_init(void*);
void big2uni_init(void*);
void uni2big_init(void*);
big2gb_init(NULL);
gb2big_init(NULL);
big2uni_init(NULL);
uni2big_init(NULL);
#endif
#ifndef NO_FORK
#ifdef PRE_FORK
if( listen_port == 23 ){ // only pre-fork in port 23
int i;
for( i = 0 ; i < PRE_FORK ; ++i )
if( fork() <= 0 )
break;
}
#endif
#endif
snprintf(buf, sizeof(buf), "run/mbbsd.%d.%d.pid", listen_port, (int)getpid());
if ((fp = fopen(buf, "w"))) {
fprintf(fp, "%d\n", (int)getpid());
fclose(fp);
}
/* main loop */
while( 1 ){
len_of_sock_addr = sizeof(xsin);
#if defined(Solaris) && __OS_MAJOR_VERSION__ == 5 && __OS_MINOR_VERSION__ < 7
if( (csock = accept(msock, (struct sockaddr *)&xsin,
&len_of_sock_addr)) < 0 ){
#else
if( (csock = accept(msock, (struct sockaddr *)&xsin,
(socklen_t *)&len_of_sock_addr)) < 0 ){
#endif
if (errno != EINTR)
sleep(1);
continue;
}
overloading = check_ban_and_load(csock);
#if OVERLOADBLOCKFDS
if( (!overloading && nblocked) ||
(overloading && nblocked == OVERLOADBLOCKFDS) ){
for( i = 0 ; i < OVERLOADBLOCKFDS ; ++i )
if( blockfd[i] != csock && blockfd[i] != msock )
/* blockfd[i] should not be msock, but it happened */
close(blockfd[i]);
nblocked = 0;
}
#endif
if( overloading ){
#if OVERLOADBLOCKFDS
blockfd[nblocked++] = csock;
#else
close(csock);
#endif
continue;
}
#ifdef NO_FORK
break;
#else
if (fork() == 0)
break;
else
close(csock);
#endif
}
/* here is only child running */
#ifndef VALGRIND
setproctitle("%s: ...login wait... ", margs);
#endif
close(msock);
dup2(csock, 0);
close(csock);
getremotename(&xsin, fromhost, remoteusername);
telnet_init();
return 1;
}
/*
* check if we're banning login and if the load is too high. if login is
* permitted, return 0; else return -1; approriate message is output to fd.
*/
static int
check_ban_and_load(int fd)
{
FILE *fp;
static time4_t chkload_time = 0;
static int overload = 0; /* overload or banned, update every 1
* sec */
static int banned = 0;
#ifdef INSCREEN
write(fd, INSCREEN, sizeof(INSCREEN));
#else
#define BANNER \
"【" BBSNAME "】◎ 台大流行網 ◎(" MYHOSTNAME ") 調幅(" MYIP ") \r\n"
write(fd, BANNER, sizeof(BANNER));
#endif
if ((time(0) - chkload_time) > 1) {
overload = 0;
banned = 0;
if(cpuload(NULL) > MAX_CPULOAD)
overload = 1;
else if (SHM->UTMPnumber >= MAX_ACTIVE
#ifdef DYMAX_ACTIVE
|| (SHM->GV2.e.dymaxactive > 2000 &&
SHM->UTMPnumber >= SHM->GV2.e.dymaxactive)
#endif
) {
++SHM->GV2.e.toomanyusers;
overload = 2;
} else if(!access(BBSHOME "/" BAN_FILE, R_OK))
banned = 1;
chkload_time = time(0);
}
if(overload == 1)
write(fd, "系統過載, 請稍後再來\r\n", 22);
else if(overload == 2)
write(fd, "由於人數過多,請您稍後再來。", 28);
else if (banned && (fp = fopen(BBSHOME "/" BAN_FILE, "r"))) {
char buf[256];
while (fgets(buf, sizeof(buf), fp))
write(fd, buf, strlen(buf));
fclose(fp);
}
if (banned || overload)
return -1;
return 0;
}