summaryrefslogblamecommitdiffstats
path: root/mbbsd/bbs.c
blob: bd37b7d7b8106d2357459ebaeb26ddc3770e06f4 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
                                                     














                      
                               
  



                                               
  


                                   
                  




















                                                                            











































                                                                               
                                                                 










                                                       
                                                                  












                                                              
  



                                
  



































                                                                               
                                 






























                                                                        
                                        


















































                                                                        
 








                                                                 




                                                               













                                                      
                         
























































































































                                                                              
                          

















































































































































































































































































































































                                                                            







                                            




                                                                     






































































































                                                                        
                                                    



























































































































































































































































































































































































































































































































                                                                                
                                                                         
                                                       
                                                                           

























































































































                                                                       
                                                   


                                                                          
                                                          



































                                                                    
                                                                                     




































                                                  

                                                        







































































































































































































                                                                             

                                                              


                                                                       
                                          









                                                                   

                                                            







































































                                                           
                         









                                            
                        




                                                          
                                        

                 
                       












                                            
                  





                                                   


                      













                                                          
/* $Id: bbs.c,v 1.11 2002/05/24 17:44:39 ptt Exp $ */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "config.h"
#include "pttstruct.h"
#include "perm.h"
#include "modes.h"
#include "common.h"
#include "proto.h"

extern struct bcache_t *brdshm;
/*
static int g_board_names(boardheader_t *fhdr) {
    AddNameList(fhdr->brdname);
    return 0;
}
*/
extern userec_t cuser;
extern void touchdircache(int bid);
extern int TagNum;
extern time_t now;

static void mail_by_link(char* owner, char* title, char* path) {
    char genbuf[200];
    fileheader_t mymail;     
    
    sprintf(genbuf,BBSHOME"/home/%c/%s", cuser.userid[0], cuser.userid);
    stampfile(genbuf, &mymail);
    strcpy(mymail.owner, owner);
    sprintf(mymail.title, title); 
    mymail.savemode = 0;
    unlink(genbuf);
    Link(path, genbuf);
    sprintf(genbuf,BBSHOME"/home/%c/%s/.DIR",cuser.userid[0],cuser.userid); 
                                                                  
    append_record(genbuf, &mymail, sizeof(mymail));      
}

extern int usernum;

void anticrosspost() {
    char buf[200];
    
    sprintf(buf,
        "\033[1;33;46m%s \033[37;45mcross post 文章 \033[37m %s\033[m",
        cuser.userid, ctime(&now));
    log_file("etc/illegal_money", buf);
    
    post_violatelaw(cuser.userid, "Ptt系統警察", "Cross-post", "罰單處份");    
    cuser.userlevel |= PERM_VIOLATELAW;
    cuser.vl_count ++;
    mail_by_link("Ptt警察部隊", "Cross-Post罰單",
         BBSHOME "/etc/crosspost.txt");
    passwd_update(usernum, &cuser);
    exit(0);
}

/* Heat CharlieL*/
int save_violatelaw() {
    char buf[128], ok[3];
    
    setutmpmode(VIOLATELAW); 
    clear();
    stand_title("繳罰單中心");

    if(!(cuser.userlevel & PERM_VIOLATELAW)) {
    mprints(22, 0, "\033[1;31m你無聊啊? 你又沒有被開罰單~~\033[m");
    pressanykey();
    return 0;
    }
  
    reload_money();
    if(cuser.money < (int)cuser.vl_count*1000) {
    sprintf(buf, "\033[1;31m這是你第 %d 次違反本站法規"
        "必須繳出 %d $Ptt ,你只有 %d 元, 錢不夠啦!!\033[m",
        (int)cuser.vl_count, (int)cuser.vl_count * 1000, cuser.money);
    mprints(22, 0, buf);
    pressanykey();
    return 0;
    }
                            
    move(5,0);
    prints("\033[1;37m你知道嗎? 因為你的違法 "
       "已經造成很多人的不便\033[m\n");
    prints("\033[1;37m你是否確定以後不會再犯了?\033[m\n");
    
    if(!getdata(10,0,"確定嗎?[y/n]:", ok, sizeof(ok), LCECHO) ||
       ok[0] == 'n' || ok[0] == 'N') {
    mprints(22,0,"\033[1;31m等你想通了再來吧!! "
        "我相信你不會知錯不改的~~~\033[m");
    pressanykey();
    return 0;
    }

    sprintf(buf, "這是你第 %d 次違法 必須繳出 %d $Ptt",
        cuser.vl_count, cuser.vl_count*1000);
    mprints(11,0,buf);

    if(!getdata(10, 0, "要付錢[y/n]:", ok, sizeof(ok), LCECHO) || 
       ok[0] == 'N' || ok[0] == 'n') {

    mprints(22,0, "\033[1;31m 嗯 存夠錢 再來吧!!!\033[m");
    pressanykey();
    return 0;
    }
    
    demoney(-1000*cuser.vl_count);
    cuser.userlevel &= (~PERM_VIOLATELAW);
    passwd_update(usernum, &cuser);
    return 0;
}

/*
void make_blist() {
    CreateNameList();
    apply_boards(g_board_names);
}
*/

extern int currbid;
extern char currBM[];
extern int currmode;
extern char currboard[];
static time_t board_note_time;
static char *brd_title;

void set_board() {
    boardheader_t *bp;

    bp = getbcache(currbid);
    board_note_time = bp->bupdate;
    brd_title = bp->BM;
    if(brd_title[0] <= ' ')
    brd_title = "徵求中";
    sprintf(currBM, "板主:%s", brd_title);
    brd_title = ((bp->bvote != 2 && bp->bvote) ? "本看板進行投票中" :
         bp->title + 7);
    currmode = (currmode & (MODE_DIRTY | MODE_MENU)) | MODE_STARTED ;
         
    if (HAS_PERM(PERM_ALLBOARD) || is_BM(bp->BM))
    currmode = currmode | MODE_BOARD | MODE_POST;
    else if(haspostperm(currboard))
        currmode |= MODE_POST;
}

static void readtitle() {
    showtitle(currBM, brd_title);
    outs("[←]離開 [→]閱\讀 [^P]發表文章 [b]備忘錄 [d]刪除 [z]精華區 "
     "[TAB]文摘 [h]elp\n\033[7m  編號   日 期  作  者       文  章  標  題"
     "                                   \033[m");
}

extern int brc_num;
extern int brc_list[];
extern char currtitle[TTLEN + 1];

extern int Tagger();

static void readdoent(int num, fileheader_t *ent) {
    int type;
    char *mark, *title, color;
    
    type = brc_unread(ent->filename,brc_num,brc_list) ? '+' : ' ';
    
    if((currmode & MODE_BOARD) && (ent->filemode & FILE_DIGEST))
    type = (type == ' ') ? '*' : '#';
    else if(currmode & MODE_BOARD || HAS_PERM(PERM_LOGINOK)) {
    if(ent->filemode & FILE_MARKED)
        type = (type == ' ') ? 'm' : 'M';

        else if (TagNum && !Tagger(atoi(ent->filename + 2), 0, TAG_NIN))
            type = 'D';

    else if (ent->filemode & FILE_SOLVED)
        type = 's';
    }
    
    title = subject(mark = ent->title);
    if(title == mark)
    color = '1', mark = "□";
    else
    color = '3', mark = "R:";
    
    if(title[47])
    strcpy(title + 44, " …");  /* 把多餘的 string 砍掉 */

    if(strncmp(currtitle, title, TTLEN))
    prints("%6d %c %-7s%-13.12s%s %s\n", num, type,
           ent->date, ent->owner, mark, title);
    else
    prints("%6d %c %-7s%-13.12s\033[1;3%cm%s %s\033[m\n", num, type,
           ent->date, ent->owner, color, mark, title);
}

extern char currfile[];

int cmpfilename(fileheader_t *fhdr) {
    return (!strcmp(fhdr->filename, currfile));
}

extern unsigned char currfmode;

int cmpfmode(fileheader_t *fhdr) {
    return (fhdr->filemode & currfmode);
}

extern char currowner[];

