summaryrefslogtreecommitdiffstats
path: root/console/mail.c
diff options
context:
space:
mode:
Diffstat (limited to 'console/mail.c')
-rw-r--r--console/mail.c2092
1 files changed, 2092 insertions, 0 deletions
diff --git a/console/mail.c b/console/mail.c
new file mode 100644
index 00000000..38e19619
--- /dev/null
+++ b/console/mail.c
@@ -0,0 +1,2092 @@
+/* $Id$ */
+#include "bbs.h"
+static int mailkeep = 0, mailsum = 0;
+static int mailsumlimit = 0, mailmaxkeep = 0;
+static char currmaildir[32];
+static char msg_cc[] = ANSI_COLOR(32) "[群組名單]" ANSI_RESET "\n";
+static char listfile[] = "list.0";
+
+// check only 20 mails (one page) is enough.
+// #define NEWMAIL_CHECK_RANGE (1)
+// checking only 1 mail works more like brc style.
+#define NEWMAIL_CHECK_RANGE (5)
+
+enum SHOWMAIL_MODES {
+ SHOWMAIL_NORM = 0,
+ SHOWMAIL_SUM,
+ SHOWMAIL_RANGE,
+};
+static int showmail_mode = SHOWMAIL_NORM;
+
+int
+setforward(void)
+{
+ char buf[80], ip[50] = "", yn[4];
+ FILE *fp;
+ int flIdiotSent2Self = 0;
+ int oidlen = strlen(cuser.userid);
+
+ sethomepath(buf, cuser.userid);
+ strcat(buf, "/.forward");
+ if ((fp = fopen(buf, "r"))) {
+ fscanf(fp, "%" toSTR(sizeof(ip)) "s", ip);
+ fclose(fp);
+ }
+ getdata_buf(b_lines - 1, 0, "請輸入自動轉寄的Email: ",
+ ip, sizeof(ip), DOECHO);
+
+ /* anti idiots */
+ if (strncasecmp(ip, cuser.userid, oidlen) == 0)
+ {
+ int addrlen = strlen(ip);
+ if( addrlen == oidlen ||
+ (addrlen > oidlen &&
+ strcasecmp(ip + oidlen, str_mail_address) == 0))
+ flIdiotSent2Self = 1;
+ }
+
+ if (ip[0] && ip[0] != ' ' && !flIdiotSent2Self) {
+ getdata(b_lines, 0, "確定開啟自動轉信功\能?(Y/n)", yn, sizeof(yn),
+ LCECHO);
+ if (yn[0] != 'n' && (fp = fopen(buf, "w"))) {
+ fputs(ip, fp);
+ fclose(fp);
+ vmsg("設定完成!");
+ return 0;
+ }
+ }
+ unlink(buf);
+ if(flIdiotSent2Self)
+ vmsg("自動轉寄是不會設定給自己的,想取消用空白就可以了。");
+ else
+ vmsg("取消自動轉信!");
+ return 0;
+}
+
+int
+toggle_showmail_mode(void)
+{
+ showmail_mode ++;
+ showmail_mode %= SHOWMAIL_RANGE;
+ return FULLUPDATE;
+}
+
+int
+built_mail_index(void)
+{
+ char genbuf[128];
+
+ move(b_lines - 4, 0);
+ outs("本功\能只在信箱檔毀損時使用," ANSI_COLOR(1;33) "無法" ANSI_RESET "救回被刪除的信件。\n"
+ "除非您清楚這個功\能的作用,否則" ANSI_COLOR(1;33) "請不要使用" ANSI_RESET "。\n"
+ "警告:任意的使用將導致" ANSI_COLOR(1;33) "不可預期的結果" ANSI_RESET "!\n");
+ getdata(b_lines - 1, 0,
+ "確定重建信箱?(y/N)", genbuf, 3,
+ LCECHO);
+ if (genbuf[0] != 'y')
+ return 0;
+
+ snprintf(genbuf, sizeof(genbuf),
+ BBSHOME "/bin/buildir " BBSHOME "/home/%c/%s > /dev/null",
+ cuser.userid[0], cuser.userid);
+ mouts(b_lines - 1, 0, ANSI_COLOR(1;31) "已經處理完畢!! 諸多不便 敬請原諒~" ANSI_RESET);
+ system(genbuf);
+ pressanykey();
+ return 0;
+}
+
+int
+sendalert(const char *userid, int alert)
+{
+ userinfo_t *uentp = NULL;
+ int n, tuid, i;
+
+ if ((tuid = searchuser(userid, NULL)) == 0)
+ return -1;
+
+ n = count_logins(tuid, 0);
+ for (i = 1; i <= n; i++)
+ if ((uentp = (userinfo_t *) search_ulistn(tuid, i)))
+ uentp->alerts |= alert;
+ return 0;
+}
+
+int
+mail_muser(userec_t muser, const char *title, const char *filename)
+{
+ return mail_id(muser.userid, title, filename, cuser.userid);
+}
+
+int
+mail_id(const char *id, const char *title, const char *src, const char *owner)
+{
+ fileheader_t mhdr;
+ char dst[128], dirf[128];
+ sethomepath(dst, id);
+ if (stampfile(dst, &mhdr))
+ return 0;
+ strlcpy(mhdr.owner, owner, sizeof(mhdr.owner));
+ strlcpy(mhdr.title, title, sizeof(mhdr.title));
+ mhdr.filemode = 0;
+ Copy(src, dst);
+
+ sethomedir(dirf, id);
+ append_record_forward(dirf, &mhdr, sizeof(mhdr), id);
+ sendalert(id, ALERT_NEW_MAIL);
+ return 0;
+}
+
+int
+invalidaddr(const char *addr)
+{
+#ifdef DEBUG_FWDADDRERR
+ const char *origaddr = addr;
+ char errmsg[PATHLEN];
+#endif
+
+ if (*addr == '\0')
+ return 1; /* blank */
+
+ while (*addr) {
+#ifdef DEBUG_FWDADDRERR
+ if (not_alnum(*addr) && !strchr("[].@-_+", *addr))
+ {
+ int c = (*addr) & 0xff;
+ clear();
+ move(2,0);
+ outs(
+ "您輸入的位址錯誤 (address error)。 \n\n"
+ "由於最近許\多人反應打入正確的位址(id或email)後系統會判斷錯誤\n"
+ "但檢查不出原因,所以我們需要正確的錯誤回報。\n\n"
+ "如果你確實打錯了,請直接略過下面的說明。\n"
+ "如果你認為你輸入的位址確實是對的,請把下面的訊息複製起來\n"
+ "並貼到 " GLOBAL_BUGREPORT " 板。本站為造成不便深感抱歉。\n\n"
+ ANSI_COLOR(1;33));
+ sprintf(errmsg, "原始輸入位址: [%s]\n"
+ "錯誤位置: 第 %d 字元: 0x%02X [ %c ]\n",
+ origaddr, (int)(addr - origaddr+1), c, c);
+ outs(errmsg);
+ outs(ANSI_RESET);
+ vmsg("請按任意鍵繼續");
+ clear();
+ return 1;
+ }
+#else
+ if (not_alnum(*addr) && !strchr("[].@-_", *addr))
+ return 1;
+#endif
+ addr++;
+ }
+ return 0;
+}
+
+int
+m_internet(void)
+{
+ char receiver[60];
+ char title[STRLEN];
+
+ getdata(20, 0, "收信人:", receiver, sizeof(receiver), DOECHO);
+ trim(receiver);
+ if (strchr(receiver, '@') && !invalidaddr(receiver) &&
+ getdata(21, 0, "主 題:", title, sizeof(title), DOECHO))
+ do_send(receiver, title);
+ else {
+ vmsg("收信人或主題不正確,請重新選取指令");
+ }
+ return 0;
+}
+
+void
+m_init(void)
+{
+ sethomedir(currmaildir, cuser.userid);
+}
+
+static void
+loadmailusage(void)
+{
+ mailkeep=get_num_records(currmaildir,sizeof(fileheader_t));
+ mailsum =get_sum_records(currmaildir, sizeof(fileheader_t));
+}
+
+void
+setupmailusage(void)
+{ // Ptt: get_sum_records is a bad function
+ int max_keepmail = MAX_KEEPMAIL;
+#ifdef PLAY_ANGEL
+ if (HasUserPerm(PERM_SYSSUPERSUBOP | PERM_ANGEL))
+#else
+ if (HasUserPerm(PERM_SYSSUPERSUBOP))
+#endif
+ {
+ mailsumlimit = 900;
+ max_keepmail = 700;
+ }
+ else if (HasUserPerm(PERM_SYSSUBOP | PERM_ACCTREG | PERM_PRG |
+ PERM_ACTION | PERM_PAINT)) {
+ mailsumlimit = 700;
+ max_keepmail = 500;
+ } else if (HasUserPerm(PERM_BM)) {
+ mailsumlimit = 500;
+ max_keepmail = 300;
+ } else if (HasUserPerm(PERM_LOGINOK))
+ mailsumlimit = 200;
+ else
+ mailsumlimit = 50;
+ mailsumlimit += (cuser.exmailbox + ADD_EXMAILBOX) * 10;
+ mailmaxkeep = max_keepmail + cuser.exmailbox;
+ loadmailusage();
+}
+
+#define MAILBOX_LIM_OK 0
+#define MAILBOX_LIM_KEEP 1
+#define MAILBOX_LIM_SUM 2
+static int
+chk_mailbox_limit(void)
+{
+ if (HasUserPerm(PERM_SYSOP) || HasUserPerm(PERM_MAILLIMIT))
+ return MAILBOX_LIM_OK;
+
+ if (!mailkeep)
+ setupmailusage();
+
+ if (mailkeep > mailmaxkeep)
+ return MAILBOX_LIM_KEEP;
+ if (mailsum > mailsumlimit)
+ return MAILBOX_LIM_SUM;
+ return MAILBOX_LIM_OK;
+}
+
+int
+chkmailbox(void)
+{
+ m_init();
+
+ switch (chk_mailbox_limit()) {
+ case MAILBOX_LIM_KEEP:
+ bell();
+ bell();
+ vmsgf("您保存信件數目 %d 超出上限 %d, 請整理", mailkeep, mailmaxkeep);
+ return mailkeep;
+
+ case MAILBOX_LIM_SUM:
+ bell();
+ bell();
+ vmsgf("信箱容量(大小,非件數) %d 超出上限 %d, "
+ "請砍過長的水球記錄或信件", mailsum, mailsumlimit);
+ if(showmail_mode != SHOWMAIL_SUM)
+ {
+ showmail_mode = SHOWMAIL_SUM;
+ vmsg("信箱顯示模式已自動改為顯示大小,請盡速整理");
+ }
+ return mailsum;
+
+ default:
+ return 0;
+ }
+}
+
+static void
+do_hold_mail(const char *fpath, const char *receiver, const char *holder)
+{
+ char buf[80], title[128];
+
+ fileheader_t mymail;
+
+ sethomepath(buf, holder);
+ stampfile(buf, &mymail);
+
+ mymail.filemode = FILE_READ ;
+ strlcpy(mymail.owner, "[備.忘.錄]", sizeof(mymail.owner));
+ if (receiver) {
+ snprintf(title, sizeof(title), "(%s) %s", receiver, save_title);
+ strlcpy(mymail.title, title, sizeof(mymail.title));
+ } else
+ strlcpy(mymail.title, save_title, sizeof(mymail.title));
+
+ sethomedir(title, holder);
+
+ unlink(buf);
+ Copy(fpath, buf);
+ append_record_forward(title, &mymail, sizeof(mymail), holder);
+}
+
+void
+hold_mail(const char *fpath, const char *receiver)
+{
+ char buf[4];
+
+ getdata(b_lines - 1, 0,
+ (cuser.uflag & DEFBACKUP_FLAG) ?
+ "已順利寄出,是否自存底稿(Y/N)?[Y] " :
+ "已順利寄出,是否自存底稿(Y/N)?[N] ",
+ buf, sizeof(buf), LCECHO);
+
+ if (TOBACKUP(buf[0]))
+ do_hold_mail(fpath, receiver, cuser.userid);
+}
+
+int
+do_send(const char *userid, const char *title)
+{
+ fileheader_t mhdr;
+ char fpath[STRLEN];
+ char receiver[IDLEN + 1];
+ char genbuf[200];
+ int internet_mail, i;
+ userec_t xuser;
+
+ STATINC(STAT_DOSEND);
+ if (strchr(userid, '@'))
+ internet_mail = 1;
+ else {
+ internet_mail = 0;
+ if (!getuser(userid, &xuser))
+ return -1;
+ if (!(xuser.userlevel & PERM_READMAIL))
+ return -3;
+
+ curredit |= EDIT_MAIL;
+ curredit &= ~EDIT_ITEM;
+ }
+ /* process title */
+ if (title)
+ strlcpy(save_title, title, sizeof(save_title));
+ else {
+ char tmp_title[STRLEN-20];
+ getdata(2, 0, "主題:", tmp_title, sizeof(tmp_title), DOECHO);
+ strlcpy(save_title, tmp_title, sizeof(save_title));
+ }
+
+ setutmpmode(SMAIL);
+
+ fpath[0] = '\0';
+
+ if (internet_mail) {
+ int res, ch;
+
+ if (vedit(fpath, NA, NULL) == -1) {
+ unlink(fpath);
+ clear();
+ return -2;
+ }
+ clear();
+ prints("信件即將寄給 %s\n標題為:%s\n確定要寄出嗎? (Y/N) [Y]",
+ userid, save_title);
+ ch = igetch();
+ switch (ch) {
+ case 'N':
+ case 'n':
+ outs("N\n信件已取消");
+ res = -2;
+ break;
+ default:
+ outs("Y\n請稍候, 信件傳遞中...\n");
+ res =
+#ifndef USE_BSMTP
+ bbs_sendmail(fpath, save_title, userid);
+#else
+ bsmtp(fpath, save_title, userid);
+#endif
+ hold_mail(fpath, userid);
+ }
+ unlink(fpath);
+ return res;
+ } else {
+ strlcpy(receiver, userid, sizeof(receiver));
+ sethomepath(genbuf, userid);
+ stampfile(genbuf, &mhdr);
+ strlcpy(mhdr.owner, cuser.userid, sizeof(mhdr.owner));
+ if (vedit(genbuf, YEA, NULL) == -1) {
+ unlink(genbuf);
+ clear();
+ return -2;
+ }
+ /* why not make title here? */
+ strlcpy(mhdr.title, save_title, sizeof(mhdr.title));
+ clear();
+ sethomefile(fpath, userid, FN_OVERRIDES);
+ i = belong(fpath, cuser.userid);
+ sethomefile(fpath, userid, FN_REJECT);
+
+ if (i || !belong(fpath, cuser.userid)) {/* Ptt: 用belong有點討厭 */
+ sethomedir(fpath, userid);
+ if (append_record_forward(fpath, &mhdr, sizeof(mhdr), userid) == -1)
+ return -1;
+ sendalert(userid,ALERT_NEW_MAIL);
+ }
+ hold_mail(genbuf, userid);
+ return 0;
+ }
+}
+
+void
+my_send(const char *uident)
+{
+ switch (do_send(uident, NULL)) {
+ case -1:
+ outs(err_uid);
+ break;
+ case -2:
+ outs(msg_cancel);
+ break;
+ case -3:
+ prints("使用者 [%s] 無法收信", uident);
+ break;
+ }
+ pressanykey();
+}
+
+int
+m_send(void)
+{
+ char uident[40];
+
+ stand_title("且聽風的話");
+ usercomplete(msg_uid, uident);
+ showplans(uident);
+ if (uident[0])
+ my_send(uident);
+ return 0;
+}
+
+/* 群組寄信、回信 : multi_send, multi_reply */
+static void
+multi_list(int *reciper)
+{
+ char uid[16];
+ char genbuf[200];
+
+ while (1) {
+ stand_title("群組寄信名單");
+ ShowNameList(3, 0, msg_cc);
+ move(1, 0);
+ outs("(I)引入好友 (O)引入上線通知 (N)引入新文章通知 (0-9)引入其他特別名單");
+ getdata(2, 0,
+ "(A)增加 (D)刪除 (M)確認寄信名單 (Q)取消 ?[M]",
+ genbuf, 4, LCECHO);
+ switch (genbuf[0]) {
+ case 'a':
+ while (1) {
+ move(1, 0);
+ usercomplete("請輸入要增加的代號(只按 ENTER 結束新增): ", uid);
+ if (uid[0] == '\0')
+ break;
+
+ move(2, 0);
+ clrtoeol();
+
+ if (!searchuser(uid, uid))
+ outs(err_uid);
+ else if (!InNameList(uid)) {
+ AddNameList(uid);
+ (*reciper)++;
+ }
+ ShowNameList(3, 0, msg_cc);
+ }
+ break;
+ case 'd':
+ while (*reciper) {
+ move(1, 0);
+ namecomplete("請輸入要刪除的代號(只按 ENTER 結束刪除): ", uid);
+ if (uid[0] == '\0')
+ break;
+ if (RemoveNameList(uid))
+ (*reciper)--;
+ ShowNameList(3, 0, msg_cc);
+ }
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ listfile[5] = genbuf[0];
+ genbuf[0] = '1';
+ case 'i':
+ setuserfile(genbuf, genbuf[0] == '1' ? listfile : fn_overrides);
+ ToggleNameList(reciper, genbuf, msg_cc);
+ break;
+ case 'o':
+ setuserfile(genbuf, "alohaed");
+ ToggleNameList(reciper, genbuf, msg_cc);
+ break;
+ case 'n':
+ setuserfile(genbuf, "postlist");
+ ToggleNameList(reciper, genbuf, msg_cc);
+ break;
+ case 'q':
+ *reciper = 0;
+ return;
+ default:
+ return;
+ }
+ }
+}
+
+static void
+multi_send(char *title)
+{
+ FILE *fp;
+ struct word_t *p = NULL;
+ fileheader_t mymail;
+ char fpath[TTLEN], *ptr;
+ int reciper, listing;
+ char genbuf[256];
+
+ CreateNameList();
+ listing = reciper = 0;
+ if (*quote_file) {
+ AddNameList(quote_user);
+ reciper = 1;
+ fp = fopen(quote_file, "r");
+ assert(fp);
+ while (fgets(genbuf, sizeof(genbuf), fp)) {
+ if (strncmp(genbuf, "※ ", 3)) {
+ if (listing)
+ break;
+ } else {
+ if (listing) {
+ char *strtok_pos;
+ ptr = genbuf + 3;
+ for (ptr = strtok_r(ptr, " \n\r", &strtok_pos);
+ ptr;
+ ptr = strtok_r(NULL, " \n\r", &strtok_pos)) {
+ if (searchuser(ptr, ptr) && !InNameList(ptr) &&
+ strcmp(cuser.userid, ptr)) {
+ AddNameList(ptr);
+ reciper++;
+ }
+ }
+ } else if (!strncmp(genbuf + 3, "[通告]", 6))
+ listing = 1;
+ }
+ }
+ fclose(fp);
+ ShowNameList(3, 0, msg_cc);
+ }
+ multi_list(&reciper);
+ move(1, 0);
+ clrtobot();
+
+ if (reciper) {
+ setutmpmode(SMAIL);
+ if (title)
+ do_reply_title(2, title);
+ else {
+ getdata(2, 0, "主題:", fpath, sizeof(fpath), DOECHO);
+ snprintf(save_title, sizeof(save_title), "[通告] %s", fpath);
+ }
+
+ setuserfile(fpath, fn_notes);
+
+ if ((fp = fopen(fpath, "w"))) {
+ fprintf(fp, "※ [通告] 共 %d 人收件", reciper);
+ listing = 80;
+
+ for (p = toplev; p; p = p->next) {
+ reciper = strlen(p->word) + 1;
+ if (listing + reciper > 75) {
+ listing = reciper;
+ fprintf(fp, "\n※");
+ } else
+ listing += reciper;
+
+ fprintf(fp, " %s", p->word);
+ }
+ memset(genbuf, '-', 75);
+ genbuf[75] = '\0';
+ fprintf(fp, "\n%s\n\n", genbuf);
+ fclose(fp);
+ }
+ curredit |= EDIT_LIST;
+
+ if (vedit(fpath, YEA, NULL) == -1) {
+ unlink(fpath);
+ curredit = 0;
+ vmsg(msg_cancel);
+ return;
+ }
+ listing = 80;
+
+ for (p = toplev; p; p = p->next) {
+ reciper = strlen(p->word) + 1;
+ if (listing + reciper > 75) {
+ listing = reciper;
+ outc('\n');
+ } else {
+ listing += reciper;
+ outc(' ');
+ }
+ outs(p->word);
+ if (searchuser(p->word, p->word) && strcmp(STR_GUEST, p->word)) {
+ sethomefile(genbuf, p->word, FN_OVERRIDES);
+ if (!belong(genbuf, cuser.userid)) { // not friend, check if rejected
+ sethomefile(genbuf, p->word, FN_REJECT);
+ if (belong(genbuf, cuser.userid))
+ continue;
+ }
+ sethomepath(genbuf, p->word);
+ } else
+ continue;
+ stampfile(genbuf, &mymail);
+ unlink(genbuf);
+ Copy(fpath, genbuf);
+
+ strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner));
+ strlcpy(mymail.title, save_title, sizeof(mymail.title));
+ mymail.filemode |= FILE_MULTI; /* multi-send flag */
+ sethomedir(genbuf, p->word);
+ if (append_record_forward(genbuf, &mymail, sizeof(mymail), p->word) == -1)
+ vmsg(err_uid);
+ sendalert(p->word, ALERT_NEW_MAIL);
+ }
+ hold_mail(fpath, NULL);
+ unlink(fpath);
+ curredit = 0;
+ } else
+ vmsg(msg_cancel);
+}
+
+static int
+multi_reply(int ent, fileheader_t * fhdr, const char *direct)
+{
+ if (!fhdr || !fhdr->filename[0])
+ return DONOTHING;
+
+ if (!(fhdr->filemode & FILE_MULTI))
+ return mail_reply(ent, fhdr, direct);
+
+ stand_title("群組回信");
+ strlcpy(quote_user, fhdr->owner, sizeof(quote_user));
+ setuserfile(quote_file, fhdr->filename);
+ if (!dashf(quote_file))
+ {
+ vmsg("原檔案已消失。");
+ return FULLUPDATE;
+ }
+ multi_send(fhdr->title);
+ quote_user[0]='\0';
+ quote_file[0]='\0';
+ return FULLUPDATE;
+}
+
+int
+mail_list(void)
+{
+ stand_title("群組作業");
+ multi_send(NULL);
+ return 0;
+}
+
+int
+mail_all(void)
+{
+ FILE *fp;
+ fileheader_t mymail;
+ char fpath[TTLEN];
+ char genbuf[200];
+ int i, unum;
+ char *userid;
+
+ stand_title("給所有使用者的系統通告");
+ setutmpmode(SMAIL);
+ getdata(2, 0, "主題:", fpath, sizeof(fpath), DOECHO);
+ snprintf(save_title, sizeof(save_title),
+ "[系統通告]" ANSI_COLOR(1;32) " %s" ANSI_RESET, fpath);
+
+ setuserfile(fpath, fn_notes);
+
+ if ((fp = fopen(fpath, "w"))) {
+ fprintf(fp, "※ [" ANSI_COLOR(1) "系統通告" ANSI_RESET "] 這是封給所有使用者的信\n");
+ fprintf(fp, "-----------------------------------------------------"
+ "----------------------\n");
+ fclose(fp);
+ }
+ *quote_file = 0;
+
+ curredit |= EDIT_MAIL;
+ curredit &= ~EDIT_ITEM;
+ if (vedit(fpath, YEA, NULL) == -1) {
+ curredit = 0;
+ unlink(fpath);
+ outs(msg_cancel);
+ pressanykey();
+ return 0;
+ }
+ curredit = 0;
+
+ setutmpmode(MAILALL);
+ stand_title("寄信中...");
+
+ sethomepath(genbuf, cuser.userid);
+ stampfile(genbuf, &mymail);
+ unlink(genbuf);
+ Copy(fpath, genbuf);
+ unlink(fpath);
+ strcpy(fpath, genbuf);
+
+ strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner)); /* 站長 ID */
+ strlcpy(mymail.title, save_title, sizeof(mymail.title));
+
+ sethomedir(genbuf, cuser.userid);
+ if (append_record_forward(genbuf, &mymail, sizeof(mymail), cuser.userid) == -1)
+ outs(err_uid);
+
+ for (unum = SHM->number, i = 0; i < unum; i++) {
+ if (bad_user_id(SHM->userid[i]))
+ continue; /* Ptt */
+
+ userid = SHM->userid[i];
+ if (strcmp(userid, STR_GUEST) && strcmp(userid, "new") &&
+ strcmp(userid, cuser.userid)) {
+ sethomepath(genbuf, userid);
+ stampfile(genbuf, &mymail);
+ unlink(genbuf);
+ Copy(fpath, genbuf);
+
+ strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner));
+ strlcpy(mymail.title, save_title, sizeof(mymail.title));
+ /* mymail.filemode |= FILE_MARKED; Ptt 公告改成不會mark */
+ sethomedir(genbuf, userid);
+ if (append_record_forward(genbuf, &mymail, sizeof(mymail), userid) == -1)
+ outs(err_uid);
+ vmsgf("%*s %5d / %5d", IDLEN + 1, userid, i + 1, unum);
+ }
+ }
+ return 0;
+}
+
+int
+mail_mbox(void)
+{
+ char cmd[100];
+ fileheader_t fhdr;
+
+ snprintf(cmd, sizeof(cmd), "/tmp/%s.uu", cuser.userid);
+ snprintf(fhdr.title, sizeof(fhdr.title), "%s 私人資料", cuser.userid);
+ doforward(cmd, &fhdr, 'Z');
+ return 0;
+}
+
+static int
+m_forward(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char uid[STRLEN];
+
+ stand_title("轉達信件");
+ usercomplete(msg_uid, uid);
+ if (uid[0] == '\0')
+ return FULLUPDATE;
+
+ strlcpy(quote_user, fhdr->owner, sizeof(quote_user));
+ setuserfile(quote_file, fhdr->filename);
+ snprintf(save_title, sizeof(save_title), "%.64s (fwd)", fhdr->title);
+ move(1, 0);
+ clrtobot();
+ prints("轉信給: %s\n標 題: %s\n", uid, save_title);
+
+ switch (do_send(uid, save_title)) {
+ case -1:
+ outs(err_uid);
+ break;
+ case -2:
+ outs(msg_cancel);
+ break;
+ case -3:
+ prints("使用者 [%s] 無法收信", uid);
+ break;
+ }
+ pressanykey();
+ quote_user[0]='\0';
+ quote_file[0]='\0';
+ if (strcasecmp(uid, cuser.userid) == 0)
+ return DIRCHANGED;
+ return FULLUPDATE;
+}
+
+struct ReadNewMailArg {
+ int idc;
+ int *delmsgs;
+ int delcnt;
+ int mrd;
+};
+
+static int
+read_new_mail(void * voidfptr, void *optarg)
+{
+ fileheader_t *fptr=(fileheader_t*)voidfptr;
+ struct ReadNewMailArg *arg=(struct ReadNewMailArg*)optarg;
+ char done = NA, delete_it;
+ char fname[PATHLEN];
+ char genbuf[4];
+
+ arg->idc++;
+ // XXX fptr->filename may be invalid.
+ if (fptr->filemode || !fptr->filename[0])
+ return 0;
+ clear();
+ move(10, 0);
+ prints("您要讀來自[%s]的訊息(%s)嗎?", fptr->owner, fptr->title);
+ getdata(11, 0, "請您確定(Y/N/Q)?[Y] ", genbuf, 3, DOECHO);
+ if (genbuf[0] == 'q')
+ return QUIT;
+ if (genbuf[0] == 'n')
+ return 0;
+
+ setuserfile(fname, fptr->filename);
+ fptr->filemode |= FILE_READ;
+ if (substitute_record(currmaildir, fptr, sizeof(*fptr), arg->idc))
+ return -1;
+
+ arg->mrd = 1;
+ delete_it = NA;
+ while (!done) {
+ int more_result = more(fname, YEA);
+
+ switch (more_result) {
+ case RET_DOREPLY:
+ mail_reply(arg->idc, fptr, currmaildir);
+ return FULLUPDATE;
+ case RET_DOREPLYALL:
+ multi_reply(arg->idc, fptr, currmaildir);
+ return FULLUPDATE;
+ case RET_DORECOMMEND: // we don't accept this.
+ return FULLUPDATE;
+ case -1:
+ return READ_SKIP;
+ case 0:
+ break;
+ default:
+ return more_result;
+ }
+
+ outmsglr(MSG_MAILER, MSG_MAILER_LEN, "", 0);
+
+ switch (igetch()) {
+ case 'r':
+ case 'R':
+ mail_reply(arg->idc, fptr, currmaildir);
+ break;
+ case 'y':
+ multi_reply(arg->idc, fptr, currmaildir);
+ break;
+ case 'x':
+ m_forward(arg->idc, fptr, currmaildir);
+ break;
+ case 'd':
+ case 'D':
+ delete_it = YEA;
+ default:
+ done = YEA;
+ }
+ }
+ if (delete_it) {
+ if(arg->delcnt==1000) {
+ vmsg("一次最多刪 1000 封信");
+ return 0;
+ }
+ clear();
+ prints("刪除信件《%s》", fptr->title);
+ getdata(1, 0, msg_sure_ny, genbuf, 2, LCECHO);
+ if (genbuf[0] == 'y') {
+ if(arg->delmsgs==NULL) {
+ arg->delmsgs=(int*)malloc(sizeof(int)*1000);
+ if(arg->delmsgs==NULL) {
+ vmsg("失敗, 請洽站長");
+ return 0;
+ }
+ }
+ unlink(fname);
+ arg->delmsgs[arg->delcnt++] = arg->idc;
+
+ loadmailusage();
+ }
+ }
+ clear();
+ return 0;
+}
+
+void setmailalert()
+{
+ if(load_mailalert(cuser.userid))
+ currutmp->alerts |= ALERT_NEW_MAIL;
+ else
+ currutmp->alerts &= ~ALERT_NEW_MAIL;
+}
+int
+m_new(void)
+{
+ struct ReadNewMailArg arg;
+ clear();
+ setutmpmode(RMAIL);
+ memset(&arg, 0, sizeof(arg));
+ clear();
+ curredit |= EDIT_MAIL;
+ curredit &= ~EDIT_ITEM;
+ if (apply_record(currmaildir, read_new_mail, sizeof(fileheader_t), &arg) == -1) {
+ if(arg.delmsgs)
+ free(arg.delmsgs);
+ vmsg("沒有新信件了");
+ return -1;
+ }
+ curredit = 0;
+ setmailalert();
+ while (arg.delcnt--)
+ delete_record(currmaildir, sizeof(fileheader_t), arg.delmsgs[arg.delcnt]);
+ if(arg.delmsgs)
+ free(arg.delmsgs);
+ vmsg(arg.mrd ? "信已閱\畢" : "沒有新信件了");
+ return -1;
+}
+
+static void
+mailtitle(void)
+{
+ char buf[STRLEN];
+ int msglen = 0;
+
+ showtitle("郵件選單", BBSName);
+ prints("[←]離開[↑↓]選擇[→]閱\讀信件 [X]轉錄看板[F]轉寄站外 "
+ " [O]站外信:%s [h]求助\n"
+ ANSI_COLOR(7) " 編號 %s 作 者 信 件 標 題"
+ "",
+ REJECT_OUTTAMAIL ? ANSI_COLOR(31) "關" ANSI_RESET : "開",
+ (showmail_mode == SHOWMAIL_SUM) ? "大 小":"日 期");
+
+ /* 43 columns in length, used later. */
+ buf[0] = 0;
+
+ if (mailsumlimit)
+ {
+ /* warning: snprintf returns length "if not limited".
+ * however if this case, they should be the same. */
+
+ msglen = snprintf(buf, sizeof(buf),
+ ANSI_COLOR(32)
+ " (容量:%d/%dk %d/%d篇) ",
+ mailsum, mailsumlimit,
+ mailkeep, mailmaxkeep);
+ msglen -= strlen(ANSI_COLOR(32));
+ }
+ outslr("", 44, buf, msglen);
+ outs(ANSI_RESET);
+}
+
+static void
+maildoent(int num, fileheader_t * ent)
+{
+ char *title, *mark, *color = NULL, type = ' ';
+ char datepart[6];
+ char isonline = 0;
+
+ if (ent->filemode & FILE_MARKED)
+ {
+ type = (ent->filemode & FILE_READ) ?
+ 'm' : 'M';
+ }
+ else if (ent->filemode & FILE_REPLIED)
+ {
+ type = (ent->filemode & FILE_READ) ?
+ 'r' : 'R';
+ }
+ else
+ {
+ type = (ent->filemode & FILE_READ) ?
+ ' ' : '+';
+ }
+
+ if (TagNum && !Tagger(atoi(ent->filename + 2), 0, TAG_NIN))
+ type = 'D';
+
+ title = subject(mark = ent->title);
+ if (title == mark) {
+ color = ANSI_COLOR(1;31);
+ mark = "◇";
+ } else {
+ color = ANSI_COLOR(1;33);
+ mark = "R:";
+ }
+
+ strlcpy(datepart, ent->date, sizeof(datepart));
+
+ isonline = query_online(ent->owner);
+
+ switch(showmail_mode)
+ {
+ case SHOWMAIL_SUM:
+ {
+ /* evaluate size */
+ size_t filesz = 0;
+ char ut = 'k';
+ char buf[MAXPATHLEN];
+ struct stat st;
+
+ if( !ent->filename[0] ){
+ filesz = 0;
+ } else {
+ setuserfile(buf, ent->filename);
+ if (stat(buf, &st) >= 0) {
+ filesz = st.st_size;
+ /* find printing unit */
+ filesz = (filesz + 1023) / 1024;
+ if(filesz > 9999){
+ filesz = (filesz+512) / 1024;
+ ut = 'M';
+ }
+ if(filesz > 9999) {
+ filesz = (filesz+512) / 1024;
+ ut = 'G';
+ }
+ }
+ }
+ sprintf(datepart, "%4lu%c", (unsigned long)filesz, ut);
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* print out */
+ if (strncmp(currtitle, title, TTLEN) != 0)
+ {
+ /* is title. */
+ color = "";
+ }
+
+ prints("%6d %c %-6s%s%-15.14s%s%s %s%-*.*s%s\n",
+ num, type, datepart,
+ isonline ? ANSI_COLOR(1) : "",
+ ent->owner,
+ isonline ? ANSI_RESET : "",
+ mark, color,
+ t_columns - 34, t_columns - 34,
+ title,
+ *color ? ANSI_RESET : "");
+}
+
+
+static int
+mail_del(int ent, const fileheader_t * fhdr, const char *direct)
+{
+ char genbuf[200];
+
+ if (fhdr->filemode & FILE_MARKED)
+ return DONOTHING;
+
+ if (currmode & MODE_SELECT) {
+ vmsg("請先回到正常模式後再進行刪除...");
+ return READ_REDRAW;
+ }
+
+ if (getans(msg_del_ny) == 'y') {
+ if (!delete_record(direct, sizeof(*fhdr), ent)) {
+ setupmailusage();
+ setdirpath(genbuf, direct, fhdr->filename);
+#ifdef USE_RECYCLE
+ RcyAddFile(fhdr, 0, genbuf);
+#endif // USE_RECYCLE
+ unlink(genbuf);
+ loadmailusage();
+ return DIRCHANGED;
+ }
+ }
+ return READ_REDRAW;
+}
+
+int b_call_in(int ent, const fileheader_t * fhdr, const char *direct);
+
+static int
+mail_read(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char buf[PATHLEN];
+ char done, delete_it, replied;
+
+ clear();
+ setdirpath(buf, direct, fhdr->filename);
+ strlcpy(currtitle, subject(fhdr->title), sizeof(currtitle));
+ done = delete_it = replied = NA;
+ while (!done) {
+ int more_result = more(buf, YEA);
+
+ /* whether success or not, update flag.
+ * or users may bug about "black-hole" mails
+ * and blinking notification */
+ if( !(fhdr->filemode & FILE_READ))
+ {
+ fhdr->filemode |= FILE_READ;
+ substitute_ref_record(direct, fhdr, ent);
+ }
+ switch (more_result) {
+ case -1:
+ /* no such file */
+ clear();
+ vmsg("此封信無內容。");
+ return FULLUPDATE;
+ case RET_DOREPLY:
+ mail_reply(ent, fhdr, direct);
+ return FULLUPDATE;
+ case RET_DOREPLYALL:
+ multi_reply(ent, fhdr, direct);
+ return FULLUPDATE;
+ case RET_DORECOMMEND: // we don't accept this.
+ return FULLUPDATE;
+ case 0:
+ break;
+ default:
+ return more_result;
+ }
+ outmsglr(MSG_MAILER, MSG_MAILER_LEN, "", 0);
+
+ switch (igetch()) {
+ case 'r':
+ case 'R':
+ replied = YEA;
+ mail_reply(ent, fhdr, direct);
+ break;
+ case 'y':
+ multi_reply(ent, fhdr, direct);
+ break;
+ case 'x':
+ m_forward(ent, fhdr, direct);
+ break;
+ case 'd':
+ delete_it = YEA;
+ default:
+ done = YEA;
+ }
+ }
+ if (delete_it)
+ mail_del(ent, fhdr, direct);
+ else {
+ fhdr->filemode |= FILE_READ;
+ substitute_ref_record(direct, fhdr, ent);
+ }
+ return FULLUPDATE;
+}
+
+static int
+mail_read_all(int ent, fileheader_t * fhdr, const char *direct)
+{
+ off_t i = 0, num = 0;
+ int fd = 0;
+ fileheader_t xfhdr;
+
+ currutmp->alerts &= ~ALERT_NEW_MAIL;
+ if ((fd = open(currmaildir, O_RDWR)) < 0)
+ return DONOTHING;
+
+ if ((num = lseek(fd, 0, SEEK_END)) < 0)
+ num = 0;
+ num /= sizeof(fileheader_t);
+
+ i = num - NEWMAIL_CHECK_RANGE;
+ if (i < 0) i = 0;
+
+ if (lseek(fd, i * (off_t)sizeof(fileheader_t), SEEK_SET) < 0)
+ i = num;
+
+ for (; i < num; i++)
+ {
+ if (read(fd, &xfhdr, sizeof(xfhdr)) <= 0)
+ break;
+ if (xfhdr.filemode & FILE_READ)
+ continue;
+ xfhdr.filemode |= FILE_READ;
+ if (lseek(fd, i * (off_t)sizeof(fileheader_t), SEEK_SET) < 0)
+ break;
+ write(fd, &xfhdr, sizeof(xfhdr));
+ }
+
+ close(fd);
+ return DIRCHANGED;
+}
+
+static int
+mail_unread(int ent, fileheader_t * fhdr, const char *direct)
+{
+ // this function may cause arguments, so please specify
+ // if you want this to be enabled.
+#ifdef USE_USER_MAIL_UNREAD
+ if (fhdr && fhdr->filemode & FILE_READ)
+ {
+ fhdr->filemode &= ~FILE_READ;
+ substitute_record(direct, fhdr, ent);
+ return FULLUPDATE;
+ }
+#endif // USE_USER_MAIL_UNREAD
+ return DONOTHING;
+}
+
+/* in boards/mail 回信給原作者,轉信站亦可 */
+int
+mail_reply(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char uid[STRLEN];
+ FILE *fp;
+ char genbuf[512];
+ int oent = ent;
+
+ if (!fhdr || !fhdr->filename[0])
+ return DONOTHING;
+
+ stand_title("回 信");
+
+ /* 判斷是 boards 或 mail */
+ if (curredit & EDIT_MAIL)
+ setuserfile(quote_file, fhdr->filename);
+ else
+ setbfile(quote_file, currboard, fhdr->filename);
+
+ /* find the author */
+ strlcpy(quote_user, fhdr->owner, sizeof(quote_user));
+ if (strchr(quote_user, '.')) {
+ char *t;
+ char *strtok_pos;
+ genbuf[0] = '\0';
+ if ((fp = fopen(quote_file, "r"))) {
+ fgets(genbuf, sizeof(genbuf), fp);
+ fclose(fp);
+ }
+ t = strtok_r(genbuf, str_space, &strtok_pos);
+ if (t && (strcmp(t, str_author1)==0 || strcmp(t, str_author2)==0)
+ && (t=strtok_r(NULL, str_space, &strtok_pos)) != NULL)
+ strlcpy(uid, t, sizeof(uid));
+ else {
+ vmsg("錯誤: 找不到作者。");
+ quote_user[0]='\0';
+ quote_file[0]='\0';
+ return FULLUPDATE;
+ }
+ } else
+ strlcpy(uid, quote_user, sizeof(uid));
+
+ /* make the title */
+ do_reply_title(3, fhdr->title);
+ prints("\n收信人: %s\n標 題: %s\n", uid, save_title);
+
+ /* edit, then send the mail */
+ ent = curredit;
+ switch (do_send(uid, save_title)) {
+ case -1:
+ outs(err_uid);
+ break;
+ case -2:
+ outs(msg_cancel);
+ break;
+ case -3:
+ prints("使用者 [%s] 無法收信", uid);
+ break;
+
+ case 0:
+ /* success */
+ if ( direct && /* for board, no direct */
+ (curredit & EDIT_MAIL) &&
+ !(fhdr->filemode & FILE_REPLIED))
+ {
+ fhdr->filemode |= FILE_REPLIED;
+ substitute_ref_record(direct, fhdr, oent);
+ }
+ break;
+ }
+ curredit = ent;
+ pressanykey();
+ quote_user[0]='\0';
+ quote_file[0]='\0';
+ if (strcasecmp(uid, cuser.userid) == 0)
+ return DIRCHANGED;
+ return FULLUPDATE;
+}
+
+static int
+mail_edit(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char genbuf[200];
+
+ if (!HasUserPerm(PERM_SYSOP))
+ return DONOTHING;
+
+ setdirpath(genbuf, direct, fhdr->filename);
+ vedit(genbuf, NA, NULL);
+ return FULLUPDATE;
+}
+
+static int
+mail_nooutmail(int ent, fileheader_t * fhdr, const char *direct)
+{
+ cuser.uflag2 ^= REJ_OUTTAMAIL;
+ passwd_update(usernum, &cuser);
+ return FULLUPDATE;
+
+}
+
+static int
+mail_mark(int ent, fileheader_t * fhdr, const char *direct)
+{
+ fhdr->filemode ^= FILE_MARKED;
+
+ substitute_ref_record(direct, fhdr, ent);
+ return PART_REDRAW;
+}
+
+/* help for mail reading */
+static const char * const mail_help[] = {
+ "\0電子信箱操作說明",
+ "\01基本命令",
+ "(p/↑)(n/↓) 前一篇/下一篇文章",
+ "(P)(PgUp) 前一頁",
+ "(N)(PgDn) 下一頁",
+ "(數字鍵) 跳到第 ## 筆",
+ "($) 跳到最後一筆",
+ "(r)(→) 讀信",
+ "(R)/(y) 回信 / 群組回信",
+ "\01進階命令",
+ "(TAB) 切換顯示模式(目前有一般及顯示大小)",
+ "(O) 關閉/開啟 站外信件轉入",
+ "(c)/(z) 此信件收入私人信件夾/進入私人信件夾",
+ "(x)/(X) 轉信給其它使用者/轉錄文章到其他看板",
+ "(F)/(u) 將信傳送回您的電子信箱/水球整理寄回信箱",
+ "(d) 殺掉此信",
+ "(D) 殺掉指定範圍的信",
+ "(m) 將信標記,以防被清除",
+ "(^G) 立即重建信箱 (信箱毀損時用)",
+ "(t) 標記欲刪除信件",
+ "(^D) 刪除已標記信件",
+ NULL
+};
+
+static int
+m_help(void)
+{
+ show_help(mail_help);
+ return FULLUPDATE;
+}
+
+static int
+mail_cross_post(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char xboard[20], fname[80], xfpath[80], xtitle[80], inputbuf[10];
+ fileheader_t xfile;
+ FILE *xptr;
+ int author = 0;
+ char genbuf[200];
+ char genbuf2[4];
+
+#if 0
+ // 除非有人明白為何要先 ChekPostPerm 並修復,
+ // 否則先 disable 這段 code - 目前常造成 crash。
+ //
+ // XXX (will crash sometimes because currborad is not defined yet)
+ // 麻煩 in2 來修復這裡: 確認轉錄為何要先 CheckPostPerm
+ if (!currboard || currboard[0] == 0)
+ {
+ enter_board(DEFAULT_BOARD);
+ }
+ assert(0<=ent-1 && ent-1<MAX_BOARD);
+
+ if (!CheckPostPerm()) {
+ vmsg("對不起,您目前無法轉錄文章!");
+ return FULLUPDATE;
+ }
+#endif
+
+ move(2, 0);
+ clrtoeol();
+ if (postrecord.times > 1)
+ {
+ outs(ANSI_COLOR(1;31)
+ "請注意: 若過量重複轉錄將視為洗板,導致被開罰單停權。\n" ANSI_RESET
+ "若有特別需求請洽各板主,請他們幫你轉文。\n\n");
+ }
+ move(1, 0);
+ CompleteBoard("轉錄本文章於看板:", xboard);
+
+ if (*xboard == '\0' || !haspostperm(xboard))
+ {
+ vmsg("無法轉錄");
+ return FULLUPDATE;
+ }
+
+ /* 借用變數 */
+ ent = StringHash(fhdr->title);
+ /* 同樣 title 不管對哪個板都算 cross post , 所以不用檢查 author */
+
+ if ((ent != 0 && ent == postrecord.checksum[0])) {
+ /* 檢查 cross post 次數 */
+ if (postrecord.times++ > MAX_CROSSNUM)
+ anticrosspost();
+ } else {
+ postrecord.times = 0;
+ postrecord.last_bid = 0;
+ postrecord.checksum[0] = ent;
+ }
+
+ ent = getbnum(xboard);
+ assert(0<=ent-1 && ent-1<MAX_BOARD);
+ if (!CheckPostRestriction(ent))
+ {
+ vmsg("你不夠資深喔! (可在看板內按 i 查看限制)");
+ return FULLUPDATE;
+ }
+
+#ifdef USE_COOLDOWN
+ if(check_cooldown(&bcache[ent - 1]))
+ return READ_REDRAW;
+#endif
+
+ ent = 1;
+ if (HasUserPerm(PERM_SYSOP) || !strcmp(fhdr->owner, cuser.userid)) {
+ getdata(2, 0, "(1)原文轉載 (2)舊轉錄格式?[1] ",
+ genbuf, 3, DOECHO);
+ if (genbuf[0] != '2') {
+ ent = 0;
+ getdata(2, 0, "保留原作者名稱嗎?[Y] ", inputbuf, 3, DOECHO);
+ if (inputbuf[0] != 'n' && inputbuf[0] != 'N')
+ author = 1;
+ }
+ }
+ if (ent)
+ snprintf(xtitle, sizeof(xtitle), "[轉錄]%.66s", fhdr->title);
+ else
+ strlcpy(xtitle, fhdr->title, sizeof(xtitle));
+
+ snprintf(genbuf, sizeof(genbuf), "採用原標題《%.60s》嗎?[Y] ", xtitle);
+ getdata(2, 0, genbuf, genbuf2, sizeof(genbuf2), LCECHO);
+ if (*genbuf2 == 'n')
+ if (getdata(2, 0, "標題:", genbuf, TTLEN, DOECHO))
+ strlcpy(xtitle, genbuf, sizeof(xtitle));
+
+ getdata(2, 0, "(S)存檔 (L)站內 (Q)取消?[Q] ", genbuf, 3, LCECHO);
+ if (genbuf[0] == 'l' || genbuf[0] == 's') {
+ int currmode0 = currmode;
+
+ currmode = 0;
+ setbpath(xfpath, xboard);
+ stampfile(xfpath, &xfile);
+ if (author)
+ strlcpy(xfile.owner, fhdr->owner, sizeof(xfile.owner));
+ else
+ strlcpy(xfile.owner, cuser.userid, sizeof(xfile.owner));
+ strlcpy(xfile.title, xtitle, sizeof(xfile.title));
+ if (genbuf[0] == 'l') {
+ xfile.filemode = FILE_LOCAL;
+ }
+ setuserfile(fname, fhdr->filename);
+ {
+ const char *save_currboard;
+ xptr = fopen(xfpath, "w");
+ assert(xptr);
+
+ strlcpy(save_title, xfile.title, sizeof(save_title));
+ save_currboard = currboard;
+ currboard = xboard;
+ write_header(xptr, save_title);
+ currboard = save_currboard;
+
+ fprintf(xptr, "※ [本文轉錄自 %s 信箱]\n\n", cuser.userid);
+
+ b_suckinfile(xptr, fname);
+ addsignature(xptr, 0);
+ fclose(xptr);
+ }
+
+ setbdir(fname, xboard);
+ append_record(fname, &xfile, sizeof(xfile));
+ setbtotal(getbnum(xboard));
+
+ if (!xfile.filemode)
+ outgo_post(&xfile, xboard, cuser.userid, cuser.nickname);
+#ifdef USE_COOLDOWN
+ if (bcache[getbnum(xboard) - 1].brdattr & BRD_COOLDOWN)
+ add_cooldowntime(usernum, 5);
+ add_posttimes(usernum, 1);
+#endif
+
+ // cross-post does not add numpost.
+ outs("轉錄信件不增加文章數,敬請包涵。");
+
+ vmsg("文章轉錄完成");
+ currmode = currmode0;
+ }
+ return FULLUPDATE;
+}
+
+int
+mail_man(void)
+{
+ char buf[PATHLEN], buf1[64];
+ int mode0 = currutmp->mode;
+ int stat0 = currstat;
+
+ // TODO if someday we put things in user man...?
+ sethomeman(buf, cuser.userid);
+
+ // if user already has man directory or permission,
+ // allow entering mail-man folder.
+
+ if (!dashd(buf) && !HasUserPerm(PERM_MAILLIMIT))
+ return DONOTHING;
+
+ snprintf(buf1, sizeof(buf1), "%s 的信件夾", cuser.userid);
+ a_menu(buf1, buf, HasUserPerm(PERM_MAILLIMIT) ? 1 : 0, 0, NULL);
+ currutmp->mode = mode0;
+ currstat = stat0;
+ return FULLUPDATE;
+}
+
+// XXX BUG mail_cite 有可能會跳進 a_menu, 而 a_menu 會 check
+// currbid。 一整個糟糕的邏輯錯誤...
+static int
+mail_cite(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char fpath[PATHLEN];
+ char title[TTLEN + 1];
+ static char xboard[20] = "";
+ char buf[20];
+ int bid;
+
+ setuserfile(fpath, fhdr->filename);
+ strlcpy(title, "◇ ", sizeof(title));
+ strlcpy(title + 3, fhdr->title, sizeof(title) - 3);
+ a_copyitem(fpath, title, 0, 1);
+
+ if (cuser.userlevel >= PERM_BM) {
+ move(2, 0);
+ clrtoeol();
+ move(3, 0);
+ clrtoeol();
+ move(1, 0);
+
+ CompleteBoard(
+ HasUserPerm(PERM_MAILLIMIT) ?
+ "輸入看板名稱 (直接Enter進入私人信件夾):" :
+ "輸入看板名稱:",
+ buf);
+ if (*buf)
+ strlcpy(xboard, buf, sizeof(xboard));
+ if (*xboard && ((bid = getbnum(xboard)) > 0)){ /* XXXbid */
+ setapath(fpath, xboard);
+ setutmpmode(ANNOUNCE);
+ a_menu(xboard, fpath,
+ HasUserPerm(PERM_ALLBOARD) ? 2 : is_BM_cache(bid) ? 1 : 0,
+ bid,
+ NULL);
+ } else {
+ mail_man();
+ }
+ return FULLUPDATE;
+ } else {
+ mail_man();
+ return FULLUPDATE;
+ }
+}
+
+static int
+mail_save(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char fpath[PATHLEN];
+ char title[TTLEN + 1];
+
+ if (HasUserPerm(PERM_MAILLIMIT)) {
+ setuserfile(fpath, fhdr->filename);
+ strlcpy(title, "◇ ", sizeof(title));
+ strlcpy(title + 3, fhdr->title, sizeof(title) - 3);
+ a_copyitem(fpath, title, fhdr->owner, 1);
+ sethomeman(fpath, cuser.userid);
+ a_menu(cuser.userid, fpath, 1, 0, NULL);
+ return FULLUPDATE;
+ }
+ return DONOTHING;
+}
+
+#ifdef OUTJOBSPOOL
+static int
+mail_waterball(int ent, fileheader_t * fhdr, const char *direct)
+{
+ static char address[60] = "", cmode = 1;
+ char fname[500], genbuf[200];
+ FILE *fp;
+
+ if (!(strstr(fhdr->title, "熱線") && strstr(fhdr->title, "記錄"))) {
+ vmsg("必須是 熱線記錄 才能使用水球整理的唷!");
+ return 1;
+ }
+
+ if (!address[0])
+ strlcpy(address, cuser.email, sizeof(address));
+
+ move(b_lines - 8, 0); clrtobot();
+ outs(ANSI_COLOR(1;33;45) "★水球整理程式 " ANSI_RESET "\n"
+ "系統將會按照和不同人丟的水球各自獨立\n"
+ "於整點的時候 (尖峰時段除外) 將資料整理好寄送給您\n\n\n");
+
+ if (address[0]) {
+ snprintf(genbuf, sizeof(genbuf), "寄往 [%s] 嗎[Y/n/q]? ", address);
+ getdata(b_lines - 5, 0, genbuf, fname, 3, LCECHO);
+ if (fname[0] == 'q') {
+ outmsg("取消處理");
+ return 1;
+ }
+ if (fname[0] == 'n')
+ address[0] = '\0';
+ }
+ if (!address[0]) {
+ move(b_lines-4, 0);
+ prints( "請注意目前只支援寄往標準 e-mail 地址。\n"
+ "若想寄回此信箱請用輸入 %s.bbs@" MYHOSTNAME "\n", cuser.userid);
+
+ getdata(b_lines - 5, 0, "請輸入郵件地址:", fname, 60, DOECHO);
+ if (fname[0] && strchr(fname, '.')) {
+ strlcpy(address, fname, sizeof(address));
+ } else {
+ vmsg("地址格式不正確,取消處理");
+ return 1;
+ }
+ }
+ trim(address);
+ if (invalidaddr(address))
+ return -2;
+ move(b_lines-4, 0); clrtobot();
+
+ if( strstr(address, ".bbs") && REJECT_OUTTAMAIL ){
+ outs("\n您必須要打開接受站外信, 水球整理系統才能寄入結果\n"
+ "請麻煩到【郵件選單】按大寫 O改成接受站外信 (在右上角)\n"
+ "再重新執行本功\能 :)\n");
+ vmsg("請打開站外信, 再重新執行本功\能");
+ return FULLUPDATE;
+ }
+
+ //snprintf(fname, sizeof(fname), "%d\n", cmode);
+ outs("系統提供兩種模式: \n"
+ "模式 0: 精簡模式, 將不含顏色控制碼, 方便以純文字編輯器整理收藏\n"
+ "模式 1: 華麗模式, 包含顏色控制碼等, 方便在 bbs上直接編輯收藏\n");
+ getdata(b_lines - 1, 0, "使用模式(0/1/Q)? [1]", fname, 3, LCECHO);
+ if (fname[0] == 'Q' || fname[0] == 'q') {
+ outmsg("取消處理");
+ return FULLUPDATE;
+ }
+ cmode = (fname[0] != '0' && fname[0] != '1') ? 1 : fname[0] - '0';
+
+ snprintf(fname, sizeof(fname), BBSHOME "/jobspool/water.src.%s-%d",
+ cuser.userid, (int)now);
+ snprintf(genbuf, sizeof(genbuf), "cp " BBSHOME "/home/%c/%s/%s %s",
+ cuser.userid[0], cuser.userid, fhdr->filename, fname);
+ system(genbuf);
+ /* dirty code ;x */
+ snprintf(fname, sizeof(fname), BBSHOME "/jobspool/water.des.%s-%d",
+ cuser.userid, (int)now);
+ fp = fopen(fname, "wt");
+ assert(fp);
+ fprintf(fp, "%s\n%s\n%d\n", cuser.userid, address, cmode);
+ fclose(fp);
+ vmsg("設定完成, 系統將在下一個整點(尖峰時段除外)將資料寄給您");
+ return FULLUPDATE;
+}
+#endif
+static const onekey_t mail_comms[] = {
+ { 0, NULL }, // Ctrl('A')
+ { 0, NULL }, // Ctrl('B')
+ { 0, NULL }, // Ctrl('C')
+ { 0, NULL }, // Ctrl('D')
+ { 0, NULL }, // Ctrl('E')
+ { 0, NULL }, // Ctrl('F')
+ { 0, built_mail_index }, // Ctrl('G')
+ { 0, NULL }, // Ctrl('H')
+ { 0, toggle_showmail_mode }, // Ctrl('I')
+ { 0, NULL }, // Ctrl('J')
+ { 0, NULL }, // Ctrl('K')
+ { 0, NULL }, // Ctrl('L')
+ { 0, NULL }, // Ctrl('M')
+ { 0, NULL }, // Ctrl('N')
+ { 0, NULL }, // Ctrl('O') // DO NOT USE THIS KEY - UNIX not sending
+ { 0, NULL }, // Ctrl('P')
+ { 0, NULL }, // Ctrl('Q')
+ { 0, NULL }, // Ctrl('R')
+ { 0, NULL }, // Ctrl('S')
+ { 0, NULL }, // Ctrl('T')
+ { 0, NULL }, // Ctrl('U')
+ { 0, NULL }, // Ctrl('V')
+ { 0, NULL }, // Ctrl('W')
+ { 0, NULL }, // Ctrl('X')
+ { 0, NULL }, // Ctrl('Y')
+ { 0, NULL }, // Ctrl('Z') 26
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, // 'A' 65
+ { 0, NULL }, // 'B'
+ { 0, NULL }, // 'C'
+ { 1, del_range }, // 'D'
+ { 1, mail_edit }, // 'E'
+ { 0, NULL }, // 'F'
+ { 0, NULL }, // 'G'
+ { 0, NULL }, // 'H'
+ { 0, NULL }, // 'I'
+ { 0, NULL }, // 'J'
+ { 0, NULL }, // 'K'
+ { 0, NULL }, // 'L'
+ { 0, NULL }, // 'M'
+ { 0, NULL }, // 'N'
+ { 1, mail_nooutmail }, // 'O'
+ { 0, NULL }, // 'P'
+ { 0, NULL }, // 'Q'
+ { 1, mail_reply }, // 'R'
+ { 0, NULL }, // 'S'
+ { 1, edit_title }, // 'T'
+ { 0, NULL }, // 'U'
+ { 1, mail_unread }, // 'V'
+ { 0, NULL }, // 'W'
+ { 1, mail_cross_post }, // 'X'
+ { 0, NULL }, // 'Y'
+ { 0, NULL }, // 'Z' 90
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, // 'a' 97
+ { 0, NULL }, // 'b'
+ { 1, mail_cite }, // 'c'
+ { 1, mail_del }, // 'd'
+ { 0, NULL }, // 'e'
+ { 0, NULL }, // 'f'
+ { 0, NULL }, // 'g'
+ { 0, m_help }, // 'h'
+ { 0, NULL }, // 'i'
+ { 0, NULL }, // 'j'
+ { 0, NULL }, // 'k'
+ { 0, NULL }, // 'l'
+ { 1, mail_mark }, // 'm'
+ { 0, NULL }, // 'n'
+ { 0, NULL }, // 'o'
+ { 0, NULL }, // 'p'
+ { 0, NULL }, // 'q'
+ { 1, mail_read }, // 'r'
+ { 1, mail_save }, // 's'
+ { 0, NULL }, // 't'
+#ifdef OUTJOBSPOOL
+ { 1, mail_waterball }, // 'u'
+#else
+ { 0, NULL }, // 'u'
+#endif
+ { 0, mail_read_all }, // 'v'
+ { 1, b_call_in }, // 'w'
+ { 1, m_forward }, // 'x'
+ { 1, multi_reply }, // 'y'
+ { 0, mail_man }, // 'z' 122
+};
+
+int
+m_read(void)
+{
+ int back_bid;
+ if (get_num_records(currmaildir, sizeof(fileheader_t))) {
+ curredit = EDIT_MAIL;
+ curredit &= ~EDIT_ITEM;
+ back_bid = currbid;
+ currbid = 0;
+ i_read(RMAIL, currmaildir, mailtitle, maildoent, mail_comms, -1);
+ currbid = back_bid;
+ curredit = 0;
+ setmailalert();
+ return 0;
+ } else {
+ outs("您沒有來信");
+ return XEASY;
+ }
+}
+
+/* 寄站內信 */
+static int
+send_inner_mail(const char *fpath, const char *title, const char *receiver)
+{
+ char fname[PATHLEN];
+ fileheader_t mymail;
+ char rightid[IDLEN+1];
+
+ if (!searchuser(receiver, rightid))
+ return -2;
+
+ /* to avoid DDOS of disk */
+ sethomedir(fname, rightid);
+ if (strcmp(rightid, cuser.userid) == 0) {
+ if (chk_mailbox_limit())
+ return -4;
+ }
+
+ sethomepath(fname, rightid);
+ stampfile(fname, &mymail);
+ if (!strcmp(rightid, cuser.userid)) {
+ /* Using BBSNAME may be too loooooong. */
+ strlcpy(mymail.owner, "[站內]", sizeof(mymail.owner));
+ mymail.filemode = FILE_READ;
+ } else
+ strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner));
+ strlcpy(mymail.title, title, sizeof(mymail.title));
+ unlink(fname);
+ Copy(fpath, fname);
+ sethomedir(fname, rightid);
+ append_record_forward(fname, &mymail, sizeof(mymail), rightid);
+ sendalert(receiver, ALERT_NEW_MAIL);
+ return 0;
+}
+
+#include <netdb.h>
+#include <pwd.h>
+#include <time.h>
+
+#ifndef USE_BSMTP
+static int
+bbs_sendmail(const char *fpath, const char *title, char *receiver)
+{
+ char *ptr;
+ char genbuf[256];
+ FILE *fin, *fout;
+
+ /* 中途攔截 */
+ if ((ptr = strchr(receiver, ';'))) {
+ *ptr = '\0';
+ }
+ if ((ptr = strstr(receiver, str_mail_address)) || !strchr(receiver, '@')) {
+ char hacker[20];
+ int len;
+
+ if (strchr(receiver, '@')) {
+ len = ptr - receiver;
+ memcpy(hacker, receiver, len);
+ hacker[len] = '\0';
+ } else
+ strlcpy(hacker, receiver, sizeof(hacker));
+ return send_inner_mail(fpath, title, hacker);
+ }
+ /* Running the sendmail */
+ if (fpath == NULL) {
+ snprintf(genbuf, sizeof(genbuf),
+ "/usr/sbin/sendmail %s > /dev/null", receiver);
+ fin = fopen("etc/confirm", "r");
+ } else {
+ snprintf(genbuf, sizeof(genbuf),
+ "/usr/sbin/sendmail -f %s%s %s > /dev/null",
+ cuser.userid, str_mail_address, receiver);
+ fin = fopen(fpath, "r");
+ }
+ if (fin == NULL)
+ return -1;
+ fout = popen(genbuf, "w");
+ if (fout == NULL) {
+ fclose(fin);
+ return -1;
+ }
+
+ if (fpath)
+ fprintf(fout, "Reply-To: %s%s\nFrom: %s <%s%s>\n",
+ cuser.userid, str_mail_address,
+ cuser.nickname,
+ cuser.userid, str_mail_address);
+ fprintf(fout,"To: %s\nSubject: %s\n"
+ "Mime-Version: 1.0\r\n"
+ "Content-Type: text/plain; charset=\"big5\"\r\n"
+ "Content-Transfer-Encoding: 8bit\r\n"
+ "X-Disclaimer: " BBSNAME "對本信內容恕不負責。\n\n",
+ receiver, title);
+
+ while (fgets(genbuf, sizeof(genbuf), fin)) {
+ if (genbuf[0] == '.' && genbuf[1] == '\n')
+ fputs(". \n", fout);
+ else
+ fputs(genbuf, fout);
+ }
+ fclose(fin);
+ fprintf(fout, ".\n");
+ pclose(fout);
+ return 0;
+}
+#else /* USE_BSMTP */
+
+int
+bsmtp(const char *fpath, const char *title, const char *rcpt)
+{
+ char buf[80], *ptr;
+ time4_t chrono;
+ MailQueue mqueue;
+
+ /* check if the mail is a inner mail */
+ if ((ptr = strstr(rcpt, str_mail_address)) || !strchr(rcpt, '@')) {
+ char hacker[20];
+ int len;
+
+ if (strchr(rcpt, '@')) {
+ len = ptr - rcpt;
+ memcpy(hacker, rcpt, len);
+ hacker[len] = '\0';
+ } else
+ strlcpy(hacker, rcpt, sizeof(hacker));
+ return send_inner_mail(fpath, title, hacker);
+ }
+ chrono = now;
+
+ /* stamp the queue file */
+ strlcpy(buf, "out/", sizeof(buf));
+ for (;;) {
+ snprintf(buf + 4, sizeof(buf) - 4, "M.%d.%d.A", (int)++chrono, getpid());
+ if (!dashf(buf)) {
+ Copy(fpath, buf);
+ break;
+ }
+ }
+
+ fpath = buf;
+
+ /* setup mail queue */
+ mqueue.mailtime = chrono;
+ // XXX (unused) mqueue.method = method;
+ strlcpy(mqueue.filepath, fpath, sizeof(mqueue.filepath));
+ strlcpy(mqueue.subject, title, sizeof(mqueue.subject));
+ strlcpy(mqueue.sender, cuser.userid, sizeof(mqueue.sender));
+ strlcpy(mqueue.username, cuser.nickname, sizeof(mqueue.username));
+ strlcpy(mqueue.rcpt, rcpt, sizeof(mqueue.rcpt));
+
+ if (append_record("out/" FN_DIR, (fileheader_t *) & mqueue, sizeof(mqueue)) < 0)
+ return 0;
+ return chrono;
+}
+#endif /* USE_BSMTP */
+
+int
+doforward(const char *direct, const fileheader_t * fh, int mode)
+{
+ static char address[STRLEN] = "";
+ char fname[PATHLEN];
+ char genbuf[PATHLEN];
+ int return_no;
+
+ if (!address[0] && strcmp(cuser.email, "x") != 0)
+ strlcpy(address, cuser.email, sizeof(address));
+
+ if( mode == 'U' ){
+ vmsg("將進行 uuencode 。若您不清楚什麼是 uuencode 請改用 F轉寄。");
+ }
+ trim(address);
+
+ // if user has address and not the default 'x' (no-email)...
+ if (address[0]) {
+ snprintf(genbuf, sizeof(genbuf),
+ "確定轉寄給 [%s] 嗎(Y/N/Q)?[Y] ", address);
+ getdata(b_lines, 0, genbuf, fname, 3, LCECHO);
+
+ if (fname[0] == 'q') {
+ outmsg("取消轉寄");
+ return 1;
+ }
+ if (fname[0] == 'n')
+ address[0] = '\0';
+ }
+ if (!address[0]) {
+ do {
+ getdata(b_lines - 1, 0, "請輸入轉寄地址:", fname, 60, DOECHO);
+ if (fname[0]) {
+ if (strchr(fname, '.'))
+ strlcpy(address, fname, sizeof(address));
+ else
+ snprintf(address, sizeof(address),
+ "%s.bbs@%s", fname, MYHOSTNAME);
+ } else {
+ vmsg("取消轉寄");
+ return 1;
+ }
+ } while (mode == 'Z' && strstr(address, MYHOSTNAME));
+ }
+ /* according to our experiment, many users leave blanks */
+ trim(address);
+ if (invalidaddr(address))
+ return -2;
+
+ outmsg("正轉寄請稍候...");
+ refresh();
+
+ /* 追蹤使用者 */
+ if (HasUserPerm(PERM_LOGUSER))
+ log_user("mailforward to %s ",address);
+ if (mode == 'Z') {
+ snprintf(fname, sizeof(fname),
+ TAR_PATH " cfz /tmp/home.%s.tgz home/%c/%s; "
+ MUTT_PATH " -a /tmp/home.%s.tgz -s 'home.%s.tgz' '%s' </dev/null;"
+ "rm /tmp/home.%s.tgz",
+ cuser.userid, cuser.userid[0], cuser.userid,
+ cuser.userid, cuser.userid, address, cuser.userid);
+ system(fname);
+ return 0;
+ snprintf(fname, sizeof(fname), TAR_PATH " cfz - home/%c/%s | "
+ "/usr/bin/uuencode %s.tgz > %s",
+ cuser.userid[0], cuser.userid, cuser.userid, direct);
+ system(fname);
+ strlcpy(fname, direct, sizeof(fname));
+ } else if (mode == 'U') {
+ char tmp_buf[128];
+
+ snprintf(fname, sizeof(fname), "/tmp/bbs.uu%05d", (int)currpid);
+ snprintf(tmp_buf, sizeof(tmp_buf),
+ "/usr/bin/uuencode %s/%s uu.%05d > %s",
+ direct, fh->filename, (int)currpid, fname);
+ system(tmp_buf);
+ } else if (mode == 'F') {
+ char tmp_buf[128];
+
+ snprintf(fname, sizeof(fname), "/tmp/bbs.f%05d", (int)currpid);
+ snprintf(tmp_buf, sizeof(tmp_buf), "%s/%s", direct, fh->filename);
+ Copy(tmp_buf, fname);
+ } else
+ return -1;
+
+ return_no =
+#ifndef USE_BSMTP
+ bbs_sendmail(fname, fh->title, address);
+#else
+ bsmtp(fname, fh->title, address);
+#endif
+ unlink(fname);
+ return (return_no);
+}
+
+int
+load_mailalert(const char *userid)
+{
+ struct stat st;
+ char maildir[MAXPATHLEN];
+ int fd;
+ register int num;
+ fileheader_t my_mail;
+
+ sethomedir(maildir, userid);
+ if (!HasUserPerm(PERM_BASIC))
+ return 0;
+ if (stat(maildir, &st) < 0)
+ return 0;
+ num = st.st_size / sizeof(fileheader_t);
+ if (num <= 0)
+ return 0;
+ if (num > NEWMAIL_CHECK_RANGE)
+ num = NEWMAIL_CHECK_RANGE;
+
+ /* 看看有沒有信件還沒讀過?從檔尾回頭檢查,效率較高 */
+ if ((fd = open(maildir, O_RDONLY)) > 0) {
+ lseek(fd, st.st_size - sizeof(fileheader_t), SEEK_SET);
+ while (num--) {
+ read(fd, &my_mail, sizeof(fileheader_t));
+ if (!(my_mail.filemode & FILE_READ)) {
+ close(fd);
+ return ALERT_NEW_MAIL;
+ }
+ lseek(fd, -(off_t) 2 * sizeof(fileheader_t), SEEK_CUR);
+ }
+ close(fd);
+ }
+ return 0;
+}