diff options
Diffstat (limited to 'daemon/bpop3d.c')
-rw-r--r-- | daemon/bpop3d.c | 1871 |
1 files changed, 1871 insertions, 0 deletions
diff --git a/daemon/bpop3d.c b/daemon/bpop3d.c new file mode 100644 index 0000000..3b76e8c --- /dev/null +++ b/daemon/bpop3d.c @@ -0,0 +1,1871 @@ +/*-------------------------------------------------------*/ +/* util/bpop3d.c ( NTHU CS MapleBBS Ver 3.00 ) */ +/*-------------------------------------------------------*/ +/* target : Simple POP3 server for BBS user */ +/* create : 96/05/10 */ +/* update : 96/11/15 */ +/*-------------------------------------------------------*/ +/* notice : single process concurrent server */ +/*-------------------------------------------------------*/ + + +#include "bbs.h" + + +#undef DEBUG +#define WATCH_DOG +#define SERVER_USAGE + + +#include <netinet/tcp.h> +#include <sys/wait.h> +#include <sys/resource.h> + + +#ifdef HAVE_DNS_LOOKUP +#include "dns.ic" +#endif + + +#define POP3_HOME (BBSHOME "/usr") +#define POP3_TIMEOUT (60 * 30) + +#define POP3_LOGFILE (BBSHOME "/run/pop3.log") +#define POP3_PIDFILE (BBSHOME "/run/pop3.pid") +#define POP3_DEBUGFILE (BBSHOME "/run/pop3.debug") + +#define POP3_FQDN (".bbs@" MYHOSTNAME) + +#define SOCK_BACKLOG 3 + + +#define SNDBUFSIZ (256 * 14) +#define SNDLINSIZ 256 /* Thor.990522: 註解: 送出每行最長 */ +#define RCVBUFSIZ 128 /* Thor.990522: 註解: 收到每行最長 */ + + +/* ----------------------------------------------------- */ +/* MapleBBS pop3d message strings */ +/* ----------------------------------------------------- */ + + +#define POP3_BYE_MSG "+OK POP3 server sign off\r\n" +#define POP3_BYE_LEN (sizeof(POP3_BYE_MSG) - 1) + + +#define POP3_ERRCMD_MSG "-ERR invalid command\r\n" +#define POP3_ERRARG_MSG "-ERR invalid argument\r\n" +#define POP3_ERRNUM_MSG "-ERR message number out of range\r\n" +#define POP3_DELETE_MSG "-ERR message has been deleted\r\n" + + +/* ----------------------------------------------------- */ +/* POPH : pop3header extended from HDR */ +/* ----------------------------------------------------- */ + + +typedef struct +{ + time_t chrono; /* timestamp */ + int xmode; + + int xid; /* reserved */ + + char xname[24]; /* 檔案名稱 */ + int pmode; /* Thor.990522: 是否刪信 等 */ + int psize; /* Thor.990522: 本信傳送總大小 */ + + char owner[80]; /* 作者 (E-mail address) */ + char nick[50]; /* 暱稱 */ + + char date[9]; /* [96/12/01] */ + char title[TTLEN + 1]; /* 主題 */ +} POPH; + + +/* ----------------------------------------------------- */ +/* client connection structure */ +/* ----------------------------------------------------- */ + + +typedef struct Client +{ + struct Client *next; + int sno; /* serial number */ + + int sock; + int state; + time_t tbirth; + time_t uptime; + + int mode; +#ifdef DEBUG + int debug; +#endif + int pcount; /* Thor.990522: 信件數 */ + int pbytes; /* Thor.990522: 總大小 */ + POPH *cache; /* Thor.990522: 存 .DIR */ + + char userid[IDLEN + 1]; + char passwd[PASSLEN + 1]; + char home[IDLEN + 4]; /* Thor.990122: 4個字元中, 2個是'/', 1個是英文字 1個是'\0' */ + + char pool[SNDBUFSIZ]; /* output pool */ + int locus; /* Thor.990522: output pool 內可送data之量 */ + + char *body; /* mail body */ + char *bptr; /* Thor.990522: body pointer, 已送多少 */ + + int lcur; /* Thor.990522: 目前送至哪行 */ + int lmax; /* Thor.990522: 最大行數 */ + + int xbytes; /* Thor.990522: 傳送總量 */ +} Client; + + +/* ----------------------------------------------------- */ +/* connection state */ +/* ----------------------------------------------------- */ + + +#define CS_FREE 0x00 /* Thor.990522: 目前未用 */ +#define CS_READ 0x01 /* Thor.990522: 等待user輸入 */ +#define CS_INDEX 0x02 /* Thor.990522: 信箱條列 */ +#define CS_UIDL 0x03 /* Thor.990522: uniq id條列 */ +#define CS_FILE 0x04 /* Thor.990522: 信件傳送中 */ +#define CS_WRITE 0x05 /* Thor.990522: 傳送中 */ +#define CS_FLUSH 0x06 /* Thor.990522: 最終一次傳送 */ + + +#define CM_LOGIN 1 +#define CM_DIRTY 2 /* 有刪除信件 */ + + +/* ----------------------------------------------------- */ +/* operation log and debug information */ +/* ----------------------------------------------------- */ + + +static FILE *flog; /* log file descriptor */ +static int gline; + + +#ifdef WATCH_DOG +# define MYDOG gline = __LINE__ +#else +# define MYDOG /* NOOP */ +#endif + + +static void +logit(key, msg) + char *key; + char *msg; +{ + time_t now; + struct tm *p; + + time(&now); + p = localtime(&now); + fprintf(flog, "%02d/%02d %02d:%02d:%02d %-8s%s\n", + p->tm_mon + 1, p->tm_mday, + p->tm_hour, p->tm_min, p->tm_sec, key, msg); +} + + +static void +log_init() +{ + FILE *fp; + + if (fp = fopen(POP3_PIDFILE, "w")) + { + fprintf(fp, "%d\n", getpid()); + fclose(fp); + } + + flog = fopen(POP3_LOGFILE, "a"); + logit("START", "pop3 daemon"); +} + + +/* ----------------------------------------------------- */ +/* parse string token */ +/* ----------------------------------------------------- */ + + +#define LOWER 1 + + +static char *trail_token; + + +static char * +parse_token(str, lower) + char *str; + int lower; +{ + char *token; + int ch; + + if (str == NULL) + { + str = trail_token; + if (str == NULL) + return NULL; + } + + token = NULL; + while (ch = *str) + { + if (ch == ' ' /* || ch == '\t' || ch == '\r' || ch == '\n' */ ) + { + if (token) + { + *str++ = '\0'; + break; + } + } + else + { + if (token == NULL) + token = str; + + if (lower && ch >= 'A' && ch <= 'Z') + *str = ch | 0x20; + } + str++; + } + + trail_token = str; + return token; +} + + +/* ----------------------------------------------------- */ +/* server side stuff */ +/* ----------------------------------------------------- */ + + +static void +mbox_open(cn) + Client *cn; +{ + int fd, fsize, pbytes, pad; + struct stat st; + char fpath[80], *fname; + POPH *hdr; + + cn->pcount = cn->pbytes = 0; + /* Thor.990522: cache, body 等, 已在 accept時initial */ + + sprintf(fpath, "%s"FN_DIR, cn->home); + fd = open(fpath, O_RDONLY); + + if (fd < 0) + return; + + if (fstat(fd, &st) || (fsize = st.st_size) < sizeof(HDR)) + { + close(fd); + return; + } + + hdr = (POPH *) malloc(fsize); + fsize = read(fd, hdr, fsize) / sizeof(HDR); + close(fd); + + if (fsize <= 0) + { + free(hdr); + return; + } + + cn->cache = hdr; + cn->pcount = fsize; + pbytes = 0; + fname = strchr(fpath, '.'); + *fname++ = '@'; + *fname++ = '/'; + + pad = 10 + strlen(cn->userid) + strlen(POP3_FQDN) + 9 + 44; + for (;;) + { + char *author; + int psize; + + strcpy(fname, hdr->xname); + psize = 0; + if (!stat(fpath, &st)) + psize = st.st_size; + + author = hdr->owner; + if (!strchr(author, '@')) + strcat(author, POP3_FQDN); + + psize += pad + strlen(author) + strlen(hdr->title) + 10 + 40 + 80; + + hdr->pmode = 0; + hdr->psize = psize; + pbytes += psize; + + if (--fsize == 0) + break; + + hdr++; + } + cn->pbytes = pbytes; +} + + +static void +mbox_read(cn, phdr, lmax) + Client *cn; + POPH *phdr; + int lmax; /* Thor.990522: 允許之總行數 */ +{ + char *pool, *head, *body, buf[80]; + /* struct tm *mytm; */ + int fd; + + /* mytm = localtime(&phdr->chrono); + strftime(buf, 46, "%a, %e %h %Y %T +0800 (%Z)", mytm); */ + + + pool = cn->pool; + sprintf(pool, "+OK %d octets\r\nFrom: %s\r\nTo: %s%s\r\n" + "Subject: %s\r\nDate: %s\r\n%s\r\n", + phdr->psize, phdr->owner, cn->userid, POP3_FQDN, + phdr->title, Atime(&phdr->chrono), (phdr->xmode & MAIL_READ ? "Status: RO\r\n" : "")); + + head = pool + strlen(pool); + if (!lmax) + { + *head++ = '.'; + *head++ = '\r'; + *head++ = '\n'; + cn->locus = head - pool; + cn->state = CS_WRITE; + return; + } + + cn->locus = head - pool; + cn->state = CS_FILE; + + if (body = cn->body) + { + /* Thor.990522: 有執行的機會嗎? */ + free(body); + } + body = NULL; + + sprintf(buf, "%s@/%s", cn->home, phdr->xname); + fd = open(buf, O_RDONLY); + if (fd >= 0) + { + int size; + struct stat st; + + if (!fstat(fd, &st) && ((size = st.st_size) > 0)) + { + if (body = (char *) malloc(size + 1)) + { + size = read(fd, body, size); + if (size > 0) + { + body[size] = '\0'; + } + else + { + free(body); + body = NULL; + } + } + } +#if 0 + else + { + phdr->pmode = MAIL_DELETE; + *head++ = '.'; + *head++ = '\r'; + *head++ = '\n'; + cn->locus = head - pool; + cn->state = CS_WRITE; + lmax = 0; + } +#endif + close(fd); + } + + if (!body) /* Thor.991220: for open fail, size==0, and read fail */ + { + phdr->pmode = MAIL_DELETE; + cn->mode = CM_DIRTY; /* Thor.991221: for incorrect mail delete */ + *head++ = '.'; + *head++ = '\r'; + *head++ = '\n'; + cn->locus = head - pool; + cn->state = CS_WRITE; + lmax = 0; + } + + cn->bptr = cn->body = body; + cn->lcur = 0; + cn->lmax = lmax; +} + + +static void +mbox_close(cn) + Client *cn; +{ + int n, max, saved; + FILE *fpr, *fpw; + char fpath[80], fold[80], fnew[80], *fname; + HDR mhdr; + POPH *phdr; + + sprintf(fold, "%s"FN_DIR, cn->home); + if ((fpr = fopen(fold, "r")) == NULL) + return; + + fpw = f_new(fold, fnew); + if (fpw == NULL) + { + fclose(fpr); + return; + } + + strcpy(fpath, fold); + fname = strchr(fpath, '.'); + *fname++ = '@'; + *fname++ = '/'; + + n = saved = 0; + max = cn->pcount; + phdr = cn->cache; + + while (fread(&mhdr, sizeof(mhdr), 1, fpr) == 1) + { + if (n >= max || !phdr->pmode) + { + saved = fwrite(&mhdr, sizeof(mhdr), 1, fpw); + if (saved <= 0) + { + saved = -1; + break; + } + } + else + { + strcpy(fname, mhdr.xname); + unlink(fpath); + } + n++; + phdr++; + } + fclose(fpr); + fclose(fpw); + + if (saved < 0) + { + unlink(fnew); + } + else if (saved) + { + rename(fnew, fold); + } + else + { + unlink(fnew); + unlink(fold); + } +} + + +static void +mbox_index(cn, state) + Client *cn; + int state; +{ + char *pool, *head, *tail; + int cur, max; + POPH *hdr; + + pool = cn->pool; + tail = pool + SNDBUFSIZ - 32; + + cur = cn->lcur; + if (cur) + { + head = pool + cn->locus; + } + else + { + memcpy(pool, "+OK\r\n", 5); + head = pool + 5; + } + + hdr = &cn->cache[cur]; + max = cn->pcount; + do + { + cur++; + + MYDOG; + + if (cur > max) + { + *head++ = '.'; + *head++ = '\r'; + *head++ = '\n'; + cn->state = CS_WRITE; + break; + } + + if (!hdr->pmode) + { + if (state == CS_UIDL) + sprintf(head, "%d %s\r\n", cur, hdr->xname + 1); + else + sprintf(head, "%d %d\r\n", cur, hdr->psize); + head += strlen(head); + } + hdr++; + + } while (head < tail); + + MYDOG; + + cn->locus = head - pool; + cn->lcur = cur; +} + + +static void +mbox_file(cn) + Client *cn; +{ + int lcur, lmax, ch, cx, cc; /* Thor.990522: 控制單行最大傳送量, Char Count */ + char *bptr, *pool, *head, *tail; + + /* send out a text file in MIME mode */ + + bptr = cn->bptr; + if (!bptr) + return; + + MYDOG; + + pool = cn->pool; + head = pool + cn->locus; + tail = pool + SNDBUFSIZ - SNDLINSIZ - 4 - 3; /* Thor.991129: 結尾的.\r\n 3字 */ + + lcur = cn->lcur; + lmax = cn->lmax; + cx = '\n'; cc = 0; + + while (ch = *bptr) + { + /* Thor.990522: 控制單行最大傳送量, Char Count. 寫得醜了點, 會跑就好:p */ + if (cc++ >= SNDLINSIZ) + ch = '\n'; + else + bptr++; + + if (ch == '\r') + continue; + + if (ch == '\n') + { + cc = 0; /* Thor.990522: 控制單行最大傳送量, Char Count */ + + *head++ = '\r'; + *head++ = '\n'; + if (++lcur >= lmax) + break; + + if (head > tail) + { + cn->state = CS_FILE; + cn->bptr = bptr; + cn->lcur = lcur; + cn->locus = head - pool; + return; + } + cx = ch; + continue; + } + + if (ch == '.' && cx == '\n') + *head++ = ch; + + *head++ = ch; + cx = ch; + } + + /* end of mail body transmission */ + +#if 1 + /* Thor.981206: Odysseus patch: + 作者 Odysseus.bbs@bbs.cs.nccu.edu.tw (由奢入儉難~~), 看板 plan + 標題 Re: bpop3d? + 時間 政大貓空行館 (Thu Oct 15 01:19:56 1998) +─────────────────────────────────────── +※ 引述《dreamer.bbs@Rouge.Dorm10.NCTU.edu.tw (碎心拼圖)》之銘言: +: 但是實際用 Netscape Messager 去試試看, +: 得到總共信件數目之後, +: 當要去 retreive 第一封信就當住了....-_- +: ps. Solaris 2.5.1, Maple 3.02 + */ + *head++ = '\r'; + *head++ = '\n'; +#endif + + *head++ = '.'; + *head++ = '\r'; + *head++ = '\n'; + cn->locus = head - pool; + cn->state = CS_WRITE; + + MYDOG; + free(cn->body); + MYDOG; + cn->bptr = cn->body = NULL; +} + + +/* ----------------------------------------------------- */ +/* supporting commands */ +/* ----------------------------------------------------- */ + + +static void cmd_xxxx(); +static void client_flush(); /* Thor.991221: 註解: 不用在msg加 \r\n, 程式會加 */ + +/* Thor.991221: 註解: 要在msg加 \r\n, 程式不加 */ +#define CMD_MSG(cn, msg) \ + cn->state = CS_WRITE; memcpy(cn->pool, msg, cn->locus = sizeof(msg) - 1); + + +static void +do_argument(cn) + Client *cn; +{ + CMD_MSG(cn, POP3_ERRARG_MSG); +} + + +/* return value : -1(all), 0, n */ + + +static int +do_number(cn, n) + Client *cn; + int n; /* 0 ==> exact 1 arg -1 ==> 1 or 0 arg */ +{ + char *cmd; + + if (cn->mode < CM_LOGIN) + { + cmd_xxxx(cn); + return 0; + } + + cmd = parse_token(NULL, 0); + + if (!cmd || !*cmd) + { + if (n == 0) + do_argument(cn); + return n; + } + + n = atoi(cmd); + if (n <= 0 || n > cn->pcount) + { + CMD_MSG(cn, POP3_ERRNUM_MSG); + return 0; + } + + return n; +} + + +/* ----------------------------------------------------- */ +/* main commands */ +/* ----------------------------------------------------- */ + + +static void +cmd_xxxx(cn) + Client *cn; +{ + CMD_MSG(cn, POP3_ERRCMD_MSG); +} + + +static void +cmd_noop(cn) + Client *cn; +{ + CMD_MSG(cn, "+OK\r\n"); +} + + +static void +cmd_user(cn) + Client *cn; +{ + int fd; + ACCT acct; + char *userid, *ptr, fpath[80], msg[128]; + char *msg_no_user = "-ERR no such user in our server"; + + MYDOG; + + if (cn->mode >= CM_LOGIN) + { + cmd_xxxx(cn); + return; + } + + userid = parse_token(NULL, LOWER); + + if (!userid || !*userid) + { + do_argument(cn); + return; + } + + /* userid is "userid.bbs" or "userid" */ + + if (ptr = strchr(userid, '.')) + { + if (strcmp(ptr, ".bbs")) + { + client_flush(cn, msg_no_user); + return; + } + *ptr = '\0'; + } + + /* Thor.981122: chc@gaisnews.iis.sinica.edu.tw patch: + 可能發生的問題: + 當 connect 到 bpop3d 時, 如果輸入 userid 太長, 就會造成 overflow, + 狀況輕時會 disconnect, 嚴重時會使 bpop3d segmentation fault. + 有心人士可能會以此破壞系統的安全性. */ + /* Thor.990122: check 完 *.bbs 再看 idlen */ + if (strlen(userid) > IDLEN) + { + client_flush(cn, msg_no_user); + return; + } + +#ifdef DEBUG + if (!strcmp(userid, STR_SYSOP)) /* 只有 sysop 取信才 debug */ + { + cn->debug = 1; + } +#endif + + ptr = cn->home; + sprintf(ptr, "%c/%s/", *userid, userid); + sprintf(fpath, "%s.ACCT", ptr); + if ((fd = open(fpath, O_RDONLY)) >= 0) + { + read(fd, &acct, sizeof(ACCT)); + close(fd); + + if (acct.userlevel & PERM_DENYLOGIN) + { + sprintf(msg, "-ERR %s denied", acct.userid); + } + else + { + strcpy(cn->userid, acct.userid); + memcpy(cn->passwd, acct.passwd, PASSLEN + 1); + /* SoC: follow the behavior of qpopper */ + /* sprintf(msg, "+OK Password required for %s%s", acct.userid, POP3_FQDN); */ + sprintf(msg, "+OK Password required for %s.", acct.userid); + } + } + else + { + strcpy(msg, msg_no_user); + } + + client_flush(cn, msg); +} + + +static void +cmd_password(cn) + Client *cn; +{ + char *cmd, *passwd, msg[128]; + + MYDOG; + + if (cn->mode >= CM_LOGIN) + { + cmd_xxxx(cn); + return; + } + + passwd = cn->passwd; + MYDOG; + if (!*passwd) + { + CMD_MSG(cn, "-ERR need USER command\r\n"); /* Thor.991221: 加上 \r\n */ + return; + } + + cmd = trail_token; /* to support password with [space] */ + + /* cmd = parse_token(NULL, 0); */ + + if (!cmd || !*cmd) + { + do_argument(cn); + return; + } + + MYDOG; + /* Thor.990214: 統一用 dao library */ + /* if (!checkpasswd(passwd, cmd)) */ + if (chkpasswd(passwd, cmd)) + { + fprintf(flog, "PASS\t[%d]\t%s\n", cn->sno, cn->userid); + + MYDOG; + *passwd = '\0'; + CMD_MSG(cn, "-ERR password incorrect\r\n"); /* Thor.991221: 加上 \r\n */ + return; + } + + MYDOG; + mbox_open(cn); + + MYDOG; + sprintf(msg, "+OK %s has %d messages (%d octets)", + cn->userid, cn->pcount, cn->pbytes); + + MYDOG; + client_flush(cn, msg); + + MYDOG; + fprintf(flog, "ENTER\t[%d]%s\n", cn->sno, msg + 3); + + MYDOG; + cn->mode = CM_LOGIN; +} + + +static void +cmd_stat(cn) + Client *cn; +{ + char *ptr; + + if (cn->mode < CM_LOGIN) + { + cmd_xxxx(cn); + return; + } + + ptr = cn->pool; + sprintf(ptr, "+OK %d %d\r\n", cn->pcount, cn->pbytes); + cn->locus = strlen(ptr); + cn->state = CS_WRITE; +} + + +static void +cmd_last(cn) + Client *cn; +{ + char *ptr; + + if (cn->mode < CM_LOGIN) + { + cmd_xxxx(cn); + return; + } + + ptr = cn->pool; + sprintf(ptr, "+OK %d\r\n", cn->pcount); + cn->locus = strlen(ptr); + cn->state = CS_WRITE; +} + + +static void +cmd_reset(cn) + Client *cn; +{ + int n, max; + POPH *phdr; + char *ptr; + + n = cn->mode; + if (n < CM_LOGIN) + { + cmd_xxxx(cn); + return; + } + + max = cn->pcount; + if (n == CM_DIRTY) + { + for (n = 0, phdr = cn->cache; n < max; phdr++, n++) + phdr->pmode = 0; + } + + ptr = cn->pool; + sprintf(ptr, "+OK mail reset %d messages %d octets\r\n", max, cn->pbytes); + cn->locus = strlen(ptr); + cn->mode = CM_LOGIN; + cn->state = CS_WRITE; +} + + +static void +cmd_retrive(cn) + Client *cn; +{ + int n; + POPH *phdr; + + n = do_number(cn, 0); + if (!n) + return; + + phdr = &cn->cache[n - 1]; + if (phdr->pmode) + { + CMD_MSG(cn, POP3_DELETE_MSG); + return; + } + + mbox_read(cn, phdr, 0x400000); +} + + +static void +cmd_top(cn) + Client *cn; +{ + int n; + POPH *phdr; + char *cmd; + + MYDOG; + + n = do_number(cn, 0); + if (!n) + return; + + phdr = &cn->cache[n - 1]; + if (phdr->pmode) + { + CMD_MSG(cn, POP3_DELETE_MSG); + return; + } + + cmd = parse_token(NULL, 0); + + if (!cmd || !*cmd) + { + do_argument(cn); + return; + } + + n = atoi(cmd); + if (n < 0) + { + CMD_MSG(cn, POP3_ERRNUM_MSG); + return; + } + + mbox_read(cn, phdr, n); +} + + +static void +cmd_delete(cn) + Client *cn; +{ + int n; + POPH *phdr; + + MYDOG; + + if (!(n = do_number(cn, 0))) + return; + + phdr = &cn->cache[n - 1]; + if (phdr->pmode) + { + CMD_MSG(cn, POP3_DELETE_MSG); + return; + } + + phdr->pmode = MAIL_DELETE; + cn->mode = CM_DIRTY; + CMD_MSG(cn, "+OK message deleted\r\n"); +} + + +static void +cmd_list(cn) + Client *cn; +{ + int n; + POPH *phdr; + char buf[80]; + + MYDOG; + + n = do_number(cn, -1); + + MYDOG; + + if (n < 0) + { + cn->lcur = 0; + cn->state = CS_INDEX; + } + else if (n > 0) + { + phdr = &cn->cache[n - 1]; + if (phdr->pmode) + { + CMD_MSG(cn, POP3_DELETE_MSG); + } + else + { + sprintf(buf, "+OK %d %d", n, phdr->psize); + client_flush(cn, buf); + } + } +} + + +static void +cmd_uidl(cn) + Client *cn; +{ + int n; + POPH *phdr; + char buf[80]; + + n = do_number(cn, -1); + + if (n < 0) + { + cn->lcur = 0; + cn->state = CS_UIDL; + } + else if (n > 0) + { + phdr = &cn->cache[n - 1]; + if (phdr->pmode) + { + CMD_MSG(cn, POP3_DELETE_MSG); + } + else + { + sprintf(buf, "+OK %d %s", n, phdr->xname + 1); + client_flush(cn, buf); + } + } +} + + +static void +cmd_quit(cn) + Client *cn; +{ + if (cn->mode >= CM_DIRTY) + { + mbox_close(cn); + } + + fprintf(flog, "QUIT\t[%d] %s S%d T%d\n", + cn->sno, cn->userid, cn->xbytes, time(0) - cn->tbirth); + + cn->state = CS_FLUSH; + strcpy(cn->pool, POP3_BYE_MSG); + cn->locus = POP3_BYE_LEN; +} + + +typedef struct +{ + char *cmd; + void (*fun) (); +} PopCmd; + + +static PopCmd cmdlist[] = +{ + "list", cmd_list, + "uidl", cmd_uidl, + "retr", cmd_retrive, + "dele", cmd_delete, + + "user", cmd_user, + "pass", cmd_password, + "stat", cmd_stat, + "quit", cmd_quit, + + "last", cmd_last, + "top", cmd_top, + + "rset", cmd_reset, + "noop", cmd_noop, + NULL, cmd_xxxx +}; + + +/* ----------------------------------------------------- */ +/* put a line into client's buffer, padding with "\r\n" */ +/* ----------------------------------------------------- */ + + +static void +client_flush(cn, msg) + Client *cn; + char *msg; +{ + char *pool, *head; + + head = pool = cn->pool; + + +#if 0 /* itoc.010606: 如果 client 是以換行字元來判別訊息結束的話,就會卡死了 */ + while (*head++ = *msg++) + ; +#endif + while (*head = *msg++) + head++; + + + /* SoC: Previous trick causes the output msg ending with "00 0D 0A" */ + head--; + + *head++ = '\r'; + *head++ = '\n'; + cn->locus = head - pool; + cn->state = CS_WRITE; +} + + +/* ----------------------------------------------------- */ +/* send data to client */ +/* ----------------------------------------------------- */ + + +static int +client_write(cn) + Client *cn; +{ + int len, cc; + char *data; + + len = cn->locus; + data = cn->pool; + MYDOG; + cc = send(cn->sock, data, len, 0); + MYDOG; + + if (cc <= 0) + { + MYDOG; + cc = errno; + if (cc != EWOULDBLOCK) + { + fprintf(flog, "SEND\t[%d] %s\n", cn->sno, strerror(cc)); + return 0; + } + + return -1; /* would block, so leave it to do later */ + } + +#ifdef DEBUG + if (cn->debug) + { + char msg[SNDBUFSIZ + 10]; + sprintf(msg, "%s\t[%d]bpop3>>>\n", Now(), cn->sno); + f_cat(POP3_DEBUGFILE, msg); + str_ncpy(msg, data, len+1); + f_cat(POP3_DEBUGFILE, msg); + sprintf(msg, "\t[%d]bpop3<<<\n", cn->sno); + f_cat(POP3_DEBUGFILE, msg); + MYDOG; + } +#endif + + MYDOG; + cn->xbytes += cc; + MYDOG; + len -= cc; + MYDOG; + cn->locus = len; + MYDOG; + if (len) + { + MYDOG; + memcpy(data, data + cc, len); + MYDOG; + return cc; + } + + MYDOG; + len = cn->state; + MYDOG; + + if (len == CS_FLUSH) + return 0; + + if (len == CS_WRITE) + cn->state = CS_READ; + + MYDOG; + return cc; +} + + +/* ----------------------------------------------------- */ +/* client's service dispatcher */ +/* ----------------------------------------------------- */ + + +static void +client_serve(cn) + Client *cn; +{ + char *cmd, *str; + PopCmd *pc; + + cn->locus = 0; /* reset buffer pool position */ + + cmd = parse_token(cn->pool, LOWER); + + if (!cmd || !*cmd) + return; + + for (pc = cmdlist; str = pc->cmd; pc++) + { + if (!strcmp(cmd, str)) + break; + } + +#ifdef DEBUG + logit(cmd, cn->userid); +#endif + + MYDOG; + (*pc->fun) (cn); + MYDOG; + +#ifdef DEBUG + { + char buf[80]; + + /* Thor.990222: 怕 str找不到cmd時 為 NULL */ + /* sprintf(buf, "%s-", str); */ + sprintf(buf, "%s-", cmd); + logit(buf, cn->userid); + } +#endif +} + + +/* ----------------------------------------------------- */ +/* receive command from client */ +/* ----------------------------------------------------- */ + + +static int +client_read(cn) + Client *cn; +{ + int cc, pos, len; + char *str; + + MYDOG; + + pos = cn->locus; + str = &cn->pool[pos]; + len = recv(cn->sock, str, RCVBUFSIZ, 0); + + if (len <= 0) + { + cc = errno; + if (cc != EWOULDBLOCK) + { + fprintf(flog, "RECV\t[%d] %s\n", cn->sno, strerror(cc)); + return 0; + } + + return -1; + } + + str[len] = '\0'; + +#ifdef DEBUG + if (cn->debug) + { + char msg[80]; + sprintf(msg, "%s\t[%d]peer>>>\n", Now(), cn->sno); + f_cat(POP3_DEBUGFILE, msg); + f_cat(POP3_DEBUGFILE, str); + sprintf(msg, "\t[%d]peer<<<\n", cn->sno); + f_cat(POP3_DEBUGFILE, msg); + } +#endif + + while (cc = *str) + { + switch (cc) + { + case '\r': + case '\n': + *str = '\0'; + client_serve(cn); + return 1; + + case '\t': + *str = ' '; + } + str++; + } + + pos += len; + if (pos >= RCVBUFSIZ) + { + sprintf(str, "[%d] buffer overflow", cn->sno); + logit("HACK", str); + return 0; + } + + cn->locus = pos; + return 1; +} + + +/* ----------------------------------------------------- */ +/* release idle/timeout connections */ +/* ----------------------------------------------------- */ + + +static void +client_close(cn) + Client *cn; +{ + int sock; + char *ptr; + + if (cn->mode >= CM_LOGIN) + { + if (ptr = (char *) cn->cache) + free(ptr); + + if (ptr = cn->body) + free(ptr); + } + + sock = cn->sock; + shutdown(sock, 2); + close(sock); +} + + +/* ----------------------------------------------------- */ +/* server core routines */ +/* ----------------------------------------------------- */ + + +static void +/* start_daemon() */ +servo_daemon(inetd) + int inetd; +{ + int fd, value; + char buf[80]; + struct sockaddr_in sin; + struct linger ld; +#ifdef HAVE_RLIMIT + struct rlimit limit; +#endif + + /* + * More idiot speed-hacking --- the first time conversion makes the C + * library open the files containing the locale definition and time zone. + * If this hasn't happened in the parent process, it happens in the + * children, once per connection --- and it does add up. + */ + + time((time_t *)&value); + gmtime((time_t *)&value); + strftime(buf, 80, "%d/%b/%Y:%H:%M:%S", localtime((time_t *)&value)); + +#ifdef HAVE_RLIMIT + /* --------------------------------------------------- */ + /* adjust the resource limit */ + /* --------------------------------------------------- */ + + limit.rlim_cur = limit.rlim_max = 8 * 1024 * 1024; + setrlimit(RLIMIT_DATA, &limit); + +#ifdef SOLARIS +#define RLIMIT_RSS RLIMIT_AS /* Thor.981206: port for solaris 2.6 */ +#endif + + setrlimit(RLIMIT_RSS, &limit); + + limit.rlim_cur = limit.rlim_max = 0; + setrlimit(RLIMIT_CORE, &limit); +#endif + + /* --------------------------------------------------- */ + /* detach daemon process */ + /* --------------------------------------------------- */ + + close(2); + close(1); + + if (inetd) + return; + + close(0); + + if (fork()) + exit(0); + + setsid(); + + if (fork()) + exit(0); + + /* --------------------------------------------------- */ + /* setup socket */ + /* --------------------------------------------------- */ + + fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + value = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &value, sizeof(value)); + + ld.l_onoff = ld.l_linger = 0; + setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &ld, sizeof(ld)); + + sin.sin_family = AF_INET; + sin.sin_port = htons(POP3_PORT); + sin.sin_addr.s_addr = INADDR_ANY; + memset(sin.sin_zero, 0, sizeof(sin.sin_zero)); + + if (bind(fd, (struct sockaddr *) & sin, sizeof(sin)) || + listen(fd, SOCK_BACKLOG)) + exit(1); +} + + +static void +abort_server() +{ + fclose(flog); + exit(0); +} + + +static void +reaper() +{ + while (waitpid(-1, NULL, WNOHANG | WUNTRACED) > 0); +} + + +static void +sig_trap(sig) + int sig; +{ + char buf[32]; + + sprintf(buf, "[%d] at %d (err: %d)", sig, gline, errno); + logit("TRAP", buf); + abort_server(); +} + + +#ifdef SERVER_USAGE +static void +server_usage() +{ + struct rusage ru; + + if (getrusage(RUSAGE_SELF, &ru)) + return; + + fprintf(flog, "\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: %d\n" + "page faults requiring physical I/O: %d\n" + "swaps: %d\n" + "block input operations: %d\n" + "block output operations: %d\n" + "messages sent: %d\n" + "messages received: %d\n" + "signals received: %d\n" + "voluntary context switches: %d\n" + "involuntary context switches: %d\ngline: %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); + + fflush(flog); +} +#endif + + +/* ----------------------------------------------------- */ +/* signal routines */ +/* ----------------------------------------------------- */ + + +static void +main_signals() +{ + struct sigaction act; + + /* sigblock(sigmask(SIGPIPE)); */ /* Thor.981206: 統一 POSIX 標準用法 */ + + /* act.sa_mask = 0; */ /* Thor.981105: 標準用法 */ + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + + act.sa_handler = sig_trap; + sigaction(SIGBUS, &act, NULL); + sigaction(SIGSEGV, &act, NULL); + + act.sa_handler = reaper; + sigaction(SIGCHLD, &act, NULL); + + act.sa_handler = abort_server; + sigaction(SIGTERM, &act, NULL); + +#ifdef SERVER_USAGE + act.sa_handler = server_usage; + sigaction(SIGPROF, &act, NULL); +#endif + + /* Thor.981206: lkchu patch: 統一 POSIX 標準用法 */ + /* 在此借用 sigset_t act.sa_mask */ + sigaddset(&act.sa_mask, SIGPIPE); + sigprocmask(SIG_BLOCK, &act.sa_mask, NULL); + +} + + +int +main(argc, argv) + int argc; + char *argv[]; +{ + int main_sno, csock, nfds, state; + Client **FBI, *Scully, *Mulder, *cn; + fd_set rset, wset, xset; + struct timeval tv; + time_t uptime, tcheck; + + state = 0; + + while ((nfds = getopt(argc, argv, "hid")) != -1) + { + switch (nfds) + { + case 'i': + state = 1; + break; + + case 'd': + break; + + case 'h': + default: + + fprintf(stderr, "Usage: %s [options]\n" + "\t-i start from inetd with wait option\n" + "\t-d debug mode\n" + "\t-h help\n", + argv[0]); + exit(0); + } + } + + servo_daemon(state); + /* start_daemon(); */ + + setgid(BBSGID); + setuid(BBSUID); + chdir(POP3_HOME); + main_signals(); + + umask(077); + log_init(); + +#if 0 + dns_init(); +#endif + + tv.tv_sec = POP3_TIMEOUT; + tv.tv_usec = 0; + + tcheck = time(0) + POP3_TIMEOUT; + Scully = Mulder = NULL; + main_sno = 0; + + for (;;) + { + /* resource and garbage collection */ + + uptime = time(0); + if (tcheck < uptime) + { + tcheck = uptime - POP3_TIMEOUT; + + for (FBI = &Scully; cn = *FBI;) + { + if (cn->uptime < tcheck) + { + client_close(cn); + + *FBI = cn->next; + + cn->next = Mulder; + Mulder = cn; + } + else + { + FBI = &(cn->next); + } + } + + fflush(flog); + tcheck = uptime + POP3_TIMEOUT; + } + + /* ------------------------------------------------- */ + /* Set up the fdsets */ + /* ------------------------------------------------- */ + + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&xset); + + FD_SET(0, &rset); + nfds = 0; + + for (cn = Scully; cn; cn = cn->next) + { + csock = cn->sock; + state = cn->state; + + if (nfds < csock) + nfds = csock; + + if (state == CS_READ) + { + FD_SET(csock, &rset); + } + else + { + switch (state) + { + case CS_FILE: + mbox_file(cn); + break; + + case CS_INDEX: + case CS_UIDL: + mbox_index(cn, state); + break; + } + + FD_SET(csock, &wset); + } + + FD_SET(csock, &xset); + } + + { + struct timeval tv_tmp = tv; + /* Thor.981221: for future reservation bug */ + nfds = select(nfds + 1, &rset, &wset, &xset, &tv_tmp); + } + + if (nfds == 0) + { + continue; + } + + if (nfds < 0) + { + csock = errno; + if (csock != EINTR) + { + logit("select", strerror(csock)); + } + continue; + } + + /* ------------------------------------------------- */ + /* serve active agents */ + /* ------------------------------------------------- */ + + uptime = time(0); + + for (FBI = &Scully; cn = *FBI;) + { + csock = cn->sock; + + if (FD_ISSET(csock, &wset)) + { + MYDOG; + state = client_write(cn); + MYDOG; + } + else if (FD_ISSET(csock, &rset)) + { + MYDOG; + state = client_read(cn); + MYDOG; + } + else if (FD_ISSET(csock, &xset)) + { + MYDOG; + state = 0; + MYDOG; + } + else + { + state = -1; + } + + if (state == 0) /* fire this agent */ + { + client_close(cn); + + *FBI = cn->next; + + cn->next = Mulder; + Mulder = cn; + + continue; + } + + if (state > 0) + { + MYDOG; + cn->uptime = uptime; + MYDOG; + } + + MYDOG; + FBI = &(cn->next); + MYDOG; + } + + /* ------------------------------------------------- */ + /* serve new connection */ + /* ------------------------------------------------- */ + + if (FD_ISSET(0, &rset)) + { + /* Thor.990222: 查出對方 ip */ + int len; + struct sockaddr_in csin; + len = sizeof csin; + + for (;;) + { + /* csock = accept(0, NULL, NULL); */ + /* Thor.990222: 查出對方 ip */ + csock = accept(0, (struct sockaddr *)&csin, &len); + + if (csock > 0) + break; + + state = errno; + if (state != EINTR) + { + logit("accept", strerror(state)); + break; + } + + while (waitpid(-1, NULL, WNOHANG | WUNTRACED) > 0); + } + + if (csock <= 0) + continue; + + if (cn = Mulder) + { + Mulder = cn->next; + } + else + { + cn = (Client *) malloc(sizeof(Client)); + } + + *FBI = cn; + + /* variable initialization */ + + /* sprintf(cn->pool, "[%d]", ++main_sno); */ + /* Thor.990222: 查出對方 ip */ + sprintf(cn->pool, "[%d] ip:%08x", ++main_sno, csin.sin_addr.s_addr); + logit("CONN", cn->pool); + + cn->next = NULL; + cn->sock = csock; + cn->sno = main_sno; + +#ifdef DEBUG + cn->debug = 0; +#endif + cn->mode = 0; + cn->passwd[0] = '\0'; + cn->cache = NULL; + cn->body = NULL; + cn->bptr = NULL; + + cn->state = CS_WRITE; + sprintf(cn->pool, "+OK MapleBBS POP3 server ready\r\n"); + cn->locus = strlen(cn->pool); + cn->tbirth = cn->uptime = uptime; + cn->xbytes = 0; + } + + /* ------------------------------------------------- */ + /* tail of main loop */ + /* ------------------------------------------------- */ + } +} |