int cmpfowner(fileheader_t *fhdr) {
    return !strcasecmp(fhdr->owner, currowner);
}

extern char *err_bid;
extern userinfo_t *currutmp;

int whereami(int ent, fileheader_t *fhdr, char *direct) {
    boardheader_t *bh, *p[32], *root;
    int i,j;

    if(!currutmp->brc_id) return 0;

    move(1,0);
    clrtobot();
    bh=getbcache(currutmp->brc_id); 
    root=getbcache(1);
    p[0]=bh;
    for(i=0;i<31 && p[i]->parent!=root && p[i]->parent;i++)
            p[i+1]=p[i]->parent;
    j=i;
    prints("我在哪?\n%-40.40s %.13s\n", p[j]->title+7, p[j]->BM);
    for(j--;j>=0;j--)
       prints("%*s %-13.13s %-37.37s %.13s\n", (i-j)*2, "",
               p[j]->brdname, p[j]->title, 
                p[j]->BM);

    pressanykey();
    return FULLUPDATE;
}

static int do_select(int ent, fileheader_t *fhdr, char *direct) {
    char bname[20];
    char bpath[60];
    boardheader_t *bh;
    struct stat st;
    int i; 

    move(0, 0);
    clrtoeol();
    generalnamecomplete(MSG_SELECT_BOARD, bname, sizeof(bname),
            brdshm->number,
            completeboard_compar,
            completeboard_permission,
            completeboard_getname);
    if(bname[0]=='\0' || !(i = getbnum(bname)))
       return FULLUPDATE;
    bh = getbcache(i);
    if(!Ben_Perm(bh))  return FULLUPDATE; 
    strcpy(bname, bh->brdname);
    currbid=i;
    
    setbpath(bpath, bname);
    if((*bname == '\0') || (stat(bpath, &st) == -1)) {
    move(2, 0);
    clrtoeol();
    outs(err_bid);
    return FULLUPDATE;
    }
    setutmpbid(currbid); 

    brc_initial(bname);
    set_board();
    setbdir(direct, currboard);

    move(1, 0);
    clrtoeol();
    return NEWDIRECT;
}

/* ----------------------------------------------------- */
/* 改良 innbbsd 轉出信件、連線砍信之處理程序             */
/* ----------------------------------------------------- */
void outgo_post(fileheader_t *fh, char *board) {
    FILE *foo;
    
    if((foo = fopen("innd/out.bntp", "a"))) {
    fprintf(foo, "%s\t%s\t%s\t%s\t%s\n", board,
        fh->filename, cuser.userid, cuser.username, fh->title);
    fclose(foo);
    }
}

extern char *str_author1;
extern char *str_author2;

static void cancelpost(fileheader_t *fh, int by_BM) {
    FILE *fin, *fout;
    char *ptr, *brd;
    fileheader_t postfile;
    char genbuf[200];
    char nick[STRLEN], fn1[STRLEN], fn2[STRLEN];
    
    setbfile(fn1, currboard, fh->filename);
    if((fin = fopen(fn1, "r"))) {
    brd = by_BM ? "deleted" : "junk";

    setbpath(fn2, brd);
    stampfile(fn2, &postfile);
    memcpy(postfile.owner, fh->owner, IDLEN + TTLEN + 10);
    postfile.savemode = 'D';

    if(fh->savemode == 'S') {
        nick[0] = '\0';
        while(fgets(genbuf, sizeof(genbuf), fin)) {
        if (!strncmp(genbuf, str_author1, LEN_AUTHOR1) ||
            !strncmp(genbuf, str_author2, LEN_AUTHOR2)) {
            if((ptr = strrchr(genbuf, ')')))
            *ptr = '\0';
            if((ptr = (char *)strchr(genbuf, '(')))
            strcpy(nick, ptr + 1);
            break;
        }
        }

        if((fout = fopen("innd/cancel.bntp", "a"))) {
        fprintf(fout, "%s\t%s\t%s\t%s\t%s\n", currboard, fh->filename,
            cuser.userid, nick, fh->title);
        fclose(fout);
        }
    }
    
    fclose(fin);
    Rename(fn1, fn2);
    setbdir(genbuf, brd);
    append_record(genbuf, &postfile, sizeof(postfile));
    }
}

extern char *str_reply;
extern char save_title[];

/* ----------------------------------------------------- */
/* 發表、回應、編輯、轉錄文章                            */
/* ----------------------------------------------------- */
void do_reply_title(int row, char *title) {
    char genbuf[200];
    char genbuf2[4];

    if(strncasecmp(title, str_reply, 4))
    sprintf(save_title, "Re: %s", title);
    else
    strcpy(save_title, title);
    save_title[TTLEN - 1] = '\0';
    sprintf(genbuf, "採用原標題《%.60s》嗎?[Y] ", save_title);
    getdata(row, 0, genbuf, genbuf2, 4, LCECHO);
    if(genbuf2[0] == 'n' || genbuf2[0] == 'N')
    getdata(++row, 0, "標題:", save_title, TTLEN, DOECHO);
}

static void do_unanonymous_post(char* fpath) {
    fileheader_t mhdr;
    char title[128];
    char genbuf[200];

    setbpath(genbuf, "UnAnonymous");
    if(dashd(genbuf)) {
    stampfile(genbuf, &mhdr);
    unlink(genbuf);
    Link(fpath, genbuf);
    strcpy(mhdr.owner, cuser.userid);
    strcpy(mhdr.title, save_title);
    mhdr.savemode = 0;
    mhdr.filemode = 0;
    setbdir(title, "UnAnonymous");
    append_record(title, &mhdr, sizeof(mhdr));
    }
}

extern char quote_file[];
extern char quote_user[];
extern int curredit;
extern unsigned int currbrdattr;
extern char currdirect[];
extern char *err_uid;

#ifdef NO_WATER_POST
static time_t last_post_time = 0;
static time_t water_counts = 0;
#endif
int local_article;
char real_name[IDLEN + 1];

