From 1ff37fcf607bfc356c60d423ad05fca9d9eab190 Mon Sep 17 00:00:00 2001 From: piaip Date: Thu, 6 Mar 2008 12:09:00 +0000 Subject: - [code refine] move all registration code to register.c git-svn-id: http://opensvn.csie.org/pttbbs/trunk/pttbbs@3966 63ad8ddf-47c3-0310-b6dd-a9e9d9715204 --- mbbsd/admin.c | 1087 -------------------------------- mbbsd/register.c | 1811 +++++++++++++++++++++++++++++++++++++++++++++++++++++- mbbsd/user.c | 751 +--------------------- 3 files changed, 1813 insertions(+), 1836 deletions(-) (limited to 'mbbsd') diff --git a/mbbsd/admin.c b/mbbsd/admin.c index 0bb424db..d0efa3e1 100644 --- a/mbbsd/admin.c +++ b/mbbsd/admin.c @@ -164,9 +164,6 @@ search_key_user(const char *passwdfile, int mode) else if ((!keytype || keytype == 7) && strcasestr(user.justify, key)) keymatch = user.justify; - else if ((!keytype) && - strcasestr(user.mychicken.name, key)) - keymatch = user.mychicken.name; } if(keymatch) { @@ -1061,1090 +1058,6 @@ int make_symbolic_link_interactively(int gid) return 0; } -/* FIXME 真是一團垃圾 - * - * fdata 用了太多 magic number - * return value 應該是指 reason (return index + 1) - * ans[0] 指的是帳管選擇的「錯誤的欄位」 (Register 選單裡看到的那些) - */ -static int -auto_scan(char fdata[][STRLEN], char ans[]) -{ - int good = 0; - int count = 0; - int i; - char temp[10]; - - if (!strncmp(fdata[1], "小", 2) || strstr(fdata[1], "丫") - || strstr(fdata[1], "誰") || strstr(fdata[1], "不")) { - ans[0] = '0'; - return 1; - } - strlcpy(temp, fdata[1], 3); - - /* 疊字 */ - if (!strncmp(temp, &(fdata[1][2]), 2)) { - ans[0] = '0'; - return 1; - } - if (strlen(fdata[1]) >= 6) { - if (strstr(fdata[1], "陳水扁")) { - ans[0] = '0'; - return 1; - } - if (strstr("趙錢孫李周吳鄭王", temp)) - good++; - else if (strstr("杜顏黃林陳官余辛劉", temp)) - good++; - else if (strstr("蘇方吳呂李邵張廖應蘇", temp)) - good++; - else if (strstr("徐謝石盧施戴翁唐", temp)) - good++; - } - if (!good) - return 0; - - if (!strcmp(fdata[2], fdata[3]) || - !strcmp(fdata[2], fdata[4]) || - !strcmp(fdata[3], fdata[4])) { - ans[0] = '4'; - return 5; - } - if (strstr(fdata[2], "大")) { - if (strstr(fdata[2], "台") || strstr(fdata[2], "淡") || - strstr(fdata[2], "交") || strstr(fdata[2], "政") || - strstr(fdata[2], "清") || strstr(fdata[2], "警") || - strstr(fdata[2], "師") || strstr(fdata[2], "銘傳") || - strstr(fdata[2], "中央") || strstr(fdata[2], "成") || - strstr(fdata[2], "輔") || strstr(fdata[2], "東吳")) - good++; - } else if (strstr(fdata[2], "女中")) - good++; - - if (strstr(fdata[3], "地球") || strstr(fdata[3], "宇宙") || - strstr(fdata[3], "信箱")) { - ans[0] = '2'; - return 3; - } - if (strstr(fdata[3], "市") || strstr(fdata[3], "縣")) { - if (strstr(fdata[3], "路") || strstr(fdata[3], "街")) { - if (strstr(fdata[3], "號")) - good++; - } - } - for (i = 0; fdata[4][i]; i++) { - if (isdigit((int)fdata[4][i])) - count++; - } - - if (count <= 4) { - ans[0] = '3'; - return 4; - } else if (count >= 7) - good++; - - if (good >= 3) { - ans[0] = 'y'; - return -1; - } else - return 0; -} - -#define REJECT_REASONS (6) -#define FN_REGISTER_LOG "register.log" - -// read count entries from regsrc to a temp buffer -FILE * -pull_regform(const char *regfile, char *workfn, int count) -{ - FILE *fp = NULL; - - snprintf(workfn, PATHLEN, "%s.tmp", regfile); - if (dashf(workfn)) { - vmsg("其他 SYSOP 也在審核註冊申請單"); - return NULL; - } - - // count < 0 means unlimited pulling - Rename(regfile, workfn); - if ((fp = fopen(workfn, "r")) == NULL) { - vmsgf("系統錯誤,無法讀取註冊資料檔: %s", workfn); - return NULL; - } - return fp; -} - -// write all left in "remains" to regfn. -void -pump_regform(const char *regfn, FILE *remains) -{ - // restore trailing tickets - char buf[PATHLEN]; - FILE *fout = fopen(regfn, "at"); - if (!fout) - return; - - while (fgets(buf, sizeof(buf), remains)) - fputs(buf, fout); - fclose(fout); -} - -/* 處理 Register Form */ -// TODO XXX process someone directly, according to target_uid. -int -scan_register_form(const char *regfile, int automode, const char *target_uid) -{ - char genbuf[200]; - char *logfile = FN_REGISTER_LOG; - char *field[] = { - "uid", "name", "career", "addr", "phone", "email", NULL - }; - char *finfo[] = { - "帳號", "真實姓名", "服務單位", "目前住址", - "連絡電話", "電子郵件信箱", NULL - }; - char *reason[REJECT_REASONS+1] = { - "輸入真實姓名", - "詳填「(畢業)學校及『系』『級』」或「服務單位(含所屬縣市及職稱)」", - "填寫完整的住址資料 (含縣市名稱, 台北市請含行政區域)", - "詳填連絡電話 (含區域碼, 中間不用加 '-', '(', ')'等符號", - "精確並完整填寫註冊申請表", - "用中文填寫申請單", - NULL - }; - char *autoid = "AutoScan"; - userec_t muser; - FILE *fn, *fout, *freg; - char fdata[6][STRLEN]; - char fname[STRLEN] = "", buf[STRLEN]; - char ans[4], *ptr, *uid; - int n = 0, unum = 0, tid = 0; - int nSelf = 0, nAuto = 0; - - uid = cuser.userid; - move(2, 0); - - fn = pull_regform(regfile, fname, -1); - if (!fn) - return -1; - - while( fgets(genbuf, STRLEN, fn) ){ - memset(fdata, 0, sizeof(fdata)); - do { - if( genbuf[0] == '-' ) - break; - if ((ptr = (char *)strstr(genbuf, ": "))) { - *ptr = '\0'; - for (n = 0; field[n]; n++) { - if (strcmp(genbuf, field[n]) == 0) { - strlcpy(fdata[n], ptr + 2, sizeof(fdata[n])); - if ((ptr = (char *)strchr(fdata[n], '\n'))) - *ptr = '\0'; - } - } - } - } while( fgets(genbuf, STRLEN, fn) ); - tid ++; - - if ((unum = getuser(fdata[0], &muser)) == 0) { - move(2, 0); - clrtobot(); - outs("系統錯誤,查無此人\n\n"); - for (n = 0; field[n]; n++) - prints("%s : %s\n", finfo[n], fdata[n]); - pressanykey(); - } else { - if (automode) - uid = autoid; - - if ((!automode || !auto_scan(fdata, ans))) { - uid = cuser.userid; - - move(1, 0); - clrtobot(); - prints("帳號位置 : %d\n", unum); - user_display(&muser, 1); - move(14, 0); - prints(ANSI_COLOR(1;32) "------------- " - "請站長嚴格審核使用者資料,這是第 %d 份" - "------------" ANSI_RESET "\n", tid); - prints(" %-12s: %s\n", finfo[0], fdata[0]); -#ifdef FOREIGN_REG - prints("0.%-12s: %s%s\n", finfo[1], fdata[1], - muser.uflag2 & FOREIGN ? " (外籍)" : ""); -#else - prints("0.%-12s: %s\n", finfo[1], fdata[1]); -#endif - for (n = 2; field[n]; n++) { - prints("%d.%-12s: %s\n", n - 1, finfo[n], fdata[n]); - } - if (muser.userlevel & PERM_LOGINOK) { - ans[0] = getkey("此帳號已經完成註冊, " - "更新(Y/N/Skip)?[N] "); - if (ans[0] != 'y' && ans[0] != 's') - ans[0] = 'd'; - } else { - if (search_ulist(unum) == NULL) - { - move(b_lines, 0); clrtoeol(); - outs("是否接受此資料(Y/N/Q/Del/Skip)?[S] "); - // FIXME if the user got online here - ans[0] = igetch(); - } - else - ans[0] = 's'; - ans[0] = tolower(ans[0]); - if (ans[0] != 'y' && ans[0] != 'n' && - ans[0] != 'q' && ans[0] != 'd' && - !('0' <= ans[0] && ans[0] < ('0' + REJECT_REASONS))) - ans[0] = 's'; - ans[1] = 0; - } - nSelf++; - } else - nAuto++; - - switch (ans[0]) { - case 'q': - if ((freg = fopen(regfile, "a"))) { - for (n = 0; field[n]; n++) - fprintf(freg, "%s: %s\n", field[n], fdata[n]); - fprintf(freg, "----\n"); - while (fgets(genbuf, STRLEN, fn)) - fputs(genbuf, freg); - fclose(freg); - } - case 'd': - break; - - case '0': case '1': case '2': - case '3': case '4': case '5': - /* please confirm match REJECT_REASONS here */ - case 'n': - if (ans[0] == 'n') { - int nf = 0; - move(8, 0); - clrtobot(); - outs("請提出退回申請表原因,按 取消\n"); - for (n = 0; n < REJECT_REASONS; n++) - prints("%d) 請%s\n", n, reason[n]); - outs("\n"); // preserved for prompt - for (nf = 0; field[nf]; nf++) - prints("%s: %s\n", finfo[nf], fdata[nf]); - } else - buf[0] = ans[0]; - - if (ans[0] != 'n' || - getdata(9 + n, 0, "退回原因: ", buf, 60, DOECHO)) - if ((buf[0] - '0') >= 0 && (buf[0] - '0') < n) { - int i; - fileheader_t mhdr; - char title[128], buf1[80]; - FILE *fp; - - sethomepath(buf1, muser.userid); - stampfile(buf1, &mhdr); - strlcpy(mhdr.owner, cuser.userid, sizeof(mhdr.owner)); - strlcpy(mhdr.title, "[註冊失敗]", TTLEN); - mhdr.filemode = 0; - sethomedir(title, muser.userid); - if (append_record(title, &mhdr, sizeof(mhdr)) != -1) { - char rejfn[PATHLEN]; - fp = fopen(buf1, "w"); - - for(i = 0; buf[i] && i < sizeof(buf); i++){ - if (buf[i] >= '0' && buf[i] < '0'+n) - { - fprintf(fp, "[退回原因] 請%s\n", - reason[buf[i] - '0']); - } - } - - fclose(fp); - - // build reject file - setuserfile(rejfn, "justify.reject"); - Copy(buf1, rejfn); - } - if ((fout = fopen(logfile, "a"))) { - for (n = 0; field[n]; n++) - fprintf(fout, "%s: %s\n", field[n], fdata[n]); - fprintf(fout, "Date: %s\n", Cdate(&now)); - fprintf(fout, "Rejected: %s [%s]\n----\n", - uid, buf); - fclose(fout); - } - break; - } - move(10, 0); - clrtobot(); - outs("取消退回此註冊申請表"); - /* no break? */ - - case 's': - if ((freg = fopen(regfile, "a"))) { - for (n = 0; field[n]; n++) - fprintf(freg, "%s: %s\n", field[n], fdata[n]); - fprintf(freg, "----\n"); - fclose(freg); - } - break; - - default: - outs("以下使用者資料已經更新:\n"); - mail_muser(muser, "[註冊成功\囉]", "etc/registered"); - -#if FOREIGN_REG_DAY > 0 - if(muser.uflag2 & FOREIGN) - mail_muser(muser, "[出入境管理局]", "etc/foreign_welcome"); -#endif - - muser.userlevel |= (PERM_LOGINOK | PERM_POST); - strlcpy(muser.realname, fdata[1], sizeof(muser.realname)); - strlcpy(muser.address, fdata[3], sizeof(muser.address)); - strlcpy(muser.email, fdata[5], sizeof(muser.email)); - snprintf(genbuf, sizeof(genbuf), "%s:%s:%s", - fdata[4], fdata[2], uid); - strlcpy(muser.justify, genbuf, sizeof(muser.justify)); - - passwd_update(unum, &muser); - // XXX TODO notify users? - sendalert(muser.userid, ALERT_PWD_PERM); // force to reload perm - - sethomefile(buf, muser.userid, "justify"); - log_file(buf, LOG_CREAT, genbuf); - - if ((fout = fopen(logfile, "a"))) { - for (n = 0; field[n]; n++) - fprintf(fout, "%s: %s\n", field[n], fdata[n]); - fprintf(fout, "Date: %s\n", Cdate(&now)); - fprintf(fout, "Approved: %s\n", uid); - fprintf(fout, "----\n"); - fclose(fout); - } - sethomefile(genbuf, muser.userid, "justify.wait"); - unlink(genbuf); - break; - } - } - } - - fclose(fn); - unlink(fname); - - move(0, 0); - clrtobot(); - - move(5, 0); - prints("您審了 %d 份註冊單,AutoScan 審了 %d 份", nSelf, nAuto); - - pressanykey(); - return (0); -} - -#ifdef EXP_ADMIN_REGFORM - -#define FORMS_IN_PAGE (10) -#define REASON_LEN (60) -static const char *reasonstr[REJECT_REASONS] = { - "輸入真實姓名", - "詳填(畢業)學校『系』『級』或服務單位(含所屬縣市及職稱)", - "填寫完整的住址資料 (含縣市名稱, 台北市請含行政區域)", - "詳填連絡電話 (含區碼, 中間不加 '-', '(', ')' 等符號)", - "精確並完整填寫註冊申請表", - "用中文填寫申請單", -}; - -#define REASON_FIRSTABBREV '0' -#define REASON_IN_ABBREV(x) \ - ((x) >= REASON_FIRSTABBREV && (x) - REASON_FIRSTABBREV < REJECT_REASONS) -#define REASON_EXPANDABBREV(x) reasonstr[(x) - REASON_FIRSTABBREV] - -static void -prompt_regform_ui() -{ - move(b_lines, 0); - outs(ANSI_COLOR(30;47) " " - ANSI_COLOR(31) "y" ANSI_COLOR(30) "接受 " - ANSI_COLOR(31) "n" ANSI_COLOR(30) "拒絕 " - ANSI_COLOR(31) "d" ANSI_COLOR(30) "刪除 " - ANSI_COLOR(31) "s" ANSI_COLOR(30) "跳過 " - ANSI_COLOR(31) "u" ANSI_COLOR(30) "復原 " - " " - ANSI_COLOR(31) "0-9jk↑↓" ANSI_COLOR(30) "移動 " - ANSI_COLOR(31) "空白/PgDn" ANSI_COLOR(30) "儲存+下頁 " - " " - ANSI_COLOR(31) "q/END" ANSI_COLOR(30) "結束 " - ANSI_RESET); -} - -void -resolve_reason(char *s, int y) -{ - // should start with REASON_FIRSTABBREV - const char *reason_prompt = - " (0)真實姓名 (1)詳填系級 (2)完整住址" - " (3)詳填電話 (4)確實填寫 (5)中文填寫"; - - s[0] = 0; - move(y, 0); - outs(reason_prompt); outs("\n"); - - do { - getdata(y+1, 0, - "退回原因: ", s, REASON_LEN, DOECHO); - - // convert abbrev reasons (format: single digit, or multiple digites) - if (REASON_IN_ABBREV(s[0])) - { - if (s[1] == 0) // simple replace ment - { - strlcpy(s+2, REASON_EXPANDABBREV(s[0]), - REASON_LEN-2); - s[0] = 0xbd; // '請'[0]; - s[1] = 0xd0; // '請'[1]; - } else { - // strip until all digites - char *p = s; - while (*p) - { - if (!REASON_IN_ABBREV(*p)) - *p = ' '; - p++; - } - strip_blank(s, s); - strlcat(s, " [多重原因]", REASON_LEN); - } - } - - if (strlen(s) < 4) - { - if (vmsg("原因太短。 要取消退回嗎? (y/N): ") == 'y') - { - *s = 0; - return; - } - } - } while (strlen(s) < 4); -} - -void -regform_accept(const char *userid, const char *justify) -{ - char buf[PATHLEN]; - int unum = 0; - userec_t muser; - - unum = getuser(userid, &muser); - if (unum == 0) - return; // invalid user - - muser.userlevel |= (PERM_LOGINOK | PERM_POST); - strlcpy(muser.justify, justify, sizeof(muser.justify)); - // manual accept sets email to 'x' - strlcpy(muser.email, "x", sizeof(muser.email)); - - // handle files - sethomefile(buf, muser.userid, "justify.wait"); - unlink(buf); - sethomefile(buf, muser.userid, "justify.reject"); - unlink(buf); - sethomefile(buf, muser.userid, "justify"); - log_filef(buf, LOG_CREAT, "%s\n", muser.justify); - - // update password file - passwd_update(unum, &muser); - - // alert online users? - sendalert(muser.userid, ALERT_PWD_PERM|ALERT_PWD_JUSTIFY); // force to reload perm - -#if FOREIGN_REG_DAY > 0 - if(muser.uflag2 & FOREIGN) - mail_muser(muser, "[出入境管理局]", "etc/foreign_welcome"); - else -#endif - // last: send notification mail - mail_muser(muser, "[註冊成功\囉]", "etc/registered"); -} - -void -regform_reject(const char *userid, char *reason) -{ - char buf[PATHLEN]; - FILE *fp = NULL; - int unum = 0; - userec_t muser; - - unum = getuser(userid, &muser); - if (unum == 0) - return; // invalid user - - muser.userlevel &= ~(PERM_LOGINOK | PERM_POST); - - // handle files - sethomefile(buf, muser.userid, "justify.wait"); - unlink(buf); - - // update password file - passwd_update(unum, &muser); - - // alert notify users? - sendalert(muser.userid, ALERT_PWD_PERM); // force to reload perm - - // last: send notification - mkuserdir(muser.userid); - sethomefile(buf, muser.userid, "justify.reject"); - fp = fopen(buf, "wt"); - assert(fp); - syncnow(); - fprintf(fp, "%s 註冊失敗。\n", Cdate(&now)); - - // multiple abbrev loop - if (REASON_IN_ABBREV(reason[0])) - { - int i = 0; - for (i = 0; i < REASON_LEN && REASON_IN_ABBREV(reason[i]); i++) - fprintf(fp, "[退回原因] 請%s\n", REASON_EXPANDABBREV(reason[i])); - } else { - fprintf(fp, "[退回原因] %s\n", reason); - } - fclose(fp); - mail_muser(muser, "[註冊失敗]", buf); -} - -// TODO define and use structure instead, even in reg request file. -// -typedef struct { - // current format: - // (optional) num: unum, date - // [0] uid: xxxxx (IDLEN=12) - // [1] name: RRRRRR (20) - // [2] career: YYYYYYYYYYYYYYYYYYYYYYYYYY (40) - // [3] addr: TTTTTTTTT (50) - // [4] phone: 02DDDDDDDD (20) - // [5] email: x (50) (deprecated) - // [6] mobile: (deprecated) - // [7] ---- - // lasthost: 16 - char userid[IDLEN+1]; - - char exist; - char online; - char pad [ 5]; // IDLEN(12)+1+1+1+5=20 - - char name [20]; - char career[40]; - char addr [50]; - char phone [20]; -} RegformEntry; - -int -load_regform_entry(RegformEntry *pre, FILE *fp) -{ - char buf[STRLEN]; - char *v; - - memset(pre, 0, sizeof(RegformEntry)); - while (fgets(buf, sizeof(buf), fp)) - { - if (buf[0] == '-') - break; - buf[sizeof(buf)-1] = 0; - v = strchr(buf, ':'); - if (v == NULL) - continue; - *v++ = 0; - if (*v == ' ') v++; - chomp(v); - - if (strcmp(buf, "uid") == 0) - strlcpy(pre->userid, v, sizeof(pre->userid)); - else if (strcmp(buf, "name") == 0) - strlcpy(pre->name, v, sizeof(pre->name)); - else if (strcmp(buf, "career") == 0) - strlcpy(pre->career, v, sizeof(pre->career)); - else if (strcmp(buf, "addr") == 0) - strlcpy(pre->addr, v, sizeof(pre->addr)); - else if (strcmp(buf, "phone") == 0) - strlcpy(pre->phone, v, sizeof(pre->phone)); - } - return pre->userid[0] ? 1 : 0; -} - -int -print_regform_entry(const RegformEntry *pre, FILE *fp, int close) -{ - fprintf(fp, "uid: %s\n", pre->userid); - fprintf(fp, "name: %s\n", pre->name); - fprintf(fp, "career: %s\n", pre->career); - fprintf(fp, "addr: %s\n", pre->addr); - fprintf(fp, "phone: %s\n", pre->phone); - fprintf(fp, "email: %s\n", "x"); - if (close) - fprintf(fp, "----\n"); - return 1; -} - -int -append_regform(const RegformEntry *pre, const char *logfn, - const char *varname, const char *varval1, const char *varval2) -{ - FILE *fout = fopen(logfn, "at"); - if (!fout) - return 0; - - print_regform_entry(pre, fout, 0); - if (varname && *varname) - { - syncnow(); - fprintf(fout, "Date: %s\n", Cdate(&now)); - if (!varval1) varval1 = ""; - fprintf(fout, "%s: %s", varname, varval1); - if (varval2) fprintf(fout, " %s", varval2); - fprintf(fout, "\n"); - } - // close it - fprintf(fout, "----\n"); - fclose(fout); - return 1; -} - -// #define REGFORM_DISABLE_ONLINE_USER - -int -handle_register_form(const char *regfile, int dryrun) -{ - int unum = 0; - int yMsg = FORMS_IN_PAGE*2+1; - FILE *fp = NULL; - userec_t muser; - RegformEntry forms [FORMS_IN_PAGE]; - char ans [FORMS_IN_PAGE]; - char rejects[FORMS_IN_PAGE][REASON_LEN]; // reject reason length - char fname [PATHLEN] = ""; - char justify[REGLEN+1]; - char rsn [REASON_LEN]; - int cforms = 0, // current loaded forms - parsed = 0, // total parsed forms - ci = 0, // cursor index - ch = 0, // input key - i, blanks; - long fsz = 0, fpos = 0; - - // prepare reg tickets - if (dryrun) - { - // directly open regfile to try - fp = fopen(regfile, "rt"); - } else { - fp = pull_regform(regfile, fname, -1); - } - - if (!fp) - return 0; - - // retreieve file info - fpos = ftell(fp); - fseek(fp, 0, SEEK_END); - fsz = ftell(fp); - fseek(fp, fpos, SEEK_SET); - if (!fsz) fsz = 1; - - while (ch != 'q') - { - // initialize and prepare - memset(ans, 0, sizeof(ans)); - memset(rejects, 0, sizeof(rejects)); - memset(forms, 0, sizeof(forms)); - cforms = 0; - - // load forms - while (cforms < FORMS_IN_PAGE && load_regform_entry(&forms[cforms], fp)) - cforms++, parsed ++; - - // if no more forms then leave. - // TODO what if regform error? - if (cforms < 1) - break; - - // adjust cursor if required - if (ci >= cforms) - ci = cforms-1; - - // display them all. - clear(); - for (i = 0; i < cforms; i++) - { - // fetch user information - memset(&muser, 0, sizeof(muser)); - unum = getuser(forms[i].userid, &muser); - forms[i].exist = unum ? 1 : 0; - if (unum) forms[i].online = search_ulist(unum) ? 1 : 0; - - // if already got login level, delete by default. - if (!unum) - ans[i] = 'd'; - else { - if (muser.userlevel & PERM_LOGINOK) - ans[i] = 'd'; -#ifdef REGFORM_DISABLE_ONLINE_USER - else if (forms[i].online) - ans[i] = 's'; -#endif // REGFORM_DISABLE_ONLINE_USER - } - - // print - move(i*2, 0); - prints(" %2d%s %s%-12s " ANSI_RESET, - i+1, - (unum == 0) ? ANSI_COLOR(1;31) "D" : - ( (muser.userlevel & PERM_LOGINOK) ? - ANSI_COLOR(1;33) "Y" : -#ifdef REGFORM_DISABLE_ONLINE_USER - forms[i].online ? "s" : -#endif - "."), - forms[i].online ? ANSI_COLOR(1;35) : ANSI_COLOR(1), - forms[i].userid); - - prints( ANSI_COLOR(1;31) "%19s " - ANSI_COLOR(1;32) "%-40s" ANSI_RESET"\n", - forms[i].name, forms[i].career); - - move(i*2+1, 0); - prints(" %s %-50s%20s\n", - (muser.userlevel & PERM_NOREGCODE) ? - ANSI_COLOR(1;31) "T" ANSI_RESET : " ", - forms[i].addr, forms[i].phone); - } - - // display page info - { - char msg[STRLEN]; - fpos = ftell(fp); - if (fpos > fsz) fsz = fpos*10; - snprintf(msg, sizeof(msg), - " 已顯示 %d 份註冊單 (%2d%%) ", - parsed, (int)(fpos*100/fsz)); - prints(ANSI_COLOR(7) "\n%78s" ANSI_RESET "\n", msg); - } - - // handle user input - prompt_regform_ui(); - ch = 0; - while (ch != 'q' && ch != ' ') { - ch = cursor_key(ci*2, 0); - switch (ch) - { - // nav keys - case KEY_UP: - case 'k': - if (ci > 0) ci--; - break; - - case KEY_DOWN: - case 'j': - ch = 'j'; // go next - break; - - // quick nav (assuming to FORMS_IN_PAGE=10) - case '1': case '2': case '3': case '4': case '5': - case '6': case '7': case '8': case '9': - ci = ch - '1'; - if (ci >= cforms) ci = cforms-1; - break; - case '0': - ci = 10-1; - if (ci >= cforms) ci = cforms-1; - break; - - /* - case KEY_HOME: ci = 0; break; - case KEY_END: ci = cforms-1; break; - */ - - // abort - case KEY_END: - case 'q': - ch = 'q'; - if (getans("確定要離開了嗎? (本頁變更將不會儲存) [y/N]: ") != 'y') - { - prompt_regform_ui(); - ch = 0; - continue; - } - break; - - // prepare to go next page - case KEY_PGDN: - case ' ': - ch = ' '; - - // solving blank (undecided entries) - for (i = 0, blanks = 0; i < cforms; i++) - if (ans[i] == 0) blanks ++; - - if (!blanks) - break; - - // have more blanks - ch = getans("尚未指定的 %d 個項目要: (S跳過/y通過/n拒絕/e繼續編輯): ", - blanks); - - if (ch == 'e') - { - prompt_regform_ui(); - ch = 0; - continue; - } - if (ch == 'y') { - // do nothing. - } else if (ch == 'n') { - // query reject reason - resolve_reason(rsn, yMsg); - if (*rsn == 0) - ch = 's'; - } else ch = 's'; - - // filling answers - for (i = 0; i < cforms; i++) - { - if (ans[i] != 0) - continue; - ans[i] = ch; - if (ch != 'n') - continue; - strlcpy(rejects[i], rsn, REASON_LEN); - } - - ch = ' '; // go to page mode! - break; - - // function keys - case 'y': // accept -#ifdef REGFORM_DISABLE_ONLINE_USER - if (forms[ci].online) - { - vmsg("暫不開放審核在線上使用者。"); - break; - } -#endif - case 's': // skip - case 'd': // delete - case KEY_DEL: //delete - if (ch == KEY_DEL) ch = 'd'; - - grayout(ci*2, ci*2+1, GRAYOUT_DARK); - move_ansi(ci*2, 4); outc(ch); - ans[ci] = ch; - ch = 'j'; // go next - break; - - case 'u': // undo -#ifdef REGFORM_DISABLE_ONLINE_USER - if (forms[ci].online) - { - vmsg("暫不開放審核在線上使用者。"); - break; - } -#endif - grayout(ci*2, ci*2+1, GRAYOUT_NORM); - move_ansi(ci*2, 4); outc('.'); - ans[ci] = 0; - ch = 'j'; // go next - break; - - case 'n': // reject -#ifdef REGFORM_DISABLE_ONLINE_USER - if (forms[ci].online) - { - vmsg("暫不開放審核在線上使用者。"); - break; - } -#endif - // query for reason - resolve_reason(rejects[ci], yMsg); - prompt_regform_ui(); - - if (!rejects[ci][0]) - break; - - move(yMsg, 0); - prints("退回 %s 註冊單原因:\n %s\n", forms[ci].userid, rejects[ci]); - - // do reject - grayout(ci*2, ci*2+1, GRAYOUT_DARK); - move_ansi(ci*2, 4); outc(ch); - ans[ci] = ch; - ch = 'j'; // go next - - break; - } // switch(ch) - - // change cursor - if (ch == 'j' && ++ci >= cforms) - ci = cforms -1; - } // while(ch != QUIT/SAVE) - - // if exit, we still need to skip all read forms - if (ch == 'q') - { - for (i = 0; i < cforms; i++) - ans[i] = 's'; - } - - // page complete (save). - assert(ch == ' ' || ch == 'q'); - - // save/commit if required. - if (dryrun) - { - // prmopt for debug - clear(); - stand_title("測試模式"); - outs("您正在執行測試模式,所以剛審的註冊單並不會生效。\n" - "下面列出的是剛才您審完的結果:\n\n"); - - for (i = 0; i < cforms; i++) - { - if (ans[i] == 'y') - snprintf(justify, sizeof(justify), // build justify string - "%s:%s:%s", forms[i].phone, forms[i].career, cuser.userid); - - prints("%2d. %-12s - %c %s\n", i+1, forms[i].userid, ans[i], - ans[i] == 'n' ? rejects[i] : - ans[i] == 'y' ? justify : ""); - } - if (ch != 'q') - pressanykey(); - } - else - { - // real functionality - for (i = 0; i < cforms; i++) - { - if (ans[i] == 'y') - { - // build justify string - snprintf(justify, sizeof(justify), - "%s:%s:%s", forms[i].phone, forms[i].career, cuser.userid); - - regform_accept(forms[i].userid, justify); - // log form to FN_REGISTER_LOG - append_regform(&forms[i], FN_REGISTER_LOG, - "Approved", cuser.userid, NULL); - } - else if (ans[i] == 'n') - { - regform_reject(forms[i].userid, rejects[i]); - // log form to FN_REGISTER_LOG - append_regform(&forms[i], FN_REGISTER_LOG, - "Rejected", cuser.userid, rejects[i]); - } - else if (ans[i] == 's') - { - // append form back to fn_register - append_regform(&forms[i], fn_register, - NULL, NULL, NULL); - } - } - } // !dryrun - - } // while (ch != 'q') - - // cleaning left regforms - if (!dryrun) - { - pump_regform(regfile, fp); - fclose(fp); - unlink(fname); - } else { - // directly close file should be OK. - fclose(fp); - } - - return 0; -} - -#endif // EXP_ADMIN_REGFORM - -int -m_register(void) -{ - FILE *fn; - int x, y, wid, len; - char ans[4]; - char genbuf[200]; - - if ((fn = fopen(fn_register, "r")) == NULL) { - outs("目前並無新註冊資料"); - return XEASY; - } - stand_title("審核使用者註冊資料"); - y = 2; - x = wid = 0; - - while (fgets(genbuf, STRLEN, fn) && x < 65) { - if (strncmp(genbuf, "uid: ", 5) == 0) { - move(y++, x); - outs(genbuf + 5); - len = strlen(genbuf + 5); - if (len > wid) - wid = len; - if (y >= t_lines - 3) { - y = 2; - x += wid + 2; - } - } - } - fclose(fn); - getdata(b_lines - 1, 0, -#ifdef EXP_ADMIN_REGFORM - "開始審核嗎(Auto自動/Yes手動/No不審/Exp新界面)?[N] ", -#else - "開始審核嗎(Auto自動/Yes手動/No不審)?[N] ", -#endif - ans, sizeof(ans), LCECHO); - if (ans[0] == 'a') - scan_register_form(fn_register, 1, NULL); - else if (ans[0] == 'y') - scan_register_form(fn_register, 0, NULL); - -#ifdef EXP_ADMIN_REGFORM - else if (ans[0] == 'e') - { -#ifdef EXP_ADMIN_REGFORM_DRYRUN - int dryrun = 0; - if (getans("你要進行純測試(T)還是真的執行審核(y)?") == 'y') - { - vmsg("進入實際執行模式,所有審核動作都是真的。"); - dryrun = 0; - } else { - vmsg("測試模式。"); - dryrun = 1; - } - handle_register_form(fn_register, dryrun); -#else - // run directly. - handle_register_form(fn_register, 0); -#endif - } -#endif - - return 0; -} - -int -cat_register(void) -{ - if (system("cat register.new.tmp >> register.new") == 0 && - unlink("register.new.tmp") == 0) - vmsg("OK 嚕~~ 繼續去奮鬥吧!!"); - else - vmsg("沒辦法CAT過去呢 去檢查一下系統吧!!"); - return 0; -} - static void give_id_money(const char *user_id, int money, const char *mail_title) { diff --git a/mbbsd/register.c b/mbbsd/register.c index 8ec38316..6475c8dd 100644 --- a/mbbsd/register.c +++ b/mbbsd/register.c @@ -1,6 +1,10 @@ /* $Id$ */ #include "bbs.h" +//////////////////////////////////////////////////////////////////////////// +// Password Hash +//////////////////////////////////////////////////////////////////////////// + // prototype of crypt() char *crypt(const char *key, const char *salt); @@ -44,7 +48,50 @@ checkpasswd(const char *passwd, char *plain) return ok; } -/* 檢查 user 註冊情況 */ +//////////////////////////////////////////////////////////////////////////// +// Value Validation +//////////////////////////////////////////////////////////////////////////// +static int HaveRejectStr(const char *s, const char **rej) +{ + int i; + char *ptr, *rejectstr[] = + {"幹", "阿", "不", "你媽", "某", "笨", "呆", "..", "xx", + "你管", "管我", "猜", "天才", "超人", + "ㄅ", "ㄆ", "ㄇ", "ㄈ", "ㄉ", "ㄊ", "ㄋ", "ㄌ", "ㄍ", "ㄎ", "ㄏ", + "ㄐ", "ㄑ", "ㄒ", "ㄓ",/*"ㄔ",*/ "ㄕ", "ㄖ", "ㄗ", "ㄘ", "ㄙ", + "ㄧ", "ㄨ", "ㄩ", "ㄚ", "ㄛ", "ㄜ", "ㄝ", "ㄞ", "ㄟ", "ㄠ", "ㄡ", + "ㄢ", "ㄣ", "ㄤ", "ㄥ", "ㄦ", NULL}; + + if( rej != NULL ) + for( i = 0 ; rej[i] != NULL ; ++i ) + if( strstr(s, rej[i]) ) + return 1; + + for( i = 0 ; rejectstr[i] != NULL ; ++i ) + if( strstr(s, rejectstr[i]) ) + return 1; + + if( (ptr = strstr(s, "ㄔ")) != NULL ){ + if( ptr != s && strncmp(ptr - 1, "都市", 4) == 0 ) + return 0; + return 1; + } + return 0; +} + +static int +removespace(char *s) +{ + int i, index; + + for (i = 0, index = 0; s[i]; i++) { + if (s[i] != ' ') + s[index++] = s[i]; + } + s[index] = '\0'; + return index; +} + int bad_user_id(const char *userid) { @@ -71,6 +118,94 @@ bad_user_id(const char *userid) return 0; } +char *isvalidname(char *rname) +{ +#ifdef FOREIGN_REG + return NULL; +#else + const char *rejectstr[] = + {"肥", "胖", "豬頭", "小白", "小明", "路人", "老王", "老李", "寶貝", + "先生", "帥哥", "老頭", "小姊", "小姐", "美女", "小妹", "大頭", + "公主", "同學", "寶寶", "公子", "大頭", "小小", "小弟", "小妹", + "妹妹", "嘿", "嗯", "爺爺", "大哥", "無", + NULL}; + if( removespace(rname) && rname[0] < 0 && + strlen(rname) >= 4 && + !HaveRejectStr(rname, rejectstr) && + strncmp(rname, "小", 2) != 0 && //起頭是「小」 + strncmp(rname, "我是", 4) != 0 && //起頭是「我是」 + !(strlen(rname) == 4 && strncmp(&rname[2], "兒", 2) == 0) && + !(strlen(rname) >= 4 && strncmp(&rname[0], &rname[2], 2) == 0)) + return NULL; + return "您的輸入不正確"; +#endif + +} + +static char *isvalidcareer(char *career) +{ +#ifndef FOREIGN_REG + const char *rejectstr[] = {NULL}; + if (!(removespace(career) && career[0] < 0 && strlen(career) >= 6) || + strcmp(career, "家裡") == 0 || HaveRejectStr(career, rejectstr) ) + return "您的輸入不正確"; + if (strcmp(&career[strlen(career) - 2], "大") == 0 || + strcmp(&career[strlen(career) - 4], "大學") == 0 || + strcmp(career, "學生大學") == 0) + return "麻煩請加學校系所"; + if (strcmp(career, "學生高中") == 0) + return "麻煩輸入學校名稱"; +#else + if( strlen(career) < 6 ) + return "您的輸入不正確"; +#endif + return NULL; +} + +char *isvalidaddr(char *addr) +{ + const char *rejectstr[] = + {"地球", "銀河", "火星", NULL}; + + // addr[0] > 0: check if address is starting by Chinese. + if (!removespace(addr) || strlen(addr) < 15) + return "這個地址似乎並不完整"; + if (strstr(addr, "信箱") != NULL || strstr(addr, "郵政") != NULL) + return "抱歉我們不接受郵政信箱"; + if ((strstr(addr, "市") == NULL && strstr(addr, "巿") == NULL && + strstr(addr, "縣") == NULL && strstr(addr, "室") == NULL) || + HaveRejectStr(addr, rejectstr) || + strcmp(&addr[strlen(addr) - 2], "段") == 0 || + strcmp(&addr[strlen(addr) - 2], "路") == 0 || + strcmp(&addr[strlen(addr) - 2], "巷") == 0 || + strcmp(&addr[strlen(addr) - 2], "弄") == 0 || + strcmp(&addr[strlen(addr) - 2], "區") == 0 || + strcmp(&addr[strlen(addr) - 2], "市") == 0 || + strcmp(&addr[strlen(addr) - 2], "街") == 0 ) + return "這個地址似乎並不完整"; + return NULL; +} + +static char *isvalidphone(char *phone) +{ + int i; + for( i = 0 ; phone[i] != 0 ; ++i ) + if( !isdigit((int)phone[i]) ) + return "請不要加分隔符號"; + if (!removespace(phone) || + strlen(phone) < 9 || + strstr(phone, "00000000") != NULL || + strstr(phone, "22222222") != NULL ) { + return "這個電話號碼並不正確(請含區碼)" ; + } + return NULL; +} + + +//////////////////////////////////////////////////////////////////////////// +// Account Expiring +//////////////////////////////////////////////////////////////////////////// + /* -------------------------------- */ /* New policy for allocate new user */ /* (a) is the worst user currently */ @@ -124,6 +259,144 @@ check_and_expire_account(int uid, const userec_t * urec) return val; } +//////////////////////////////////////////////////////////////////////////// +// Regcode Support +//////////////////////////////////////////////////////////////////////////// + +#define REGCODE_INITIAL "v6" // always 2 characters + +static char * +getregfile(char *buf) +{ + // not in user's home because s/he could zip his/her home + snprintf(buf, PATHLEN, "jobspool/.regcode.%s", cuser.userid); + return buf; +} + +static char * +makeregcode(char *buf) +{ + char fpath[PATHLEN]; + int fd, i; + // prevent ambigious characters: oOlI + const char *alphabet = "qwertyuipasdfghjkzxcvbnmoQWERTYUPASDFGHJKLZXCVBNM"; + + /* generate a new regcode */ + buf[13] = 0; + buf[0] = REGCODE_INITIAL[0]; + buf[1] = REGCODE_INITIAL[1]; + for( i = 2 ; i < 13 ; ++i ) + buf[i] = alphabet[random() % strlen(alphabet)]; + + getregfile(fpath); + if( (fd = open(fpath, O_WRONLY | O_CREAT, 0600)) == -1 ){ + perror("open"); + exit(1); + } + write(fd, buf, 13); + close(fd); + + return buf; +} + +static char * +getregcode(char *buf) +{ + int fd; + char fpath[PATHLEN]; + + getregfile(fpath); + if( (fd = open(fpath, O_RDONLY)) == -1 ){ + buf[0] = 0; + return buf; + } + read(fd, buf, 13); + close(fd); + buf[13] = 0; + return buf; +} + +void +delregcodefile(void) +{ + char fpath[PATHLEN]; + getregfile(fpath); + unlink(fpath); +} + +//////////////////////////////////////////////////////////////////////////// +// Justify Utilities +//////////////////////////////////////////////////////////////////////////// + +static void +justify_wait(char *userid, char *phone, char *career, + char *rname, char *addr, char *mobile) +{ + char buf[PATHLEN]; + sethomefile(buf, userid, "justify.wait"); + if (phone[0] != 0) { + FILE* fn = fopen(buf, "w"); + assert(fn); + fprintf(fn, "%s\n%s\ndummy\n%s\n%s\n%s\n", + phone, career, rname, addr, mobile); + fclose(fn); + } +} + +static void email_justify(const userec_t *muser) +{ + char tmp[IDLEN + 1], buf[256], genbuf[256]; + /* + * It is intended to use BBSENAME instead of BBSNAME here. + * Because recently many poor users with poor mail clients + * (or evil mail servers) cannot handle/decode Chinese + * subjects (BBSNAME) correctly, so we'd like to use + * BBSENAME here to prevent subject being messed up. + * And please keep BBSENAME short or it may be truncated + * by evil mail servers. + */ + snprintf(buf, sizeof(buf), + " " BBSENAME " - [ %s ]", makeregcode(genbuf)); + + strlcpy(tmp, cuser.userid, sizeof(tmp)); + // XXX dirty, set userid=SYSOP + strlcpy(cuser.userid, str_sysop, sizeof(cuser.userid)); +#ifdef HAVEMOBILE + if (strcmp(muser->email, "m") == 0 || strcmp(muser->email, "M") == 0) + mobile_message(mobile, buf); + else +#endif + bsmtp("etc/registermail", buf, muser->email); + strlcpy(cuser.userid, tmp, sizeof(cuser.userid)); + move(20,0); + clrtobot(); + outs("我們即將寄出認證信 (您應該會在 10 分鐘內收到)\n" + "收到後您可以根據認證信標題的認證碼\n" + "輸入到 (U)ser -> (R)egister 後就可以完成註冊"); + pressanykey(); + return; +} + + +/* 使用者填寫註冊表格 */ +static void +getfield(int line, const char *info, const char *desc, char *buf, int len) +{ + char prompt[STRLEN]; + char genbuf[200]; + + // clear first + move(line+1, 0); clrtoeol(); + move(line, 0); clrtoeol(); + prints(" 原先設定:%-30.30s (%s)", buf, info); + snprintf(prompt, sizeof(prompt), " %s:", desc); + if (getdata_str(line + 1, 0, prompt, genbuf, len, DOECHO, buf)) + strcpy(buf, genbuf); + move(line+1, 0); clrtoeol(); + move(line, 0); clrtoeol(); + prints(" %s:%s", desc, buf); +} + int setupnewuser(const userec_t *user) @@ -197,8 +470,9 @@ setupnewuser(const userec_t *user) return uid; } -// checking functions (in user.c now...) -char *isvalidname(char *rname); +///////////////////////////////////////////////////////////////////////////// +// New Registration (Phase 1) +///////////////////////////////////////////////////////////////////////////// void new_register(void) @@ -460,5 +734,1536 @@ check_register(void) #endif } } + +///////////////////////////////////////////////////////////////////////////// +// User Registration (Phase 2) +///////////////////////////////////////////////////////////////////////////// + +static void +toregister(char *email, char *phone, char *career, + char *rname, char *addr, char *mobile) +{ + FILE *fn = NULL; + + justify_wait(cuser.userid, phone, career, rname, addr, mobile); + + clear(); + stand_title("認證設定"); + if (cuser.userlevel & PERM_NOREGCODE){ + strcpy(email, "x"); + goto REGFORM2; + } + move(1, 0); + outs("您好, 本站認證認證的方式有:\n" + " 1.若您有 E-Mail (本站不接受 yahoo, kimo等免費的 E-Mail)\n" + " 請輸入您的 E-Mail , 我們會寄發含有認證碼的信件給您\n" + " 收到後請到 (U)ser => (R)egister 輸入認證碼, 即可通過認證\n" + "\n" + " 2.若您沒有 E-Mail 或是一直無法收到認證信, 請輸入 x \n" + " 會有站長親自人工審核註冊資料," ANSI_COLOR(1;33) + "但注意這可能會花上數週或更多時間。" ANSI_RESET "\n" + "**********************************************************\n" + "* 注意! *\n" + "* 通常應該會在輸入完成後十分鐘內收到認證信, 若過久未收到 *\n" + "* 請到郵件垃圾桶檢查是否被當作垃圾信(SPAM)了,另外若是 *\n" + "* 輸入後發生認證碼錯誤請重填一次 E-Mail *\n" + "**********************************************************\n"); + +#ifdef HAVEMOBILE + outs(" 3.若您有手機門號且想採取手機簡訊認證的方式 , 請輸入 m \n" + " 我們將會寄發含有認證碼的簡訊給您 \n" + " 收到後請到(U)ser => (R)egister 輸入認證碼, 即可通過認證\n"); +#endif + + while (1) { + email[0] = 0; + getfield(15, "身分認證用", "E-Mail Address", email, 50); + if (strcmp(email, "x") == 0 || strcmp(email, "X") == 0) + break; +#ifdef HAVEMOBILE + else if (strcmp(email, "m") == 0 || strcmp(email, "M") == 0) { + if (isvalidmobile(mobile)) { + char yn[3]; + getdata(16, 0, "請再次確認您輸入的手機號碼正確嘛? [y/N]", + yn, sizeof(yn), LCECHO); + if (yn[0] == 'Y' || yn[0] == 'y') + break; + } else { + move(15, 0); clrtobot(); + move(17, 0); + outs("指定的手機號碼不正確," + "若您無手機門號請選擇其他方式認證"); + } + + } +#endif + else if (isvalidemail(email)) { + char yn[3]; +#ifdef USE_EMAILDB + int email_count = emaildb_check_email(email, strlen(email)); + + if (email_count < 0) { + move(15, 0); clrtobot(); + move(17, 0); + outs("暫時不允許\ email 認證註冊, 請稍後再試\n"); + pressanykey(); + return; + } else if (email_count >= EMAILDB_LIMIT) { + move(15, 0); clrtobot(); + move(17, 0); + outs("指定的 E-Mail 已註冊過多帳號, 請使用其他 E-Mail, 或輸入 x 採手動認證\n"); + outs("但注意手動認證通常會花上數週以上的時間。\n"); + } else { +#endif + getdata(16, 0, "請再次確認您輸入的 E-Mail 位置正確嘛? [y/N]", + yn, sizeof(yn), LCECHO); + if (yn[0] == 'Y' || yn[0] == 'y') + break; +#ifdef USE_EMAILDB + } +#endif + } else { + move(15, 0); clrtobot(); + move(17, 0); + outs("指定的 E-Mail 不正確, 若您無 E-Mail 請輸入 x 由站長手動認證\n"); + outs("但注意手動認證通常會花上數週以上的時間。\n"); + } + } +#ifdef USE_EMAILDB + if (emaildb_update_email(cuser.userid, strlen(cuser.userid), + email, strlen(email)) < 0) { + move(15, 0); clrtobot(); + move(17, 0); + outs("暫時不允許\ email 認證註冊, 請稍後再試\n"); + pressanykey(); + return; + } +#endif + strlcpy(cuser.email, email, sizeof(cuser.email)); + REGFORM2: + if (strcasecmp(email, "x") == 0) { /* 手動認證 */ + if ((fn = fopen(fn_register, "a"))) { + fprintf(fn, "num: %d, %s", usernum, ctime4(&now)); + fprintf(fn, "uid: %s\n", cuser.userid); + fprintf(fn, "name: %s\n", rname); + fprintf(fn, "career: %s\n", career); + fprintf(fn, "addr: %s\n", addr); + fprintf(fn, "phone: %s\n", phone); + fprintf(fn, "mobile: %s\n", mobile); + fprintf(fn, "email: %s\n", email); + fprintf(fn, "----\n"); + fclose(fn); + // save justify information + snprintf(cuser.justify, sizeof(cuser.justify), + "%s:%s:", phone, career); + } + // XXX what if we cannot open register form? + } else { + // register by mail of phone + snprintf(cuser.justify, sizeof(cuser.justify), + "%s:%s:", phone, career); +#ifdef HAVEMOBILE + if (phone != NULL && email[1] == 0 && tolower(email[0]) == 'm') + sprintf(cuser.justify, sizeof(cuser.justify), + "%s:%s:", phone, career); +#endif + email_justify(&cuser); + } +} + + +int +u_register(void) +{ + char rname[20], addr[50], mobile[16]; +#ifdef FOREIGN_REG + char fore[2]; +#endif + char phone[20], career[40], email[50], birthday[11], sex_is[2]; + unsigned char year, mon, day; + char inregcode[14], regcode[50]; + char ans[3], *ptr, *errcode; + char genbuf[200]; + FILE *fn; + + if (cuser.userlevel & PERM_LOGINOK) { + outs("您的身份確認已經完成,不需填寫申請表"); + return XEASY; + } + if ((fn = fopen(fn_register, "r"))) { + int i =0; + while (fgets(genbuf, STRLEN, fn)) { + if ((ptr = strchr(genbuf, '\n'))) + *ptr = '\0'; + if (strncmp(genbuf, "uid: ", 5) != 0) + continue; + i++; + if(strcmp(genbuf + 5, cuser.userid) != 0) + continue; + fclose(fn); + /* idiots complain about this, so bug them */ + clear(); + move(3, 0); + prints(" 您的註冊申請單尚在處理中(處理順位: %d),請耐心等候\n\n", i); + outs(" 如果您已收到註冊碼卻看到這個畫面,那代表您在使用 Email 註冊後\n"); + outs(" " ANSI_COLOR(1;31) "又另外申請了站長直接人工審核的註冊申請單。" + ANSI_RESET "\n\n"); + // outs("該死,都不看說明的...\n"); + outs(" 進入人工審核程序後 Email 註冊自動失效,有註冊碼也沒用,\n"); + outs(" 要等到審核完成 (會多花很多時間,通常起碼數天) ,所以請耐心等候。\n\n"); + + /* 下面是國王的 code 所需要的 message */ +#if 0 + outs(" 另外請注意,若站長審註冊單時您正在站上則會無法審核、自動跳過。\n"); + outs(" 所以等候審核時請勿掛站。若超過兩三天仍未被審到,通常就是這個原因。\n"); +#endif + + vmsg("您的註冊申請單尚在處理中"); + return FULLUPDATE; + } + fclose(fn); + } + strlcpy(rname, cuser.realname, sizeof(rname)); + strlcpy(addr, cuser.address, sizeof(addr)); + strlcpy(email, cuser.email, sizeof(email)); + if (cuser.mobile) + snprintf(mobile, sizeof(mobile), "0%09d", cuser.mobile); + else + mobile[0] = 0; + if (cuser.month == 0 && cuser.day == 0 && cuser.year == 0) + birthday[0] = 0; + else + snprintf(birthday, sizeof(birthday), "%04i/%02i/%02i", + 1900 + cuser.year, cuser.month, cuser.day); + sex_is[0] = (cuser.sex % 8) + '1'; + sex_is[1] = 0; + career[0] = phone[0] = '\0'; + sethomefile(genbuf, cuser.userid, "justify.wait"); + if ((fn = fopen(genbuf, "r"))) { + fgets(genbuf, sizeof(genbuf), fn); + chomp(genbuf); + strlcpy(phone, genbuf, sizeof(phone)); + + fgets(genbuf, sizeof(genbuf), fn); + chomp(genbuf); + strlcpy(career, genbuf, sizeof(career)); + + fgets(genbuf, sizeof(genbuf), fn); // old version compatible + + fgets(genbuf, sizeof(genbuf), fn); + chomp(genbuf); + strlcpy(rname, genbuf, sizeof(rname)); + + fgets(genbuf, sizeof(genbuf), fn); + chomp(genbuf); + strlcpy(addr, genbuf, sizeof(addr)); + + fgets(genbuf, sizeof(genbuf), fn); + chomp(genbuf); + strlcpy(mobile, genbuf, sizeof(mobile)); + + fclose(fn); + } + + if (cuser.userlevel & PERM_NOREGCODE) { + vmsg("您不被允許\使用認證碼認證。請填寫註冊申請單"); + goto REGFORM; + } + + // getregcode(regcode); + + // XXX why check by year? + // birthday is moved to earlier, so let's check email instead. + if (cuser.email[0] && // cuser.year != 0 && /* 已經第一次填過了~ ^^" */ + strcmp(cuser.email, "x") != 0 && /* 上次手動認證失敗 */ + strcmp(cuser.email, "X") != 0) + { + clear(); + stand_title("EMail認證"); + move(2, 0); + + prints("請輸入您的認證碼。(由 %s 開頭無空白的十三碼)\n" + "或輸入 x 來重新填寫 E-Mail 或改由站長手動認證\n", REGCODE_INITIAL); + inregcode[0] = 0; + + do{ + getdata(10, 0, "您的認證碼:", + inregcode, sizeof(inregcode), DOECHO); + if( strcmp(inregcode, "x") == 0 || strcmp(inregcode, "X") == 0 ) + break; + if( strlen(inregcode) != 13 || inregcode[0] == ' ') + vmsg("認證碼輸入不完整,總共應有十三碼,沒有空白字元。"); + else if( inregcode[0] != REGCODE_INITIAL[0] || inregcode[1] != REGCODE_INITIAL[1] ) { + /* old regcode */ + vmsg("輸入的認證碼錯誤," // "或因系統昇級已失效," + "請輸入 x 重填一次 E-Mail"); + } + else + break; + } while( 1 ); + + // make it case insensitive. + if (strcasecmp(inregcode, getregcode(regcode)) == 0) { + int unum; + delregcodefile(); + if ((unum = searchuser(cuser.userid, NULL)) == 0) { + vmsg("系統錯誤,查無此人!"); + u_exit("getuser error"); + exit(0); + } + mail_muser(cuser, "[註冊成功\囉]", "etc/registeredmail"); +#if FOREIGN_REG_DAY > 0 + if(cuser.uflag2 & FOREIGN) + mail_muser(cuser, "[出入境管理局]", "etc/foreign_welcome"); +#endif + cuser.userlevel |= (PERM_LOGINOK | PERM_POST); + outs("\n註冊成功\, 重新上站後將取得完整權限\n" + "請按下任一鍵跳離後重新上站~ :)"); + sethomefile(genbuf, cuser.userid, "justify.wait"); + unlink(genbuf); + snprintf(cuser.justify, sizeof(cuser.justify), + "%s:%s:email", phone, career); + sethomefile(genbuf, cuser.userid, "justify"); + log_file(genbuf, LOG_CREAT, cuser.justify); + pressanykey(); + u_exit("registed"); + exit(0); + return QUIT; + } else if (strcasecmp(inregcode, "x") != 0) { + if (regcode[0]) + { + vmsg("認證碼錯誤!"); + return FULLUPDATE; + } + else + { + vmsg("認證碼已過期,請重新註冊。"); + toregister(email, phone, career, rname, addr, mobile); + return FULLUPDATE; + } + } else { + toregister(email, phone, career, rname, addr, mobile); + return FULLUPDATE; + } + } + + REGFORM: + getdata(b_lines - 1, 0, "您確定要填寫註冊單嗎(Y/N)?[N] ", + ans, 3, LCECHO); + if (ans[0] != 'y') + return FULLUPDATE; + + move(2, 0); + clrtobot(); + while (1) { + clear(); + move(1, 0); + prints("%s(%s) 您好,請據實填寫以下的資料:", + cuser.userid, cuser.nickname); +#ifdef FOREIGN_REG + fore[0] = 'y'; + fore[1] = 0; + getfield(2, "Y/n", "是否現在住在台灣", fore, 2); + if (fore[0] == 'n') + fore[0] |= FOREIGN; + else + fore[0] = 0; +#endif + while (1) { + getfield(8, +#ifdef FOREIGN_REG + "請用本名", +#else + "請用中文", +#endif + "真實姓名", rname, 20); + if( (errcode = isvalidname(rname)) == NULL ) + break; + else + vmsg(errcode); + } + + move(11, 0); + outs(" 請盡量詳細的填寫您的服務單位,大專院校請麻煩" + "加" ANSI_COLOR(1;33) "系所" ANSI_RESET ",公司單位請加" ANSI_COLOR(1;33) "職稱" ANSI_RESET ",\n" + " 暫無工作請麻煩填寫" ANSI_COLOR(1;33) "畢業學校" ANSI_RESET "。\n"); + while (1) { + getfield(9, "(畢業)學校(含" ANSI_COLOR(1;33) "系所年級" ANSI_RESET ")或單位職稱", + "服務單位", career, 40); + if( (errcode = isvalidcareer(career)) == NULL ) + break; + else + vmsg(errcode); + } + move(10, 0); clrtobot(); + while (1) { + getfield(10, "含" ANSI_COLOR(1;33) "縣市" ANSI_RESET "及門寢號碼" + "(台北請加" ANSI_COLOR(1;33) "行政區" ANSI_RESET ")", + "目前住址", addr, sizeof(addr)); + if( (errcode = isvalidaddr(addr)) == NULL +#ifdef FOREIGN_REG + || fore[0] +#endif + ) + break; + else + vmsg(errcode); + } + while (1) { + getfield(11, "不加-(), 包括長途區號", "連絡電話", phone, 11); + if( (errcode = isvalidphone(phone)) == NULL ) + break; + else + vmsg(errcode); + } + getfield(12, "只輸入數字 如:0912345678 (可不填)", + "手機號碼", mobile, 20); + while (1) { + getfield(13, "西元/月月/日日 如:1984/02/29", "生日", birthday, sizeof(birthday)); + if (birthday[0] == 0) { + snprintf(birthday, sizeof(birthday), "%04i/%02i/%02i", + 1900 + cuser.year, cuser.month, cuser.day); + mon = cuser.month; + day = cuser.day; + year = cuser.year; + } else { + int y, m, d; + if (ParseDate(birthday, &y, &m, &d)) { + vmsg("您的輸入不正確"); + continue; + } + mon = (unsigned char)m; + day = (unsigned char)d; + year = (unsigned char)(y - 1900); + } + if (year < 40) { + vmsg("您的輸入不正確"); + continue; + } + break; + } + getfield(14, "1.葛格 2.姐接 ", "性別", sex_is, 2); + getdata(20, 0, "以上資料是否正確(Y/N)?(Q)取消註冊 [N] ", + ans, 3, LCECHO); + if (ans[0] == 'q') + return 0; + if (ans[0] == 'y') + break; + } + strlcpy(cuser.realname, rname, sizeof(cuser.realname)); + strlcpy(cuser.address, addr, sizeof(cuser.address)); + strlcpy(cuser.email, email, sizeof(cuser.email)); + cuser.mobile = atoi(mobile); + cuser.sex = (sex_is[0] - '1') % 8; + cuser.month = mon; + cuser.day = day; + cuser.year = year; +#ifdef FOREIGN_REG + if (fore[0]) + cuser.uflag2 |= FOREIGN; + else + cuser.uflag2 &= ~FOREIGN; +#endif + trim(career); + trim(addr); + trim(phone); + + toregister(email, phone, career, rname, addr, mobile); + + // update cuser + passwd_update(usernum, &cuser); + + return FULLUPDATE; +} + +///////////////////////////////////////////////////////////////////////////// +// Administration (SYSOP Validation) +///////////////////////////////////////////////////////////////////////////// + +//////////// +/* FIXME 真是一團垃圾 + * + * fdata 用了太多 magic number + * return value 應該是指 reason (return index + 1) + * ans[0] 指的是帳管選擇的「錯誤的欄位」 (Register 選單裡看到的那些) + */ +static int +auto_scan(char fdata[][STRLEN], char ans[]) +{ + int good = 0; + int count = 0; + int i; + char temp[10]; + + if (!strncmp(fdata[1], "小", 2) || strstr(fdata[1], "丫") + || strstr(fdata[1], "誰") || strstr(fdata[1], "不")) { + ans[0] = '0'; + return 1; + } + strlcpy(temp, fdata[1], 3); + + /* 疊字 */ + if (!strncmp(temp, &(fdata[1][2]), 2)) { + ans[0] = '0'; + return 1; + } + if (strlen(fdata[1]) >= 6) { + if (strstr(fdata[1], "陳水扁")) { + ans[0] = '0'; + return 1; + } + if (strstr("趙錢孫李周吳鄭王", temp)) + good++; + else if (strstr("杜顏黃林陳官余辛劉", temp)) + good++; + else if (strstr("蘇方吳呂李邵張廖應蘇", temp)) + good++; + else if (strstr("徐謝石盧施戴翁唐", temp)) + good++; + } + if (!good) + return 0; + + if (!strcmp(fdata[2], fdata[3]) || + !strcmp(fdata[2], fdata[4]) || + !strcmp(fdata[3], fdata[4])) { + ans[0] = '4'; + return 5; + } + if (strstr(fdata[2], "大")) { + if (strstr(fdata[2], "台") || strstr(fdata[2], "淡") || + strstr(fdata[2], "交") || strstr(fdata[2], "政") || + strstr(fdata[2], "清") || strstr(fdata[2], "警") || + strstr(fdata[2], "師") || strstr(fdata[2], "銘傳") || + strstr(fdata[2], "中央") || strstr(fdata[2], "成") || + strstr(fdata[2], "輔") || strstr(fdata[2], "東吳")) + good++; + } else if (strstr(fdata[2], "女中")) + good++; + + if (strstr(fdata[3], "地球") || strstr(fdata[3], "宇宙") || + strstr(fdata[3], "信箱")) { + ans[0] = '2'; + return 3; + } + if (strstr(fdata[3], "市") || strstr(fdata[3], "縣")) { + if (strstr(fdata[3], "路") || strstr(fdata[3], "街")) { + if (strstr(fdata[3], "號")) + good++; + } + } + for (i = 0; fdata[4][i]; i++) { + if (isdigit((int)fdata[4][i])) + count++; + } + + if (count <= 4) { + ans[0] = '3'; + return 4; + } else if (count >= 7) + good++; + + if (good >= 3) { + ans[0] = 'y'; + return -1; + } else + return 0; +} + +#define REJECT_REASONS (6) +#define FN_REGISTER_LOG "register.log" + +// read count entries from regsrc to a temp buffer +FILE * +pull_regform(const char *regfile, char *workfn, int count) +{ + FILE *fp = NULL; + + snprintf(workfn, PATHLEN, "%s.tmp", regfile); + if (dashf(workfn)) { + vmsg("其他 SYSOP 也在審核註冊申請單"); + return NULL; + } + + // count < 0 means unlimited pulling + Rename(regfile, workfn); + if ((fp = fopen(workfn, "r")) == NULL) { + vmsgf("系統錯誤,無法讀取註冊資料檔: %s", workfn); + return NULL; + } + return fp; +} + +// write all left in "remains" to regfn. +void +pump_regform(const char *regfn, FILE *remains) +{ + // restore trailing tickets + char buf[PATHLEN]; + FILE *fout = fopen(regfn, "at"); + if (!fout) + return; + + while (fgets(buf, sizeof(buf), remains)) + fputs(buf, fout); + fclose(fout); +} + +/* 處理 Register Form */ +// TODO XXX process someone directly, according to target_uid. +int +scan_register_form(const char *regfile, int automode, const char *target_uid) +{ + char genbuf[200]; + char *logfile = FN_REGISTER_LOG; + char *field[] = { + "uid", "name", "career", "addr", "phone", "email", NULL + }; + char *finfo[] = { + "帳號", "真實姓名", "服務單位", "目前住址", + "連絡電話", "電子郵件信箱", NULL + }; + char *reason[REJECT_REASONS+1] = { + "輸入真實姓名", + "詳填「(畢業)學校及『系』『級』」或「服務單位(含所屬縣市及職稱)」", + "填寫完整的住址資料 (含縣市名稱, 台北市請含行政區域)", + "詳填連絡電話 (含區域碼, 中間不用加 '-', '(', ')'等符號", + "精確並完整填寫註冊申請表", + "用中文填寫申請單", + NULL + }; + char *autoid = "AutoScan"; + userec_t muser; + FILE *fn, *fout, *freg; + char fdata[6][STRLEN]; + char fname[STRLEN] = "", buf[STRLEN]; + char ans[4], *ptr, *uid; + int n = 0, unum = 0, tid = 0; + int nSelf = 0, nAuto = 0; + + uid = cuser.userid; + move(2, 0); + + fn = pull_regform(regfile, fname, -1); + if (!fn) + return -1; + + while( fgets(genbuf, STRLEN, fn) ){ + memset(fdata, 0, sizeof(fdata)); + do { + if( genbuf[0] == '-' ) + break; + if ((ptr = (char *)strstr(genbuf, ": "))) { + *ptr = '\0'; + for (n = 0; field[n]; n++) { + if (strcmp(genbuf, field[n]) == 0) { + strlcpy(fdata[n], ptr + 2, sizeof(fdata[n])); + if ((ptr = (char *)strchr(fdata[n], '\n'))) + *ptr = '\0'; + } + } + } + } while( fgets(genbuf, STRLEN, fn) ); + tid ++; + + if ((unum = getuser(fdata[0], &muser)) == 0) { + move(2, 0); + clrtobot(); + outs("系統錯誤,查無此人\n\n"); + for (n = 0; field[n]; n++) + prints("%s : %s\n", finfo[n], fdata[n]); + pressanykey(); + } else { + if (automode) + uid = autoid; + + if ((!automode || !auto_scan(fdata, ans))) { + uid = cuser.userid; + + move(1, 0); + clrtobot(); + prints("帳號位置 : %d\n", unum); + user_display(&muser, 1); + move(14, 0); + prints(ANSI_COLOR(1;32) "------------- " + "請站長嚴格審核使用者資料,這是第 %d 份" + "------------" ANSI_RESET "\n", tid); + prints(" %-12s: %s\n", finfo[0], fdata[0]); +#ifdef FOREIGN_REG + prints("0.%-12s: %s%s\n", finfo[1], fdata[1], + muser.uflag2 & FOREIGN ? " (外籍)" : ""); +#else + prints("0.%-12s: %s\n", finfo[1], fdata[1]); +#endif + for (n = 2; field[n]; n++) { + prints("%d.%-12s: %s\n", n - 1, finfo[n], fdata[n]); + } + if (muser.userlevel & PERM_LOGINOK) { + ans[0] = getkey("此帳號已經完成註冊, " + "更新(Y/N/Skip)?[N] "); + if (ans[0] != 'y' && ans[0] != 's') + ans[0] = 'd'; + } else { + if (search_ulist(unum) == NULL) + { + move(b_lines, 0); clrtoeol(); + outs("是否接受此資料(Y/N/Q/Del/Skip)?[S] "); + // FIXME if the user got online here + ans[0] = igetch(); + } + else + ans[0] = 's'; + ans[0] = tolower(ans[0]); + if (ans[0] != 'y' && ans[0] != 'n' && + ans[0] != 'q' && ans[0] != 'd' && + !('0' <= ans[0] && ans[0] < ('0' + REJECT_REASONS))) + ans[0] = 's'; + ans[1] = 0; + } + nSelf++; + } else + nAuto++; + + switch (ans[0]) { + case 'q': + if ((freg = fopen(regfile, "a"))) { + for (n = 0; field[n]; n++) + fprintf(freg, "%s: %s\n", field[n], fdata[n]); + fprintf(freg, "----\n"); + while (fgets(genbuf, STRLEN, fn)) + fputs(genbuf, freg); + fclose(freg); + } + case 'd': + break; + + case '0': case '1': case '2': + case '3': case '4': case '5': + /* please confirm match REJECT_REASONS here */ + case 'n': + if (ans[0] == 'n') { + int nf = 0; + move(8, 0); + clrtobot(); + outs("請提出退回申請表原因,按 取消\n"); + for (n = 0; n < REJECT_REASONS; n++) + prints("%d) 請%s\n", n, reason[n]); + outs("\n"); // preserved for prompt + for (nf = 0; field[nf]; nf++) + prints("%s: %s\n", finfo[nf], fdata[nf]); + } else + buf[0] = ans[0]; + + if (ans[0] != 'n' || + getdata(9 + n, 0, "退回原因: ", buf, 60, DOECHO)) + if ((buf[0] - '0') >= 0 && (buf[0] - '0') < n) { + int i; + fileheader_t mhdr; + char title[128], buf1[80]; + FILE *fp; + + sethomepath(buf1, muser.userid); + stampfile(buf1, &mhdr); + strlcpy(mhdr.owner, cuser.userid, sizeof(mhdr.owner)); + strlcpy(mhdr.title, "[註冊失敗]", TTLEN); + mhdr.filemode = 0; + sethomedir(title, muser.userid); + if (append_record(title, &mhdr, sizeof(mhdr)) != -1) { + char rejfn[PATHLEN]; + fp = fopen(buf1, "w"); + + for(i = 0; buf[i] && i < sizeof(buf); i++){ + if (buf[i] >= '0' && buf[i] < '0'+n) + { + fprintf(fp, "[退回原因] 請%s\n", + reason[buf[i] - '0']); + } + } + + fclose(fp); + + // build reject file + setuserfile(rejfn, "justify.reject"); + Copy(buf1, rejfn); + } + if ((fout = fopen(logfile, "a"))) { + for (n = 0; field[n]; n++) + fprintf(fout, "%s: %s\n", field[n], fdata[n]); + fprintf(fout, "Date: %s\n", Cdate(&now)); + fprintf(fout, "Rejected: %s [%s]\n----\n", + uid, buf); + fclose(fout); + } + break; + } + move(10, 0); + clrtobot(); + outs("取消退回此註冊申請表"); + /* no break? */ + + case 's': + if ((freg = fopen(regfile, "a"))) { + for (n = 0; field[n]; n++) + fprintf(freg, "%s: %s\n", field[n], fdata[n]); + fprintf(freg, "----\n"); + fclose(freg); + } + break; + + default: + outs("以下使用者資料已經更新:\n"); + mail_muser(muser, "[註冊成功\囉]", "etc/registered"); + +#if FOREIGN_REG_DAY > 0 + if(muser.uflag2 & FOREIGN) + mail_muser(muser, "[出入境管理局]", "etc/foreign_welcome"); +#endif + + muser.userlevel |= (PERM_LOGINOK | PERM_POST); + strlcpy(muser.realname, fdata[1], sizeof(muser.realname)); + strlcpy(muser.address, fdata[3], sizeof(muser.address)); + strlcpy(muser.email, fdata[5], sizeof(muser.email)); + snprintf(genbuf, sizeof(genbuf), "%s:%s:%s", + fdata[4], fdata[2], uid); + strlcpy(muser.justify, genbuf, sizeof(muser.justify)); + + passwd_update(unum, &muser); + // XXX TODO notify users? + sendalert(muser.userid, ALERT_PWD_PERM); // force to reload perm + + sethomefile(buf, muser.userid, "justify"); + log_file(buf, LOG_CREAT, genbuf); + + if ((fout = fopen(logfile, "a"))) { + for (n = 0; field[n]; n++) + fprintf(fout, "%s: %s\n", field[n], fdata[n]); + fprintf(fout, "Date: %s\n", Cdate(&now)); + fprintf(fout, "Approved: %s\n", uid); + fprintf(fout, "----\n"); + fclose(fout); + } + sethomefile(genbuf, muser.userid, "justify.wait"); + unlink(genbuf); + break; + } + } + } + + fclose(fn); + unlink(fname); + + move(0, 0); + clrtobot(); + + move(5, 0); + prints("您審了 %d 份註冊單,AutoScan 審了 %d 份", nSelf, nAuto); + + pressanykey(); + return (0); +} + +#ifdef EXP_ADMIN_REGFORM + +#define FORMS_IN_PAGE (10) +#define REASON_LEN (60) +static const char *reasonstr[REJECT_REASONS] = { + "輸入真實姓名", + "詳填(畢業)學校『系』『級』或服務單位(含所屬縣市及職稱)", + "填寫完整的住址資料 (含縣市名稱, 台北市請含行政區域)", + "詳填連絡電話 (含區碼, 中間不加 '-', '(', ')' 等符號)", + "精確並完整填寫註冊申請表", + "用中文填寫申請單", +}; + +#define REASON_FIRSTABBREV '0' +#define REASON_IN_ABBREV(x) \ + ((x) >= REASON_FIRSTABBREV && (x) - REASON_FIRSTABBREV < REJECT_REASONS) +#define REASON_EXPANDABBREV(x) reasonstr[(x) - REASON_FIRSTABBREV] + +static void +prompt_regform_ui() +{ + move(b_lines, 0); + outs(ANSI_COLOR(30;47) " " + ANSI_COLOR(31) "y" ANSI_COLOR(30) "接受 " + ANSI_COLOR(31) "n" ANSI_COLOR(30) "拒絕 " + ANSI_COLOR(31) "d" ANSI_COLOR(30) "刪除 " + ANSI_COLOR(31) "s" ANSI_COLOR(30) "跳過 " + ANSI_COLOR(31) "u" ANSI_COLOR(30) "復原 " + " " + ANSI_COLOR(31) "0-9jk↑↓" ANSI_COLOR(30) "移動 " + ANSI_COLOR(31) "空白/PgDn" ANSI_COLOR(30) "儲存+下頁 " + " " + ANSI_COLOR(31) "q/END" ANSI_COLOR(30) "結束 " + ANSI_RESET); +} + +void +resolve_reason(char *s, int y) +{ + // should start with REASON_FIRSTABBREV + const char *reason_prompt = + " (0)真實姓名 (1)詳填系級 (2)完整住址" + " (3)詳填電話 (4)確實填寫 (5)中文填寫"; + + s[0] = 0; + move(y, 0); + outs(reason_prompt); outs("\n"); + + do { + getdata(y+1, 0, + "退回原因: ", s, REASON_LEN, DOECHO); + + // convert abbrev reasons (format: single digit, or multiple digites) + if (REASON_IN_ABBREV(s[0])) + { + if (s[1] == 0) // simple replace ment + { + strlcpy(s+2, REASON_EXPANDABBREV(s[0]), + REASON_LEN-2); + s[0] = 0xbd; // '請'[0]; + s[1] = 0xd0; // '請'[1]; + } else { + // strip until all digites + char *p = s; + while (*p) + { + if (!REASON_IN_ABBREV(*p)) + *p = ' '; + p++; + } + strip_blank(s, s); + strlcat(s, " [多重原因]", REASON_LEN); + } + } + + if (strlen(s) < 4) + { + if (vmsg("原因太短。 要取消退回嗎? (y/N): ") == 'y') + { + *s = 0; + return; + } + } + } while (strlen(s) < 4); +} + +void +regform_accept(const char *userid, const char *justify) +{ + char buf[PATHLEN]; + int unum = 0; + userec_t muser; + + unum = getuser(userid, &muser); + if (unum == 0) + return; // invalid user + + muser.userlevel |= (PERM_LOGINOK | PERM_POST); + strlcpy(muser.justify, justify, sizeof(muser.justify)); + // manual accept sets email to 'x' + strlcpy(muser.email, "x", sizeof(muser.email)); + + // handle files + sethomefile(buf, muser.userid, "justify.wait"); + unlink(buf); + sethomefile(buf, muser.userid, "justify.reject"); + unlink(buf); + sethomefile(buf, muser.userid, "justify"); + log_filef(buf, LOG_CREAT, "%s\n", muser.justify); + + // update password file + passwd_update(unum, &muser); + + // alert online users? + sendalert(muser.userid, ALERT_PWD_PERM|ALERT_PWD_JUSTIFY); // force to reload perm + +#if FOREIGN_REG_DAY > 0 + if(muser.uflag2 & FOREIGN) + mail_muser(muser, "[出入境管理局]", "etc/foreign_welcome"); + else +#endif + // last: send notification mail + mail_muser(muser, "[註冊成功\囉]", "etc/registered"); +} + +void +regform_reject(const char *userid, char *reason) +{ + char buf[PATHLEN]; + FILE *fp = NULL; + int unum = 0; + userec_t muser; + + unum = getuser(userid, &muser); + if (unum == 0) + return; // invalid user + + muser.userlevel &= ~(PERM_LOGINOK | PERM_POST); + + // handle files + sethomefile(buf, muser.userid, "justify.wait"); + unlink(buf); + + // update password file + passwd_update(unum, &muser); + + // alert notify users? + sendalert(muser.userid, ALERT_PWD_PERM); // force to reload perm + + // last: send notification + mkuserdir(muser.userid); + sethomefile(buf, muser.userid, "justify.reject"); + fp = fopen(buf, "wt"); + assert(fp); + syncnow(); + fprintf(fp, "%s 註冊失敗。\n", Cdate(&now)); + + // multiple abbrev loop + if (REASON_IN_ABBREV(reason[0])) + { + int i = 0; + for (i = 0; i < REASON_LEN && REASON_IN_ABBREV(reason[i]); i++) + fprintf(fp, "[退回原因] 請%s\n", REASON_EXPANDABBREV(reason[i])); + } else { + fprintf(fp, "[退回原因] %s\n", reason); + } + fclose(fp); + mail_muser(muser, "[註冊失敗]", buf); +} + +// TODO define and use structure instead, even in reg request file. +// +typedef struct { + // current format: + // (optional) num: unum, date + // [0] uid: xxxxx (IDLEN=12) + // [1] name: RRRRRR (20) + // [2] career: YYYYYYYYYYYYYYYYYYYYYYYYYY (40) + // [3] addr: TTTTTTTTT (50) + // [4] phone: 02DDDDDDDD (20) + // [5] email: x (50) (deprecated) + // [6] mobile: (deprecated) + // [7] ---- + // lasthost: 16 + char userid[IDLEN+1]; + + char exist; + char online; + char pad [ 5]; // IDLEN(12)+1+1+1+5=20 + + char name [20]; + char career[40]; + char addr [50]; + char phone [20]; +} RegformEntry; + +int +load_regform_entry(RegformEntry *pre, FILE *fp) +{ + char buf[STRLEN]; + char *v; + + memset(pre, 0, sizeof(RegformEntry)); + while (fgets(buf, sizeof(buf), fp)) + { + if (buf[0] == '-') + break; + buf[sizeof(buf)-1] = 0; + v = strchr(buf, ':'); + if (v == NULL) + continue; + *v++ = 0; + if (*v == ' ') v++; + chomp(v); + + if (strcmp(buf, "uid") == 0) + strlcpy(pre->userid, v, sizeof(pre->userid)); + else if (strcmp(buf, "name") == 0) + strlcpy(pre->name, v, sizeof(pre->name)); + else if (strcmp(buf, "career") == 0) + strlcpy(pre->career, v, sizeof(pre->career)); + else if (strcmp(buf, "addr") == 0) + strlcpy(pre->addr, v, sizeof(pre->addr)); + else if (strcmp(buf, "phone") == 0) + strlcpy(pre->phone, v, sizeof(pre->phone)); + } + return pre->userid[0] ? 1 : 0; +} + +int +print_regform_entry(const RegformEntry *pre, FILE *fp, int close) +{ + fprintf(fp, "uid: %s\n", pre->userid); + fprintf(fp, "name: %s\n", pre->name); + fprintf(fp, "career: %s\n", pre->career); + fprintf(fp, "addr: %s\n", pre->addr); + fprintf(fp, "phone: %s\n", pre->phone); + fprintf(fp, "email: %s\n", "x"); + if (close) + fprintf(fp, "----\n"); + return 1; +} + +int +append_regform(const RegformEntry *pre, const char *logfn, + const char *varname, const char *varval1, const char *varval2) +{ + FILE *fout = fopen(logfn, "at"); + if (!fout) + return 0; + + print_regform_entry(pre, fout, 0); + if (varname && *varname) + { + syncnow(); + fprintf(fout, "Date: %s\n", Cdate(&now)); + if (!varval1) varval1 = ""; + fprintf(fout, "%s: %s", varname, varval1); + if (varval2) fprintf(fout, " %s", varval2); + fprintf(fout, "\n"); + } + // close it + fprintf(fout, "----\n"); + fclose(fout); + return 1; +} + +// #define REGFORM_DISABLE_ONLINE_USER + +int +handle_register_form(const char *regfile, int dryrun) +{ + int unum = 0; + int yMsg = FORMS_IN_PAGE*2+1; + FILE *fp = NULL; + userec_t muser; + RegformEntry forms [FORMS_IN_PAGE]; + char ans [FORMS_IN_PAGE]; + char rejects[FORMS_IN_PAGE][REASON_LEN]; // reject reason length + char fname [PATHLEN] = ""; + char justify[REGLEN+1]; + char rsn [REASON_LEN]; + int cforms = 0, // current loaded forms + parsed = 0, // total parsed forms + ci = 0, // cursor index + ch = 0, // input key + i, blanks; + long fsz = 0, fpos = 0; + + // prepare reg tickets + if (dryrun) + { + // directly open regfile to try + fp = fopen(regfile, "rt"); + } else { + fp = pull_regform(regfile, fname, -1); + } + + if (!fp) + return 0; + + // retreieve file info + fpos = ftell(fp); + fseek(fp, 0, SEEK_END); + fsz = ftell(fp); + fseek(fp, fpos, SEEK_SET); + if (!fsz) fsz = 1; + + while (ch != 'q') + { + // initialize and prepare + memset(ans, 0, sizeof(ans)); + memset(rejects, 0, sizeof(rejects)); + memset(forms, 0, sizeof(forms)); + cforms = 0; + + // load forms + while (cforms < FORMS_IN_PAGE && load_regform_entry(&forms[cforms], fp)) + cforms++, parsed ++; + + // if no more forms then leave. + // TODO what if regform error? + if (cforms < 1) + break; + + // adjust cursor if required + if (ci >= cforms) + ci = cforms-1; + + // display them all. + clear(); + for (i = 0; i < cforms; i++) + { + // fetch user information + memset(&muser, 0, sizeof(muser)); + unum = getuser(forms[i].userid, &muser); + forms[i].exist = unum ? 1 : 0; + if (unum) forms[i].online = search_ulist(unum) ? 1 : 0; + + // if already got login level, delete by default. + if (!unum) + ans[i] = 'd'; + else { + if (muser.userlevel & PERM_LOGINOK) + ans[i] = 'd'; +#ifdef REGFORM_DISABLE_ONLINE_USER + else if (forms[i].online) + ans[i] = 's'; +#endif // REGFORM_DISABLE_ONLINE_USER + } + + // print + move(i*2, 0); + prints(" %2d%s %s%-12s " ANSI_RESET, + i+1, + (unum == 0) ? ANSI_COLOR(1;31) "D" : + ( (muser.userlevel & PERM_LOGINOK) ? + ANSI_COLOR(1;33) "Y" : +#ifdef REGFORM_DISABLE_ONLINE_USER + forms[i].online ? "s" : +#endif + "."), + forms[i].online ? ANSI_COLOR(1;35) : ANSI_COLOR(1), + forms[i].userid); + + prints( ANSI_COLOR(1;31) "%19s " + ANSI_COLOR(1;32) "%-40s" ANSI_RESET"\n", + forms[i].name, forms[i].career); + + move(i*2+1, 0); + prints(" %s %-50s%20s\n", + (muser.userlevel & PERM_NOREGCODE) ? + ANSI_COLOR(1;31) "T" ANSI_RESET : " ", + forms[i].addr, forms[i].phone); + } + + // display page info + { + char msg[STRLEN]; + fpos = ftell(fp); + if (fpos > fsz) fsz = fpos*10; + snprintf(msg, sizeof(msg), + " 已顯示 %d 份註冊單 (%2d%%) ", + parsed, (int)(fpos*100/fsz)); + prints(ANSI_COLOR(7) "\n%78s" ANSI_RESET "\n", msg); + } + + // handle user input + prompt_regform_ui(); + ch = 0; + while (ch != 'q' && ch != ' ') { + ch = cursor_key(ci*2, 0); + switch (ch) + { + // nav keys + case KEY_UP: + case 'k': + if (ci > 0) ci--; + break; + + case KEY_DOWN: + case 'j': + ch = 'j'; // go next + break; + + // quick nav (assuming to FORMS_IN_PAGE=10) + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + ci = ch - '1'; + if (ci >= cforms) ci = cforms-1; + break; + case '0': + ci = 10-1; + if (ci >= cforms) ci = cforms-1; + break; + + /* + case KEY_HOME: ci = 0; break; + case KEY_END: ci = cforms-1; break; + */ + + // abort + case KEY_END: + case 'q': + ch = 'q'; + if (getans("確定要離開了嗎? (本頁變更將不會儲存) [y/N]: ") != 'y') + { + prompt_regform_ui(); + ch = 0; + continue; + } + break; + + // prepare to go next page + case KEY_PGDN: + case ' ': + ch = ' '; + + // solving blank (undecided entries) + for (i = 0, blanks = 0; i < cforms; i++) + if (ans[i] == 0) blanks ++; + + if (!blanks) + break; + + // have more blanks + ch = getans("尚未指定的 %d 個項目要: (S跳過/y通過/n拒絕/e繼續編輯): ", + blanks); + + if (ch == 'e') + { + prompt_regform_ui(); + ch = 0; + continue; + } + if (ch == 'y') { + // do nothing. + } else if (ch == 'n') { + // query reject reason + resolve_reason(rsn, yMsg); + if (*rsn == 0) + ch = 's'; + } else ch = 's'; + + // filling answers + for (i = 0; i < cforms; i++) + { + if (ans[i] != 0) + continue; + ans[i] = ch; + if (ch != 'n') + continue; + strlcpy(rejects[i], rsn, REASON_LEN); + } + + ch = ' '; // go to page mode! + break; + + // function keys + case 'y': // accept +#ifdef REGFORM_DISABLE_ONLINE_USER + if (forms[ci].online) + { + vmsg("暫不開放審核在線上使用者。"); + break; + } +#endif + case 's': // skip + case 'd': // delete + case KEY_DEL: //delete + if (ch == KEY_DEL) ch = 'd'; + + grayout(ci*2, ci*2+1, GRAYOUT_DARK); + move_ansi(ci*2, 4); outc(ch); + ans[ci] = ch; + ch = 'j'; // go next + break; + + case 'u': // undo +#ifdef REGFORM_DISABLE_ONLINE_USER + if (forms[ci].online) + { + vmsg("暫不開放審核在線上使用者。"); + break; + } +#endif + grayout(ci*2, ci*2+1, GRAYOUT_NORM); + move_ansi(ci*2, 4); outc('.'); + ans[ci] = 0; + ch = 'j'; // go next + break; + + case 'n': // reject +#ifdef REGFORM_DISABLE_ONLINE_USER + if (forms[ci].online) + { + vmsg("暫不開放審核在線上使用者。"); + break; + } +#endif + // query for reason + resolve_reason(rejects[ci], yMsg); + prompt_regform_ui(); + + if (!rejects[ci][0]) + break; + + move(yMsg, 0); + prints("退回 %s 註冊單原因:\n %s\n", forms[ci].userid, rejects[ci]); + + // do reject + grayout(ci*2, ci*2+1, GRAYOUT_DARK); + move_ansi(ci*2, 4); outc(ch); + ans[ci] = ch; + ch = 'j'; // go next + + break; + } // switch(ch) + + // change cursor + if (ch == 'j' && ++ci >= cforms) + ci = cforms -1; + } // while(ch != QUIT/SAVE) + + // if exit, we still need to skip all read forms + if (ch == 'q') + { + for (i = 0; i < cforms; i++) + ans[i] = 's'; + } + + // page complete (save). + assert(ch == ' ' || ch == 'q'); + + // save/commit if required. + if (dryrun) + { + // prmopt for debug + clear(); + stand_title("測試模式"); + outs("您正在執行測試模式,所以剛審的註冊單並不會生效。\n" + "下面列出的是剛才您審完的結果:\n\n"); + + for (i = 0; i < cforms; i++) + { + if (ans[i] == 'y') + snprintf(justify, sizeof(justify), // build justify string + "%s:%s:%s", forms[i].phone, forms[i].career, cuser.userid); + + prints("%2d. %-12s - %c %s\n", i+1, forms[i].userid, ans[i], + ans[i] == 'n' ? rejects[i] : + ans[i] == 'y' ? justify : ""); + } + if (ch != 'q') + pressanykey(); + } + else + { + // real functionality + for (i = 0; i < cforms; i++) + { + if (ans[i] == 'y') + { + // build justify string + snprintf(justify, sizeof(justify), + "%s:%s:%s", forms[i].phone, forms[i].career, cuser.userid); + + regform_accept(forms[i].userid, justify); + // log form to FN_REGISTER_LOG + append_regform(&forms[i], FN_REGISTER_LOG, + "Approved", cuser.userid, NULL); + } + else if (ans[i] == 'n') + { + regform_reject(forms[i].userid, rejects[i]); + // log form to FN_REGISTER_LOG + append_regform(&forms[i], FN_REGISTER_LOG, + "Rejected", cuser.userid, rejects[i]); + } + else if (ans[i] == 's') + { + // append form back to fn_register + append_regform(&forms[i], fn_register, + NULL, NULL, NULL); + } + } + } // !dryrun + + } // while (ch != 'q') + + // cleaning left regforms + if (!dryrun) + { + pump_regform(regfile, fp); + fclose(fp); + unlink(fname); + } else { + // directly close file should be OK. + fclose(fp); + } + + return 0; +} + +#endif // EXP_ADMIN_REGFORM + +int +m_register(void) +{ + FILE *fn; + int x, y, wid, len; + char ans[4]; + char genbuf[200]; + + if ((fn = fopen(fn_register, "r")) == NULL) { + outs("目前並無新註冊資料"); + return XEASY; + } + stand_title("審核使用者註冊資料"); + y = 2; + x = wid = 0; + + while (fgets(genbuf, STRLEN, fn) && x < 65) { + if (strncmp(genbuf, "uid: ", 5) == 0) { + move(y++, x); + outs(genbuf + 5); + len = strlen(genbuf + 5); + if (len > wid) + wid = len; + if (y >= t_lines - 3) { + y = 2; + x += wid + 2; + } + } + } + fclose(fn); + getdata(b_lines - 1, 0, +#ifdef EXP_ADMIN_REGFORM + "開始審核嗎(Auto自動/Yes手動/No不審/Exp新界面)?[N] ", +#else + "開始審核嗎(Auto自動/Yes手動/No不審)?[N] ", +#endif + ans, sizeof(ans), LCECHO); + if (ans[0] == 'a') + scan_register_form(fn_register, 1, NULL); + else if (ans[0] == 'y') + scan_register_form(fn_register, 0, NULL); + +#ifdef EXP_ADMIN_REGFORM + else if (ans[0] == 'e') + { +#ifdef EXP_ADMIN_REGFORM_DRYRUN + int dryrun = 0; + if (getans("你要進行純測試(T)還是真的執行審核(y)?") == 'y') + { + vmsg("進入實際執行模式,所有審核動作都是真的。"); + dryrun = 0; + } else { + vmsg("測試模式。"); + dryrun = 1; + } + handle_register_form(fn_register, dryrun); +#else + // run directly. + handle_register_form(fn_register, 0); +#endif + } +#endif + + return 0; +} + +int +cat_register(void) +{ + if (system("cat register.new.tmp >> register.new") == 0 && + unlink("register.new.tmp") == 0) + vmsg("OK 嚕~~ 繼續去奮鬥吧!!"); + else + vmsg("沒辦法CAT過去呢 去檢查一下系統吧!!"); + return 0; +} + /* vim:sw=4 */ diff --git a/mbbsd/user.c b/mbbsd/user.c index a44e500e..9c686b24 100644 --- a/mbbsd/user.c +++ b/mbbsd/user.c @@ -15,8 +15,6 @@ static const char * const chess_type[3] = { }; #endif -#define REGCODE_INITIAL "v6" // always 2 characters - int kill_user(int num, const char *userid) { @@ -525,146 +523,6 @@ void Customize(void) vmsg("設定完成"); } -static char * -getregfile(char *buf) -{ - // not in user's home because s/he could zip his/her home - snprintf(buf, PATHLEN, "jobspool/.regcode.%s", cuser.userid); - return buf; -} - -static char * -makeregcode(char *buf) -{ - char fpath[PATHLEN]; - int fd, i; - // prevent ambigious characters: oOlI - const char *alphabet = "qwertyuipasdfghjkzxcvbnmoQWERTYUPASDFGHJKLZXCVBNM"; - - /* generate a new regcode */ - buf[13] = 0; - buf[0] = REGCODE_INITIAL[0]; - buf[1] = REGCODE_INITIAL[1]; - for( i = 2 ; i < 13 ; ++i ) - buf[i] = alphabet[random() % strlen(alphabet)]; - - getregfile(fpath); - if( (fd = open(fpath, O_WRONLY | O_CREAT, 0600)) == -1 ){ - perror("open"); - exit(1); - } - write(fd, buf, 13); - close(fd); - - return buf; -} - -static char * -getregcode(char *buf) -{ - int fd; - char fpath[PATHLEN]; - - getregfile(fpath); - if( (fd = open(fpath, O_RDONLY)) == -1 ){ - buf[0] = 0; - return buf; - } - read(fd, buf, 13); - close(fd); - buf[13] = 0; - return buf; -} - -static void -delregcodefile(void) -{ - char fpath[PATHLEN]; - getregfile(fpath); - unlink(fpath); -} - -#ifdef DEBUG -int -_debug_testregcode() -{ - char buf[16], rcode[16]; - char myid[16]; - int i = 1; - - clear(); - strcpy(myid, cuser.userid); - do { - getdata(0, 0, "輸入 id (空白結束): ", - buf, IDLEN+1, DOECHO); - if(buf[0]) - { - move(i++, 0); - i %= t_lines; - if(i == 0) - i = 1; - strcpy(cuser.userid, buf); - prints("id: [%s], regcode: [%s]\n", - cuser.userid, getregcode(rcode)); - move(i, 0); - clrtoeol(); - } - } while (buf[0]); - strcpy(cuser.userid, myid); - - pressanykey(); - return 0; -} -#endif - -static void -justify_wait(char *userid, char *phone, char *career, - char *rname, char *addr, char *mobile) -{ - char buf[PATHLEN]; - sethomefile(buf, userid, "justify.wait"); - if (phone[0] != 0) { - FILE* fn = fopen(buf, "w"); - assert(fn); - fprintf(fn, "%s\n%s\ndummy\n%s\n%s\n%s\n", - phone, career, rname, addr, mobile); - fclose(fn); - } -} - -static void email_justify(const userec_t *muser) -{ - char tmp[IDLEN + 1], buf[256], genbuf[256]; - /* - * It is intended to use BBSENAME instead of BBSNAME here. - * Because recently many poor users with poor mail clients - * (or evil mail servers) cannot handle/decode Chinese - * subjects (BBSNAME) correctly, so we'd like to use - * BBSENAME here to prevent subject being messed up. - * And please keep BBSENAME short or it may be truncated - * by evil mail servers. - */ - snprintf(buf, sizeof(buf), - " " BBSENAME " - [ %s ]", makeregcode(genbuf)); - - strlcpy(tmp, cuser.userid, sizeof(tmp)); - // XXX dirty, set userid=SYSOP - strlcpy(cuser.userid, str_sysop, sizeof(cuser.userid)); -#ifdef HAVEMOBILE - if (strcmp(muser->email, "m") == 0 || strcmp(muser->email, "M") == 0) - mobile_message(mobile, buf); - else -#endif - bsmtp("etc/registermail", buf, muser->email); - strlcpy(cuser.userid, tmp, sizeof(cuser.userid)); - move(20,0); - clrtobot(); - outs("我們即將寄出認證信 (您應該會在 10 分鐘內收到)\n" - "收到後您可以根據認證信標題的認證碼\n" - "輸入到 (U)ser -> (R)egister 後就可以完成註冊"); - pressanykey(); - return; -} void uinfo_query(userec_t *u, int adminmode, int unum) @@ -696,7 +554,7 @@ uinfo_query(userec_t *u, int adminmode, int unum) memcpy(&x, u, sizeof(userec_t)); ans = getans(adminmode ? - "(1)改資料(2)密碼(3)權限(4)砍帳號(5)改ID(6)寵物(7)審判(M)信箱 [0]結束 " : + "(1)改資料(2)密碼(3)權限(4)砍帳號(5)改ID(7)審判(M)信箱 [0]結束 " : "請選擇 (1)修改資料 (2)設定密碼 (M)修改信箱 (C) 個人化設定 ==> [0]結束 "); if (ans > '2' && ans != 'm' && ans != 'c' && !adminmode) @@ -1050,6 +908,9 @@ uinfo_query(userec_t *u, int adminmode, int unum) } strlcpy(genbuf, buf, PASSLEN); + move(y+1, 0); + outs("請注意設定密碼只有前八個字元有效,超過的將自動忽略。"); + getdata(y++, 0, "請檢查新密碼:", buf, PASSLEN, NOECHO); if (strncmp(buf, genbuf, PASSLEN)) { outs("\n\n新密碼確認失敗, 無法設定新密碼\n"); @@ -1087,12 +948,6 @@ uinfo_query(userec_t *u, int adminmode, int unum) strlcpy(x.userid, genbuf, sizeof(x.userid)); } break; - case '6': - if (x.mychicken.name[0]) - x.mychicken.name[0] = 0; - else - strlcpy(x.mychicken.name, "[死]", sizeof(x.mychicken.name)); - break; default: return; } @@ -1118,22 +973,8 @@ uinfo_query(userec_t *u, int adminmode, int unum) setuserid(unum, x.userid); } if (mail_changed && !adminmode) { - char justify_tmp[REGLEN + 1]; - char *phone, *career; - char *strtok_pos; - strlcpy(justify_tmp, u->justify, sizeof(justify_tmp)); - + // wait registration. x.userlevel &= ~(PERM_LOGINOK | PERM_POST); - - phone = strtok_r(justify_tmp, ":", &strtok_pos); - career = strtok_r(NULL, ":", &strtok_pos); - - if (phone == NULL) phone = ""; - if (career == NULL) career = ""; - - snprintf(buf, sizeof(buf), "%d", x.mobile); - - justify_wait(x.userid, phone, career, x.realname, x.address, buf); } memcpy(u, &x, sizeof(x)); if (tokill) { @@ -1380,41 +1221,6 @@ u_editplan(void) return 0; } - -/* 使用者填寫註冊表格 */ -static void -getfield(int line, const char *info, const char *desc, char *buf, int len) -{ - char prompt[STRLEN]; - char genbuf[200]; - - // clear first - move(line+1, 0); clrtoeol(); - move(line, 0); clrtoeol(); - prints(" 原先設定:%-30.30s (%s)", buf, info); - snprintf(prompt, sizeof(prompt), " %s:", desc); - if (getdata_str(line + 1, 0, prompt, genbuf, len, DOECHO, buf)) - strcpy(buf, genbuf); - move(line+1, 0); clrtoeol(); - move(line, 0); clrtoeol(); - prints(" %s:%s", desc, buf); -} - -static int -removespace(char *s) -{ - int i, index; - - for (i = 0, index = 0; s[i]; i++) { - if (s[i] != ' ') - s[index++] = s[i]; - } - s[index] = '\0'; - return index; -} - - - int isvalidemail(const char *email) { @@ -1443,553 +1249,6 @@ isvalidemail(const char *email) return 1; } -static void -toregister(char *email, char *phone, char *career, - char *rname, char *addr, char *mobile) -{ - FILE *fn = NULL; - - justify_wait(cuser.userid, phone, career, rname, addr, mobile); - - clear(); - stand_title("認證設定"); - if (cuser.userlevel & PERM_NOREGCODE){ - strcpy(email, "x"); - goto REGFORM2; - } - move(1, 0); - outs("您好, 本站認證認證的方式有:\n" - " 1.若您有 E-Mail (本站不接受 yahoo, kimo等免費的 E-Mail)\n" - " 請輸入您的 E-Mail , 我們會寄發含有認證碼的信件給您\n" - " 收到後請到 (U)ser => (R)egister 輸入認證碼, 即可通過認證\n" - "\n" - " 2.若您沒有 E-Mail 或是一直無法收到認證信, 請輸入 x \n" - " 會有站長親自人工審核註冊資料," ANSI_COLOR(1;33) - "但注意這可能會花上數週或更多時間。" ANSI_RESET "\n" - "**********************************************************\n" - "* 注意! *\n" - "* 通常應該會在輸入完成後十分鐘內收到認證信, 若過久未收到 *\n" - "* 請到郵件垃圾桶檢查是否被當作垃圾信(SPAM)了,另外若是 *\n" - "* 輸入後發生認證碼錯誤請重填一次 E-Mail *\n" - "**********************************************************\n"); - -#ifdef HAVEMOBILE - outs(" 3.若您有手機門號且想採取手機簡訊認證的方式 , 請輸入 m \n" - " 我們將會寄發含有認證碼的簡訊給您 \n" - " 收到後請到(U)ser => (R)egister 輸入認證碼, 即可通過認證\n"); -#endif - - while (1) { - email[0] = 0; - getfield(15, "身分認證用", "E-Mail Address", email, 50); - if (strcmp(email, "x") == 0 || strcmp(email, "X") == 0) - break; -#ifdef HAVEMOBILE - else if (strcmp(email, "m") == 0 || strcmp(email, "M") == 0) { - if (isvalidmobile(mobile)) { - char yn[3]; - getdata(16, 0, "請再次確認您輸入的手機號碼正確嘛? [y/N]", - yn, sizeof(yn), LCECHO); - if (yn[0] == 'Y' || yn[0] == 'y') - break; - } else { - move(15, 0); clrtobot(); - move(17, 0); - outs("指定的手機號碼不正確," - "若您無手機門號請選擇其他方式認證"); - } - - } -#endif - else if (isvalidemail(email)) { - char yn[3]; -#ifdef USE_EMAILDB - int email_count = emaildb_check_email(email, strlen(email)); - - if (email_count < 0) { - move(15, 0); clrtobot(); - move(17, 0); - outs("暫時不允許\ email 認證註冊, 請稍後再試\n"); - pressanykey(); - return; - } else if (email_count >= EMAILDB_LIMIT) { - move(15, 0); clrtobot(); - move(17, 0); - outs("指定的 E-Mail 已註冊過多帳號, 請使用其他 E-Mail, 或輸入 x 採手動認證\n"); - outs("但注意手動認證通常會花上數週以上的時間。\n"); - } else { -#endif - getdata(16, 0, "請再次確認您輸入的 E-Mail 位置正確嘛? [y/N]", - yn, sizeof(yn), LCECHO); - if (yn[0] == 'Y' || yn[0] == 'y') - break; -#ifdef USE_EMAILDB - } -#endif - } else { - move(15, 0); clrtobot(); - move(17, 0); - outs("指定的 E-Mail 不正確, 若您無 E-Mail 請輸入 x 由站長手動認證\n"); - outs("但注意手動認證通常會花上數週以上的時間。\n"); - } - } -#ifdef USE_EMAILDB - if (emaildb_update_email(cuser.userid, strlen(cuser.userid), - email, strlen(email)) < 0) { - move(15, 0); clrtobot(); - move(17, 0); - outs("暫時不允許\ email 認證註冊, 請稍後再試\n"); - pressanykey(); - return; - } -#endif - strlcpy(cuser.email, email, sizeof(cuser.email)); - REGFORM2: - if (strcasecmp(email, "x") == 0) { /* 手動認證 */ - if ((fn = fopen(fn_register, "a"))) { - fprintf(fn, "num: %d, %s", usernum, ctime4(&now)); - fprintf(fn, "uid: %s\n", cuser.userid); - fprintf(fn, "name: %s\n", rname); - fprintf(fn, "career: %s\n", career); - fprintf(fn, "addr: %s\n", addr); - fprintf(fn, "phone: %s\n", phone); - fprintf(fn, "mobile: %s\n", mobile); - fprintf(fn, "email: %s\n", email); - fprintf(fn, "----\n"); - fclose(fn); - // save justify information - snprintf(cuser.justify, sizeof(cuser.justify), - "%s:%s:", phone, career); - } - // XXX what if we cannot open register form? - } else { - // register by mail of phone - snprintf(cuser.justify, sizeof(cuser.justify), - "%s:%s:", phone, career); -#ifdef HAVEMOBILE - if (phone != NULL && email[1] == 0 && tolower(email[0]) == 'm') - sprintf(cuser.justify, sizeof(cuser.justify), - "%s:%s:", phone, career); -#endif - email_justify(&cuser); - } -} - -static int HaveRejectStr(const char *s, const char **rej) -{ - int i; - char *ptr, *rejectstr[] = - {"幹", "阿", "不", "你媽", "某", "笨", "呆", "..", "xx", - "你管", "管我", "猜", "天才", "超人", - "ㄅ", "ㄆ", "ㄇ", "ㄈ", "ㄉ", "ㄊ", "ㄋ", "ㄌ", "ㄍ", "ㄎ", "ㄏ", - "ㄐ", "ㄑ", "ㄒ", "ㄓ",/*"ㄔ",*/ "ㄕ", "ㄖ", "ㄗ", "ㄘ", "ㄙ", - "ㄧ", "ㄨ", "ㄩ", "ㄚ", "ㄛ", "ㄜ", "ㄝ", "ㄞ", "ㄟ", "ㄠ", "ㄡ", - "ㄢ", "ㄣ", "ㄤ", "ㄥ", "ㄦ", NULL}; - - if( rej != NULL ) - for( i = 0 ; rej[i] != NULL ; ++i ) - if( strstr(s, rej[i]) ) - return 1; - - for( i = 0 ; rejectstr[i] != NULL ; ++i ) - if( strstr(s, rejectstr[i]) ) - return 1; - - if( (ptr = strstr(s, "ㄔ")) != NULL ){ - if( ptr != s && strncmp(ptr - 1, "都市", 4) == 0 ) - return 0; - return 1; - } - return 0; -} - -char *isvalidname(char *rname) -{ -#ifdef FOREIGN_REG - return NULL; -#else - const char *rejectstr[] = - {"肥", "胖", "豬頭", "小白", "小明", "路人", "老王", "老李", "寶貝", - "先生", "帥哥", "老頭", "小姊", "小姐", "美女", "小妹", "大頭", - "公主", "同學", "寶寶", "公子", "大頭", "小小", "小弟", "小妹", - "妹妹", "嘿", "嗯", "爺爺", "大哥", "無", - NULL}; - if( removespace(rname) && rname[0] < 0 && - strlen(rname) >= 4 && - !HaveRejectStr(rname, rejectstr) && - strncmp(rname, "小", 2) != 0 && //起頭是「小」 - strncmp(rname, "我是", 4) != 0 && //起頭是「我是」 - !(strlen(rname) == 4 && strncmp(&rname[2], "兒", 2) == 0) && - !(strlen(rname) >= 4 && strncmp(&rname[0], &rname[2], 2) == 0)) - return NULL; - return "您的輸入不正確"; -#endif - -} - -static char *isvalidcareer(char *career) -{ -#ifndef FOREIGN_REG - const char *rejectstr[] = {NULL}; - if (!(removespace(career) && career[0] < 0 && strlen(career) >= 6) || - strcmp(career, "家裡") == 0 || HaveRejectStr(career, rejectstr) ) - return "您的輸入不正確"; - if (strcmp(&career[strlen(career) - 2], "大") == 0 || - strcmp(&career[strlen(career) - 4], "大學") == 0 || - strcmp(career, "學生大學") == 0) - return "麻煩請加學校系所"; - if (strcmp(career, "學生高中") == 0) - return "麻煩輸入學校名稱"; -#else - if( strlen(career) < 6 ) - return "您的輸入不正確"; -#endif - return NULL; -} - -char *isvalidaddr(char *addr) -{ - const char *rejectstr[] = - {"地球", "銀河", "火星", NULL}; - - // addr[0] > 0: check if address is starting by Chinese. - if (!removespace(addr) || strlen(addr) < 15) - return "這個地址似乎並不完整"; - if (strstr(addr, "信箱") != NULL || strstr(addr, "郵政") != NULL) - return "抱歉我們不接受郵政信箱"; - if ((strstr(addr, "市") == NULL && strstr(addr, "巿") == NULL && - strstr(addr, "縣") == NULL && strstr(addr, "室") == NULL) || - HaveRejectStr(addr, rejectstr) || - strcmp(&addr[strlen(addr) - 2], "段") == 0 || - strcmp(&addr[strlen(addr) - 2], "路") == 0 || - strcmp(&addr[strlen(addr) - 2], "巷") == 0 || - strcmp(&addr[strlen(addr) - 2], "弄") == 0 || - strcmp(&addr[strlen(addr) - 2], "區") == 0 || - strcmp(&addr[strlen(addr) - 2], "市") == 0 || - strcmp(&addr[strlen(addr) - 2], "街") == 0 ) - return "這個地址似乎並不完整"; - return NULL; -} - -static char *isvalidphone(char *phone) -{ - int i; - for( i = 0 ; phone[i] != 0 ; ++i ) - if( !isdigit((int)phone[i]) ) - return "請不要加分隔符號"; - if (!removespace(phone) || - strlen(phone) < 9 || - strstr(phone, "00000000") != NULL || - strstr(phone, "22222222") != NULL ) { - return "這個電話號碼並不正確(請含區碼)" ; - } - return NULL; -} - -int -u_register(void) -{ - char rname[20], addr[50], mobile[16]; -#ifdef FOREIGN_REG - char fore[2]; -#endif - char phone[20], career[40], email[50], birthday[11], sex_is[2]; - unsigned char year, mon, day; - char inregcode[14], regcode[50]; - char ans[3], *ptr, *errcode; - char genbuf[200]; - FILE *fn; - - if (cuser.userlevel & PERM_LOGINOK) { - outs("您的身份確認已經完成,不需填寫申請表"); - return XEASY; - } - if ((fn = fopen(fn_register, "r"))) { - int i =0; - while (fgets(genbuf, STRLEN, fn)) { - if ((ptr = strchr(genbuf, '\n'))) - *ptr = '\0'; - if (strncmp(genbuf, "uid: ", 5) != 0) - continue; - i++; - if(strcmp(genbuf + 5, cuser.userid) != 0) - continue; - fclose(fn); - /* idiots complain about this, so bug them */ - clear(); - move(3, 0); - prints(" 您的註冊申請單尚在處理中(處理順位: %d),請耐心等候\n\n", i); - outs(" 如果您已收到註冊碼卻看到這個畫面,那代表您在使用 Email 註冊後\n"); - outs(" " ANSI_COLOR(1;31) "又另外申請了站長直接人工審核的註冊申請單。" - ANSI_RESET "\n\n"); - // outs("該死,都不看說明的...\n"); - outs(" 進入人工審核程序後 Email 註冊自動失效,有註冊碼也沒用,\n"); - outs(" 要等到審核完成 (會多花很多時間,通常起碼數天) ,所以請耐心等候。\n\n"); - - /* 下面是國王的 code 所需要的 message */ -#if 0 - outs(" 另外請注意,若站長審註冊單時您正在站上則會無法審核、自動跳過。\n"); - outs(" 所以等候審核時請勿掛站。若超過兩三天仍未被審到,通常就是這個原因。\n"); -#endif - - vmsg("您的註冊申請單尚在處理中"); - return FULLUPDATE; - } - fclose(fn); - } - strlcpy(rname, cuser.realname, sizeof(rname)); - strlcpy(addr, cuser.address, sizeof(addr)); - strlcpy(email, cuser.email, sizeof(email)); - if (cuser.mobile) - snprintf(mobile, sizeof(mobile), "0%09d", cuser.mobile); - else - mobile[0] = 0; - if (cuser.month == 0 && cuser.day == 0 && cuser.year == 0) - birthday[0] = 0; - else - snprintf(birthday, sizeof(birthday), "%04i/%02i/%02i", - 1900 + cuser.year, cuser.month, cuser.day); - sex_is[0] = (cuser.sex % 8) + '1'; - sex_is[1] = 0; - career[0] = phone[0] = '\0'; - sethomefile(genbuf, cuser.userid, "justify.wait"); - if ((fn = fopen(genbuf, "r"))) { - fgets(genbuf, sizeof(genbuf), fn); - chomp(genbuf); - strlcpy(phone, genbuf, sizeof(phone)); - - fgets(genbuf, sizeof(genbuf), fn); - chomp(genbuf); - strlcpy(career, genbuf, sizeof(career)); - - fgets(genbuf, sizeof(genbuf), fn); // old version compatible - - fgets(genbuf, sizeof(genbuf), fn); - chomp(genbuf); - strlcpy(rname, genbuf, sizeof(rname)); - - fgets(genbuf, sizeof(genbuf), fn); - chomp(genbuf); - strlcpy(addr, genbuf, sizeof(addr)); - - fgets(genbuf, sizeof(genbuf), fn); - chomp(genbuf); - strlcpy(mobile, genbuf, sizeof(mobile)); - - fclose(fn); - } - - if (cuser.userlevel & PERM_NOREGCODE) { - vmsg("您不被允許\使用認證碼認證。請填寫註冊申請單"); - goto REGFORM; - } - - // getregcode(regcode); - - // XXX why check by year? - // birthday is moved to earlier, so let's check email instead. - if (cuser.email[0] && // cuser.year != 0 && /* 已經第一次填過了~ ^^" */ - strcmp(cuser.email, "x") != 0 && /* 上次手動認證失敗 */ - strcmp(cuser.email, "X") != 0) - { - clear(); - stand_title("EMail認證"); - move(2, 0); - - prints("請輸入您的認證碼。(由 %s 開頭無空白的十三碼)\n" - "或輸入 x 來重新填寫 E-Mail 或改由站長手動認證\n", REGCODE_INITIAL); - inregcode[0] = 0; - - do{ - getdata(10, 0, "您的認證碼:", - inregcode, sizeof(inregcode), DOECHO); - if( strcmp(inregcode, "x") == 0 || strcmp(inregcode, "X") == 0 ) - break; - if( strlen(inregcode) != 13 || inregcode[0] == ' ') - vmsg("認證碼輸入不完整,總共應有十三碼,沒有空白字元。"); - else if( inregcode[0] != REGCODE_INITIAL[0] || inregcode[1] != REGCODE_INITIAL[1] ) { - /* old regcode */ - vmsg("輸入的認證碼錯誤," // "或因系統昇級已失效," - "請輸入 x 重填一次 E-Mail"); - } - else - break; - } while( 1 ); - - // make it case insensitive. - if (strcasecmp(inregcode, getregcode(regcode)) == 0) { - int unum; - delregcodefile(); - if ((unum = searchuser(cuser.userid, NULL)) == 0) { - vmsg("系統錯誤,查無此人!"); - u_exit("getuser error"); - exit(0); - } - mail_muser(cuser, "[註冊成功\囉]", "etc/registeredmail"); -#if FOREIGN_REG_DAY > 0 - if(cuser.uflag2 & FOREIGN) - mail_muser(cuser, "[出入境管理局]", "etc/foreign_welcome"); -#endif - cuser.userlevel |= (PERM_LOGINOK | PERM_POST); - outs("\n註冊成功\, 重新上站後將取得完整權限\n" - "請按下任一鍵跳離後重新上站~ :)"); - sethomefile(genbuf, cuser.userid, "justify.wait"); - unlink(genbuf); - snprintf(cuser.justify, sizeof(cuser.justify), - "%s:%s:email", phone, career); - sethomefile(genbuf, cuser.userid, "justify"); - log_file(genbuf, LOG_CREAT, cuser.justify); - pressanykey(); - u_exit("registed"); - exit(0); - return QUIT; - } else if (strcasecmp(inregcode, "x") != 0) { - if (regcode[0]) - { - vmsg("認證碼錯誤!"); - return FULLUPDATE; - } - else - { - vmsg("認證碼已過期,請重新註冊。"); - toregister(email, phone, career, rname, addr, mobile); - return FULLUPDATE; - } - } else { - toregister(email, phone, career, rname, addr, mobile); - return FULLUPDATE; - } - } - - REGFORM: - getdata(b_lines - 1, 0, "您確定要填寫註冊單嗎(Y/N)?[N] ", - ans, 3, LCECHO); - if (ans[0] != 'y') - return FULLUPDATE; - - move(2, 0); - clrtobot(); - while (1) { - clear(); - move(1, 0); - prints("%s(%s) 您好,請據實填寫以下的資料:", - cuser.userid, cuser.nickname); -#ifdef FOREIGN_REG - fore[0] = 'y'; - fore[1] = 0; - getfield(2, "Y/n", "是否現在住在台灣", fore, 2); - if (fore[0] == 'n') - fore[0] |= FOREIGN; - else - fore[0] = 0; -#endif - while (1) { - getfield(8, -#ifdef FOREIGN_REG - "請用本名", -#else - "請用中文", -#endif - "真實姓名", rname, 20); - if( (errcode = isvalidname(rname)) == NULL ) - break; - else - vmsg(errcode); - } - - move(11, 0); - outs(" 請盡量詳細的填寫您的服務單位,大專院校請麻煩" - "加" ANSI_COLOR(1;33) "系所" ANSI_RESET ",公司單位請加" ANSI_COLOR(1;33) "職稱" ANSI_RESET ",\n" - " 暫無工作請麻煩填寫" ANSI_COLOR(1;33) "畢業學校" ANSI_RESET "。\n"); - while (1) { - getfield(9, "(畢業)學校(含" ANSI_COLOR(1;33) "系所年級" ANSI_RESET ")或單位職稱", - "服務單位", career, 40); - if( (errcode = isvalidcareer(career)) == NULL ) - break; - else - vmsg(errcode); - } - move(10, 0); clrtobot(); - while (1) { - getfield(10, "含" ANSI_COLOR(1;33) "縣市" ANSI_RESET "及門寢號碼" - "(台北請加" ANSI_COLOR(1;33) "行政區" ANSI_RESET ")", - "目前住址", addr, sizeof(addr)); - if( (errcode = isvalidaddr(addr)) == NULL -#ifdef FOREIGN_REG - || fore[0] -#endif - ) - break; - else - vmsg(errcode); - } - while (1) { - getfield(11, "不加-(), 包括長途區號", "連絡電話", phone, 11); - if( (errcode = isvalidphone(phone)) == NULL ) - break; - else - vmsg(errcode); - } - getfield(12, "只輸入數字 如:0912345678 (可不填)", - "手機號碼", mobile, 20); - while (1) { - getfield(13, "西元/月月/日日 如:1984/02/29", "生日", birthday, sizeof(birthday)); - if (birthday[0] == 0) { - snprintf(birthday, sizeof(birthday), "%04i/%02i/%02i", - 1900 + cuser.year, cuser.month, cuser.day); - mon = cuser.month; - day = cuser.day; - year = cuser.year; - } else { - int y, m, d; - if (ParseDate(birthday, &y, &m, &d)) { - vmsg("您的輸入不正確"); - continue; - } - mon = (unsigned char)m; - day = (unsigned char)d; - year = (unsigned char)(y - 1900); - } - if (year < 40) { - vmsg("您的輸入不正確"); - continue; - } - break; - } - getfield(14, "1.葛格 2.姐接 ", "性別", sex_is, 2); - getdata(20, 0, "以上資料是否正確(Y/N)?(Q)取消註冊 [N] ", - ans, 3, LCECHO); - if (ans[0] == 'q') - return 0; - if (ans[0] == 'y') - break; - } - strlcpy(cuser.realname, rname, sizeof(cuser.realname)); - strlcpy(cuser.address, addr, sizeof(cuser.address)); - strlcpy(cuser.email, email, sizeof(cuser.email)); - cuser.mobile = atoi(mobile); - cuser.sex = (sex_is[0] - '1') % 8; - cuser.month = mon; - cuser.day = day; - cuser.year = year; -#ifdef FOREIGN_REG - if (fore[0]) - cuser.uflag2 |= FOREIGN; - else - cuser.uflag2 &= ~FOREIGN; -#endif - trim(career); - trim(addr); - trim(phone); - - toregister(email, phone, career, rname, addr, mobile); - - // update cuser - passwd_update(usernum, &cuser); - - return FULLUPDATE; -} - /* 列出所有註冊使用者 */ struct ListAllUsetCtx { int usercounter; -- cgit v1.2.3