summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/proto.h8
-rw-r--r--mbbsd/assess.c3
-rw-r--r--mbbsd/file.c105
-rw-r--r--mbbsd/menu.c6
-rw-r--r--mbbsd/passwd.c8
-rw-r--r--mbbsd/register.c409
-rw-r--r--src/libbbsutil/file.c8
7 files changed, 523 insertions, 24 deletions
diff --git a/include/proto.h b/include/proto.h
index 84c2ae5a..1c594cdb 100644
--- a/include/proto.h
+++ b/include/proto.h
@@ -287,8 +287,11 @@ void reginit_fav(void);
/* file */
int file_count_line(const char *file);
-int file_append_line(const char *file, const char *string);
-int file_exist_record(const char *file, const char *string);
+int file_append_line(const char *file, const char *string); // does not append "\n"
+int file_append_record(const char *file, const char *key); // will append "\n"
+int file_exist_record(const char *file, const char *key);
+int file_find_record(const char *file, const char *key);
+int file_delete_record(const char *file, const char *key, int case_sensitive);
/* friend */
void friend_edit(int type);
@@ -828,6 +831,7 @@ int passwd_apply(void *data, int (*fptr)(void *, int, userec_t *));
void passwd_lock(void);
void passwd_unlock(void);
int passwd_update_money(int num);
+void passwd_force_update(int flag);
int initcuser(const char *userid);
int freecuser(void);
diff --git a/mbbsd/assess.c b/mbbsd/assess.c
index e4bcc231..4d63a8d8 100644
--- a/mbbsd/assess.c
+++ b/mbbsd/assess.c
@@ -256,9 +256,8 @@ u_fixgoodpost(void)
"%s %s 自動修正優文數: 由 %d 變為 %d\n", Cdate(&now), cuser.userid,
cuser.goodpost, newgp);
cuser.goodpost = newgp;
- if ((currutmp) && (currutmp->alerts & ALERT_PWD_GOODPOST))
- currutmp->alerts &= ~ALERT_PWD_GOODPOST; // use my version
// update passwd file here?
+ passwd_force_update(ALERT_PWD_GOODPOST);
passwd_update(usernum, &cuser);
vmsgf("更新優文數目為%d。", newgp);
}
diff --git a/mbbsd/file.c b/mbbsd/file.c
index 34af866a..3b657acc 100644
--- a/mbbsd/file.c
+++ b/mbbsd/file.c
@@ -28,7 +28,7 @@ int file_count_line(const char *file)
}
/**
- * 將 string append 到檔案 file 後端
+ * 將 string append 到檔案 file 後端 (不加換行)
* @param file 要被 append 的檔
* @param string
* @return 成功傳回 0,失敗傳回 -1。
@@ -46,21 +46,43 @@ int file_append_line(const char *file, const char *string)
}
/**
- * 傳回檔案 file 中是否有 string 這個字串。
+ * 將 "$key\n" append 到檔案 file 後端
+ * @param file 要被 append 的檔
+ * @param key 沒有換行的字串
+ * @return 成功傳回 0,失敗傳回 -1。
*/
-int file_exist_record(const char *file, const char *string)
+int file_append_record(const char *file, const char *key)
+{
+ FILE *fp;
+ if (!key || !*key) return -1;
+ if ((fp = fopen(file, "a")) == NULL)
+ return -1;
+ flock(fileno(fp), LOCK_EX);
+ fputs(key, fp);
+ fputs("\n", fp);
+ flock(fileno(fp), LOCK_UN);
+ fclose(fp);
+ return 0;
+}
+
+/**
+ * 傳回檔案 file 中 key 所在行數
+ */
+int file_find_record(const char *file, const char *key)
{
FILE *fp;
char buf[STRLEN], *ptr;
+ int i = 0;
if ((fp = fopen(file, "r")) == NULL)
return 0;
while (fgets(buf, STRLEN, fp)) {
char *strtok_pos;
- if ((ptr = strtok_r(buf, str_space, &strtok_pos)) && !strcasecmp(ptr, string)) {
+ i++;
+ if ((ptr = strtok_r(buf, str_space, &strtok_pos)) && !strcasecmp(ptr, key)) {
fclose(fp);
- return 1;
+ return i;
}
}
fclose(fp);
@@ -68,6 +90,79 @@ int file_exist_record(const char *file, const char *string)
}
/**
+ * 傳回檔案 file 中是否有 key
+ */
+int file_exist_record(const char *file, const char *key)
+{
+ return file_find_record(file, key) > 0 ? 1 : 0;
+}
+
+/**
+ * 刪除檔案 file 中以 string 開頭的行
+ * @param file 要處理的檔案
+ * @param string 尋找的 key name
+ * @param case_sensitive 是否要處理大小寫
+ * @return 成功傳回 0,失敗傳回 -1。
+ */
+int
+file_delete_record(const char *file, const char *string, int case_sensitive)
+{
+ // TODO nfp 用 tmpfile() 比較好? 不過 Rename 會變慢...
+ FILE *fp = NULL, *nfp = NULL;
+ char fnew[PATHLEN];
+ char buf[STRLEN + 1];
+ int ret = -1, i = 0;
+ const size_t toklen = strlen(string);
+
+ if (!toklen)
+ return 0;
+
+ do {
+ snprintf(fnew, sizeof(fnew), "%s.%3.3X", file, (unsigned int)(random() & 0xFFF));
+ if (access(fnew, 0) != 0)
+ break;
+ } while (i++ < 10); // max tries = 10
+
+ if (access(fnew, 0) == 0) return -1; // cannot create temp file.
+
+ i = 0;
+ if ((fp = fopen(file, "r")) && (nfp = fopen(fnew, "w"))) {
+ while (fgets(buf, sizeof(buf), fp))
+ {
+ size_t klen = strcspn(buf, str_space);
+ if (toklen == klen)
+ {
+ if (((case_sensitive && strncmp(buf, string, toklen) == 0) ||
+ (!case_sensitive && strncasecmp(buf, string, toklen) == 0)))
+ {
+ // found line. skip it.
+ i++;
+ continue;
+ }
+ }
+ // other wise, keep the line.
+ fputs(buf, nfp);
+ }
+ fclose(nfp); nfp = NULL;
+ if (i > 0)
+ {
+ if(Rename(fnew, file) < 0)
+ ret = -1;
+ else
+ ret = 0;
+ } else {
+ unlink(fnew);
+ ret = 0;
+ }
+ }
+ if(fp)
+ fclose(fp);
+ if(nfp)
+ fclose(nfp);
+ return ret;
+}
+
+/**
* 對每一筆 record 做 func 這件事。
* @param file
* @param func 處理每筆 record 的 handler,為一 function pointer。
diff --git a/mbbsd/menu.c b/mbbsd/menu.c
index 20fed243..7eaaf53c 100644
--- a/mbbsd/menu.c
+++ b/mbbsd/menu.c
@@ -400,12 +400,12 @@ static const commands_t maillist[] = {
{m_new, PERM_READMAIL, "RNew 閱\讀新進郵件"},
{m_read, PERM_READMAIL, "RRead 多功\能讀信選單"},
{m_send, PERM_LOGINOK, "RSend 站內寄信"},
- {x_love, PERM_LOGINOK, "PPaper 情書產生器"},
{mail_list, PERM_LOGINOK, "RMail List 群組寄信"},
+ {x_love, PERM_LOGINOK, "PPaper 情書產生器"},
{setforward, PERM_LOGINOK, "FForward " ANSI_COLOR(1;32)
"設定信箱自動轉寄" ANSI_RESET},
- {m_sysop, 0, "YYes, sir! 諂媚站長"},
- {m_internet, PERM_INTERNET, "RInternet 寄信到 Internet"},
+ {m_sysop, 0, "YYes, sir! 寫信給站長"},
+ {m_internet, PERM_INTERNET, "RInternet 寄信到站外"},
{mail_mbox, PERM_INTERNET, "RZip UserHome 把所有私人資料打包回去"},
{built_mail_index, PERM_LOGINOK, "SSavemail 重建信箱索引"},
{mail_all, PERM_SYSOP, "RAll 寄信給所有使用者"},
diff --git a/mbbsd/passwd.c b/mbbsd/passwd.c
index 0446ddf2..d0dd4015 100644
--- a/mbbsd/passwd.c
+++ b/mbbsd/passwd.c
@@ -69,6 +69,14 @@ passwd_update_money(int num)
return 0;
}
+void
+passwd_force_update(int flag)
+{
+ if(!currutmp || (currutmp->alerts & ALERT_PWD) == 0)
+ return;
+ currutmp->alerts &= ~flag;
+}
+
int
passwd_update(int num, userec_t * buf)
{
diff --git a/mbbsd/register.c b/mbbsd/register.c
index 3eb9e8e3..c71ed66a 100644
--- a/mbbsd/register.c
+++ b/mbbsd/register.c
@@ -1,11 +1,16 @@
/* $Id$ */
#include "bbs.h"
-#define FN_REGISTER_LOG "register.log"
+#define FN_REGISTER_LOG "register.log" // global registration history
#define FN_JUSTIFY "justify"
#define FN_JUSTIFY_WAIT "justify.wait"
#define FN_REJECT_NOTIFY "justify.reject"
+// New style (Regform2) file names:
+#define FN_REGFORM "regform" // registration form in user home
+#define FN_REGFORM_LOG "regform.log" // regform history in user home
+#define FN_REQLIST "reg.wait" // request list file, in global directory (replacing fn_register)
+
////////////////////////////////////////////////////////////////////////////
// Password Hash
////////////////////////////////////////////////////////////////////////////
@@ -13,7 +18,7 @@
// prototype of crypt()
char *crypt(const char *key, const char *salt);
-char *
+char *
genpasswd(char *pw)
{
if (pw[0]) {
@@ -1245,7 +1250,7 @@ regform_accept(const char *userid, const char *justify)
}
void
-regform_reject(const char *userid, char *reason)
+regform_reject(const char *userid, const char *reason)
{
char buf[PATHLEN];
FILE *fp = NULL;
@@ -1270,7 +1275,7 @@ regform_reject(const char *userid, char *reason)
// last: send notification
mkuserdir(muser.userid);
- sethomefile(buf, muser.userid, "justify.reject");
+ sethomefile(buf, muser.userid, FN_REJECT_NOTIFY);
fp = fopen(buf, "wt");
assert(fp);
syncnow();
@@ -1289,6 +1294,7 @@ regform_reject(const char *userid, char *reason)
mail_muser(muser, "[註冊失敗]", buf);
}
+// Regform v1 API
// read count entries from regsrc to a temp buffer
FILE *
pull_regform(const char *regfile, char *workfn, int count)
@@ -1325,6 +1331,7 @@ pump_regform(const char *regfn, FILE *remains)
fclose(fout);
}
+// New Regform UI
static void
prompt_regform_ui()
{
@@ -1393,6 +1400,10 @@ resolve_reason(char *s, int y)
} while (strlen(s) < 4);
}
+////////////////////////////////////////////////////////////////////////////
+// Regform Utilities
+////////////////////////////////////////////////////////////////////////////
+
// TODO define and use structure instead, even in reg request file.
typedef struct {
// current format:
@@ -1418,6 +1429,7 @@ typedef struct {
char phone [20];
} RegformEntry;
+// regform format utilities
int
load_regform_entry(RegformEntry *pre, FILE *fp)
{
@@ -1459,7 +1471,6 @@ print_regform_entry(const RegformEntry *pre, FILE *fp, int close)
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;
@@ -1489,6 +1500,386 @@ append_regform(const RegformEntry *pre, const char *logfn,
return 1;
}
+////////////////////////////////////////////////////////////////////////////
+// Regform2 API
+////////////////////////////////////////////////////////////////////////////
+
+// registration queue
+int
+regq_append(const char *userid)
+{
+ if (file_append_record(FN_REQLIST, userid) < 0)
+ return 0;
+ return 1;
+}
+
+int
+regq_find(const char *userid)
+{
+ return file_find_record(FN_REQLIST, userid);
+}
+
+int
+regq_delete(const char *userid)
+{
+ return file_delete_record(FN_REQLIST, userid, 0);
+}
+
+// user home regform operation
+int
+regfrm_exist(const char *userid)
+{
+ char fn[PATHLEN];
+ sethomefile(fn, userid, FN_REGFORM);
+ return dashf(fn) ? 1 : 0;
+}
+
+int
+regfrm_load(const char *userid, RegformEntry *pre)
+{
+ FILE *fp = NULL;
+ char fn[PATHLEN];
+ int ret = 0;
+ sethomefile(fn, userid, FN_REGFORM);
+ if (!dashf(fn))
+ return 0;
+
+ fp = fopen(fn, "rt");
+ if (!fp)
+ return 0;
+ ret = load_regform_entry(pre, fp);
+ fclose(fp);
+ return ret;
+}
+
+int
+regfrm_save(const char *userid, const RegformEntry *pre)
+{
+ FILE *fp = NULL;
+ char fn[PATHLEN];
+ int ret = 0;
+ sethomefile(fn, userid, FN_REGFORM);
+
+ fp = fopen(fn, "wt");
+ if (!fp)
+ return 0;
+ ret = print_regform_entry(pre, fp, 1);
+ fclose(fp);
+ return ret;
+}
+
+int
+regfrm_trylock(const char *userid)
+{
+ int fd = 0;
+ char fn[PATHLEN];
+ sethomefile(fn, userid, FN_REGFORM);
+ if (!dashf(fn)) return 0;
+ fd = open(fn, O_RDONLY);
+ if (fd < 0) return 0;
+ if (flock(fd, LOCK_EX|LOCK_NB) == 0)
+ return fd;
+ close(fd);
+ return 0;
+}
+
+int
+regfrm_unlock(int lockfd)
+{
+ int fd = lockfd;
+ if (lockfd <= 0)
+ return 0;
+ lockfd = flock(fd, LOCK_UN) == 0 ? 1 : 0;
+ close(fd);
+ return lockfd;
+}
+
+// regform processors
+int
+regfrm_accept(RegformEntry *pre)
+{
+ char justify[REGLEN+1], buf[STRLEN*2];
+ char fn[PATHLEN], fnlog[PATHLEN];
+
+ // dry run!
+ vmsg("regfrm_accept");
+ return 1;
+
+ sethomefile(fn, pre->userid, FN_REGFORM);
+
+ // build justify string
+ removespace(pre->phone);
+ removespace(pre->career);
+ snprintf(justify, sizeof(justify),
+ "%s:%s:%s", pre->phone, pre->career, cuser.userid);
+
+ // call handler
+ regform_accept(pre->userid, justify);
+
+ // append current form to history.
+ sethomefile(fnlog, pre->userid, FN_REGFORM_LOG);
+ AppendTail(fn, fnlog, 0);
+ // global history
+ snprintf(buf, sizeof(buf), "Approved: %s -> %s\nDate: %s\n",
+ cuser.userid, pre->userid, Cdate(&now));
+ file_append_line(FN_REGISTER_LOG, buf);
+ AppendTail(fn, FN_REGISTER_LOG, 0);
+
+ // remove from queue
+ unlink(fn);
+ regq_delete(pre->userid);
+ return 1;
+}
+
+int
+regfrm_reject(RegformEntry *pre, const char *reason)
+{
+ char buf[STRLEN*2];
+ char fn[PATHLEN];
+
+ // dry run!
+ vmsg("regfrm_reject");
+ return 1;
+
+ sethomefile(fn, pre->userid, FN_REGFORM);
+
+ // call handler
+ regform_reject(pre->userid, reason);
+
+ // log it
+ snprintf(buf, sizeof(buf), "Rejected: %s -> %s [%s]\nDate: %s\n",
+ cuser.userid, pre->userid, reason, Cdate(&now));
+ file_append_line(FN_REGISTER_LOG, buf);
+ AppendTail(fn, FN_REGISTER_LOG, 0);
+
+ // remove from queue
+ unlink(fn);
+ regq_delete(pre->userid);
+ return 1;
+}
+
+int
+regfrm_delete(const char *userid)
+{
+ char fn[PATHLEN];
+ sethomefile(fn, userid, FN_REGFORM);
+
+ // dry run!
+ vmsgf("regfrm_delete (%s)", userid);
+ return 1;
+
+ // directly delete.
+ unlink(fn);
+
+ // remove from queue
+ regq_delete(userid);
+ return 1;
+}
+
+// working queue
+FILE *
+regq_init_pull()
+{
+ FILE *fp = tmpfile(), *src =NULL;
+ char buf[STRLEN];
+ if (!fp) return NULL;
+ src = fopen(FN_REQLIST, "rt");
+ if (!src) { fclose(fp); return NULL; }
+ while (fgets(buf, sizeof(buf), src))
+ fputs(buf, fp);
+ fclose(src);
+ rewind(fp);
+ return fp;
+}
+
+int
+regq_pull(FILE *fp, char *uid)
+{
+ char buf[STRLEN];
+ size_t idlen = 0;
+ uid[0] = 0;
+ if (fgets(buf, sizeof(buf), fp) == NULL)
+ return 0;
+ idlen = strcspn(buf, str_space);
+ if (idlen < 1) return 0;
+ if (idlen > IDLEN) idlen = IDLEN;
+ strlcpy(uid, buf, idlen+1);
+ return 1;
+}
+
+int
+regq_end_pull(FILE *fp)
+{
+ // no need to unlink because fp is a tmpfile.
+ if (!fp) return 0;
+ fclose(fp);
+ return 1;
+}
+
+// UI part
+int
+ui_display_regform_single(
+ const userec_t *xuser,
+ const RegformEntry *pre,
+ int tid, char *reason)
+{
+ int c;
+
+ while (1)
+ {
+ move(1, 0);
+ user_display(xuser, 1);
+ move(14, 0);
+ prints(ANSI_COLOR(1;32)
+ "--------------- 這是第 %2d 份註冊單 ------------------"
+ ANSI_RESET "\n", tid);
+ prints(" %-12s: %s\n", "帳號", pre->userid);
+ prints("0.%-12s: %s%s\n", "真實姓名", pre->name,
+ xuser->uflag2 & FOREIGN ? " (外籍)" :
+ "");
+ prints("1.%-12s: %s\n", "服務單位", pre->career);
+ prints("2.%-12s: %s\n", "目前住址", pre->addr);
+ prints("3.%-12s: %s\n", "連絡電話", pre->phone);
+
+ move(b_lines, 0);
+ outs("是否接受此資料(Y/N/Q/Del/Skip)?[S] ");
+
+ c = tolower(igetch() & 0xFF); // round to ASCII
+ if (c == 'y' || c == 'q' || c == 'd' || c == 's')
+ return c;
+ if (c == 'n')
+ {
+ int n = 0;
+ move(3, 0);
+ outs("\n" ANSI_COLOR(1;31)
+ "請提出退回申請表原因,按 <Enter> 取消:\n" ANSI_RESET);
+ for (n = 0; n < REJECT_REASONS; n++)
+ prints("%d) 請%s\n", n, reasonstr[n]);
+ outs("\n\n\n"); // preserved for prompt
+
+ getdata(3+2+REJECT_REASONS+1, 0,"退回原因: ",
+ reason, REASON_LEN, DOECHO);
+ if (reason[0] == 0)
+ continue;
+ // interprete reason
+ return 'n';
+ }
+ else if (REASON_IN_ABBREV(c))
+ {
+ // quick set
+ sprintf(reason, "%c", c);
+ return 'n';
+ }
+ return 's';
+ }
+ // shall never reach here
+ return 's';
+}
+
+// sample iterator
+void
+register_sample()
+{
+ int lfd = 0;
+ int tid = 0;
+ char uid[IDLEN+1];
+ char rsn[REASON_LEN];
+ FILE *fpregq = regq_init_pull();
+ RegformEntry re;
+
+ if (!fpregq)
+ return;
+
+ while (regq_pull(fpregq, uid))
+ {
+ userec_t muser;
+ int unum = 0;
+ int abort = 0;
+
+ // check if user exists.
+ memset(&muser, 0, sizeof(muser));
+ unum = getuser(uid, &muser);
+
+ if (unum < 1)
+ {
+ regq_delete(uid);
+ continue;
+ }
+
+ // check if regform exists.
+ if (!regfrm_exist(uid))
+ {
+ // TODO delete here?
+ regq_delete(uid);
+ continue;
+ }
+
+ // TODO check if user is already registered
+#if 0
+ if (muser.userlevel & PERM_LOGINOK)
+ {
+ regfrm_delete(uid);
+ continue;
+ }
+#endif
+
+ // try to lock
+ lfd = regfrm_trylock(uid);
+ if (lfd <= 0)
+ continue;
+
+ // load it
+ if (!regfrm_load(uid, &re))
+ {
+ regfrm_delete(uid);
+ regfrm_unlock(lfd);
+ // regq_delete(uid); // done in regfrm_delete
+ continue;
+ }
+
+ tid ++;
+ // display regform and process
+ switch(ui_display_regform_single(&muser, &re, tid, rsn))
+ {
+ case 'a': // accept
+ regfrm_accept(&re);
+ break;
+
+ case 'd': // delete
+ regfrm_delete(uid);
+ break;
+
+ case 'q': // quit
+ abort = 1;
+ break;
+
+ case 'n': // reject
+ regfrm_reject(&re, rsn);
+ break;
+
+ case 's': // skip
+ // do nothing.
+ break;
+
+ default: // shall never reach here
+ assert(0);
+ break;
+ }
+
+ // final processing
+ regfrm_unlock(lfd);
+
+ if (abort)
+ break;
+ }
+ regq_end_pull(fpregq);
+
+ // finishing
+ clear(); move(5, 0);
+ prints("您審了 %d 份註冊單份。", tid);
+ pressanykey();
+}
+
/////////////////////////////////////////////////////////////////////////////
// Regform UI
// 處理 Register Form
@@ -1759,7 +2150,7 @@ scan_register_form(const char *regfile, int automode, const char *target_uid)
fclose(fp);
// build reject file
- setuserfile(rejfn, "justify.reject");
+ sethomefile(rejfn, muser.userid, FN_REJECT_NOTIFY);
Copy(buf1, rejfn);
}
if ((fout = fopen(FN_REGISTER_LOG, "a"))) {
@@ -1828,12 +2219,8 @@ scan_register_form(const char *regfile, int automode, const char *target_uid)
fclose(fn);
unlink(fname);
- move(0, 0);
- clrtobot();
-
- move(5, 0);
+ clear(); move(5, 0);
prints("您審了 %d 份註冊單,AutoScan 審了 %d 份", nSelf, nAuto);
-
pressanykey();
return (0);
}
diff --git a/src/libbbsutil/file.c b/src/libbbsutil/file.c
index 5cc0b541..21313283 100644
--- a/src/libbbsutil/file.c
+++ b/src/libbbsutil/file.c
@@ -203,7 +203,11 @@ Rename(const char *src, const char *dst)
if (pid == 0)
execl("/bin/mv", "mv", "-f", src, dst, (char *)NULL);
else if (pid > 0)
- waitpid(pid, NULL, 0);
+ {
+ int status = -1;
+ waitpid(pid, &status, 0);
+ return WEXITSTATUS(status) == 0 ? 0 : -1;
+ }
else
return -1;
}
@@ -262,6 +266,7 @@ AppendTail(const char *src, const char *dst, int off)
fo=open(dst, O_WRONLY | O_APPEND | O_CREAT, 0600);
if(fo<0) {close(fi); return -1;}
+ // flock(dst, LOCK_SH);
if(off > 0)
lseek(fi, (off_t)off, SEEK_SET);
@@ -270,6 +275,7 @@ AppendTail(const char *src, const char *dst, int off)
{
write(fo, buf, bytes);
}
+ // flock(dst, LOCK_UN);
close(fo);
close(fi);
return 0;