static int do_general() {
    fileheader_t postfile;
    char fpath[80], buf[80];
    int aborted, defanony, ifuseanony;
    char genbuf[200],*owner;
    boardheader_t *bp;
    int islocal;
    
    ifuseanony = 0;
    bp = getbcache(currbid);
    
    clear();
    if(!(currmode & MODE_POST)) {
    move(5, 10);
    outs("對不起,您目前無法在此發表文章!");
    pressanykey();
    return FULLUPDATE;
    }

#ifdef NO_WATER_POST
    /* 三分鐘內最多發表五篇文章 */
    if(currutmp->lastact - last_post_time < 60 * 3) {
    if(water_counts >= 5) {
        move(5, 10);
        outs("對不起,您的文章太水囉,多思考一下,待會再post吧!");
        pressanykey();
        return FULLUPDATE;
        }
    } else {
    last_post_time = currutmp->lastact;
    water_counts = 0;
    }
#endif
    
    setbfile(genbuf, currboard, FN_POST_NOTE );
    
    if(more(genbuf,NA) == -1)
    more("etc/"FN_POST_NOTE , NA);
    
    move(19,0);
    prints("發表文章於【\033[33m %s\033[m 】 \033[32m%s\033[m 看板\n\n",
       currboard, bp->title + 7);
    
    if(quote_file[0])
    do_reply_title(20, currtitle);
    else {
    getdata(21, 0, "標題:", save_title, TTLEN, DOECHO);
    strip_ansi(save_title,save_title,0);
    }
    if(save_title[0] == '\0')
    return FULLUPDATE;
    
    curredit &= ~EDIT_MAIL;
    curredit &= ~EDIT_ITEM;
    setutmpmode(POSTING);
    
    /* 未具備 Internet 權限者,只能在站內發表文章 */
    if(HAS_PERM(PERM_INTERNET))
    local_article = 0;
    else
    local_article = 1;

    /* build filename */
    setbpath(fpath, currboard);
    stampfile(fpath, &postfile);
    
    aborted = vedit(fpath, YEA, &islocal);
    if(aborted == -1) {
    unlink(fpath);
    pressanykey();
    return FULLUPDATE;
    }
    water_counts++;  /* po成功 */

    /* set owner to Anonymous , for Anonymous board */

#ifdef HAVE_ANONYMOUS
    /* Ptt and Jaky */
    defanony=currbrdattr & BRD_DEFAULTANONYMOUS;
    if((currbrdattr & BRD_ANONYMOUS) && 
       ((strcmp(real_name,"r") && defanony) || (real_name[0] && !defanony)) 
    ) {
    strcat(real_name,".");
    owner = real_name;
    ifuseanony=1;
    } else
    owner = cuser.userid;
#else
    owner = cuser.userid;
#endif
    /* 錢 */
    aborted = (aborted > MAX_POST_MONEY * 2) ? MAX_POST_MONEY : aborted / 2;
    postfile.money = aborted;
    strcpy(postfile.owner, owner);
    strcpy(postfile.title, save_title);
    if(islocal) {            /* local save */
    postfile.savemode = 'L';
    postfile.filemode = FILE_LOCAL;
    } else
    postfile.savemode = 'S';
    
    setbdir(buf, currboard);
    if(append_record(buf, &postfile, sizeof(postfile)) != -1) {
    setbtotal(currbid);

    if(currmode & MODE_SELECT)
        append_record(currdirect,&postfile,sizeof(postfile));
    if(!islocal && !(bp->brdattr & BRD_NOTRAN))
        outgo_post(&postfile, currboard);
    brc_addlist(postfile.filename);

    if(!(currbrdattr  & BRD_HIDE) &&
       (!bp->level || (currbrdattr  & BRD_POSTMASK))) {
        setbpath(genbuf, ALLPOST);
        stampfile(genbuf, &postfile);
        unlink(genbuf);

        /* jochang: boards may spread across many disk */
        /* link doesn't work across device,
           Link doesn't work if we have same-time-across-device posts,
           we try symlink now */
        {
            /* we need absolute path for symlink */
            char abspath[256]=BBSHOME"/";
            strcat(abspath,fpath);
            symlink(abspath,genbuf);
        }
        strcpy(postfile.owner, owner);
        strcpy(postfile.title, save_title);
        postfile.savemode = 'L';
        setbdir(genbuf, ALLPOST);
        if(append_record(genbuf, &postfile, sizeof(postfile)) != -1) {
        setbtotal(getbnum(ALLPOST));
        }
    }
    
    outs("順利貼出佈告,");
    
#ifdef MAX_POST_MONEY
    aborted = (aborted > MAX_POST_MONEY) ? MAX_POST_MONEY : aborted;
#endif
    if(strcmp(currboard, "Test") && !ifuseanony) {
        prints("這是您的第 %d 篇文章。 稿酬 %d 銀。",
           ++cuser.numposts, aborted );
        demoney(aborted);
            passwd_update(usernum, &cuser); /* post 數 */
    } else
        outs("測試信件不列入紀錄,敬請包涵。");
    
    /* 回應到原作者信箱 */
    
    if(curredit & EDIT_BOTH) {
        char *str, *msg = "回應至作者信箱";

        if((str = strchr(quote_user, '.'))) {
        if(
#ifndef USE_BSMTP
            bbs_sendmail(fpath, save_title, str + 1)
#else
            bsmtp(fpath, save_title, str + 1 ,0)
#endif
            < 0)
            msg = "作者無法收信";
        } else {
        sethomepath(genbuf, quote_user);
        stampfile(genbuf, &postfile);
        unlink(genbuf);
        Link(fpath, genbuf);

        strcpy(postfile.owner, cuser.userid);
        strcpy(postfile.title, save_title);
        postfile.savemode = 'B';/* both-reply flag */
        sethomedir(genbuf, quote_user);
        if(append_record(genbuf, &postfile, sizeof(postfile)) == -1)
            msg = err_uid;
        }
        outs(msg);
        curredit ^= EDIT_BOTH;
    }
    if(currbrdattr & BRD_ANONYMOUS)
        do_unanonymous_post(fpath);
    }
    pressanykey();
    return FULLUPDATE;
}

int do_post() {
    boardheader_t *bp;
    bp = getbcache(currbid);
    if(bp->brdattr & BRD_VOTEBOARD)
        return do_voteboard();
    else if(!(bp->brdattr & BRD_GROUPBOARD))
        return do_general();
    touchdircache(currbid);
    return 0;
}

extern int b_lines;
extern int curredit;

static void do_generalboardreply(fileheader_t *fhdr){            
    char genbuf[200];
    getdata(b_lines - 1, 0,
        "▲ 回應至 (F)看板 (M)作者信箱 (B)二者皆是 (Q)取消?[F] ",
        genbuf, 3, LCECHO);
    switch(genbuf[0]) {
    case 'm':
    mail_reply(0, fhdr, 0);
    case 'q':
    break;

    case 'b':
    curredit = EDIT_BOTH;
    default:
    strcpy(currtitle, fhdr->title);
    strcpy(quote_user, fhdr->owner);
    quote_file[79] = fhdr->savemode;
    do_post();
    }
    *quote_file = 0;
}

int getindex(char *fpath, char *fname, int size) {
    int fd, now=0;
    fileheader_t fhdr;
    
    if((fd = open(fpath, O_RDONLY, 0)) != -1) {
    while((read(fd, &fhdr, size) == size)) {
        now++;
        if(!strcmp(fhdr.filename,fname)) {
        close(fd);
        return now;
        }
    }
    close(fd);
    }
    return 0;
}

int invalid_brdname(char *brd) {
    register char ch;
    
    ch = *brd++;
    if(not_alnum(ch))
    return 1;
    while((ch = *brd++)) {
    if(not_alnum(ch) && ch != '_' && ch != '-' && ch != '.')
        return 1;
    }
    return 0;
}       

static void do_reply(fileheader_t *fhdr) {
    boardheader_t *bp;
    bp = getbcache(currbid);
    if (bp->brdattr & BRD_VOTEBOARD)
        do_voteboardreply(fhdr);
    else
        do_generalboardreply(fhdr);
}

static int reply_post(int ent, fileheader_t *fhdr, char *direct) {
    if(!(currmode & MODE_POST))
    return DONOTHING;

    setdirpath(quote_file, direct, fhdr->filename);
    do_reply(fhdr);
    *quote_file = 0;
    return FULLUPDATE;
}

static int edit_post(int ent, fileheader_t *fhdr, char *direct) {
    char fpath[80], fpath0[80];
    char genbuf[200];
    fileheader_t postfile;
    boardheader_t *bp;
    bp = getbcache(currbid);
    if (!HAS_PERM(PERM_SYSOP) && (bp->brdattr & BRD_VOTEBOARD))
    return DONOTHING;

    if ((!HAS_PERM(PERM_SYSOP)) && 
        strcmp(fhdr->owner, cuser.userid))
    return DONOTHING;
    setutmpmode(REEDIT); 
    setdirpath(genbuf, direct, fhdr->filename);
    local_article = fhdr->filemode & FILE_LOCAL;
    strcpy(save_title, fhdr->title);

/* rocker.011018: 這裡是不是該檢查一下修改文章後的money和原有的比較? */
    if(vedit(genbuf, 0, NULL) != -1) {
    setbpath(fpath, currboard);
    stampfile(fpath, &postfile);
    unlink(fpath);
    setbfile(fpath0, currboard, fhdr->filename);

    Rename(fpath0, fpath);

/* rocker.011018: fix 串接模式改文章後文章就不見的bug */
    if ((currmode & MODE_SELECT) && (fhdr->money & FHR_REFERENCE))
    {
      fileheader_t hdr;
      int num;

      num = fhdr->money & ~FHR_REFERENCE;
      setbdir(fpath0, currboard);
          get_record(fpath0, &hdr, sizeof (hdr), num);

      /* 再這裡要check一下原來的dir裡面是不是有被人動過... */
      if (!strcmp (hdr.filename, fhdr->filename))
      {
        strcpy(hdr.filename, postfile.filename);
        strcpy(hdr.title, save_title);
        substitute_record(fpath0, &hdr, sizeof(hdr), num);
      }
    }

    strcpy(fhdr->filename, postfile.filename);
    strcpy(fhdr->title, save_title);
    brc_addlist(postfile.filename);
    substitute_record(direct, fhdr, sizeof(*fhdr), ent);
/* rocker.011018: 順便更新一下cache */
        touchdircache(currbid); 
    }
    return FULLUPDATE;
}

extern crosspost_t postrecord;
#define UPDATE_USEREC   (currmode |= MODE_DIRTY)

static int cross_post(int ent, fileheader_t *fhdr, char *direct) {
    char xboard[20], fname[80], xfpath[80], xtitle[80], inputbuf[10];
    fileheader_t xfile;
    FILE *xptr;
    int author = 0;
    char genbuf[200];
    char genbuf2[4];
    boardheader_t *bp;
    move(2, 0);
    clrtoeol();
    move(3, 0);
    clrtoeol();
    move(1, 0);
    bp = getbcache(currbid);
    if (bp && (bp->brdattr & BRD_VOTEBOARD))
    return FULLUPDATE;
    generalnamecomplete("轉錄本文章於看板:", xboard, sizeof(xboard),
            brdshm->number,
            completeboard_compar,
            completeboard_permission,
            completeboard_getname);
    if(*xboard == '\0' || !haspostperm(xboard))
    return FULLUPDATE;
    
    if((ent = str_checksum(fhdr->title)) != 0 &&
       ent == postrecord.checksum[0]) {
    /* 檢查 cross post 次數 */
    if(postrecord.times++ > MAX_CROSSNUM)
        anticrosspost();
    } else {
    postrecord.times = 0;
    postrecord.checksum[0] = ent;
    }

    ent = 1;
    if(HAS_PERM(PERM_SYSOP) || !strcmp(fhdr->owner, cuser.userid)) {
    getdata(2, 0, "(1)原文轉載 (2)舊轉錄格式?[1] ",
        genbuf, 3, DOECHO);
    if(genbuf[0] != '2') {
        ent = 0;
        getdata(2, 0, "保留原作者名稱嗎?[Y] ", inputbuf, 3, DOECHO);
        if (inputbuf[0] != 'n' && inputbuf[0] != 'N') author = 1;
    }
    }

    if(ent)
    sprintf(xtitle, "[轉錄]%.66s", fhdr->title);
    else
    strcpy(xtitle, fhdr->title);

    sprintf(genbuf, "採用原標題《%.60s》嗎?[Y] ", xtitle);
    getdata(2, 0, genbuf, genbuf2, 4, LCECHO);
    if(genbuf2[0] == 'n' || genbuf2[0] == 'N') {
    if(getdata_str(2, 0, "標題:", genbuf, TTLEN, DOECHO,xtitle))
        strcpy(xtitle, genbuf);
    }
    
    getdata(2, 0, "(S)存檔 (L)站內 (Q)取消?[Q] ", genbuf, 3, LCECHO);
    if(genbuf[0] == 'l' || genbuf[0] == 's') {
    int currmode0 = currmode;

    currmode = 0;
    setbpath(xfpath, xboard);
    stampfile(xfpath, &xfile);
    if(author)
        strcpy(xfile.owner, fhdr->owner);
    else
        strcpy(xfile.owner, cuser.userid);
    strcpy(xfile.title, xtitle);
    if(genbuf[0] == 'l') {
        xfile.savemode = 'L';
        xfile.filemode = FILE_LOCAL;
    } else
        xfile.savemode = 'S';

    setbfile(fname, currboard, fhdr->filename);
//  if(ent) {
    xptr = fopen(xfpath, "w");

    strcpy(save_title, xfile.title);
    strcpy(xfpath, currboard);
    strcpy(currboard, xboard);
    write_header(xptr);
    strcpy(currboard, xfpath);

    fprintf(xptr, "※ [本文轉錄自 %s 看板]\n\n", currboard);

    b_suckinfile(xptr, fname);
    addsignature(xptr,0);
    fclose(xptr);
/* Cross fs有問題
   } else {
   unlink(xfpath);
   link(fname, xfpath);
   }
*/
    setbdir(fname, xboard);
    append_record(fname, &xfile, sizeof(xfile));
    bp = getbcache(getbnum(xboard));
    if(!xfile.filemode && !(bp->brdattr && BRD_NOTRAN))
        outgo_post(&xfile, xboard);
    setbtotal(getbnum(xboard));
    cuser.numposts++;
    UPDATE_USEREC;
    outs("文章轉錄完成");
    pressanykey();
    currmode = currmode0;
    }
    return FULLUPDATE;
}

static int read_post(int ent, fileheader_t *fhdr, char *direct) {
    char genbuf[200];
    int more_result;

    if(fhdr->owner[0] == '-')
    return DONOTHING;

    setdirpath(genbuf, direct, fhdr->filename);

    if((more_result = more(genbuf, YEA)) == -1)
    return DONOTHING;

    brc_addlist(fhdr->filename);
    strncpy(currtitle, subject(fhdr->title), TTLEN);
    strncpy(currowner, subject(fhdr->owner), IDLEN + 2);

    switch (more_result) {
    case 1:
    return READ_PREV;
    case 2:
    return RELATE_PREV;
    case 3:
    return READ_NEXT;
    case 4:
    return RELATE_NEXT;
    case 5:
    return RELATE_FIRST;
    case 6:
    return FULLUPDATE;
    case 7:
    case 8:
    if((currmode & MODE_POST)) {
        strcpy(quote_file, genbuf);
        do_reply(fhdr);
        *quote_file = 0;
    }
    return FULLUPDATE;
    case 9:
    return 'A';
    case 10:
    return 'a';
    case 11:
    return '/';
    case 12:
    return '?';
    }


    outmsg("\033[34;46m  閱\讀文章  \033[31;47m  (R/Y)\033[30m回信 \033[31m"
       "(=[]<>)\033[30m相關主題 \033[31m(↑↓)\033[30m上下封 \033[31m(←)"
       "\033[30m離開  \033[m");

    switch(egetch()) {
    case 'q':
    case 'Q':
    case KEY_LEFT:
    break;

    case ' ':
    case KEY_RIGHT:
    case KEY_DOWN:
    case KEY_PGDN:
    case 'n':
    case Ctrl('N'):
    return READ_NEXT;

    case KEY_UP:
    case 'p':
    case Ctrl('P'):
    case KEY_PGUP:
    return READ_PREV;

    case '=':
    return RELATE_FIRST;

    case ']':
    case 't':
    return RELATE_NEXT;

    case '[':
    return RELATE_PREV;

    case '.':
    case '>':
    return THREAD_NEXT;

    case ',':
    case '<':
    return THREAD_PREV;

    case Ctrl('C'):
    cal();
    return FULLUPDATE;
    break;

    case Ctrl('I'):
    t_idle();
    return FULLUPDATE;
    case 'y':
    case 'r':
    case 'R':
    case 'Y':
    if((currmode & MODE_POST)) {
        strcpy(quote_file, genbuf);
        do_reply(fhdr);
        *quote_file = 0;
    }
    }
    return FULLUPDATE;
}

/* ----------------------------------------------------- */
/* 採集精華區                                            */
/* ----------------------------------------------------- */
static int b_man() {
    char buf[64];
    
    setapath(buf, currboard);
    if( (currmode & MODE_BOARD) || HAS_PERM(PERM_SYSOP) ){
        char    genbuf[128];
        int     fd;
        sprintf(genbuf, "%s/.rebuild", buf);
        if( (fd = open(genbuf, O_CREAT, 0640)) > 0 )
            close(fd);
    }
    return a_menu(currboard, buf, HAS_PERM(PERM_ALLBOARD) ? 2 :
          (currmode & MODE_BOARD ? 1 : 0));
}

#ifndef NO_GAMBLE
static int join_gamble(int ent, fileheader_t *fhdr, char *direct) {
   ticket(currbid);
   return FULLUPDATE; 
}
static int hold_gamble(int ent, fileheader_t *fhdr, char *direct) { 
   char fn_ticket[128],fn_ticket_end[128],genbuf[128],
        msg[256]="",yn[10]="";
   int i;
   FILE *fp=NULL;

   if(!(currmode & MODE_BOARD)) return 0;
   setbfile(fn_ticket, currboard, FN_TICKET);
   setbfile(fn_ticket_end, currboard, FN_TICKET_END);
   if(dashf(fn_ticket))
    {
      getdata(b_lines - 1, 0, "已經有舉辦賭盤, "
            "是否要 [停止下注]?(N/y):", yn, 3, LCECHO);
      if(yn[0]!='y') return FULLUPDATE;        
      rename(fn_ticket, fn_ticket_end);
      return FULLUPDATE;
    }
   
   if(dashf(fn_ticket_end))
    {
      getdata(b_lines - 1, 0, "已經有舉辦賭盤, "
            "是否要 [開獎]?(N/y):", yn, 3, LCECHO);
      if(yn[0]!='y') return FULLUPDATE;
      openticket(currbid);
      return FULLUPDATE; 
    }
   getdata(b_lines - 2, 0, "要舉辦賭盤 (N/y):", yn, 3, LCECHO);
   if(yn[0]!='y') return FULLUPDATE;
   getdata(b_lines - 1, 0, "賭什麼? 請輸入主題 (輸入後編輯內容):",
            msg, 20, DOECHO);
   if(msg[0]==0 ||
      vedit(fn_ticket_end, NA, NULL)<0)
        return FULLUPDATE; 
      
   clear();
   showtitle("舉辦賭盤",BBSNAME);
   setbfile(genbuf, currboard, FN_TICKET_ITEMS);

//    sprintf(genbuf, "%s/"FN_TICKET_ITEMS, direct);
    
   if(!(fp=fopen(genbuf,"w"))) return FULLUPDATE;
   do
    {
        getdata(2, 0, "輸入彩票價格 (價格:10-10000):",yn,6, LCECHO);
        i=atoi(yn);
    } while( i<10 || i>10000);
   fprintf(fp,"%d\n",i);
   move(3,0);
   sprintf(genbuf,"請到 %s 版 按'f'參與賭博!\n\n一張 %d Ptt幣, 這是%s的賭博\n", 
            currboard,
            i, i<100 ? "小賭式" : i<500 ? "平民級": 
            i<1000 ?"貴族級" : i<5000 ?"富豪級" : "傾家蕩產");
   strcat(msg, genbuf);
   prints("請依次輸入彩票名稱, 需提供2~8項. (未滿八項, 輸入直接按enter)\n");
   for(i=0; i<8; i++)
    {
           sprintf(yn, " %d)",i+1);
           getdata(6+i, 0, yn, genbuf, 9, DOECHO); 
           if(!genbuf[0] && i>1)
           break;
       fprintf(fp,"%s\n",genbuf);
    }   
   fclose(fp);
   move(8+i,0);
   prints("賭盤設定完成");
   sprintf(genbuf,"[公告] %s 版 開始賭博!", currboard);  
   post_msg(currboard, genbuf, msg, cuser.userid);
   post_msg("Record", genbuf+7, msg, "[馬路探子]"); 
      /* Tim 控制CS, 以免正在玩的user把資料已經寫進來 */
   rename(fn_ticket_end, fn_ticket); // 設定完才把檔名改過來

   return FULLUPDATE;
}
#endif

static int cite_post(int ent, fileheader_t *fhdr, char *direct) {
    char fpath[256];
    char title[TTLEN + 1];
    
    setbfile(fpath, currboard, fhdr->filename);
    strcpy(title, "◇ ");
    strncpy(title+3, fhdr->title, TTLEN-3);
    title[TTLEN] = '\0';
    a_copyitem(fpath, title, 0, 1);
    b_man();
    return FULLUPDATE;
}

int edit_title(int ent, fileheader_t *fhdr, char *direct) {
    char genbuf[200];
    fileheader_t tmpfhdr = *fhdr;
    int dirty = 0;

    if(currmode & MODE_BOARD || !strcmp(cuser.userid,fhdr->owner)) {
        if(getdata(b_lines - 1, 0, "標題:", genbuf, TTLEN, DOECHO)) {
        strcpy(tmpfhdr.title, genbuf);
        dirty++;
        }
    }

    if(HAS_PERM(PERM_SYSOP)) {
        if(getdata(b_lines - 1, 0, "作者:", genbuf, IDLEN + 2, DOECHO)) {
        strcpy(tmpfhdr.owner, genbuf);
        dirty++;
        }
    
        if(getdata(b_lines - 1, 0, "日期:", genbuf, 6, DOECHO)) {
        sprintf(tmpfhdr.date, "%.5s", genbuf);
        dirty++;
        }
    }

    if(currmode & MODE_BOARD || !strcmp(cuser.userid,fhdr->owner)) {
        getdata(b_lines-1, 0, "確定(Y/N)?[n] ", genbuf, 3, DOECHO);
    if((genbuf[0] == 'y' || genbuf[0] == 'Y') && dirty) {
        *fhdr = tmpfhdr;
        substitute_record(direct, fhdr, sizeof(*fhdr), ent);
/* rocker.011018: 這裡應該改成用reference的方式取得原來的檔案 */
#if 0
        if((currmode & MODE_SELECT)) {
        int now;

        setbdir(genbuf, currboard);
        now = getindex(genbuf, fhdr->filename, sizeof(fileheader_t));
        substitute_record(genbuf, fhdr, sizeof(*fhdr), now);
        }
#else
    if ((currmode & MODE_SELECT) && (fhdr->money & FHR_REFERENCE))
    {
      fileheader_t hdr;
      int num;

      num = fhdr->money & ~FHR_REFERENCE;
      setbdir(genbuf, currboard);
          get_record(genbuf, &hdr, sizeof (hdr), num);

      /* 再這裡要check一下原來的dir裡面是不是有被人動過... */
      if (strcmp (hdr.filename, fhdr->filename))
        num = getindex(genbuf, fhdr->filename, sizeof(fileheader_t));

      substitute_record(genbuf, fhdr, sizeof(*fhdr), num);
    }
#endif
            touchdircache(currbid); 
    }
    return FULLUPDATE;
    }
    return DONOTHING;
}

extern unsigned int currstat;

static int solve_post(int ent, fileheader_t * fhdr, char *direct){
    if (HAS_PERM(PERM_SYSOP)) {
    fhdr->filemode ^= FILE_SOLVED;
    substitute_record(direct, fhdr, sizeof(*fhdr), ent);
        touchdircache(currbid);
    return PART_REDRAW;
    }
    return DONOTHING;
}

static int mark_post(int ent, fileheader_t *fhdr, char *direct) {

    if(!(currmode & MODE_BOARD)) return DONOTHING;

    fhdr->filemode ^= FILE_MARKED;
    substitute_record(direct, fhdr, sizeof(*fhdr), ent);

    /* rocker.011018: 串接模式用reference增進效率 */
    if ((currmode & MODE_SELECT) && (fhdr->money & FHR_REFERENCE))
    {
      fileheader_t hdr;
      char genbuf[100];
      int num;

      num = fhdr->money & ~FHR_REFERENCE;
      setbdir(genbuf, currboard);
      get_record(genbuf, &hdr, sizeof (hdr), num);

      /* 再這裡要check一下原來的dir裡面是不是有被人動過... */
      if (strcmp (hdr.filename, fhdr->filename))
    num = getindex(genbuf, fhdr->filename, sizeof(fileheader_t));

      substitute_record(genbuf, fhdr, sizeof(*fhdr), num);
    }
    touchdircache(currbid);
    return PART_REDRAW;
}

extern char *msg_sure_ny;

int del_range(int ent, fileheader_t *fhdr, char *direct) {
    char num1[8], num2[8];
    int inum1, inum2;

/* rocker.011018: 串接模式下還是不允許刪除比較好 */
    if(currmode & MODE_SELECT) {
        outmsg("請先回到正常模式後再進行刪除...");
        refresh();
        /*safe_sleep(1);*/
        return FULLUPDATE;
    }

    if((currstat != READING) || (currmode & MODE_BOARD)) {
    getdata(1, 0, "[設定刪除範圍] 起點:", num1, 5, DOECHO);
    inum1 = atoi(num1);
    if(inum1 <= 0) {
        outmsg("起點有誤");
        refresh();
        /*safe_sleep(1);*/
        return FULLUPDATE;
    }
    getdata(1, 28, "終點:", num2, 5, DOECHO);
    inum2 = atoi(num2);
    if(inum2 < inum1) {
        outmsg("終點有誤");
        refresh();
        /*safe_sleep(1);*/
        return FULLUPDATE;
    }
    getdata(1, 48, msg_sure_ny, num1, 3, LCECHO);
    if(*num1 == 'y') {
        outmsg("處理中,請稍後...");
        refresh();
        if(currmode & MODE_SELECT) {
        int fd,size = sizeof(fileheader_t);
        char genbuf[100];
        fileheader_t rsfh;
        int i = inum1,now;
        if(currstat == RMAIL)
            sethomedir(genbuf, cuser.userid);
        else
            setbdir(genbuf,currboard);
        if((fd = (open(direct, O_RDONLY, 0))) != -1) {
            if(lseek(fd, (off_t)(size * (inum1 - 1)), SEEK_SET) !=
               -1) {
            while(read(fd,&rsfh,size) == size) {
                if(i > inum2)
                break;
                now = getindex(genbuf, rsfh.filename, size);
                strcpy(currfile, rsfh.filename);
                delete_file(genbuf, sizeof(fileheader_t), now,
                    cmpfilename);
                i++;
            }
            }
            close(fd);
        }
        }
        
        delete_range(direct, inum1, inum2);
        fixkeep(direct, inum1);

        if(currmode & MODE_BOARD)
        setbtotal(currbid);
        
        return DIRCHANGED;
    }
    return FULLUPDATE;
    }
    return DONOTHING;
}

extern char *msg_del_ny;
extern char *msg_del_ok;

static int del_post(int ent, fileheader_t *fhdr, char *direct) {
    char genbuf[100];
    int not_owned;
    boardheader_t *bp;
    
    bp = getbcache(currbid);

    if((fhdr->filemode & FILE_MARKED) || (fhdr->filemode & FILE_DIGEST) ||
       (fhdr->owner[0] == '-'))
    return DONOTHING;

    not_owned = strcmp(fhdr->owner, cuser.userid);
    if((!(currmode & MODE_BOARD) && not_owned) ||
       ((bp->brdattr & BRD_VOTEBOARD) && !HAS_PERM(PERM_SYSOP)) ||
       !strcmp(cuser.userid, STR_GUEST))
    return DONOTHING;

    getdata(1, 0, msg_del_ny, genbuf, 3, LCECHO);
    if(genbuf[0] == 'y' || genbuf[0] == 'Y') {
    strcpy(currfile, fhdr->filename);
    
    setbfile(genbuf,currboard,fhdr->filename);
    if(!delete_file (direct, sizeof(fileheader_t), ent, cmpfilename)) {

        if(currmode & MODE_SELECT) 
        {
        /* rocker.011018: 利用reference減低loading */
        fileheader_t hdr;
        int num;

        num = fhdr->money & ~FHR_REFERENCE;
        setbdir(genbuf, currboard);
            get_record(genbuf, &hdr, sizeof (hdr), num);

        /* 再這裡要check一下原來的dir裡面是不是有被人動過... */
        if (strcmp (hdr.filename, fhdr->filename))
        {
          num=getindex(genbuf,fhdr->filename,sizeof(fileheader_t));
              get_record(genbuf, &hdr, sizeof (hdr), num);
        }

        /* rocker.011018: 這裡要還原被破壞的money */
        fhdr->money = hdr.money;
        delete_file (genbuf, sizeof(fileheader_t), num, cmpfilename);
        }

#if 0
        {
        setbdir(genbuf,currboard);
        now=getindex(genbuf,fhdr->filename,sizeof(fileheader_t));
        delete_file (genbuf, sizeof(fileheader_t),now,cmpfilename);
        }
#endif
        cancelpost(fhdr, not_owned);

        setbtotal(currbid);
        if (fhdr->money < 0)
        fhdr->money = 0;
        if (not_owned && strcmp(currboard, "Test")){        
        deumoney(searchuser(fhdr->owner), -fhdr->money);
        }
        if(!not_owned && strcmp(currboard, "Test")) {
        if(cuser.numposts)
            cuser.numposts--;
        move(b_lines - 1, 0);
        clrtoeol();
        demoney(-fhdr->money);
                passwd_update(usernum, &cuser); /* post 數 */
        prints("%s,您的文章減為 %d 篇,支付清潔費 %d 銀", msg_del_ok,
               cuser.numposts,fhdr->money);
        refresh();
        pressanykey();
        }
        return DIRCHANGED;
    }
    }
    return FULLUPDATE;
}

static int view_postmoney(int ent, fileheader_t *fhdr, char *direct) {
    move(b_lines - 1, 0);
    clrtoeol();
    prints("這一篇文章值 %d 銀", fhdr->money);
    refresh();
    pressanykey();
    return FULLUPDATE;
}

#ifdef OUTJOBSPOOL
/* 看版備份 */
static int tar_addqueue(int ent, fileheader_t *fhdr, char *direct) {
    char    email[60], qfn[80], ans[2];
    FILE    *fp;
    char    bakboard, bakman;
    clear();
    showtitle("看版備份", BBSNAME);
    move(2, 0);
    if( !((currmode & MODE_BOARD) || HAS_PERM(PERM_SYSOP)) ) {
        move(5, 10);
        outs("妳要是版主或是站長才能醬醬啊 -.-\"\"");
        pressanykey();
        return FULLUPDATE;
    }

    sprintf(qfn, BBSHOME "/jobspool/tarqueue.%s", currboard);
    if( access(qfn, 0) == 0 ){
        outs("已經排定行程, 稍後會進行備份");
        pressanykey();
        return FULLUPDATE;
    }
    if( !getdata(4, 0, "請輸入目的信箱:", email, sizeof(email), DOECHO) )
        return FULLUPDATE;

    /* check email -.-"" */
    if( strstr(email, "@") == NULL || strstr(email, ".bbs@") != NULL ){
        move(6, 0);
        outs("您指定的信箱不正確! ");
        pressanykey();
        return FULLUPDATE;
    }

    getdata(6, 0, "要備份看版內容嗎(Y/N)?[Y]", ans, sizeof(ans), LCECHO);
    bakboard = (ans[0] == 'n' || ans[0] =='N') ? 0 : 1;
    getdata(7, 0, "要備份精華區內容嗎(Y/N)?[N]", ans, sizeof(ans), LCECHO);
    bakman = (ans[0] == 'y' || ans[0] =='Y') ? 1 : 0;
    if( !bakboard && !bakman ){
        move(8, 0);
        outs("可是我們只能備份看版或精華區的耶 ^^\"\"\"");
        pressanykey();
        return FULLUPDATE;
    }

    fp = fopen(qfn, "w");
    fprintf(fp, "%s\n", cuser.userid);
    fprintf(fp, "%s\n", email);
    fprintf(fp, "%d,%d\n", bakboard, bakman);
    fclose(fp);

    move(10, 0);
    outs("系統已經將您的備份排入行程, \n");
    outs("稍後將會在系統負荷較低的時候將資料寄給您~ :) ");
    pressanykey();
    return FULLUPDATE;
}
#endif

static int sequent_ent;
static int continue_flag;

/* ----------------------------------------------------- */
/* 依序讀新文章                                          */
/* ----------------------------------------------------- */
static int sequent_messages(fileheader_t *fptr) {
    static int idc;
    char genbuf[200];

    if(fptr == NULL)
    return (idc = 0);

    if(++idc < sequent_ent)
    return 0;

    if(!brc_unread(fptr->filename,brc_num,brc_list))
    return 0;

    if(continue_flag)
    genbuf[0] = 'y';
    else {
    prints("讀取文章於:[%s] 作者:[%s]\n標題:[%s]",
           currboard, fptr->owner, fptr->title);
    getdata(3, 0, "(Y/N/Quit) [Y]: ", genbuf, 3, LCECHO);
    }

    if(genbuf[0] != 'y' && genbuf[0]) {
    clear();
    return (genbuf[0] == 'q' ? QUIT : 0);
    }

    setbfile(genbuf, currboard, fptr->filename);
    brc_addlist(fptr->filename);

    if(more(genbuf, YEA) == 0)
    outmsg("\033[31;47m  \033[31m(R)\033[30m回信  \033[31m(↓,n)"
           "\033[30m下一封  \033[31m(←,q)\033[30m離開  \033[m");
    continue_flag = 0;

    switch(egetch()) {
    case KEY_LEFT:
    case 'e':
    case 'q':
    case 'Q':
    break;
    
    case 'y':
    case 'r':
    case 'Y':
    case 'R':
    if(currmode & MODE_POST) {
        strcpy(quote_file, genbuf);
        do_reply(fptr);
        *quote_file = 0;
    }
    break;

    case ' ':
    case KEY_DOWN:
    case '\n':
    case 'n':
    continue_flag = 1;
    }

    clear();
    return 0;
}

static int sequential_read(int ent, fileheader_t *fhdr, char *direct) {
    char buf[40];

    clear();
    sequent_messages((fileheader_t *) NULL);
    sequent_ent = ent;
    continue_flag = 0;
    setbdir(buf, currboard);
    apply_record(buf, sequent_messages, sizeof(fileheader_t));
    return FULLUPDATE;
}

extern char *fn_notes;
extern char *msg_cancel;
extern char *fn_board;

/* ----------------------------------------------------- */
/* 看板備忘錄、文摘、精華區                              */
/* ----------------------------------------------------- */
int b_note_edit_bname(int bid) {
    char buf[64];
    int aborted;
    boardheader_t *fh=getbcache(bid);

    setbfile(buf, fh->brdname, fn_notes);
    aborted = vedit(buf, NA, NULL);
    if(aborted == -1) {
    clear();
    outs(msg_cancel);
    pressanykey();
    } else {
    aborted = (fh->bupdate - now ) / 86400 + 1;
    sprintf(buf,"%d", aborted > 0 ? aborted : 0);
    getdata_buf(3, 0, "請設定有效期限(0 - 9999)天?", buf, 5, DOECHO);
    aborted = atoi(buf);
    fh->bupdate = aborted ? now + aborted * 86400 : 0;
    substitute_record(fn_board, fh, sizeof(boardheader_t), bid);
    }
    return 0;
}

static int b_notes_edit() {
    if(currmode & MODE_BOARD) {
        b_note_edit_bname(currbid);
    return FULLUPDATE;
    }
    return 0;
}

static int b_water_edit() {
    if(currmode & MODE_BOARD) {
    friend_edit(BOARD_WATER);
    return FULLUPDATE;
    }
    return 0;
}

static int visable_list_edit() {
    if(currmode & MODE_BOARD) {
    friend_edit(BOARD_VISABLE);
    hbflreload(currbid);
    return FULLUPDATE;
    }
    return 0;
}

static int b_post_note() {
    char buf[200], yn[3];
  
    if(currmode & MODE_BOARD) {
    setbfile(buf, currboard,  FN_POST_NOTE );
    if(more(buf,NA) == -1)  more("etc/"FN_POST_NOTE , NA);
    getdata(b_lines - 2, 0, "是否要用自訂post注意事項?", yn, sizeof(yn), LCECHO);
    if(yn[0] == 'y')
        vedit(buf, NA, NULL);
    else
        unlink(buf);
    return FULLUPDATE;
    }
    return 0;
}

static int b_application() {
    char buf[200];

    if(currmode & MODE_BOARD) {
    setbfile(buf, currboard,  FN_APPLICATION);
    vedit(buf, NA, NULL);
    return FULLUPDATE;
    }
    return 0;
}

static int can_vote_edit() {
    if(currmode & MODE_BOARD) {
    friend_edit(FRIEND_CANVOTE);
    return FULLUPDATE;
    }
    return 0;
}

static int bh_title_edit() {
    boardheader_t *bp;

    if(currmode & MODE_BOARD) {
    char genbuf[BTLEN];

    bp = getbcache(currbid);
    move(1,0);
    clrtoeol();
    getdata_str(1,0,"請輸入看板新中文敘述:", genbuf,
            BTLEN - 16, DOECHO, bp->title + 7);

    if(!genbuf[0])
        return 0;
    strip_ansi( genbuf,genbuf,0);
    strcpy(bp->title + 7,genbuf);
    substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
    log_usies("SetBoard", currboard);
    return FULLUPDATE;
    }
    return 0;
}

static int b_notes() {
    char buf[64];
    
    setbfile(buf, currboard, fn_notes);
    if(more(buf, NA) == -1) {
    clear();
    move(4, 20);
    outs("本看板尚無「備忘錄」。");
    }
    pressanykey();
    return FULLUPDATE;
}

int board_select() {
    char fpath[80];
    char genbuf[100];

    currmode &= ~MODE_SELECT;
    sprintf(fpath, "SR.%s", cuser.userid);
    setbfile(genbuf, currboard, fpath);
    unlink(genbuf);
    if(currstat == RMAIL)
    sethomedir(currdirect, cuser.userid);
    else
    setbdir(currdirect, currboard);
    return NEWDIRECT;
}

int board_digest() {
    if(currmode & MODE_SELECT)
    board_select();
    currmode ^= MODE_DIGEST;
    if(currmode & MODE_DIGEST)
    currmode &= ~MODE_POST;
    else if (haspostperm(currboard))
    currmode |= MODE_POST;
    
    setbdir(currdirect, currboard);
    return NEWDIRECT;
}

int board_etc() {
    if(!HAS_PERM(PERM_SYSOP))
    return DONOTHING;
    currmode ^= MODE_ETC;
    if(currmode & MODE_ETC)
    currmode &= ~MODE_POST;
    else if(haspostperm(currboard))
    currmode |= MODE_POST;
    
    setbdir(currdirect, currboard);
    return NEWDIRECT;
}

extern char *fn_mandex;

static int good_post(int ent, fileheader_t *fhdr, char *direct) {
    char genbuf[200];
    char genbuf2[200];
    int delta = 0;

    if((currmode & MODE_DIGEST) || !(currmode & MODE_BOARD))
    return DONOTHING;

    if(fhdr->filemode & FILE_DIGEST) {
    fhdr->filemode = (fhdr->filemode & ~FILE_DIGEST);
    if(!strcmp(currboard,"Note") || !strcmp(currboard,"PttBug") ||
       !strcmp(currboard,"Artdsn") || !strcmp(currboard, "PttLaw")) {
        deumoney(searchuser(fhdr->owner),-1000);
        if(!(currmode & MODE_SELECT))
          fhdr->money -= 1000;
        else
          delta = -1000;
    }
    } else {
    fileheader_t digest;
    char *ptr, buf[64];

    memcpy(&digest, fhdr, sizeof(digest));
    digest.filename[0] = 'G';
    strcpy(buf, direct);
    ptr = strrchr(buf, '/') + 1;
    ptr[0] = '\0';
    sprintf(genbuf, "%s%s", buf, digest.filename);

    if(dashf(genbuf)) unlink (genbuf);

    digest.savemode = digest.filemode = 0;
    sprintf(genbuf2, "%s%s", buf, fhdr->filename);
    Link(genbuf2, genbuf);
    strcpy(ptr, fn_mandex);
    append_record(buf, &digest, sizeof(digest));

    fhdr->filemode = (fhdr->filemode & ~FILE_MARKED) | FILE_DIGEST;
    if(!strcmp(currboard, "Note") || !strcmp(currboard, "PttBug") ||
       !strcmp(currboard,"Artdsn") || !strcmp(currboard, "PttLaw")) {
        deumoney(searchuser(fhdr->owner), 1000);
        if(!(currmode & MODE_SELECT)) fhdr->money += 1000;
        else delta = 1000;
    }
    }
    substitute_record(direct, fhdr, sizeof(*fhdr), ent);
    touchdircache(currbid); 
/* rocker.011018: 串接模式用reference增進效率 */
    if ((currmode & MODE_SELECT) && (fhdr->money & FHR_REFERENCE))
    {
      fileheader_t hdr;
      char genbuf[100];
      int num;

      num = fhdr->money & ~FHR_REFERENCE;
      setbdir(genbuf, currboard);
      get_record(genbuf, &hdr, sizeof (hdr), num);

      /* 再這裡要check一下原來的dir裡面是不是有被人動過... */
      if (strcmp (hdr.filename, fhdr->filename))
      {
    num = getindex(genbuf, fhdr->filename, sizeof(fileheader_t));
        get_record(genbuf, &hdr, sizeof (hdr), num);
      }
      fhdr->money = hdr.money + delta;

      substitute_record(genbuf, fhdr, sizeof(*fhdr), num);
    }
#if 0
    if(currmode & MODE_SELECT) {
    int now;
    char genbuf[100];

    setbdir(genbuf, currboard);
    now=getindex(genbuf, fhdr->filename, sizeof(fileheader_t));
    substitute_record(genbuf, fhdr, sizeof(*fhdr), now);
    }
#endif
    return PART_REDRAW;
}

/* help for board reading */
static char *board_help[] = {
    "\0全功\能看板操作說明",
    "\01基本命令",
    "(p)(↑)   上移一篇文章         (^P)     發表文章",
    "(n)(↓)   下移一篇文章         (d)      刪除文章",
    "(P)(PgUp) 上移一頁             (S)      串連相關文章",
    "(N)(PgDn) 下移一頁             (##)     跳到 ## 號文章",
    "(r)(→)   閱\讀此篇文章         ($)      跳到最後一篇文章",
    "\01進階命令",
    "(tab)/z   文摘模式/精華區      (a)(A)   找尋作者",
    "(b/f)     展讀備忘錄/參與賭盤  (?)(/)   找尋標題",
    "(V/R)     投票/查詢投票結果    (^W)     我在哪裡可看到看板的分類",
    "(x)       轉錄文章到其他看板   (=)/([]<>-+) 找尋首篇文章/主題式閱\讀",
#ifdef INTERNET_EMAIL
    "(F)       文章寄回Internet郵箱 (U)      將文章 uuencode 後寄回郵箱",
#endif
    "(E)       重編文章             (^H)     列出所有的 New Post(s)",
    "\01板主命令",
    "(G)       舉辦賭盤/停止下注/開獎(W/w/v) 編輯備忘錄/水桶名單/可看見名單",
    "(M/o)     舉行投票/編私投票名單 (m/c/g) 保留文章/選錄精華/文摘",
    "(D)       刪除一段範圍的文章    (T/B)   重編文章標題/重編看版標題",
    "(i)       編輯申請入會表格      (t/^D)  標記文章/砍除標記的文章",
    "(O)       編輯Post注意事項      (H)     看板隱藏/現身",
    NULL
};

static int b_help() {
    show_help(board_help);
    return FULLUPDATE;
}

/* ----------------------------------------------------- */
/* 板主設定隱形/ 解隱形                                  */
/* ----------------------------------------------------- */
char board_hidden_status;
#ifdef  BMCHS
extern char *fn_board;
static int change_hidden(int ent, fileheader_t *fhdr, char *direct)
{
    boardheader_t bh;
    int    bid;
    char   ans[4];

    if( !((currmode & MODE_BOARD) || HAS_PERM(PERM_SYSOP)) ||
    currboard[0] == 0 ||
    (bid = getbnum(currboard)) < 0 ||
    get_record(fn_board, &bh, sizeof(bh), bid) == -1 )
    return DONOTHING;

    if( ((bh.brdattr & BRD_HIDE) && (bh.brdattr & BRD_POSTMASK)) ){
    getdata(1, 0, "目前板在隱形狀態, 要解隱形嘛(Y/N)?[N]",
        ans, sizeof(ans), LCECHO);
    if( ans[0] != 'y' && ans[0] != 'Y' )
        return FULLUPDATE;
    getdata(2, 0, "再確認一次, 真的要把板板公開嘛 @____@(Y/N)?[N]",
        ans, sizeof(ans), LCECHO);
    if( ans[0] != 'y' && ans[0] != 'Y' )
        return FULLUPDATE;
    if( bh.brdattr & BRD_HIDE     ) bh.brdattr -= BRD_HIDE;
    if( bh.brdattr & BRD_POSTMASK ) bh.brdattr -= BRD_POSTMASK;
    log_usies("OpenBoard", bh.brdname);
    outs("君心今傳眾人,無處不聞弦歌。\n");
    board_hidden_status = 0;
    hbflreload(bid);
    }
    else{
    getdata(1, 0, "目前板在現形狀態, 要隱形嘛(Y/N)?[N]",
        ans, sizeof(ans), LCECHO);
    if( ans[0] != 'y' && ans[0] != 'Y' )
        return FULLUPDATE;
    bh.brdattr |= BRD_HIDE;
    bh.brdattr |= BRD_POSTMASK;
    log_usies("CloseBoard", bh.brdname);
    outs("君心今已掩抑,惟盼善自珍重。\n");
    board_hidden_status = 1;
    }
    setup_man(&bh);
    substitute_record(fn_board, &bh, sizeof(bh), bid);
    reset_board(bid);
    log_usies("SetBoard", bh.brdname);
    pressanykey();
    return FULLUPDATE;
}
#endif

/* ----------------------------------------------------- */
/* 看板功能表                                            */
/* ----------------------------------------------------- */
struct onekey_t read_comms[] = {
    {KEY_TAB, board_digest},
    {'C', board_etc},
    {'b', b_notes},
    {'c', cite_post},
    {'r', read_post},
    {'z', b_man},
    {'D', del_range},
    {'S', sequential_read},
    {'E', edit_post},
    {'T', edit_title},
    {'s', do_select},
    {'R', b_results},
    {'V', b_vote},
    {'M', b_vote_maintain},
    {'B', bh_title_edit},
    {'W', b_notes_edit},
    {'O', b_post_note},
    {'w', b_water_edit},
    {'v', visable_list_edit},
    {'i', b_application},
    {'o', can_vote_edit},
    {'x', cross_post},
    {'h', b_help},
#ifndef NO_GAMBLE
    {'f', join_gamble},
    {'G', hold_gamble},
#endif
    {'g', good_post},
    {'y', reply_post},
    {'d', del_post},
    {'m', mark_post},
    {'L', solve_post},
    {Ctrl('P'), do_post},
    {Ctrl('W'), whereami},
    {'Q', view_postmoney},
#ifdef OUTJOBSPOOL
    {'u', tar_addqueue},
#endif
#ifdef BMCHS
    {'H', change_hidden},
#endif
    {'\0', NULL}
};

time_t board_visit_time;

int Read() {
    int mode0 = currutmp->mode;
    int stat0 = currstat, tmpbid=currutmp->brc_id;
    char buf[40];
#ifdef LOG_BOARD
    time_t usetime = now;
#endif 

    setutmpmode(READING);
    set_board();

    if(board_visit_time < board_note_time) {
    setbfile(buf, currboard, fn_notes);
    more(buf, NA);
    pressanykey();
    }
    setutmpbid(currbid);
    setbdir(buf, currboard);
    curredit &= ~EDIT_MAIL;
    i_read(READING, buf, readtitle, readdoent, read_comms,
        currbid);
#ifdef LOG_BOARD
    log_board(currboard, now - usetime);
#endif
    brc_update();
    setutmpbid(tmpbid);
    currutmp->mode = mode0;
    currstat = stat0;
    return 0;
}

void ReadSelect() {
    int mode0 = currutmp->mode;
    int stat0 = currstat;
    char genbuf[200];

    currstat = XMODE;
    if(do_select(0, 0, genbuf) == NEWDIRECT)
    Read();
    setutmpbid(0);
    currutmp->mode = mode0;
    currstat = stat0;
}

#ifdef LOG_BOARD 
static void log_board(char *mode, time_t usetime) {
    char buf[ 256 ];

    if(usetime > 30) {
    sprintf(buf, "USE %-20.20s Stay: %5ld (%s) %s",
        mode, usetime ,cuser.userid ,ctime(&now));
    log_file(FN_USEBOARD,buf);
    }
}
#endif

int Select() {
    char genbuf[200];

    setutmpmode(SELECT);
    do_select(0, NULL, genbuf);
    return 0;
}