summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormhsin <mhsin@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2007-09-17 02:00:59 +0800
committermhsin <mhsin@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2007-09-17 02:00:59 +0800
commitaea71efe7bf4cde56dd6fec71386b1eeaa352a9a (patch)
tree37a32baa16fad8df404211d77af96c192167f2b1
downloadpttbbs-aea71efe7bf4cde56dd6fec71386b1eeaa352a9a.tar.gz
pttbbs-aea71efe7bf4cde56dd6fec71386b1eeaa352a9a.tar.zst
pttbbs-aea71efe7bf4cde56dd6fec71386b1eeaa352a9a.zip
Branch for bindexec mechanism.
git-svn-id: http://opensvn.csie.org/pttbbs/branches/mhsin.bindexec@3553 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
-rw-r--r--README.txt4
-rw-r--r--pttbbs/LICENSE340
-rw-r--r--pttbbs/Makefile14
-rw-r--r--pttbbs/README30
-rw-r--r--pttbbs/UPDATING59
-rw-r--r--pttbbs/blog/INSTALL79
-rwxr-xr-xpttbbs/blog/blog.pl483
-rwxr-xr-xpttbbs/blog/builddb.pl247
-rwxr-xr-xpttbbs/blog/index.pl17
-rw-r--r--pttbbs/cacheserver/Makefile28
-rw-r--r--pttbbs/cacheserver/README1
-rw-r--r--pttbbs/cacheserver/authserver.c236
-rw-r--r--pttbbs/cacheserver/friend.cpp350
-rw-r--r--pttbbs/cacheserver/utmpserver.c222
-rw-r--r--pttbbs/cacheserver/utmpserver2.c290
-rw-r--r--pttbbs/cacheserver/utmpserver3.c341
-rw-r--r--pttbbs/cacheserver/utmpsync.c27
-rw-r--r--pttbbs/docs/ADVANCE66
-rw-r--r--pttbbs/docs/ANCESTOR24
-rw-r--r--pttbbs/docs/CHANGE76
-rw-r--r--pttbbs/docs/DONATE27
-rw-r--r--pttbbs/docs/FAQ165
-rw-r--r--pttbbs/docs/FAQ.old146
-rw-r--r--pttbbs/docs/INSTALL128
-rw-r--r--pttbbs/docs/brc.txt126
-rw-r--r--pttbbs/docs/fav.txt41
-rw-r--r--pttbbs/docs/keeploc_t.txt22
-rw-r--r--pttbbs/docs/proto/Makefile18
-rw-r--r--pttbbs/docs/proto/README24
-rwxr-xr-xpttbbs/docs/proto/cdoc133
-rw-r--r--pttbbs/docs/z6ibbs.1.txt122
-rw-r--r--pttbbs/docs/z6ibbs.2.txt159
-rw-r--r--pttbbs/include/ansi.h31
-rw-r--r--pttbbs/include/assess.h6
-rw-r--r--pttbbs/include/bbs.h74
-rw-r--r--pttbbs/include/chc.h21
-rw-r--r--pttbbs/include/chess.h201
-rw-r--r--pttbbs/include/common.h253
-rw-r--r--pttbbs/include/config.h272
-rw-r--r--pttbbs/include/convert.h12
-rw-r--r--pttbbs/include/fav.h64
-rw-r--r--pttbbs/include/fnv_hash.h112
-rw-r--r--pttbbs/include/fpg.h73
-rw-r--r--pttbbs/include/gomo.h1970
-rw-r--r--pttbbs/include/modes.h197
-rw-r--r--pttbbs/include/osdep.h73
-rw-r--r--pttbbs/include/perm.h68
-rw-r--r--pttbbs/include/proto.h825
-rw-r--r--pttbbs/include/pttstruct.h726
-rw-r--r--pttbbs/include/statistic.h52
-rw-r--r--pttbbs/include/util.h6
-rw-r--r--pttbbs/innbbsd/COPYRIGHT.nocem11
-rw-r--r--pttbbs/innbbsd/Makefile71
-rw-r--r--pttbbs/innbbsd/antisplam.h25
-rw-r--r--pttbbs/innbbsd/bbslib.c773
-rw-r--r--pttbbs/innbbsd/bbslib.h62
-rw-r--r--pttbbs/innbbsd/bbslink.c1806
-rw-r--r--pttbbs/innbbsd/bbsnnrp.c1247
-rw-r--r--pttbbs/innbbsd/clibrary.h142
-rw-r--r--pttbbs/innbbsd/closeonexec.c69
-rw-r--r--pttbbs/innbbsd/connectsock.c464
-rw-r--r--pttbbs/innbbsd/ctlinnbbsd.c167
-rw-r--r--pttbbs/innbbsd/daemon.c177
-rw-r--r--pttbbs/innbbsd/daemon.h54
-rw-r--r--pttbbs/innbbsd/dbz.c1885
-rw-r--r--pttbbs/innbbsd/dbz.h36
-rw-r--r--pttbbs/innbbsd/dbztool.c97
-rw-r--r--pttbbs/innbbsd/echobbslib.c777
-rw-r--r--pttbbs/innbbsd/externs.h88
-rw-r--r--pttbbs/innbbsd/file.c205
-rw-r--r--pttbbs/innbbsd/his.c477
-rw-r--r--pttbbs/innbbsd/his.h77
-rw-r--r--pttbbs/innbbsd/innbbsconf.h186
-rw-r--r--pttbbs/innbbsd/innbbsd.c794
-rw-r--r--pttbbs/innbbsd/innbbsd.h9
-rw-r--r--pttbbs/innbbsd/inncheck.pl47
-rw-r--r--pttbbs/innbbsd/inndchannel.c681
-rw-r--r--pttbbs/innbbsd/inntobbs.c342
-rw-r--r--pttbbs/innbbsd/inntobbs.h39
-rw-r--r--pttbbs/innbbsd/mkhistory.c18
-rw-r--r--pttbbs/innbbsd/nntp.h141
-rw-r--r--pttbbs/innbbsd/nocem.c636
-rw-r--r--pttbbs/innbbsd/nocem.h57
-rw-r--r--pttbbs/innbbsd/pmain.c64
-rw-r--r--pttbbs/innbbsd/port.c35
-rw-r--r--pttbbs/innbbsd/receive_article.c1123
-rw-r--r--pttbbs/innbbsd/rfc931.c147
-rw-r--r--pttbbs/innbbsd/str_decode.c291
-rw-r--r--pttbbs/mbbsd/Makefile69
-rw-r--r--pttbbs/mbbsd/admin.c1537
-rw-r--r--pttbbs/mbbsd/alloc.c254
-rw-r--r--pttbbs/mbbsd/announce.c1511
-rw-r--r--pttbbs/mbbsd/args.c71
-rw-r--r--pttbbs/mbbsd/assess.c58
-rw-r--r--pttbbs/mbbsd/bbs.c3789
-rw-r--r--pttbbs/mbbsd/board.c1356
-rw-r--r--pttbbs/mbbsd/brc.c518
-rw-r--r--pttbbs/mbbsd/cache.c1071
-rw-r--r--pttbbs/mbbsd/cal.c521
-rw-r--r--pttbbs/mbbsd/calendar.c351
-rw-r--r--pttbbs/mbbsd/card.c663
-rw-r--r--pttbbs/mbbsd/chat.c577
-rw-r--r--pttbbs/mbbsd/chc.c1012
-rw-r--r--pttbbs/mbbsd/chc_tab.c106
-rw-r--r--pttbbs/mbbsd/chess.c1758
-rw-r--r--pttbbs/mbbsd/chicken.c1016
-rw-r--r--pttbbs/mbbsd/convert.c118
-rw-r--r--pttbbs/mbbsd/crypt.c647
-rw-r--r--pttbbs/mbbsd/dark.c565
-rw-r--r--pttbbs/mbbsd/dice.c483
-rw-r--r--pttbbs/mbbsd/edit.c3355
-rw-r--r--pttbbs/mbbsd/fav.c1285
-rw-r--r--pttbbs/mbbsd/file.c91
-rw-r--r--pttbbs/mbbsd/friend.c509
-rw-r--r--pttbbs/mbbsd/gamble.c388
-rw-r--r--pttbbs/mbbsd/go.c1111
-rw-r--r--pttbbs/mbbsd/gomo.c583
-rw-r--r--pttbbs/mbbsd/guess.c369
-rw-r--r--pttbbs/mbbsd/indict.c171
-rw-r--r--pttbbs/mbbsd/io.c1058
-rw-r--r--pttbbs/mbbsd/kaede.c184
-rw-r--r--pttbbs/mbbsd/lovepaper.c107
-rw-r--r--pttbbs/mbbsd/mail.c1941
-rw-r--r--pttbbs/mbbsd/mbbsd.c2101
-rw-r--r--pttbbs/mbbsd/menu.c708
-rw-r--r--pttbbs/mbbsd/merge.c237
-rw-r--r--pttbbs/mbbsd/more.c591
-rw-r--r--pttbbs/mbbsd/name.c1084
-rw-r--r--pttbbs/mbbsd/osdep.c643
-rw-r--r--pttbbs/mbbsd/othello.c567
-rw-r--r--pttbbs/mbbsd/passwd.c154
-rw-r--r--pttbbs/mbbsd/pmore.c2528
-rw-r--r--pttbbs/mbbsd/random.c711
-rw-r--r--pttbbs/mbbsd/read.c1138
-rw-r--r--pttbbs/mbbsd/record.c643
-rw-r--r--pttbbs/mbbsd/register.c336
-rw-r--r--pttbbs/mbbsd/reversi.c506
-rw-r--r--pttbbs/mbbsd/screen.c581
-rw-r--r--pttbbs/mbbsd/stuff.c1121
-rw-r--r--pttbbs/mbbsd/syspost.c154
-rw-r--r--pttbbs/mbbsd/talk.c3555
-rw-r--r--pttbbs/mbbsd/term.c117
-rw-r--r--pttbbs/mbbsd/time.c20
-rw-r--r--pttbbs/mbbsd/topsong.c81
-rw-r--r--pttbbs/mbbsd/user.c2069
-rw-r--r--pttbbs/mbbsd/var.c654
-rw-r--r--pttbbs/mbbsd/vice.c153
-rw-r--r--pttbbs/mbbsd/vote.c1193
-rw-r--r--pttbbs/mbbsd/voteboard.c364
-rw-r--r--pttbbs/mbbsd/xyz.c376
-rw-r--r--pttbbs/pttbbs.mk81
-rw-r--r--pttbbs/sample/FILTERMAIL.pm23
-rw-r--r--pttbbs/sample/LocalVars.pm46
-rw-r--r--pttbbs/sample/Makefile11
-rw-r--r--pttbbs/sample/README.BLOG18
-rw-r--r--pttbbs/sample/crontab119
-rw-r--r--pttbbs/sample/crontab.old56
-rw-r--r--pttbbs/sample/etc/@five16
-rw-r--r--pttbbs/sample/etc/Logout20
-rw-r--r--pttbbs/sample/etc/MRT.map66
-rw-r--r--pttbbs/sample/etc/Makefile22
-rw-r--r--pttbbs/sample/etc/Welcome18
-rw-r--r--pttbbs/sample/etc/Welcome_birth23
-rw-r--r--pttbbs/sample/etc/Welcome_birth.123
-rw-r--r--pttbbs/sample/etc/Welcome_birth.1023
-rw-r--r--pttbbs/sample/etc/Welcome_birth.1123
-rw-r--r--pttbbs/sample/etc/Welcome_birth.1223
-rw-r--r--pttbbs/sample/etc/Welcome_birth.223
-rw-r--r--pttbbs/sample/etc/Welcome_birth.323
-rw-r--r--pttbbs/sample/etc/Welcome_birth.423
-rw-r--r--pttbbs/sample/etc/Welcome_birth.523
-rw-r--r--pttbbs/sample/etc/Welcome_birth.623
-rw-r--r--pttbbs/sample/etc/Welcome_birth.723
-rw-r--r--pttbbs/sample/etc/Welcome_birth.823
-rw-r--r--pttbbs/sample/etc/Welcome_birth.923
-rw-r--r--pttbbs/sample/etc/Welcome_login12
-rw-r--r--pttbbs/sample/etc/angel_notify23
-rw-r--r--pttbbs/sample/etc/angel_usage23
-rw-r--r--pttbbs/sample/etc/bad_host0
-rw-r--r--pttbbs/sample/etc/banemail22
-rw-r--r--pttbbs/sample/etc/board.help22
-rw-r--r--pttbbs/sample/etc/boardlist.help23
-rw-r--r--pttbbs/sample/etc/chess/1.poem10
-rw-r--r--pttbbs/sample/etc/chess/10.poem10
-rw-r--r--pttbbs/sample/etc/chess/11.poem6
-rw-r--r--pttbbs/sample/etc/chess/12.poem6
-rw-r--r--pttbbs/sample/etc/chess/13.poem6
-rw-r--r--pttbbs/sample/etc/chess/14.poem10
-rw-r--r--pttbbs/sample/etc/chess/15.poem2
-rw-r--r--pttbbs/sample/etc/chess/16.poem2
-rw-r--r--pttbbs/sample/etc/chess/2.poem10
-rw-r--r--pttbbs/sample/etc/chess/3.poem6
-rw-r--r--pttbbs/sample/etc/chess/4.poem10
-rw-r--r--pttbbs/sample/etc/chess/5.poem8
-rw-r--r--pttbbs/sample/etc/chess/6.poem6
-rw-r--r--pttbbs/sample/etc/chess/7.poem10
-rw-r--r--pttbbs/sample/etc/chess/8.poem10
-rw-r--r--pttbbs/sample/etc/chess/9.poem6
-rw-r--r--pttbbs/sample/etc/chess/Makefile11
-rw-r--r--pttbbs/sample/etc/chickens/Makefile25
-rw-r--r--pttbbs/sample/etc/chickens/a011
-rw-r--r--pttbbs/sample/etc/chickens/a111
-rw-r--r--pttbbs/sample/etc/chickens/a1015
-rw-r--r--pttbbs/sample/etc/chickens/a1115
-rw-r--r--pttbbs/sample/etc/chickens/a1213
-rw-r--r--pttbbs/sample/etc/chickens/a1314
-rw-r--r--pttbbs/sample/etc/chickens/a1417
-rw-r--r--pttbbs/sample/etc/chickens/a1515
-rw-r--r--pttbbs/sample/etc/chickens/a1615
-rw-r--r--pttbbs/sample/etc/chickens/a214
-rw-r--r--pttbbs/sample/etc/chickens/a313
-rw-r--r--pttbbs/sample/etc/chickens/a413
-rw-r--r--pttbbs/sample/etc/chickens/a515
-rw-r--r--pttbbs/sample/etc/chickens/a613
-rw-r--r--pttbbs/sample/etc/chickens/a716
-rw-r--r--pttbbs/sample/etc/chickens/a813
-rw-r--r--pttbbs/sample/etc/chickens/a914
-rw-r--r--pttbbs/sample/etc/chickens/b09
-rw-r--r--pttbbs/sample/etc/chickens/b19
-rw-r--r--pttbbs/sample/etc/chickens/b1014
-rw-r--r--pttbbs/sample/etc/chickens/b1114
-rw-r--r--pttbbs/sample/etc/chickens/b1215
-rw-r--r--pttbbs/sample/etc/chickens/b1317
-rw-r--r--pttbbs/sample/etc/chickens/b1416
-rw-r--r--pttbbs/sample/etc/chickens/b1517
-rw-r--r--pttbbs/sample/etc/chickens/b1618
-rw-r--r--pttbbs/sample/etc/chickens/b211
-rw-r--r--pttbbs/sample/etc/chickens/b310
-rw-r--r--pttbbs/sample/etc/chickens/b411
-rw-r--r--pttbbs/sample/etc/chickens/b512
-rw-r--r--pttbbs/sample/etc/chickens/b612
-rw-r--r--pttbbs/sample/etc/chickens/b712
-rw-r--r--pttbbs/sample/etc/chickens/b813
-rw-r--r--pttbbs/sample/etc/chickens/b913
-rw-r--r--pttbbs/sample/etc/chickens/buymedicine14
-rw-r--r--pttbbs/sample/etc/chickens/buyoo14
-rw-r--r--pttbbs/sample/etc/chickens/c08
-rw-r--r--pttbbs/sample/etc/chickens/c19
-rw-r--r--pttbbs/sample/etc/chickens/c1011
-rw-r--r--pttbbs/sample/etc/chickens/c1110
-rw-r--r--pttbbs/sample/etc/chickens/c1212
-rw-r--r--pttbbs/sample/etc/chickens/c1311
-rw-r--r--pttbbs/sample/etc/chickens/c1412
-rw-r--r--pttbbs/sample/etc/chickens/c1512
-rw-r--r--pttbbs/sample/etc/chickens/c1611
-rw-r--r--pttbbs/sample/etc/chickens/c29
-rw-r--r--pttbbs/sample/etc/chickens/c38
-rw-r--r--pttbbs/sample/etc/chickens/c411
-rw-r--r--pttbbs/sample/etc/chickens/c512
-rw-r--r--pttbbs/sample/etc/chickens/c610
-rw-r--r--pttbbs/sample/etc/chickens/c712
-rw-r--r--pttbbs/sample/etc/chickens/c812
-rw-r--r--pttbbs/sample/etc/chickens/c911
-rw-r--r--pttbbs/sample/etc/chickens/clean14
-rw-r--r--pttbbs/sample/etc/chickens/d010
-rw-r--r--pttbbs/sample/etc/chickens/d19
-rw-r--r--pttbbs/sample/etc/chickens/d1011
-rw-r--r--pttbbs/sample/etc/chickens/d1115
-rw-r--r--pttbbs/sample/etc/chickens/d1213
-rw-r--r--pttbbs/sample/etc/chickens/d1313
-rw-r--r--pttbbs/sample/etc/chickens/d1413
-rw-r--r--pttbbs/sample/etc/chickens/d1511
-rw-r--r--pttbbs/sample/etc/chickens/d1611
-rw-r--r--pttbbs/sample/etc/chickens/d211
-rw-r--r--pttbbs/sample/etc/chickens/d316
-rw-r--r--pttbbs/sample/etc/chickens/d412
-rw-r--r--pttbbs/sample/etc/chickens/d514
-rw-r--r--pttbbs/sample/etc/chickens/d612
-rw-r--r--pttbbs/sample/etc/chickens/d716
-rw-r--r--pttbbs/sample/etc/chickens/d814
-rw-r--r--pttbbs/sample/etc/chickens/d913
-rw-r--r--pttbbs/sample/etc/chickens/deadth15
-rw-r--r--pttbbs/sample/etc/chickens/e06
-rw-r--r--pttbbs/sample/etc/chickens/e16
-rw-r--r--pttbbs/sample/etc/chickens/e1012
-rw-r--r--pttbbs/sample/etc/chickens/e1114
-rw-r--r--pttbbs/sample/etc/chickens/e1215
-rw-r--r--pttbbs/sample/etc/chickens/e1314
-rw-r--r--pttbbs/sample/etc/chickens/e1413
-rw-r--r--pttbbs/sample/etc/chickens/e1513
-rw-r--r--pttbbs/sample/etc/chickens/e1614
-rw-r--r--pttbbs/sample/etc/chickens/e26
-rw-r--r--pttbbs/sample/etc/chickens/e36
-rw-r--r--pttbbs/sample/etc/chickens/e48
-rw-r--r--pttbbs/sample/etc/chickens/e59
-rw-r--r--pttbbs/sample/etc/chickens/e69
-rw-r--r--pttbbs/sample/etc/chickens/e711
-rw-r--r--pttbbs/sample/etc/chickens/e811
-rw-r--r--pttbbs/sample/etc/chickens/e912
-rw-r--r--pttbbs/sample/etc/chickens/eat14
-rw-r--r--pttbbs/sample/etc/chickens/f09
-rw-r--r--pttbbs/sample/etc/chickens/f19
-rw-r--r--pttbbs/sample/etc/chickens/f1015
-rw-r--r--pttbbs/sample/etc/chickens/f1114
-rw-r--r--pttbbs/sample/etc/chickens/f1214
-rw-r--r--pttbbs/sample/etc/chickens/f1314
-rw-r--r--pttbbs/sample/etc/chickens/f1414
-rw-r--r--pttbbs/sample/etc/chickens/f1515
-rw-r--r--pttbbs/sample/etc/chickens/f1615
-rw-r--r--pttbbs/sample/etc/chickens/f212
-rw-r--r--pttbbs/sample/etc/chickens/f312
-rw-r--r--pttbbs/sample/etc/chickens/f413
-rw-r--r--pttbbs/sample/etc/chickens/f513
-rw-r--r--pttbbs/sample/etc/chickens/f614
-rw-r--r--pttbbs/sample/etc/chickens/f714
-rw-r--r--pttbbs/sample/etc/chickens/f815
-rw-r--r--pttbbs/sample/etc/chickens/f915
-rw-r--r--pttbbs/sample/etc/chickens/food14
-rw-r--r--pttbbs/sample/etc/chickens/g05
-rw-r--r--pttbbs/sample/etc/chickens/g15
-rw-r--r--pttbbs/sample/etc/chickens/g1013
-rw-r--r--pttbbs/sample/etc/chickens/g1115
-rw-r--r--pttbbs/sample/etc/chickens/g1215
-rw-r--r--pttbbs/sample/etc/chickens/g1314
-rw-r--r--pttbbs/sample/etc/chickens/g1414
-rw-r--r--pttbbs/sample/etc/chickens/g1514
-rw-r--r--pttbbs/sample/etc/chickens/g1614
-rw-r--r--pttbbs/sample/etc/chickens/g25
-rw-r--r--pttbbs/sample/etc/chickens/g36
-rw-r--r--pttbbs/sample/etc/chickens/g46
-rw-r--r--pttbbs/sample/etc/chickens/g58
-rw-r--r--pttbbs/sample/etc/chickens/g610
-rw-r--r--pttbbs/sample/etc/chickens/g711
-rw-r--r--pttbbs/sample/etc/chickens/g814
-rw-r--r--pttbbs/sample/etc/chickens/g914
-rw-r--r--pttbbs/sample/etc/chickens/h09
-rw-r--r--pttbbs/sample/etc/chickens/h19
-rw-r--r--pttbbs/sample/etc/chickens/h1015
-rw-r--r--pttbbs/sample/etc/chickens/h1114
-rw-r--r--pttbbs/sample/etc/chickens/h1214
-rw-r--r--pttbbs/sample/etc/chickens/h1314
-rw-r--r--pttbbs/sample/etc/chickens/h1414
-rw-r--r--pttbbs/sample/etc/chickens/h1515
-rw-r--r--pttbbs/sample/etc/chickens/h1615
-rw-r--r--pttbbs/sample/etc/chickens/h212
-rw-r--r--pttbbs/sample/etc/chickens/h312
-rw-r--r--pttbbs/sample/etc/chickens/h413
-rw-r--r--pttbbs/sample/etc/chickens/h513
-rw-r--r--pttbbs/sample/etc/chickens/h614
-rw-r--r--pttbbs/sample/etc/chickens/h714
-rw-r--r--pttbbs/sample/etc/chickens/h815
-rw-r--r--pttbbs/sample/etc/chickens/h915
-rw-r--r--pttbbs/sample/etc/chickens/hit14
-rw-r--r--pttbbs/sample/etc/chickens/i012
-rw-r--r--pttbbs/sample/etc/chickens/i111
-rw-r--r--pttbbs/sample/etc/chickens/i1013
-rw-r--r--pttbbs/sample/etc/chickens/i1112
-rw-r--r--pttbbs/sample/etc/chickens/i1213
-rw-r--r--pttbbs/sample/etc/chickens/i1313
-rw-r--r--pttbbs/sample/etc/chickens/i1414
-rw-r--r--pttbbs/sample/etc/chickens/i1513
-rw-r--r--pttbbs/sample/etc/chickens/i1613
-rw-r--r--pttbbs/sample/etc/chickens/i211
-rw-r--r--pttbbs/sample/etc/chickens/i311
-rw-r--r--pttbbs/sample/etc/chickens/i411
-rw-r--r--pttbbs/sample/etc/chickens/i511
-rw-r--r--pttbbs/sample/etc/chickens/i612
-rw-r--r--pttbbs/sample/etc/chickens/i712
-rw-r--r--pttbbs/sample/etc/chickens/i812
-rw-r--r--pttbbs/sample/etc/chickens/i912
-rw-r--r--pttbbs/sample/etc/chickens/j08
-rw-r--r--pttbbs/sample/etc/chickens/j19
-rw-r--r--pttbbs/sample/etc/chickens/j1012
-rw-r--r--pttbbs/sample/etc/chickens/j1112
-rw-r--r--pttbbs/sample/etc/chickens/j1211
-rw-r--r--pttbbs/sample/etc/chickens/j1312
-rw-r--r--pttbbs/sample/etc/chickens/j1413
-rw-r--r--pttbbs/sample/etc/chickens/j1513
-rw-r--r--pttbbs/sample/etc/chickens/j1613
-rw-r--r--pttbbs/sample/etc/chickens/j27
-rw-r--r--pttbbs/sample/etc/chickens/j310
-rw-r--r--pttbbs/sample/etc/chickens/j49
-rw-r--r--pttbbs/sample/etc/chickens/j512
-rw-r--r--pttbbs/sample/etc/chickens/j69
-rw-r--r--pttbbs/sample/etc/chickens/j710
-rw-r--r--pttbbs/sample/etc/chickens/j810
-rw-r--r--pttbbs/sample/etc/chickens/j911
-rw-r--r--pttbbs/sample/etc/chickens/k013
-rw-r--r--pttbbs/sample/etc/chickens/k111
-rw-r--r--pttbbs/sample/etc/chickens/k1013
-rw-r--r--pttbbs/sample/etc/chickens/k1113
-rw-r--r--pttbbs/sample/etc/chickens/k1212
-rw-r--r--pttbbs/sample/etc/chickens/k1312
-rw-r--r--pttbbs/sample/etc/chickens/k1412
-rw-r--r--pttbbs/sample/etc/chickens/k1514
-rw-r--r--pttbbs/sample/etc/chickens/k1614
-rw-r--r--pttbbs/sample/etc/chickens/k210
-rw-r--r--pttbbs/sample/etc/chickens/k311
-rw-r--r--pttbbs/sample/etc/chickens/k412
-rw-r--r--pttbbs/sample/etc/chickens/k512
-rw-r--r--pttbbs/sample/etc/chickens/k613
-rw-r--r--pttbbs/sample/etc/chickens/k714
-rw-r--r--pttbbs/sample/etc/chickens/k810
-rw-r--r--pttbbs/sample/etc/chickens/k911
-rw-r--r--pttbbs/sample/etc/chickens/kiss14
-rw-r--r--pttbbs/sample/etc/chickens/l09
-rw-r--r--pttbbs/sample/etc/chickens/l19
-rw-r--r--pttbbs/sample/etc/chickens/l1015
-rw-r--r--pttbbs/sample/etc/chickens/l1114
-rw-r--r--pttbbs/sample/etc/chickens/l1214
-rw-r--r--pttbbs/sample/etc/chickens/l1314
-rw-r--r--pttbbs/sample/etc/chickens/l1414
-rw-r--r--pttbbs/sample/etc/chickens/l1515
-rw-r--r--pttbbs/sample/etc/chickens/l1615
-rw-r--r--pttbbs/sample/etc/chickens/l212
-rw-r--r--pttbbs/sample/etc/chickens/l312
-rw-r--r--pttbbs/sample/etc/chickens/l413
-rw-r--r--pttbbs/sample/etc/chickens/l513
-rw-r--r--pttbbs/sample/etc/chickens/l614
-rw-r--r--pttbbs/sample/etc/chickens/l714
-rw-r--r--pttbbs/sample/etc/chickens/l815
-rw-r--r--pttbbs/sample/etc/chickens/l915
-rw-r--r--pttbbs/sample/etc/chickens/m09
-rw-r--r--pttbbs/sample/etc/chickens/m19
-rw-r--r--pttbbs/sample/etc/chickens/m1015
-rw-r--r--pttbbs/sample/etc/chickens/m1114
-rw-r--r--pttbbs/sample/etc/chickens/m1214
-rw-r--r--pttbbs/sample/etc/chickens/m1314
-rw-r--r--pttbbs/sample/etc/chickens/m1414
-rw-r--r--pttbbs/sample/etc/chickens/m1515
-rw-r--r--pttbbs/sample/etc/chickens/m1615
-rw-r--r--pttbbs/sample/etc/chickens/m212
-rw-r--r--pttbbs/sample/etc/chickens/m312
-rw-r--r--pttbbs/sample/etc/chickens/m413
-rw-r--r--pttbbs/sample/etc/chickens/m513
-rw-r--r--pttbbs/sample/etc/chickens/m614
-rw-r--r--pttbbs/sample/etc/chickens/m714
-rw-r--r--pttbbs/sample/etc/chickens/m815
-rw-r--r--pttbbs/sample/etc/chickens/m915
-rw-r--r--pttbbs/sample/etc/chickens/medicine14
-rw-r--r--pttbbs/sample/etc/chickens/n012
-rw-r--r--pttbbs/sample/etc/chickens/n112
-rw-r--r--pttbbs/sample/etc/chickens/n1012
-rw-r--r--pttbbs/sample/etc/chickens/n1112
-rw-r--r--pttbbs/sample/etc/chickens/n1212
-rw-r--r--pttbbs/sample/etc/chickens/n1312
-rw-r--r--pttbbs/sample/etc/chickens/n1412
-rw-r--r--pttbbs/sample/etc/chickens/n1512
-rw-r--r--pttbbs/sample/etc/chickens/n1612
-rw-r--r--pttbbs/sample/etc/chickens/n211
-rw-r--r--pttbbs/sample/etc/chickens/n311
-rw-r--r--pttbbs/sample/etc/chickens/n412
-rw-r--r--pttbbs/sample/etc/chickens/n512
-rw-r--r--pttbbs/sample/etc/chickens/n612
-rw-r--r--pttbbs/sample/etc/chickens/n712
-rw-r--r--pttbbs/sample/etc/chickens/n812
-rw-r--r--pttbbs/sample/etc/chickens/n912
-rw-r--r--pttbbs/sample/etc/chickens/nofood18
-rw-r--r--pttbbs/sample/etc/chickens/nohp16
-rw-r--r--pttbbs/sample/etc/chickens/nosatis6
-rw-r--r--pttbbs/sample/etc/chickens/o012
-rw-r--r--pttbbs/sample/etc/chickens/o112
-rw-r--r--pttbbs/sample/etc/chickens/o1012
-rw-r--r--pttbbs/sample/etc/chickens/o1112
-rw-r--r--pttbbs/sample/etc/chickens/o1212
-rw-r--r--pttbbs/sample/etc/chickens/o1312
-rw-r--r--pttbbs/sample/etc/chickens/o1412
-rw-r--r--pttbbs/sample/etc/chickens/o1511
-rw-r--r--pttbbs/sample/etc/chickens/o1611
-rw-r--r--pttbbs/sample/etc/chickens/o212
-rw-r--r--pttbbs/sample/etc/chickens/o312
-rw-r--r--pttbbs/sample/etc/chickens/o411
-rw-r--r--pttbbs/sample/etc/chickens/o511
-rw-r--r--pttbbs/sample/etc/chickens/o610
-rw-r--r--pttbbs/sample/etc/chickens/o712
-rw-r--r--pttbbs/sample/etc/chickens/o812
-rw-r--r--pttbbs/sample/etc/chickens/o912
-rw-r--r--pttbbs/sample/etc/chickens/oo14
-rw-r--r--pttbbs/sample/etc/chickens/read14
-rw-r--r--pttbbs/sample/etc/chickens/sell23
-rw-r--r--pttbbs/sample/etc/chickens/toofat17
-rw-r--r--pttbbs/sample/etc/chickens/tootired15
-rw-r--r--pttbbs/sample/etc/domain_name_query.cidr299
-rw-r--r--pttbbs/sample/etc/expire.conf4
-rw-r--r--pttbbs/sample/etc/feast48
-rw-r--r--pttbbs/sample/etc/goodbye19
-rw-r--r--pttbbs/sample/etc/register17
-rw-r--r--pttbbs/sample/etc/registered21
-rw-r--r--pttbbs/sample/etc/registeredmail21
-rw-r--r--pttbbs/sample/etc/registermail27
-rw-r--r--pttbbs/sample/etc/sysop6
-rw-r--r--pttbbs/sample/etc/today_boring22
-rw-r--r--pttbbs/sample/etc/ve.hlp127
-rw-r--r--pttbbs/sample/innd/Makefile9
-rw-r--r--pttbbs/sample/innd/bbsname.bbs1
-rw-r--r--pttbbs/sample/innd/ncmperm.bbs0
-rw-r--r--pttbbs/sample/innd/newsfeeds.bbs3
-rw-r--r--pttbbs/sample/innd/nodelist.bbs4
-rw-r--r--pttbbs/sample/innd/ntu.active1
-rw-r--r--pttbbs/sample/pttbbs.conf235
-rwxr-xr-xpttbbs/sample/pttbbs.sh36
-rw-r--r--pttbbs/sample/pttbbs_minimal.conf17
-rw-r--r--pttbbs/sample/rc.local7
-rw-r--r--pttbbs/staticweb/INSTALL43
-rw-r--r--pttbbs/staticweb/article.html18
-rw-r--r--pttbbs/staticweb/b2g.pm13990
-rw-r--r--pttbbs/staticweb/banner.html10
-rw-r--r--pttbbs/staticweb/dir.html43
-rw-r--r--pttbbs/staticweb/header.html15
-rw-r--r--pttbbs/staticweb/index.html47
-rwxr-xr-xpttbbs/staticweb/index.pl99
-rwxr-xr-xpttbbs/staticweb/man.pl145
-rwxr-xr-xpttbbs/staticweb/manbuilder.pl105
-rw-r--r--pttbbs/staticweb/search.html36
-rw-r--r--pttbbs/staticweb/styles.css20
-rw-r--r--pttbbs/util/BBSFileHeader.pm61
-rw-r--r--pttbbs/util/BM_money.c110
-rw-r--r--pttbbs/util/BM_money.sh5
-rw-r--r--pttbbs/util/Makefile116
-rw-r--r--pttbbs/util/account.c394
-rw-r--r--pttbbs/util/angel.c207
-rw-r--r--pttbbs/util/backpasswd.sh27
-rw-r--r--pttbbs/util/banip.pl151
-rw-r--r--pttbbs/util/bbsctl.c291
-rw-r--r--pttbbs/util/bbsenv.pl28
-rw-r--r--pttbbs/util/bbsmail.c258
-rw-r--r--pttbbs/util/bbsrf.c131
-rw-r--r--pttbbs/util/birth.c90
-rw-r--r--pttbbs/util/boardlist.c181
-rw-r--r--pttbbs/util/broadcast.c86
-rw-r--r--pttbbs/util/buildAnnounce.c89
-rw-r--r--pttbbs/util/buildAnnounce.sh5
-rw-r--r--pttbbs/util/buildir.c126
-rw-r--r--pttbbs/util/chesscountry.c207
-rw-r--r--pttbbs/util/chkhbf.c163
-rw-r--r--pttbbs/util/cleandir.pl55
-rw-r--r--pttbbs/util/cleanident.c19
-rw-r--r--pttbbs/util/cleanpasswd.c52
-rw-r--r--pttbbs/util/countalldice.c91
-rw-r--r--pttbbs/util/dailybackup.pl48
-rw-r--r--pttbbs/util/deluserfile.c139
-rw-r--r--pttbbs/util/diskstat.c788
-rw-r--r--pttbbs/util/expire.c320
-rw-r--r--pttbbs/util/filtermail.pl32
-rw-r--r--pttbbs/util/gamblegive.c29
-rw-r--r--pttbbs/util/getbackup.pl52
-rw-r--r--pttbbs/util/horoscope.c154
-rw-r--r--pttbbs/util/initbbs.c300
-rw-r--r--pttbbs/util/inndBM.c200
-rw-r--r--pttbbs/util/jungo.c194
-rw-r--r--pttbbs/util/mailangel.c143
-rw-r--r--pttbbs/util/mailog.sh9
-rw-r--r--pttbbs/util/mandex.c295
-rw-r--r--pttbbs/util/merge_board.c100
-rw-r--r--pttbbs/util/merge_dir.c48
-rw-r--r--pttbbs/util/mvdir.pl36
-rw-r--r--pttbbs/util/newvers.sh29
-rw-r--r--pttbbs/util/opendice.sh10
-rw-r--r--pttbbs/util/openticket.c125
-rw-r--r--pttbbs/util/openticket.sh10
-rw-r--r--pttbbs/util/openvice.c46
-rw-r--r--pttbbs/util/outmail.c275
-rw-r--r--pttbbs/util/parsevar.pl31
-rw-r--r--pttbbs/util/passwdconverter.c146
-rwxr-xr-xpttbbs/util/pmakev2.sh42
-rw-r--r--pttbbs/util/post.c58
-rw-r--r--pttbbs/util/poststat.c345
-rw-r--r--pttbbs/util/r2014convert.c63
-rw-r--r--pttbbs/util/reaper.c64
-rw-r--r--pttbbs/util/rebuildaloha.plbin0 -> 737 bytes
-rw-r--r--pttbbs/util/shmctl.c1221
-rw-r--r--pttbbs/util/showboard.c93
-rw-r--r--pttbbs/util/stock.perl32
-rw-r--r--pttbbs/util/stock.sh5
-rw-r--r--pttbbs/util/tarqueue.pl81
-rw-r--r--pttbbs/util/toplazyBBM.c196
-rw-r--r--pttbbs/util/toplazyBBM.sh3
-rw-r--r--pttbbs/util/toplazyBM.c206
-rw-r--r--pttbbs/util/toplazyBM.sh3
-rw-r--r--pttbbs/util/topsong.sh5
-rw-r--r--pttbbs/util/topusr.c181
-rw-r--r--pttbbs/util/transman.c56
-rw-r--r--pttbbs/util/tunepasswd.c69
-rw-r--r--pttbbs/util/udnnews.pl119
-rw-r--r--pttbbs/util/uhash_loader.c167
-rw-r--r--pttbbs/util/userlist.c53
-rw-r--r--pttbbs/util/waterball.pl107
-rw-r--r--pttbbs/util/weather.perl37
-rw-r--r--pttbbs/util/weather.sh6
-rw-r--r--pttbbs/util/wretch_man.c124
-rw-r--r--pttbbs/util/writemoney.c39
-rw-r--r--pttbbs/util/xchatd.c3122
-rw-r--r--pttbbs/util/xchatd.h109
-rw-r--r--pttbbs/util/yearsold.c105
-rw-r--r--pttbbs/util/zero_limits.c51
585 files changed, 107191 insertions, 0 deletions
diff --git a/README.txt b/README.txt
new file mode 100644
index 00000000..c85537f6
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,4 @@
+branch for bindexec mechanism.
+
+by mhsin
+
diff --git a/pttbbs/LICENSE b/pttbbs/LICENSE
new file mode 100644
index 00000000..d60c31a9
--- /dev/null
+++ b/pttbbs/LICENSE
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/pttbbs/Makefile b/pttbbs/Makefile
new file mode 100644
index 00000000..f0938dff
--- /dev/null
+++ b/pttbbs/Makefile
@@ -0,0 +1,14 @@
+SUBDIR= mbbsd util innbbsd
+
+all install clean:
+.if !exists(/usr/local/lib/libhz.so) && !exists(/usr/lib/libhz.so)
+ @echo "sorry, libhz not found."
+ @echo "above FreeBSD, please install /usr/ports/chinese/autoconvert"
+ @echo "above Debian/Linux, please install package libhz0"
+ @exit 1
+.endif
+ @for i in $(SUBDIR); do\
+ cd $$i;\
+ $(MAKE) $@;\
+ cd ..;\
+ done
diff --git a/pttbbs/README b/pttbbs/README
new file mode 100644
index 00000000..f1e640be
--- /dev/null
+++ b/pttbbs/README
@@ -0,0 +1,30 @@
+文章的版號及最後編修時間是:
+$Id$
+
+快速安裝請參考 docs/INSTALL 以及 docs/FAQ
+詳細文件請見 docs/
+
+若有任何問題, 請到批踢踢實業坊 (telnet://ptt.cc) 的 PttCurrent 看板.
+或線上瀏覽 PttCurrent 的看板於 http://webbbs.ptt.cc/PttCurrent/
+及精華區 http://PttCurrent.man.ptt.cc
+
+目錄結構:
+ LICENSE 本軟體的授權方式
+
+ docs/ 文件
+
+ ADVANCE 進階功能
+ ANCESTOR 沿承歷史
+ DONATE 贊助方式
+ FAQ 常見的問題, sendmail.cf的設定方法等等
+ INSTALL 快速安裝方式
+ proto/ mbbsd/ 裡面各個檔案的說明,詳見該目錄的 README
+ z6ibbs.[12].txt in2 隨筆
+
+ sample/ 範例
+ crontab 提供 bbs執行時須透過 crontab 定時跑的設定
+
+ blog/ PttBLOG
+ include/ include 檔
+ innbbsd/ 轉信
+ mbbsd/ bbs 主程式
diff --git a/pttbbs/UPDATING b/pttbbs/UPDATING
new file mode 100644
index 00000000..5af922f2
--- /dev/null
+++ b/pttbbs/UPDATING
@@ -0,0 +1,59 @@
+-----------------------------------------------------------------------------
+PTT BBS [Current] Updating Log (in Big5 encoding)
+$Id$
+-----------------------------------------------------------------------------
+
+這裡是 PTT Current 的重大更新記錄,主要是「檔案格式」或位置的重要改變、
+通常是更新程式碼時要注意一起更新的部份。
+
+跟著 Current 一起昇級的朋友們要注意是否有跨過下列的版號,若有請依序手動更新。
+會列在這裡的版號,強烈建議先整個關站再更新。
+
+關於該版號的進一步訊息,可用 https://opentrac.csie.org/pttbbs/changeset/
+來查詢,如下面列 r2273 的查詢網址就是
+https://opensvn.csie.org/traccgi/pttbbs/changeset/2273
+
+-----------------------------------------------------------------------------
+
+r3153: [CHESS]
+chess framework update
+!!!NOTE!!! Chess protocals are NOT backward compatible
+RESTART WHOLE system to ensure correctness
+
+r2459: [SHM]
+SHM_t 增加版本號碼, 若版本不合請關站重開.
+
+r2374: [SHM]
+把 ptt.linux merge 到 trunk
+SHM_t 中所有的 pointer 都改成 index 了
+由於這個更動有改到 SHM 的結構,所以請在關站之後再將新版本上線
+
+r2366:
+trunk 與 stable 第一次分枝
+
+r2341: [SHM]
+SHM_t update, 為了修某一個 race condition並拿掉幾個沒在用的欄位
+由於這個更動有改到 SHM 的結構,所以請在關站之後再將新版本上線
+
+r2273: [PASSWDS]
+對於 userec_t structure 的一些修改, 以下這些動作得在 bbs 關掉之後進行:
+請到 util/ 下 make passwdconverter
+然後執行 passwdconverter 會把 BBSHOME/.PASSWDS 轉換之後
+產生 BBSHOME/.PASSWDS.trans.tmp
+用這個檔蓋掉 .PASSWDS 就好了 :)
+
+r2176: [SHM]
+etc/domain_name_query 改為 etc/domain_name_query.cidr
+格式為 CIDR format , 您可以直接拿 Ptt/Ptt2 目前所使用的設定檔來用
+由於這個更動有改到 SHM 的結構,所以請在關站之後再將新版本上線
+
+r1409: [etc]
+expire 程式修正, 原本的用法是 expire [days [maxp [minp]]]
+現在透過 getopt() 來做, 變成 expire [-d days] [-M maxp] [-m minp] [board names]
+最後面可以指定一群板名, 若不指定的話表示 "全部看板"
+請檢查你的 crontab!
+
+[from OpenPTT 1.0.2]
+.DIR 有變, .BOARDS 變 .BRD, ...
+請見 PTT2 PttSrc 板
+
diff --git a/pttbbs/blog/INSTALL b/pttbbs/blog/INSTALL
new file mode 100644
index 00000000..1f216292
--- /dev/null
+++ b/pttbbs/blog/INSTALL
@@ -0,0 +1,79 @@
+這篇文章在描述怎麼架設 PttBlog, 最後的編修及版號是:
+$Id$
+
+請注意, PttBlog本來主要是設計給 Ptt2 站台使用, 目前正在開發階段,
+並未接受嚴密的測試, 可能還缺少很多功能, 以及可能有許多的 bug.
+
+您可以按照下列的步驟安裝好 PttBlog.
+1.安裝好下列的東西, 我們並同時列上 FreeBSD ports內的目錄:
+ apache /usr/ports/www/apache13/
+ perl /usr/ports/lang/perl5.8/
+ mod_perl /usr/ports/www/mod_perl/
+ mysql /usr/ports/databases/mysql323-server/
+
+ 以及下列的 module
+ Template /usr/ports/www/p5-Template-Toolkit/
+ Date::Calc /usr/ports/devel/p5-Date-Calc/
+ DBI /usr/ports/databases/p5-DBI/
+ DBD::mysql /usr/ports/databases/p5-DBD-mysql/
+ MD5 /usr/ports/security/p5-MD5/
+ Mail::Sender /usr/ports/mail/p5-Mail-Sender/
+ OurNet::FuzzyIndex (還沒有進 ports, 請用 cpan 裝)
+
+2.設定 apache 可以直接透過 mod_perl 來跑 perl script .
+ 在您的 apache.conf (or httpd.conf)中, 應該會有:
+ LoadModule perl_module libexec/apache/libperl.so
+ AddModule mod_perl.c
+ 在<IfModule mod_mime.c></IfModule>中間, 加上這兩行:
+ AddHandler perl-script .pl
+ PerlHandler Apache::Registry
+
+3.設定好 blog 的 web目錄. 裡面至少要有 index.pl, blog.pl, LocalVars.pm
+ (其中 LocalVars.pm 建議用 symbolic link 到 /home/bbs/bin/的那一份)
+ 其中 *.pl 的權限要是可以執行的 (ex: chmod 755 *.pl)
+
+4.設定 apache 指到 blog 的目錄. 並將該目錄開始 ExecCGI的 option.
+ 例如使用 Virtual Host :
+ NameVirtualHost *
+ <VirtualHost *>
+ ServerName blog.ptt2.cc
+ DocumentRoot /home/bbs/blog/web
+ <Directory "/home/bbs/blog/web">
+ Options ExecCGI
+ </Directory>
+ </VirtualHost>
+
+5.將 builddb.pl, BBSFileHeader.pm 拷貝進 ~bbs/bin
+ 您可以嘗試用 perl -c ~bbs/bin/builddb.pl 測試看看能不能過.
+ 若不行的話, 通常是 LocalVars.pm 裡面少東西,
+ 請參考 pttbbs/sample/LocalVars.pm 的 blog 區.
+
+6.參考 pttbbs/sample/pttbbs.conf中, 在您的 pttbbs.conf中加入
+ BLOGDB_HOST, BLOGDB_USER, BLOGDB_PASSWD, BLOGDB_DB, BLOGDB_PORT, BLOGDB_SOCK
+ 並且重新 compile mbbsd, 在 make 時加入 WITH_BLOG=yes .
+ 然後 install 並且 restart
+
+7.關於 Mysql共須要下面兩個 table (可以直接複製過去跑)
+ CREATE TABLE `comment` (
+ `brdname` varchar(13) NOT NULL default '',
+ `artid` int(11) NOT NULL default '0',
+ `name` varchar(32) NOT NULL default '',
+ `mail` varchar(64) NOT NULL default '',
+ `content` text NOT NULL,
+ `mtime` int(11) NOT NULL default '0',
+ `hash` varchar(32) NOT NULL default ''
+ ) TYPE=MyISAM;
+
+ CREATE TABLE `counter` (
+ `k` char(32) NOT NULL default '',
+ `v` int(11) NOT NULL default '0',
+ `mtime` int(11) NOT NULL default '0',
+ PRIMARY KEY (`k`)
+ ) TYPE=MyISAM;
+
+ CREATE TABLE `wcounter` (
+ `k` char(32) NOT NULL default '',
+ `v` int(11) NOT NULL default '0',
+ `mtime` int(11) NOT NULL default '0',
+ PRIMARY KEY (`k`)
+ ) TYPE=MyISAM;
diff --git a/pttbbs/blog/blog.pl b/pttbbs/blog/blog.pl
new file mode 100755
index 00000000..5362f4b5
--- /dev/null
+++ b/pttbbs/blog/blog.pl
@@ -0,0 +1,483 @@
+#!/usr/bin/perl
+# $Id$
+use CGI qw/:standard/;
+use lib qw/./;
+use LocalVars;
+use DB_File;
+use strict;
+use Data::Dumper;
+use Date::Calc qw(:all);
+use Template;
+use OurNet::FuzzyIndex;
+use DBI;
+use DBD::mysql;
+use POSIX;
+use MD5;
+use Mail::Sender;
+use Data::Serializer;
+use Encode;
+
+use vars qw/@emonth @cnumber %config %attr %article %th $dbh $brdname/;
+
+sub main
+{
+ my($fn, $y, $m, $d, $ofn);
+ my($tmpl);
+
+ $dbh = undef;
+ @emonth = ('', 'January', 'February', 'March', 'April', 'May',
+ 'June', 'July', 'August', 'September', 'October',
+ 'November', 'December');
+ @cnumber = ('零', '一', '二', '三', '四', '五', '六',
+ '七', '八', '九', '十', '十一', '十二');
+
+ if( $brdname = param('searchboard') ){
+ dodbi(sub {
+ my($dbh) = @_;
+ my($sth);
+ $sth = $dbh->prepare("select k from counter where k='$brdname'");
+ $sth->execute();
+ $brdname = (($sth = $sth->fetchrow_hashref()) ?
+ $sth->{k} : 'Blog');
+ });
+ return redirect("/blog.pl/$brdname/");
+ }
+
+ if( !$ENV{PATH_INFO} ){
+ print header(-status => 400);
+ return;
+ }
+ if( !(($brdname, $ofn) = $ENV{PATH_INFO} =~ m|^/([\w\-]+?)/([\.,\w]*)$|) ||
+ !( ($fn, $y, $m, $d) = parsefn($ofn) ) ||
+ !(-e "$BLOGDATA/$brdname/$fn") ||
+ !(tie %config, 'DB_File',
+ "$BLOGDATA/$brdname/config.db", O_RDONLY, 0666, $DB_HASH) ||
+ !(tie %attr, 'DB_File',
+ "$BLOGDATA/$brdname/attr.db", O_RDONLY, 0666, $DB_HASH) ){
+ return redirect("/blog.pl/$1/")
+ if( $ENV{PATH_INFO} =~ m|^/([\w\-]+?)$| );
+ print header(-status => 404);
+ return;
+ }
+
+ charset('');
+ print header(-type => GetType($fn));
+ $fn ||= 'index.html';
+
+ # first, import all settings in %config
+ %th = %config;
+ $th{BOARDNAME} = $brdname;
+ $th{key} = $y * 10000 + $m * 100 + $d;
+
+ # loadBlog ---------------------------------------------------------------
+ tie %article, 'DB_File', "$BLOGDATA/$brdname.db", O_RDONLY, 0666, $DB_HASH;
+ if( $attr{"$fn.loadBlog"} =~ /article/i ){
+ AddArticle('blog', $attr{"$fn.loadBlogFields"}, packdate($y, $m, $d));
+ }
+ elsif( $attr{"$fn.loadBlog"} =~ /monthly/i ){
+ my($s, $y1, $m1, $d1);
+ for( ($y1, $m1, $d1) = ($y, $m, 32) ; $d1 > 0 ; --$d1 ){
+ AddArticle('blog', $attr{"$fn.loadBlogFields"},
+ packdate($y1, $m1, $d1));
+ }
+ }
+ elsif( $attr{"$fn.loadBlog"} =~ /^last(\d+)/i ){
+ my($ptr, $i);
+ for( $ptr = $article{last}, $i = 0 ;
+ $ptr && $i < $1 ;
+ $ptr = $article{"$ptr.prev"}, ++$i ){
+ AddArticle('blog', $attr{"$fn.loadBlogFields"},
+ $ptr);
+ }
+ }
+ elsif( $attr{"$fn.loadBlog"} =~ /FuzzySearch/i ){
+ my $idx = OurNet::FuzzyIndex->new("$BLOGDATA/$brdname.idx");
+ my %result = $idx->query($th{SearchKey} = param('SearchKey'),
+ MATCH_FUZZY);
+ foreach my $t (sort { $result{$b} <=> $result{$a} } keys(%result)) {
+ AddArticle('blog', $attr{"$fn.loadBlogFields"},
+ $idx->getkey($t), sprintf("%5.1f", $result{$t} / 10));
+ }
+ }
+
+ if( $attr{"$fn.loadBlogPrevNext"} ){
+ my $s = packdate($y, $m, $d);
+ AddArticle('next', $attr{"$fn.loadBlogPrevNext"},
+ $article{"$s.next"});
+ AddArticle('prev', $attr{"$fn.loadBlogPrevNext"},
+ $article{"$s.prev"});
+ }
+
+ # loadArchives -----------------------------------------------------------
+ if( $attr{"$fn.loadArchives"} =~ /^monthly/i ){
+ # 找尋 +-1 year 內有資料的月份
+ my($c, $y1, $m1);
+ for( $c = 0, ($y1, $m1) = ($y + 1, $m) ;
+ $c < 48 ;
+ ++$c, --$m1 ) {
+
+ if( $m1 == 0 ){ $m1 = 12; --$y1; }
+ if( $article{ sprintf('%04d%02d', $y1, $m1) } ){
+ push @{$th{Archives}}, {year => $y1, month => $m1,
+ emonth => $emonth[$m1],
+ cmonth => $cnumber[$m1],
+ key => packdate($y1, $m1, 1)};
+ }
+ }
+ }
+
+ # loadRecentEntries ------------------------------------------------------
+ if( $attr{"$fn.loadRecentEntries"} ){
+ my($i, $ptr, $y, $m, $d);
+ print $attr{"$fn.loadRecentEntries:"};
+ for( $i = 0, $ptr = $article{'last'} ;
+ $ptr && $i < $attr{"$fn.loadRecentEntries"} ;
+ ++$i, $ptr = $article{"$ptr.prev"} ){
+ ($y, $m, $d) = unpackdate($ptr);
+ push @{$th{RecentEntries}}, {year => $y, month => $m,
+ emonth => $emonth[$m],
+ cmonth => $cnumber[$m],
+ title => $article{"$ptr.title"},
+ key => $ptr};
+ }
+ }
+
+ # topBlogs
+ my($t);
+ foreach $t ( ['loadTopBlogs', 'v', 'topBlogs', 'counter'],
+ ['loadTopWeekBlogs', 'v', 'topWeekBlogs', 'wcounter'],
+ ['loadRandomBlogs', 'RAND()', 'randomBlogs', 'counter'],
+ ){
+ if( $attr{"$fn.$t->[0]"} ){
+ dodbi(sub {
+ my($dbh) = @_;
+ my($sth);
+ $sth = $dbh->prepare("select k, v from $t->[3] ".
+ "order by $t->[1] desc ".
+ ($attr{"$fn.$t->[0]"} eq 'all' ? '' :
+ 'limit 0,'. $attr{"$fn.$t->[0]"}));
+ $sth->execute();
+ while( $_ = $sth->fetchrow_hashref() ){
+ push @{$th{$t->[2]}}, {brdname => $_->{k},
+ counter => $_->{v}};
+ }
+ });
+ }
+ }
+
+ # Counter ----------------------------------------------------------------
+ if( $attr{"$fn.loadCounter"} ){
+ $th{counter} = dodbi(sub {
+ my($dbh) = @_;
+ my($sth, $t, $time);
+ $time = time();
+ $dbh->do("update counter set v = v + 1, mtime = $time ".
+ "where k = '$brdname' && mtime < ". ($time - 2));
+ $dbh->do("update wcounter set v = v + 1, mtime = $time ".
+ "where k = '$brdname' && mtime < ". ($time - 2));
+ $sth = $dbh->prepare("select v from counter where k='$brdname'");
+ $sth->execute();
+ $t = $sth->fetchrow_hashref();
+ return $t->{v} if( $t->{v} );
+
+ $dbh->do("insert into counter (k, v) values ('$brdname', 1)");
+ $dbh->do("insert into wcounter (k, v) values ('$brdname', 1)");
+ return 1;
+ });
+ }
+
+ # Calendar ---------------------------------------------------------------
+ if( $attr{"$fn.loadCalendar"} ){
+ # 沒有合適的 module , 自己寫一個 |||b
+ my($c, $week, $day, $t, $link, $newtr);
+ $c = ("<table border=\"0\" cellspacing=\"4\" cellpadding=\"0\">\n".
+ "<caption class=\"calendarhead\">$emonth[$m] $y</caption>\n".
+ "<tr>\n");
+ $c .= ("<th abbr=\"$_->[0]\" align=\"center\">".
+ "<span class=\"calendar\">$_->[1]</span></th>\n")
+ foreach( ['Sunday', 'Sun'], ['Monday', 'Mon'],
+ ['Tuesday', 'Tue'], ['Wednesday', 'Wed'],
+ ['Thursday', 'Thu'], ['Friday', 'Fri'],
+ ['Saturday', 'Sat'] );
+
+ $week = Day_of_Week($y, $m, 1);
+ $c .= "</tr>\n<tr>\n";
+
+ if( $week == 7 ){
+ $week = 0;
+ }
+ else{
+ $c .= ("<th abbr=\"null\" align=\"center\"><span class=\"calendar\">".
+ "&nbsp;</span></th>\n")
+ foreach( 1..$week );
+ }
+ foreach( 1..31 ){
+ last if( !check_date($y, $m, $_) );
+ $c .= "<tr>\n" if( $newtr );
+ $c .= "<th abbr=\"$_\" align=\"center\"><span class=\"calendar\">";
+
+ $t = packdate($y, $m, $_);
+ if( !$article{"$t.title"} ){
+ $c .= "$_";
+ }
+ else{
+ my $link = $attr{"$fn.loadCalendar"};
+ $link =~ s/\[\% key \%\]/$t/g;
+ $c .= "<a href=\"$link\">$_</a>";
+ }
+
+ $c .= "</span></th>\n";
+ if( ++$week == 7 ){
+ $c .= "</tr>\n\n";
+ $week = 0;
+ $newtr = 1;
+ }
+ else{
+ $newtr = 0;
+ }
+ }
+
+ $c .= "</tr>\n" if( !$newtr );
+ $c .= "</table>\n";
+ $th{calendar} = $c;
+ }
+
+ # Comments ---------------------------------------------------------------
+ if( $attr{"$fn.loadRecentComments"} ){
+ dodbi(sub {
+ my($dbh) = @_;
+ my($sth, $t);
+ $sth = $dbh->prepare("select artid,name,mail,mtime ".
+ "from comment ".
+ "where brdname='$brdname' ".
+ "order by mtime desc ".
+ "LIMIT 0,". $attr{"$fn.loadRecentComments"});
+ $sth->execute();
+ while( $t = $sth->fetchrow_hashref() ){
+ $t->{title} = $article{"$t->{artid}.title"};
+ $t->{key} = $t->{artid};
+ $t->{time} = POSIX::strftime('%D', localtime($t->{mtime}));
+ push @{$th{RecentComments}}, $t;
+ }
+ });
+ }
+
+ if( $attr{"$fn.loadComments"} ){
+ my($name, $mail, $comment) = (param('name'),
+ param('mail'), param('comment'));
+
+ if( $name && $comment ){
+ if( $attr{"$fn.loadComments"} =~ /\@/ ){
+ my $sr = new Mail::Sender{smtp => 'localhost'};
+ $sr->MailMsg({from => '批踢踢部落格 <blog@ptt.cc>',
+ to => $attr{"$fn.loadComments"},
+ subject => "您的部落格收到 $name 給您的迴響",
+ charset => 'big5',
+ msg => "
+您的部落格 http://blog.ptt2.cc/blog.pl/$brdname/$ofn
+剛才收到來自 $name <$mail> 給您的迴響
+--------------------------------------------------------------------
+$comment
+--------------------------------------------------------------------
+ (這封信件是由程式自動發出, 請不要直接回複這封信^^)
+",
+ });
+ }
+ dodbi(sub {
+ my($dbh) = @_;
+ my($t, $hash);
+ $t = time();
+ $name = $dbh->quote($name);
+ $mail = $dbh->quote($mail);
+ $comment = $dbh->quote($comment);
+ $hash = MD5->hexhash("$t$th{key}$name$mail$comment");
+ $dbh->do('insert into comment '.
+ '(brdname, artid, name, mail, content, mtime, hash) '.
+ "values ('$brdname', '$th{key}', $name, $mail, ".
+ "$comment, '$t', '$hash')");
+ });
+ }
+
+ dodbi(sub {
+ my($dbh) = @_;
+ my($sth, $t);
+ $sth = $dbh->prepare("select mtime,name,mail,content,hash ".
+ "from comment ".
+ "where brdname='$brdname'&&artid='$th{key}' ".
+ "order by mtime desc");
+ $sth->execute();
+ while( $t = $sth->fetchrow_hashref() ){
+ $t->{time} = POSIX::ctime($t->{mtime});
+ $t->{content} = applyfilter($t->{content},
+ $config{outputfilter});
+ push @{$th{comment}}, $t;
+ }
+ });
+ }
+
+ # serialized -------------------------------------------------------------
+ if( $attr{"$fn.loadSerialized"} ){
+ my($obj, %h, $str);
+ $obj = Data::Serializer->new(serializer => 'Storable',
+ digester => 'MD5',
+ compress => 0,
+ );
+ open FH, '<'.$attr{"$fn.loadSerialized"};
+ FH->read($str, -s $attr{"$fn.loadSerialized"});
+ close FH;
+ %h = %{$obj->deserialize($str)};
+ $th{$_} = $h{$_} foreach( keys %h );
+ }
+
+ # 用 Template Toolkit 輸出
+ $th{LANG} =~ s/zh_TW/zh-TW/;
+ mkdir "$BLOGCACHE/$brdname";
+ $tmpl = Template->new({INCLUDE_PATH => '.',
+ ABSOLUTE => 0,
+ RELATIVE => 0,
+ RECURSION => 0,
+ EVAL_PERL => 0,
+ COMPILE_EXT => '.pl',
+ COMPILE_DIR => "$BLOGCACHE/$brdname/",
+ });
+ chdir "$BLOGDATA/$brdname/";
+ $tmpl->process($fn, \%th) ||
+ print "<pre>template error: ". $tmpl->error();
+ $dbh->disconnect() if( $dbh );
+
+ untie %attr if( %attr );
+ untie %config if( %config );
+ untie %article if( %article );
+ undef $tmpl;
+}
+
+sub utf8dump($;$)
+{
+ my($str, $prefix) = @_;
+ my $ret = $prefix || '';
+ my $ostr = $str;
+ Encode::from_to($str, 'big5', 'utf-8');
+ $ret .= '%'. sprintf('%x', ord($_))
+ foreach( split(//, $str) );
+ return "<a href=\"$ret\">$ostr</a>";
+}
+
+sub AddArticle($$$;$)
+{
+ my($cl, $fields, $s, $score) = @_;
+ my($content, $short, $nComments) = ();
+ $content = applyfilter($article{"$s.content"}, $config{outputfilter})
+ if( $fields =~ /content/i );
+
+ $short = applyfilter($article{"$s.short"}, $config{outputfilter})
+ if( $fields =~ /short/i );
+
+ if( $fields =~ /nComments/i ){
+ $nComments = dodbi(sub {
+ my($dbh) = @_;
+ my $sth = $dbh->prepare("select count(*) from comment ".
+ "where brdname='$brdname'&&artid='$s'");
+ $sth->execute();
+ return $sth->fetchrow_hashref()->{'count(*)'};
+ }) || 0;
+ }
+
+ my($y, $m, $d) = unpackdate($s);
+ push @{$th{$cl}}, {year => $y,
+ month => $m,
+ emonth => $emonth[$m],
+ cmonth => $cnumber[$m],
+ day => $d,
+ key => $s,
+ title => (($fields !~ /title/i) ? '' :
+ $article{"$s.title"}),
+ content=> $content,
+ author => (($fields !~ /author/i) ? '' :
+ $article{"$s.author"}),
+ short => $short,
+ score => $score,
+ nComments => $nComments,
+ }
+ if( $article{"$s.title"} );
+}
+
+sub applyfilter($$)
+{
+ my($c, $filter) = @_;
+ foreach( split(',', $filter) ){
+ if( /^generic$/i ){
+ $c =~ s/\n/<br \/>\n/gs;
+ }
+ elsif( /^strict$/i ){
+ $c =~ s/\</&lt;/gs;
+ $c =~ s/\>/&gt;/gs;
+ $c =~ s/\"/&quot;/gs;
+ $c =~ s/\'/&apos;/gs;
+# $c =~ s/ /&nbsp;/gs;
+ }
+ elsif( /^ubb$/i ){
+ $c =~ s|\[url\](.*?)\[/url\]|<a href="$1">$1</a>|gsi;
+ $c =~ s|\[url=(.*?)\](.*?)\[/url\]|<a href="$1">$2</a>|gsi;
+ $c =~ s|\[email\](.*?)\[/email\]|<a href="mailto:$1">$1</a>|gsi;
+ $c =~ s|\[b\](.*?)\[/b\]|<b>$1</b>|gsi;
+ $c =~ s|\[i\](.*?)\[/i\]|<i>$1</i>|gsi;
+ $c =~ s|\[img\](.*?)\[/img\]|<img src="$1" alt="(null)" style="border:0;" />|gsi;
+ }
+ elsif( /^wiki$/i ){
+ my $t;
+ $c =~ s|\[(http://\S+) (.*?)\]| <a href=\"$1\">\[$2\]</a> |gi;
+ $c =~ s|([^\>\"])(http://\S+\.(:?jpg\|gif\|png\|bmp))|$1<a href=\"$2\"><img src=\"$2\" alt="$2" style="border:0;"></a>|gsi;
+ $c =~ s|([^\>\"])(http://\S+)|$1<a href=\"$2\">$2</a>|gsi;
+ $c =~ s|\(\((.*?)\)\)|utf8dump($1, $th{wikibase})|gsie;
+ $c =~ s|^\-{4,}$|<hr />|gm;
+ }
+ }
+ return $c;
+}
+
+sub parsefn($)
+{
+ my($fs) = @_;
+ return ("$1.$3", unpackdate($2))
+ if( $fs =~ /^(.*),(\w+)\.(.*)$/ );
+ return ($fs, Today());
+}
+
+sub GetType($)
+{
+ my($f) = @_;
+ return 'text/css' if( $f =~ /.css$/i );
+ return 'text/html';
+}
+
+sub packdate($$$)
+{
+ return $_[0] * 10000 + $_[1] * 100 + $_[2];
+}
+
+sub unpackdate($)
+{
+ return (int($_[0] / 10000),
+ (int($_[0] / 100)) % 100,
+ $_[0] % 100);
+}
+
+sub dodbi
+{
+ my($func) = @_;
+ my($ret);
+ my $dbh = DBI->connect("DBI:mysql:database=$BLOGdbname;".
+ "host=$BLOGdbhost",
+ $BLOGdbuser, $BLOGdbpasswd,
+ {'RaiseError' => 1})
+ if( !$dbh );
+ eval {
+ $ret = &{$func}($dbh);
+ };
+ print "SQL: $@\n" if( $@ );
+ return $ret;
+}
+
+main();
+1;
+
diff --git a/pttbbs/blog/builddb.pl b/pttbbs/blog/builddb.pl
new file mode 100755
index 00000000..9c805d90
--- /dev/null
+++ b/pttbbs/blog/builddb.pl
@@ -0,0 +1,247 @@
+#!/usr/bin/perl
+# $Id$
+use lib '/home/bbs/bin/';
+use strict;
+use Getopt::Std;
+use LocalVars;
+use IO::Handle;
+use Data::Dumper;
+use BBSFileHeader;
+use DB_File;
+use OurNet::FuzzyIndex;
+
+sub main
+{
+ my($fh);
+ die usage() unless( getopts('cdaofn:D:') );
+ die usage() if( !@ARGV );
+ builddb($_) foreach( @ARGV );
+}
+
+sub usage
+{
+ return ("$0 [-acdfo] [-n NUMBER] [board ...]\n".
+ "\t-a\t\trebuild all files\n".
+ "\t-c\t\tbuild configure\n".
+ "\t-d\t\tprint debug message\n".
+ "\t-f\t\tforce build\n".
+ "\t-o\t\tonly build content(not building link)\n".
+ "\t-n NUMBER\tonly build \#NUMBER article\n".
+ "\t-D DATE\t\tdelete article of DATE\n");
+}
+
+sub debugmsg($)
+{
+ print "$_\n" if( $Getopt::Std::opt_d );
+}
+
+sub builddb($)
+{
+ my($board) = @_;
+ my(%bh, %ch);
+
+ debugmsg("building $board");
+ return if( !getdir("$BBSHOME/man/boards/".substr($board,0,1)."/$board",
+ \%bh, \%ch) );
+ buildconfigure($board, \%ch)
+ if( $Getopt::Std::opt_c || $Getopt::Std::opt_a );
+ builddata($board, \%bh,
+ $Getopt::Std::opt_a || '',
+ $Getopt::Std::opt_o || '',
+ $Getopt::Std::opt_n || '',
+ $Getopt::Std::opt_f || '',
+ $Getopt::Std::opt_D,);
+}
+
+sub buildconfigure($$)
+{
+ my($board, $rch) = @_;
+ my($outdir, $fn, $flag, %config, %attr);
+
+ $outdir = "$BLOGDATA/$board";
+ `/bin/rm -rf $outdir; /bin/mkdir -p $outdir`;
+
+ tie(%config, 'DB_File', "$outdir/config.db",
+ O_CREAT | O_RDWR, 0666, $DB_HASH);
+ tie(%attr, 'DB_File', "$outdir/attr.db",
+ O_CREAT | O_RDWR, 0666, $DB_HASH);
+
+ for ( 0..($rch->{num} - 1) ){
+ debugmsg("\texporting ".$rch->{"$_.title"});
+ if( $rch->{"$_.title"} =~ /^config$/i ){
+ foreach( split("\n", $rch->{"$_.content"}) ){
+ $config{$1} = $2 if( !/^\#/ && /(.*?):\s*(.*)/ );
+ }
+ }
+ else{
+ my(@ls, $c, $a, $fn);
+
+ $fn = $rch->{"$_.title"};
+ if( $fn !~ /\.(css|htm|html|js)$/i ){
+ print "not supported filetype ". $rch->{"$_.title"}. "\n";
+ next;
+ }
+
+ $c = $rch->{"$_.content"};
+ $c =~ s/<meta http-equiv=\"refresh\".*?\n//g;
+ open FH, ">$outdir/$fn";
+
+ if( $c =~ m|<attribute>(.*?)\n\s*</attribute>\s*\n(.*)|s ){
+ ($a, $c) = ($1, $2);
+ $a =~ s/^\s*\#.*?\n//gm;
+ foreach( split("\n", $a) ){
+ $attr{"$fn.$1"} = $2 if( /^\s*(\w+):\s+(.*)/ );
+ }
+ }
+ print FH $c;
+ }
+ }
+ debugmsg(Dumper(\%config));
+ debugmsg(Dumper(\%attr));
+}
+
+sub builddata($$$$$$)
+{
+ my($board, $rbh, $rebuild, $contentonly, $number, $force, $del) = @_;
+ my(%dat, $dbfn, $idxfn, $y, $m, $d, $t, $currid, $idx);
+
+ $dbfn = "$BLOGDATA/$board.db";
+ $idxfn = "$BLOGDATA/$board.idx";
+ if( $rebuild ){
+ unlink $dbfn;
+ unlink $idxfn;
+ }
+
+ tie %dat, 'DB_File', $dbfn, O_CREAT | O_RDWR, 0666, $DB_HASH;
+ $idx = OurNet::FuzzyIndex->new($idxfn);
+
+ if( $del ){
+ my($delmonth);
+ ($y, $m) = (int($del / 10000), int($del / 100) % 100);
+
+ $delmonth = 1;
+ foreach( 0..32 ){
+ $delmonth = 0
+ if( $d != $_ &&
+ exists $dat{sprintf('%04d%02d%02d', $y, $m, $d)} );
+ }
+ delete $dat{ sprintf('%04d%02d', $y, $m) }
+ if( $delmonth );
+
+ $currid = $del;
+ if( $dat{"$currid.prev"} ){
+ $dat{ $dat{"$currid.prev"}.'.next' } = $dat{"$currid.next"};
+ } else{
+ delete $dat{ $dat{"$currid.prev"}.'.next' };
+ }
+ if( $dat{"$currid.prev"} ){
+ $dat{ $dat{"$currid.next"}.'.prev' } = $dat{"$currid.prev"};
+ } else{
+ delete $dat{ $dat{"$currid.next"}.'.prev' };
+ }
+ $dat{head} = $dat{"$currid.next"} if( $dat{head} == $currid );
+ $dat{last} = $dat{"$currid.prev"} if( $dat{last} == $currid );
+
+ delete $dat{$currid};
+ delete $dat{"$currid.next"};
+ delete $dat{"$currid.prev"};
+ delete $dat{"$currid.title"};
+ delete $dat{"$currid.short"};
+ delete $dat{"$currid.content"};
+ delete $dat{"$currid.author"};
+ $idx->delete($currid);
+ goto out;
+ }
+
+ foreach( $number ? $number : (1..($rbh->{num} - 1)) ){
+ if( !(($y, $m, $d, $t) =
+ $rbh->{"$_.title"} =~ /(\d+)\.(\d+).(\d+),(.*)/) ){
+ debugmsg("\terror parsing $_: ".$rbh->{"$_.title"});
+ }
+ else{
+ $currid = sprintf('%04d%02d%02d', $y, $m, $d);
+ if( $dat{$currid} && !$force ){
+ debugmsg("\t$currid is already in db");
+ next;
+ }
+
+ debugmsg("\tbuilding $currid content");
+ $dat{ sprintf('%04d%02d', $y, $m) } = 1;
+ $dat{"$currid.title"} = $t;
+ $dat{"$currid.author"} = $rbh->{"$_.owner"};
+ # $dat{"$currid.content"} = $rbh->{"$_.content"};
+ # ugly code for making short
+ my @c = split("\n",
+ $dat{"$currid.content"} = $rbh->{"$_.content"});
+ $dat{"$currid.short"} = ("$c[0]\n$c[1]\n$c[2]\n$c[3]\n");
+
+ $idx->delete($currid) if( $idx->findkey($currid) );
+ $idx->insert($currid, ($dat{"$currid.title"}. "\n".
+ $rbh->{"$_.content"}));
+
+ if( !$contentonly ){
+ debugmsg("\tbuilding $currid linking... ");
+ if( $dat{$currid} ){
+ debugmsg("\t\talready linked");
+ }
+ elsif( !$dat{head} ){ # first article
+ $dat{head} = $currid;
+ $dat{last} = $currid;
+ }
+ elsif( $currid < $dat{head} ){ # before head ?
+ $dat{"$currid.next"} = $dat{head};
+ $dat{"$dat{head}.prev"} = $currid;
+ $dat{head} = $currid;
+ }
+ elsif( $currid > $dat{last} ){ # after last ?
+ $dat{"$currid.prev"} = $dat{last};
+ $dat{"$dat{last}.next"} = $currid;
+ $dat{last} = $currid;
+ }
+ else{ # inside ? @_@;;;
+ my($p, $c);
+ for( $p = $dat{last} ; $p>$currid ; $p = $dat{"$p.prev"} ){
+ ;
+ }
+ $c = $dat{"$p.next"};
+
+ $dat{"$currid.next"} = $c;
+ $dat{"$currid.prev"} = $p;
+ $dat{"$p.next"} = $currid;
+ $dat{"$c.prev"} = $currid;
+ }
+ $dat{$currid} = 1;
+ }
+ }
+ }
+
+out:
+ untie %dat;
+ $idx->sync();
+ undef $idx;
+ chmod 0666, $idxfn;
+}
+
+sub getdir($$$$$)
+{
+ my($bdir, $rh_bh, $rh_ch) = @_;
+ my(%h);
+ tie %h, 'BBSFileHeader', "$bdir/";
+ if( $h{"-1.title"} !~ /blog/i || !$h{"-1.isdir"} ){
+ debugmsg("blogdir not found");
+ return;
+ }
+
+ tie %{$rh_bh}, 'BBSFileHeader', "$bdir/". $h{'-1.filename'}.'/';
+ if( $rh_bh->{'0.title'} !~ /config/i ||
+ !$rh_bh->{'0.isdir'} ){
+ debugmsg("configure not found");
+ return;
+ }
+
+ tie %{$rh_ch}, 'BBSFileHeader', $rh_bh->{dir}. '/'. $rh_bh->{'0.filename'};
+ return 1;
+}
+
+main();
+1;
diff --git a/pttbbs/blog/index.pl b/pttbbs/blog/index.pl
new file mode 100755
index 00000000..b30ed178
--- /dev/null
+++ b/pttbbs/blog/index.pl
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+# $Id$
+use CGI qw/:standard/;
+use lib qw/./;
+use LocalVars;
+
+sub main
+{
+ print redirect("/blog.pl/$1/")
+ if( $ENV{REDIRECT_REQUEST_URI} =~ m|/\?(.*)| );
+
+ return redirect("/blog.pl/$BLOGdefault/");
+}
+
+main();
+1;
+
diff --git a/pttbbs/cacheserver/Makefile b/pttbbs/cacheserver/Makefile
new file mode 100644
index 00000000..c923b1a3
--- /dev/null
+++ b/pttbbs/cacheserver/Makefile
@@ -0,0 +1,28 @@
+# $Id$
+.include "../pttbbs.mk"
+
+PROGRAMS= utmpserver utmpsync utmpserver2 utmpserver3 authserver
+UTILOBJ= ../util/util_stuff.o ../util/util_var.o ../util/util_file.o ../util/util_cache.o ../util/util_passwd.o ../util/util_record.o ../util/util_osdep.o ../util/util_args.o
+
+all: ${PROGRAMS}
+
+.SUFFIXES: .c .cpp .o
+.c.o:
+ $(CCACHE) $(CC) $(CFLAGS) -c $*.c
+.cpp.o:
+ $(CCACHE) $(CXX) $(CFLAGS) -c $*.cpp
+
+utmpserver: utmpserver.o $(UTILOBJ)
+ ${CC} ${CFLAGS} ${LDFLAGS} -o $* $*.o $(UTILOBJ)
+utmpserver2: utmpserver2.o friend.o $(UTILOBJ)
+ ${CXX} ${CFLAGS} ${LDFLAGS} -o $* $*.o $(UTILOBJ) friend.o
+utmpserver3: utmpserver3.o friend.o $(UTILOBJ)
+ ${CXX} ${CFLAGS} ${LDFLAGS} -levent -o $* $*.o $(UTILOBJ) friend.o
+utmpsync: utmpsync.o $(UTILOBJ)
+ ${CC} ${CFLAGS} ${LDFLAGS} -o $* $*.o $(UTILOBJ)
+
+authserver: authserver.o $(UTILOBJ)
+ ${CC} ${CFLAGS} ${LDFLAGS} -lcrypt -levent -o $* $>
+
+clean:
+ rm -f *~ ${PROGRAMS} friend.o utmpserver.o utmpserver2.o utmpserver3.o utmpsync.o authserver.o
diff --git a/pttbbs/cacheserver/README b/pttbbs/cacheserver/README
new file mode 100644
index 00000000..3bfeddbc
--- /dev/null
+++ b/pttbbs/cacheserver/README
@@ -0,0 +1 @@
+這是一個測試的東西. 除非確定你知道這個程式在幹什麼, 否則請不理會這個目錄 :P
diff --git a/pttbbs/cacheserver/authserver.c b/pttbbs/cacheserver/authserver.c
new file mode 100644
index 00000000..5fbba03a
--- /dev/null
+++ b/pttbbs/cacheserver/authserver.c
@@ -0,0 +1,236 @@
+/* $Id$ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <event.h>
+
+#include "bbs.h"
+
+struct timeval tv = {5, 0};
+struct event ev;
+int clients = 0;
+
+#define READ_BLOCK 256
+#define MAX_CLIENTS 10
+
+#define AUTH_PASSWDS BBSHOME "/.AUTH_PASSWDS"
+#define ENTRY_SIZE 64
+
+struct client_state {
+ struct event ev;
+ int state;
+ int uid;
+ int len;
+ int response;
+ struct evbuffer *evb;
+};
+
+enum {
+ FSM_ENTER,
+ FSM_AUTH,
+ FSM_SETPASSWD,
+ FSM_RESPOND,
+ FSM_EXIT
+};
+
+/**
+ * 瑼X亙蝣
+ * @return 1 - 撖蝣潮航炊, 0 - 撖蝣潭迤蝣
+ */
+int check_passwd(int uid, char *passwd)
+{
+ char buf[ENTRY_SIZE];
+ int i, result = 1;
+
+ if ((i = open(AUTH_PASSWDS, O_WRONLY)) < 0)
+ return result;
+
+ if (lseek(i, uid * ENTRY_SIZE, SEEK_SET) < 0)
+ goto end;
+
+ if (read(i, buf, ENTRY_SIZE) < ENTRY_SIZE)
+ goto end;
+
+ if (!strcmp(buf, crypt(passwd, buf)))
+ result = 0;
+end:
+ memset(buf, 0, ENTRY_SIZE);
+ close(i);
+ return result;
+}
+
+/**
+ * 閮剖啣蝣
+ * DES crypt 撠勗末
+ */
+void set_passwd(int uid, char *passwd)
+{
+ char buf[ENTRY_SIZE];
+ char saltc[3], c;
+ int i;
+
+ i = 9 * random();
+ saltc[0] = i & 077;
+ saltc[1] = (i >> 6) & 077;
+
+ for (i = 0; i < 2; i++) {
+ c = saltc[i] + '.';
+ if (c > '9')
+ c += 7;
+ if (c > 'Z')
+ c += 6;
+ saltc[i] = c;
+ }
+ saltc[2] = '\0';
+
+ memset(buf, 0, sizeof(buf));
+ strlcpy(buf, crypt(passwd, saltc), sizeof(buf));
+
+ if ((i = open(AUTH_PASSWDS, O_WRONLY)) < 0)
+ return;
+
+ if (lseek(i, uid * ENTRY_SIZE, SEEK_SET) < 0)
+ goto close;
+ write(i, buf, ENTRY_SIZE);
+close:
+ close(i);
+}
+
+void connection_client(int cfd, short event, void *arg)
+{
+ struct client_state *cs = arg;
+ int cmd;
+ static char buf[128];
+
+ // ignore clients that timeout
+ if (event & EV_TIMEOUT)
+ cs->state = FSM_EXIT;
+
+ if (event & EV_READ) {
+ if (evbuffer_read(cs->evb, cfd, READ_BLOCK) < 0)
+ cs->state = FSM_EXIT;
+ }
+
+ while (1) {
+ switch (cs->state) {
+ case FSM_ENTER:
+ if (EVBUFFER_LENGTH(cs->evb) < sizeof(int))
+ goto break_out;
+ evbuffer_remove(cs->evb, &cmd, sizeof(cmd));
+ cs->state = FSM_AUTH;
+
+ case FSM_AUTH:
+ if (EVBUFFER_LENGTH(cs->evb) < sizeof(int) * 2)
+ goto break_out;
+
+ evbuffer_remove(cs->evb, &cs->uid, sizeof(cs->uid));
+ evbuffer_remove(cs->evb, &cs->len, sizeof(cs->len));
+ if (EVBUFFER_LENGTH(cs->evb) < cs->len)
+ goto break_out;
+
+ memset(buf, 0, sizeof(buf));
+ evbuffer_remove(cs->evb, buf, cs->len);
+ cs->response = check_passwd(cs->uid, buf);
+ memset(buf, 0, sizeof(buf));
+
+ if (cmd == 2)
+ cs->state = FSM_SETPASSWD;
+ else {
+ cs->state = FSM_RESPOND;
+ goto break_out;
+ }
+
+ case FSM_SETPASSWD:
+ if (EVBUFFER_LENGTH(cs->evb) < sizeof(int))
+ goto break_out;
+
+ evbuffer_remove(cs->evb, &cs->len, sizeof(cs->len));
+ if (EVBUFFER_LENGTH(cs->evb) < cs->len)
+ goto break_out;
+
+ memset(buf, 0, sizeof(buf));
+ evbuffer_remove(cs->evb, buf, cs->len);
+ set_passwd(cs->uid, buf);
+ memset(buf, 0, sizeof(buf));
+
+ cs->state = FSM_RESPOND;
+ goto break_out;
+
+ case FSM_RESPOND:
+ write(cfd, &cs->response, sizeof(cs->response));
+ cs->state = FSM_EXIT;
+
+ case FSM_EXIT:
+ if (clients == MAX_CLIENTS)
+ event_add(&ev, NULL);
+ close(cfd);
+ evbuffer_free(cs->evb);
+ free(cs);
+ clients--;
+ return;
+ }
+ }
+
+break_out:
+ if (cs->state == FSM_RESPOND)
+ event_set(&cs->ev, cfd, EV_WRITE, (void *) connection_client, cs);
+ event_add(&cs->ev, &tv);
+}
+
+void connection_accept(int fd, short event, void *arg)
+{
+ struct sockaddr_in clientaddr;
+ socklen_t len = sizeof(clientaddr);
+ int cfd;
+
+ if ((cfd = accept(fd, (struct sockaddr *)&clientaddr, &len)) < 0 )
+ return;
+
+ fcntl(cfd, F_SETFL, fcntl(cfd, F_GETFL, 0) | O_NONBLOCK);
+
+ struct client_state *cs = calloc(1, sizeof(struct client_state));
+ cs->state = 0;
+ cs->evb = evbuffer_new();
+
+ event_set(&cs->ev, cfd, EV_READ, (void *) connection_client, cs);
+ event_add(&cs->ev, &tv);
+ clients++;
+
+ if (clients > MAX_CLIENTS)
+ event_del(&ev);
+}
+
+int main(int argc, char *argv[])
+{
+ int ch, port = 5121, sfd;
+ char *iface_ip = NULL;
+
+ Signal(SIGPIPE, SIG_IGN);
+ while( (ch = getopt(argc, argv, "p:i:h")) != -1 )
+ switch( ch ){
+ case 'p':
+ port = atoi(optarg);
+ break;
+ case 'i':
+ iface_ip = optarg;
+ break;
+ case 'h':
+ default:
+ fprintf(stderr, "usage: authserver [-i interface_ip] [-p port]\n");
+ return 1;
+ }
+
+ if( (sfd = tobind(iface_ip, port)) < 0 )
+ return 1;
+
+ srandom(getpid() + time(NULL));
+ event_init();
+ event_set(&ev, sfd, EV_READ | EV_PERSIST, connection_accept, &ev);
+ event_add(&ev, NULL);
+ event_dispatch();
+ return 0;
+}
diff --git a/pttbbs/cacheserver/friend.cpp b/pttbbs/cacheserver/friend.cpp
new file mode 100644
index 00000000..e62a7198
--- /dev/null
+++ b/pttbbs/cacheserver/friend.cpp
@@ -0,0 +1,350 @@
+#define NDEBUG
+#include <algorithm>
+#include <cassert>
+
+// for some constant and type
+#include "bbs.h"
+
+/* for each login of user,
+ * input: my index, friend[MAX_FRIEND] of uid, reject[MAX_REJECT] of uid,
+ * for all my relation,
+ * output: his index, his uid, relation to me, relation to him
+ */
+/* 支 user utmp 銋憭, 券函 ref index 賣舫, 蝣箔 insert & delete O(1) */
+/* 嗆鈭 refer resource recycle */
+
+typedef int Uid;
+typedef int Idx;
+
+
+struct Relation {
+ Uid him;
+ short him_offset;
+
+ Relation(Uid _him, short _him_offset=-1) :him(_him),him_offset(_him_offset) {}
+};
+
+template<class T>
+struct freelist {
+ static const int KEEP = 64;
+ static T* list[8][KEEP]; // 2^0~2^7
+ static int tail[8];
+
+#define IS_2xxN(a) (a && (a&(a-1))==0)
+ static T* alloc(int n) {
+ assert(n>0);
+ if(n<256 && IS_2xxN(n) && sizeof(T)*n<65536) {
+ int t=n;
+ int slot;
+ for(slot=0; t>1; t/=2)
+ slot++;
+ assert(0<=slot && slot<8);
+ if(tail[slot]) {
+ return list[slot][--tail[slot]];
+ }
+ }
+ return (T*)malloc(sizeof(T)*n);
+ }
+ static void free(T* p, int n) {
+ assert(n>0);
+ if(n<256 && IS_2xxN(n) && sizeof(T)*n<65536) {
+ int t=n;
+ int slot;
+ for(slot=0; t>1; t/=2)
+ slot++;
+ assert(0<=slot && slot<8);
+ if(tail[slot]<KEEP) {
+ list[slot][tail[slot]++]=p;
+ return;
+ }
+ }
+ ::free(p);
+ }
+};
+
+template<class T> T* freelist<T>::list[8][KEEP];
+template<class T> int freelist<T>::tail[8]={0};
+
+template<class T,int MIN_ROOM = 8, class S = int>
+struct myvector {
+ // 憭扯港唾 STL vector, 雿 STL capacity 芣憓銝蝮桀
+ // (敺靘潛, 隞 online friend 靘隤, capacity 銝蝮桀嗅祕瘝隞暻澆蔣)
+ // 甇文, pointer 64bit 璈其閬 8bytes, basic overhead 8*3 bytes,
+ // 雿鞈瘝暻澆之, 寧 S(int or short) 摮 size & capacity 瘥頛
+ T *base;
+ S room, n;
+
+ myvector() :base(0),room(0),n(0) {}
+ ~myvector() {
+ clear();
+ }
+ S append(T data) {
+ if(room<n+1)
+ resizefor(n+1);
+ base[n++]=data;
+ return n-1;
+ }
+ void pop_back() {
+ assert(n>0);
+ n--;
+ resizefor(n);
+ }
+ void clear() {
+ n=0;
+ resizefor(n);
+ }
+ /*
+ T& operator[](int idx) {
+ return base[idx];
+ }
+ */
+
+ void resizefor(S size) {
+ assert(size>=n);
+ if(size==0) {
+ if(base) freelist<T>::free(base, room);
+ base=0;
+ room=0;
+ } else {
+ S origroom=room;
+ if(room==0)
+ room=MIN_ROOM;
+ while(room<size) room=S(room*2);
+ if(size<MIN_ROOM) size=MIN_ROOM;
+ while(room/2>size) room=S(room/2);
+ if(room!=origroom || base==0) {
+ //base=(T*)realloc(base, sizeof(T)*room);
+ T* tmp=freelist<T>::alloc(room);
+ assert(tmp);
+ if(n>0)
+ memcpy(tmp, base, sizeof(T)*n);
+ if(base!=0)
+ freelist<T>::free(base, origroom);
+ base=tmp;
+ }
+ assert(base);
+ }
+ }
+};
+
+template<class R,class B>
+struct RelationList: public myvector<Relation, 8, short> {
+ RelationList() :myvector<Relation, 8, short>() {}
+ void add(Uid me, Uid him) {
+ RelationList<B,R>& bl=R::backlist(him);
+ short me_offset=append(Relation(him));
+ short him_offset=bl.append(Relation(me,me_offset));
+
+ setbackoffset(me_offset,him_offset);
+ assert(bl.base[him_offset].him==me);
+ assert(bl.base[him_offset].him_offset==me_offset);
+ }
+ void deleteall(Uid me) {
+ for(int i=0; i<n; i++) {
+ RelationList<B,R>& bl=R::backlist(base[i].him);
+ assert(bl.base[base[i].him_offset].him==me);
+ assert(bl.base[base[i].him_offset].him_offset==i);
+ bl.delete_half(base[i].him_offset);
+ //try_recycle(base[i].him); // dirty
+ }
+ clear();
+ }
+ private:
+ void setbackoffset(short which,short offset) {
+ assert(0<=which && which<n);
+ base[which].him_offset=offset;
+ }
+ void delete_half(short offset) {
+ assert(0<=offset && offset<n);
+ if(offset<n-1) {
+ base[offset]=base[n-1];
+ R::backlist(base[offset].him).setbackoffset(base[offset].him_offset,offset);
+ }
+ pop_back();
+ }
+ friend class RelationList<B,R>;
+};
+
+struct Like;
+struct Likeby;
+struct Hate;
+struct Hateby;
+struct Like: public Relation {
+ Like(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {}
+ static RelationList<Likeby,Like>& backlist(Uid him);
+};
+struct Likeby: public Relation {
+ Likeby(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {}
+ static RelationList<Like,Likeby>& backlist(Uid him);
+};
+struct Hate: public Relation {
+ Hate(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {}
+ static RelationList<Hateby,Hate>& backlist(Uid him);
+};
+struct Hateby: public Relation {
+ Hateby(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {}
+ static RelationList<Hate,Hateby>& backlist(Uid him);
+};
+
+
+struct Utmp {
+ Utmp() {
+ for(int i=0; i<USHM_SIZE; i++)
+ utmp[i]=-1;
+ }
+ /*
+ Uid& operator[](int idx) {
+ return utmp[idx];
+ }
+ */
+ public:
+ Uid utmp[USHM_SIZE];
+};
+static Utmp utmp;
+
+struct BBSUser {
+ Uid me;
+ int online;
+ /* assume utmplist is short, so just use plain vector and linear search */
+ myvector<int,2> utmplist;
+
+ RelationList<Like,Likeby> like;
+ RelationList<Hate,Hateby> hate;
+ RelationList<Likeby,Like> likeby;
+ RelationList<Hateby,Hate> hateby;
+
+ BBSUser() :me(-1),online(0),utmplist(),like(),hate(),likeby(),hateby() {}
+ BBSUser(Uid uid) :me(uid),online(0),utmplist(),like(),hate(),likeby(),hateby() {}
+
+ void login(int utmpidx, const Uid likehim[MAX_FRIEND], const Uid hatehim[MAX_REJECT]) {
+ if(online>0) {
+ /* multiple login 閰, 隞交敺銝甈∠ like/hate 箸 */
+ like.deleteall(me);
+ hate.deleteall(me);
+ }
+ utmp.utmp[utmpidx]=me;
+ utmplist.append(utmpidx);
+ online++;
+ assert(online==utmplist.n);
+ for(int i=0; i<MAX_FRIEND && likehim[i]; i++)
+ like.add(me, likehim[i]);
+ for(int i=0; i<MAX_REJECT && hatehim[i]; i++)
+ hate.add(me, hatehim[i]);
+ }
+
+ void logout(int utmpidx) {
+ assert(utmp.utmp[utmpidx]==me);
+ assert(online==utmplist.n);
+ for(int i=0; i<utmplist.n; i++)
+ if(utmplist.base[i]==utmpidx) {
+ utmplist.base[i]=utmplist.base[utmplist.n-1];
+ utmplist.pop_back();
+ break;
+ }
+ utmp.utmp[utmpidx]=-1;
+ online--;
+ assert(online==utmplist.n);
+ if(online==0) {
+ like.deleteall(me);
+ hate.deleteall(me);
+ }
+ }
+ bool isfree() const {
+ return online==0 && like.n==0 && hate.n==0 && likeby.n==0 && hateby.n==0;
+ }
+};
+
+struct UserList {
+ BBSUser users[MAX_USERS];
+
+ UserList() {
+ for(int i=0; i<MAX_USERS; i++)
+ users[i].me=i;
+ }
+ void login(Uid uid, Idx idx, const Uid likehim[MAX_FRIEND], const Uid hatehim[MAX_REJECT]) {
+ assert(uid<MAX_USERS);
+ assert(idx<USHM_SIZE);
+ /* 望潔嗅 logout event, 甇 logout 芰潛 utmp override */
+ if(utmp.utmp[idx]!=-1) users[utmp.utmp[idx]].logout(idx);
+ users[uid].login(idx, likehim, hatehim);
+ }
+};
+
+struct UserList userlist;
+RelationList<Likeby,Like>& Like::backlist(Uid him) { return userlist.users[him].likeby; }
+RelationList<Like,Likeby>& Likeby::backlist(Uid him) { return userlist.users[him].like; }
+RelationList<Hateby,Hate>& Hate::backlist(Uid him) { return userlist.users[him].hateby; }
+RelationList<Hate,Hateby>& Hateby::backlist(Uid him) { return userlist.users[him].hate; }
+
+struct Result {
+ Uid who;
+ int bits;
+ Result(Uid _who, int _bits) :who(_who),bits(_bits) {}
+ bool operator<(const Result& b) const {
+ return who<b.who;
+ }
+};
+
+int reverse_friend_stat(int stat)
+{
+ int stat1 = 0;
+ if (stat & IFH) stat1 |= HFM;
+ if (stat & IRH) stat1 |= HRM;
+ if (stat & HFM) stat1 |= IFH;
+ if (stat & HRM) stat1 |= IRH;
+ return stat1;
+}
+
+extern "C" void utmplogin(int uid, int index, const int like[MAX_FRIEND], const int hate[MAX_REJECT])
+{
+ /* login */
+ userlist.login(uid, index, like, hate);
+}
+
+extern "C" int genfriendlist(int uid, int index, ocfs_t *fs, int maxfs)
+{
+ /* collect data */
+ BBSUser& u=userlist.users[uid];
+ myvector<Result,64,short> work;
+ for(int i=0; i<u.like.n; i++) work.append(Result(u.like.base[i].him, IFH));
+ for(int i=0; i<u.hate.n; i++) work.append(Result(u.hate.base[i].him, IRH));
+ for(int i=0; i<u.likeby.n; i++) work.append(Result(u.likeby.base[i].him, HFM));
+ for(int i=0; i<u.hateby.n; i++) work.append(Result(u.hateby.base[i].him, HRM));
+
+ /* sort */
+ std::sort(work.base, work.base+work.n);
+ /* merge */
+ if(work.n>0) {
+ int newn=1;
+ for(int i=1; i<work.n; i++)
+ if(work.base[i].who==work.base[newn-1].who) {
+ work.base[newn-1].bits|=work.base[i].bits;
+ } else {
+ work.base[newn++]=work.base[i];
+ }
+ work.n=newn;
+ }
+ /* fill */
+ int nfs=0;
+ for(int i=0; i<work.n && nfs<maxfs; i++) {
+ BBSUser& h=userlist.users[work.base[i].who];
+ for(int j=0; j<h.utmplist.n && nfs<maxfs; j++) {
+ int rstat=reverse_friend_stat(work.base[i].bits);
+ if(h.utmplist.base[j]==index) continue;
+ fs[nfs].index=h.utmplist.base[j];
+ fs[nfs].uid=h.me;
+ fs[nfs].friendstat=(work.base[i].bits<<24)|h.utmplist.base[j];
+ fs[nfs].rfriendstat=(rstat<<24)|index;
+ nfs++;
+ }
+ }
+ return nfs;
+}
+
+extern "C" void utmplogoutall(void)
+{
+ for(int i=0; i<USHM_SIZE; i++)
+ if(utmp.utmp[i]!=-1)
+ userlist.users[utmp.utmp[i]].logout(i);
+}
+
diff --git a/pttbbs/cacheserver/utmpserver.c b/pttbbs/cacheserver/utmpserver.c
new file mode 100644
index 00000000..85adf8c3
--- /dev/null
+++ b/pttbbs/cacheserver/utmpserver.c
@@ -0,0 +1,222 @@
+/* $Id$ */
+#include "bbs.h"
+#include <err.h>
+
+struct {
+ int uid;
+ int nFriends, nRejects;
+ int friend[MAX_FRIEND];
+ int reject[MAX_REJECT];
+} utmp[USHM_SIZE];
+
+#ifdef NOFLOODING
+#define MAXWAIT 1024
+#define FLUSHTIME (3600*6)
+
+struct {
+ time_t lasttime;
+ int count;
+} flooding[MAX_USERS];
+
+int nWaits, lastflushtime;
+struct {
+ int uid;
+ int fd;
+ int index;
+} waitqueue[MAXWAIT];
+#endif /* NOFLOODING */
+
+inline int countarray(int *s, int max)
+{
+ int i;
+ for( i = 0 ; i < max && s[i] ; ++i )
+ ;
+ return i;
+}
+
+int
+reverse_friend_stat(int stat)
+{
+ int stat1 = 0;
+ if (stat & IFH)
+ stat1 |= HFM;
+ if (stat & IRH)
+ stat1 |= HRM;
+ if (stat & HFM)
+ stat1 |= IFH;
+ if (stat & HRM)
+ stat1 |= IRH;
+ if (stat & IBH)
+ stat1 |= IBH;
+ return stat1;
+}
+
+int set_friend_bit(int me, int ui)
+{
+ int hit = 0;
+ /* 判斷對方是否為我的朋友 ? */
+ if( intbsearch(utmp[ui].uid, utmp[me].friend, utmp[me].nFriends) )
+ hit = IFH;
+
+ /* 判斷我是否為對方的朋友 ? */
+ if( intbsearch(utmp[me].uid, utmp[ui].friend, utmp[ui].nFriends) )
+ hit |= HFM;
+
+ /* 判斷對方是否為我的仇人 ? */
+ if( intbsearch(utmp[ui].uid, utmp[me].reject, utmp[me].nRejects) )
+ hit |= IRH;
+
+ /* 判斷我是否為對方的仇人 ? */
+ if( intbsearch(utmp[me].uid, utmp[ui].reject, utmp[ui].nRejects) )
+ hit |= HRM;
+
+ return hit;
+}
+
+void initdata(int index)
+{
+ utmp[index].nFriends = countarray(utmp[index].friend, MAX_FRIEND);
+ utmp[index].nRejects = countarray(utmp[index].reject, MAX_REJECT);
+ if( utmp[index].nFriends > 0 )
+ qsort(utmp[index].friend, utmp[index].nFriends,
+ sizeof(int), qsort_intcompar);
+ if( utmp[index].nRejects > 0 )
+ qsort(utmp[index].reject, utmp[index].nRejects,
+ sizeof(int), qsort_intcompar);
+}
+
+inline void syncutmp(int cfd)
+{
+ int nSynced = 0, i;
+ for( i = 0 ; i < USHM_SIZE ; ++i, ++nSynced )
+ if( toread(cfd, &utmp[i].uid, sizeof(utmp[i].uid)) > 0 &&
+ toread(cfd, utmp[i].friend, sizeof(utmp[i].friend)) > 0 &&
+ toread(cfd, utmp[i].reject, sizeof(utmp[i].reject)) > 0 ){
+ if( utmp[i].uid )
+ initdata(i);
+ }
+ else
+ for( ; i < USHM_SIZE ; ++i )
+ utmp[i].uid = 0;
+ close(cfd);
+ fprintf(stderr, "%d users synced\n", nSynced);
+}
+
+void processlogin(int cfd, int uid, int index)
+{
+ if( toread(cfd, utmp[index].friend, sizeof(utmp[index].friend)) > 0 &&
+ toread(cfd, utmp[index].reject, sizeof(utmp[index].reject)) > 0 ){
+ /* 因為 logout 的時候並不會通知 utmpserver , 可能會查到一些
+ 已經 logout 的帳號。所以不能只取 MAX_FRIEND 而要多取一些 */
+#define MAX_FS (2 * MAX_FRIEND)
+ int iu, nFrs, stat, rstat;
+ ocfs_t fs[MAX_FS];
+
+ utmp[index].uid = uid;
+ initdata(index);
+
+ for( nFrs = iu = 0 ; iu < USHM_SIZE && nFrs < MAX_FS ; ++iu )
+ if( iu != index && utmp[iu].uid ){
+ if( (stat = set_friend_bit(index, iu)) ){
+ rstat = reverse_friend_stat(stat);
+ fs[nFrs].index = iu;
+ fs[nFrs].uid = utmp[iu].uid;
+ fs[nFrs].friendstat = (stat << 24) + iu;
+ fs[nFrs].rfriendstat = (rstat << 24) + index;
+ ++nFrs;
+ }
+ }
+
+ towrite(cfd, &fs, sizeof(ocfs_t) * nFrs);
+ }
+ close(cfd);
+}
+
+#ifdef NOFLOODING
+void flushwaitqueue(void)
+{
+ int i;
+ for( i = 0 ; i < nWaits ; ++i )
+ processlogin(waitqueue[i].fd, waitqueue[i].uid, waitqueue[i].index);
+ lastflushtime = time(NULL);
+ nWaits = 0;
+ memset(flooding, 0, sizeof(flooding));
+}
+#endif
+
+/* XXX 具有被 DoS 的可能, 請用 firewall 之類擋起來 */
+int main(int argc, char **argv)
+{
+ struct sockaddr_in clientaddr;
+ int ch, port = 5120, sfd, cfd, len, index, uid;
+ char *iface_ip = NULL;
+
+ Signal(SIGPIPE, SIG_IGN);
+ while( (ch = getopt(argc, argv, "p:i:h")) != -1 )
+ switch( ch ){
+ case 'p':
+ port = atoi(optarg);
+ break;
+ case 'i':
+ iface_ip = optarg;
+ break;
+ case 'h':
+ default:
+ fprintf(stderr, "usage: utmpserver [-i interface_ip] [-p port]\n");
+ return 1;
+ }
+
+ if( (sfd = tobind(iface_ip, port)) < 0 )
+ return 1;
+
+#ifdef NOFLOODING
+ lastflushtime = time(NULL);
+#endif
+ while( 1 ){
+#ifdef NOFLOODING
+ if( lastflushtime < (time(NULL) - 1800) )
+ flushwaitqueue();
+#endif
+
+ len = sizeof(clientaddr);
+ if( (cfd = accept(sfd, (struct sockaddr *)&clientaddr, &len)) < 0 ){
+ if( errno != EINTR )
+ sleep(1);
+ continue;
+ }
+ toread(cfd, &index, sizeof(index));
+ if( index == -1 ){
+ syncutmp(cfd);
+ continue;
+ }
+
+ if( toread(cfd, &uid, sizeof(uid)) > 0 ){
+ if( !(0 < uid || uid > MAX_USERS) ){ /* for safety */
+ close(cfd);
+ continue;
+ }
+
+#ifdef NOFLOODING
+ if( (time(NULL) - flooding[uid].lasttime) < 20 )
+ ++flooding[uid].count;
+ if( flooding[uid].count > 10 ){
+ if( nWaits == MAXWAIT )
+ flushwaitqueue();
+ waitqueue[nWaits].uid = uid;
+ waitqueue[nWaits].index = index;
+ waitqueue[nWaits].fd = cfd;
+ ++nWaits;
+
+ continue;
+ }
+ flooding[uid].lasttime = time(NULL);
+#endif
+
+ /* cfd will be closed in processlogin() */
+ processlogin(cfd, uid, index);
+ } else {
+ close(cfd);
+ }
+ }
+ return 0;
+}
diff --git a/pttbbs/cacheserver/utmpserver2.c b/pttbbs/cacheserver/utmpserver2.c
new file mode 100644
index 00000000..12c3a299
--- /dev/null
+++ b/pttbbs/cacheserver/utmpserver2.c
@@ -0,0 +1,290 @@
+/* $Id$ */
+#include <stdio.h>
+#include <sys/time.h>
+
+#include "bbs.h"
+
+extern void utmplogin(int uid, int index, const int like[MAX_FRIEND], const int hate[MAX_REJECT]);
+extern int genfriendlist(int uid, int index, ocfs_t *fs, int maxfs);
+extern void utmplogoutall(void);
+#ifdef FAKEDATA
+FILE *fp;
+#endif
+#ifdef UTMPLOG
+FILE *logfp;
+#endif
+
+clock_t begin_clock;
+time_t begin_time;
+int count_flooding, count_login;
+
+#ifdef NOFLOODING
+/* 0 ok, 1 delay action, 2 reject */
+int action_frequently(int uid)
+{
+ int i;
+ time_t now = time(NULL);
+ time_t minute = now/60;
+ time_t hour = minute/60;
+
+ static time_t flood_base_minute;
+ static time_t flood_base_hour;
+ static struct {
+ unsigned short lastlogin; // truncated time_t
+ unsigned char minute_count;
+ unsigned char hour_count;
+ } flooding[MAX_USERS];
+
+ if(minute!=flood_base_minute) {
+ for(i=0; i<MAX_USERS; i++)
+ flooding[i].minute_count=0;
+ flood_base_minute=minute;
+ }
+ if(hour!=flood_base_hour) {
+ for(i=0; i<MAX_USERS; i++)
+ flooding[i].hour_count=0;
+ flood_base_hour=hour;
+ }
+
+ if(abs(flooding[uid].lastlogin-(unsigned short)now)<=3 ||
+ flooding[uid].minute_count>30 ||
+ flooding[uid].hour_count>60) {
+ count_flooding++;
+ return 2;
+ }
+
+ flooding[uid].minute_count++;
+ flooding[uid].hour_count++;
+ flooding[uid].lastlogin=now;
+
+ if(flooding[uid].minute_count>5 ||
+ flooding[uid].hour_count>20) {
+ count_flooding++;
+ return 1;
+ }
+ return 0;
+}
+#endif /* NOFLOODING */
+
+void syncutmp(int cfd) {
+ int i;
+ int like[MAX_FRIEND];
+ int hate[MAX_REJECT];
+
+#ifdef UTMPLOG
+ int x=-1;
+ if(logfp && ftell(logfp)> 500*(1<<20)) {
+ fclose(logfp);
+ logfp=NULL;
+ }
+ if(logfp) fwrite(&x, sizeof(x), 1, logfp);
+#endif
+
+ printf("logout all\n");
+ utmplogoutall();
+ fprintf(stderr,"sync begin\n");
+ for(i=0; i<USHM_SIZE; i++) {
+ int uid;
+#ifdef FAKEDATA
+ fread(&uid, sizeof(uid), 1, fp);
+ if(uid==-2)
+ break;
+ fread(like, sizeof(like), 1, fp);
+ fread(hate, sizeof(hate), 1, fp);
+#else
+ if( toread(cfd, &uid, sizeof(uid)) <= 0 ||
+ toread(cfd, like, sizeof(like)) <= 0 ||
+ toread(cfd, hate, sizeof(hate)) <= 0)
+ break;
+#endif
+#ifdef UTMPLOG
+ if(logfp) {
+ fwrite(&uid, sizeof(uid), 1, logfp);
+ fwrite(like, sizeof(like), 1, logfp);
+ fwrite(hate, sizeof(hate), 1, logfp);
+ }
+#endif
+
+ if(uid != 0)
+ utmplogin(uid, i, like, hate);
+ }
+ if(i<USHM_SIZE) {
+#ifdef UTMPLOG
+ int x=-2;
+ if(logfp) fwrite(&x, sizeof(x), 1, logfp);
+#endif
+ }
+
+ fprintf(stderr,"sync end\n");
+}
+
+void processlogin(int cfd, int uid, int index)
+{
+ int like[MAX_FRIEND];
+ int hate[MAX_REJECT];
+ /* logout 銝虫 utmpserver , 航賣亙唬鈭
+ 撌脩 logout 撣唾隞乩賢芸 MAX_FRIEND 閬憭銝鈭 */
+#define MAX_FS (2 * MAX_FRIEND)
+ int res;
+ int nfs;
+ ocfs_t fs[MAX_FS];
+
+#ifdef FAKEDATA
+ fread(like, sizeof(like), 1, fp);
+ fread(hate, sizeof(hate), 1, fp);
+#else
+ if(toread(cfd, like, sizeof(like)) <= 0 ||
+ toread(cfd, hate, sizeof(hate)) <= 0)
+ return;
+#endif
+#ifdef UTMPLOG
+ if(logfp) {
+ int x=-3;
+ fwrite(&x, sizeof(x), 1, logfp);
+ fwrite(&uid, sizeof(uid), 1, logfp);
+ fwrite(&index, sizeof(index), 1, logfp);
+ fwrite(like, sizeof(like), 1, logfp);
+ fwrite(hate, sizeof(hate), 1, logfp);
+ }
+#endif
+
+ utmplogin(uid, index, like, hate);
+ nfs=genfriendlist(uid, index, fs, MAX_FS);
+#ifndef FAKEDATA
+ res=0;
+#ifdef NOFLOODING
+ res=action_frequently(uid);
+#endif
+ towrite(cfd, &res, sizeof(res));
+ towrite(cfd, &nfs, sizeof(nfs));
+ towrite(cfd, fs, sizeof(ocfs_t) * nfs);
+#endif
+}
+
+void showstat(void)
+{
+ clock_t now_clock=clock();
+ time_t now_time=time(0);
+
+ time_t used_time=now_time-begin_time;
+ clock_t used_clock=now_clock-begin_clock;
+
+ printf("%.24s : real %.0f cpu %.2f : %d login %d flood, %.2f login/sec, %.2f%% load.\n",
+ ctime(&now_time), (double)used_time, (double)used_clock/CLOCKS_PER_SEC,
+ count_login, count_flooding,
+ (double)count_login/used_time, (double)used_clock/CLOCKS_PER_SEC/used_time*100);
+
+ begin_time=now_time;
+ begin_clock=now_clock;
+ count_login=0;
+ count_flooding=0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct sockaddr_in clientaddr;
+ int ch, port = 5120, sfd, cfd, len;
+ char *iface_ip = NULL;
+ int cmd;
+ int uid,index;
+ int fail;
+ int firstsync=0;
+
+#ifdef UTMPLOG
+ logfp = fopen("utmp.log","a");
+ if(logfp && ftell(logfp)> 500*(1<<20)) {
+ fclose(logfp);
+ logfp=NULL;
+ }
+#endif
+
+ Signal(SIGPIPE, SIG_IGN);
+ while( (ch = getopt(argc, argv, "p:i:h")) != -1 )
+ switch( ch ){
+ case 'p':
+ port = atoi(optarg);
+ break;
+ case 'i':
+ iface_ip = optarg;
+ break;
+ case 'h':
+ default:
+ fprintf(stderr, "usage: utmpserver [-i interface_ip] [-p port]\n");
+ return 1;
+ }
+
+#ifdef FAKEDATA
+ fp=fopen("utmp.data","rb");
+#else
+ if( (sfd = tobind(iface_ip, port)) < 0 )
+ return 1;
+#endif
+ while(1) {
+#ifdef FAKEDATA
+ if(fread(&cmd, sizeof(cmd), 1, fp)==0) break;
+#else
+ len = sizeof(clientaddr);
+ if( (cfd = accept(sfd, (struct sockaddr *)&clientaddr, &len)) < 0 ){
+ if( errno != EINTR )
+ sleep(1);
+ continue;
+ }
+ toread(cfd, &cmd, sizeof(cmd));
+#endif
+
+ if(cmd==-1) {
+ syncutmp(cfd);
+#ifndef FAKEDATA
+ close(cfd);
+#endif
+ firstsync=1;
+ continue;
+ }
+ if(!firstsync) {
+ // don't accept client before first sync, to prevent incorrect friend data
+ close(cfd);
+ continue;
+ }
+
+ fail=0;
+#ifdef FAKEDATA
+ fread(&uid, sizeof(uid), 1, fp);
+ fread(&index, sizeof(index), 1, fp);
+#else
+ if(cmd==-2) {
+ if(toread(cfd, &index, sizeof(index)) <= 0)
+ fail=1;
+ if(toread(cfd, &uid, sizeof(uid)) <= 0)
+ fail=1;
+ } else if(cmd>=0) {
+ // old client
+ fail=1;
+ } else {
+ printf("unknown cmd=%d\n",cmd);
+ }
+#endif
+ if(index>=USHM_SIZE) {
+ fprintf(stderr, "bad index=%d\n",index);
+ fail=1;
+ }
+
+ if(fail) {
+#ifndef FAKEDATA
+ close(cfd);
+#endif
+ continue;
+ }
+
+ count_login++;
+ processlogin(cfd, uid, index);
+ if(count_login>=4000 || time(NULL)-begin_time>30*60)
+ showstat();
+#ifndef FAKEDATA
+ close(cfd);
+#endif
+ }
+#ifdef FAKEDATA
+ fclose(fp);
+#endif
+ return 0;
+}
diff --git a/pttbbs/cacheserver/utmpserver3.c b/pttbbs/cacheserver/utmpserver3.c
new file mode 100644
index 00000000..e6bcb621
--- /dev/null
+++ b/pttbbs/cacheserver/utmpserver3.c
@@ -0,0 +1,341 @@
+/* $Id$ */
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <event.h>
+
+#include "bbs.h"
+
+extern void utmplogin(int uid, int index, const int like[MAX_FRIEND], const int hate[MAX_REJECT]);
+extern int genfriendlist(int uid, int index, ocfs_t *fs, int maxfs);
+extern void utmplogoutall(void);
+#ifdef UTMPLOG
+FILE *logfp;
+#endif
+
+clock_t begin_clock;
+time_t begin_time;
+int count_flooding, count_login;
+
+#ifdef NOFLOODING
+/* 0 ok, 1 delay action, 2 reject */
+int action_frequently(int uid)
+{
+ int i;
+ time_t now = time(NULL);
+ time_t minute = now/60;
+ time_t hour = minute/60;
+
+ static time_t flood_base_minute;
+ static time_t flood_base_hour;
+ static struct {
+ unsigned short lastlogin; // truncated time_t
+ unsigned char minute_count;
+ unsigned char hour_count;
+ } flooding[MAX_USERS];
+
+ if(minute!=flood_base_minute) {
+ for(i=0; i<MAX_USERS; i++)
+ flooding[i].minute_count=0;
+ flood_base_minute=minute;
+ }
+ if(hour!=flood_base_hour) {
+ for(i=0; i<MAX_USERS; i++)
+ flooding[i].hour_count=0;
+ flood_base_hour=hour;
+ }
+
+ if(abs(flooding[uid].lastlogin-(unsigned short)now)<=3 ||
+ flooding[uid].minute_count>30 ||
+ flooding[uid].hour_count>60) {
+ count_flooding++;
+ return 2;
+ }
+
+ flooding[uid].minute_count++;
+ flooding[uid].hour_count++;
+ flooding[uid].lastlogin=now;
+
+ if(flooding[uid].minute_count>5 ||
+ flooding[uid].hour_count>20) {
+ count_flooding++;
+ return 1;
+ }
+ return 0;
+}
+#endif /* NOFLOODING */
+
+void syncutmp(int cfd) {
+ int i;
+ int like[MAX_FRIEND];
+ int hate[MAX_REJECT];
+
+#ifdef UTMPLOG
+ int x=-1;
+ if(logfp && ftell(logfp)> 500*(1<<20)) {
+ fclose(logfp);
+ logfp=NULL;
+ }
+ if(logfp) fwrite(&x, sizeof(x), 1, logfp);
+#endif
+
+ printf("logout all\n");
+ utmplogoutall();
+ fprintf(stderr,"sync begin\n");
+ for(i=0; i<USHM_SIZE; i++) {
+ int uid;
+ if( toread(cfd, &uid, sizeof(uid)) <= 0 ||
+ toread(cfd, like, sizeof(like)) <= 0 ||
+ toread(cfd, hate, sizeof(hate)) <= 0)
+ break;
+#ifdef UTMPLOG
+ if(logfp) {
+ fwrite(&uid, sizeof(uid), 1, logfp);
+ fwrite(like, sizeof(like), 1, logfp);
+ fwrite(hate, sizeof(hate), 1, logfp);
+ }
+#endif
+
+ if(uid != 0)
+ utmplogin(uid, i, like, hate);
+ }
+ if(i<USHM_SIZE) {
+#ifdef UTMPLOG
+ int x=-2;
+ if(logfp) fwrite(&x, sizeof(x), 1, logfp);
+#endif
+ }
+
+ fprintf(stderr,"sync end\n");
+}
+
+struct client_state {
+ struct event ev;
+ int state;
+ struct evbuffer *evb;
+};
+
+void processlogin(struct client_state *cs, int uid, int index)
+{
+ int like[MAX_FRIEND];
+ int hate[MAX_REJECT];
+ /* logout 銝虫 utmpserver , 航賣亙唬鈭
+ 撌脩 logout 撣唾隞乩賢芸 MAX_FRIEND 閬憭銝鈭 */
+#define MAX_FS (2 * MAX_FRIEND)
+ int res;
+ int nfs;
+ ocfs_t fs[MAX_FS];
+
+ evbuffer_remove(cs->evb, like, sizeof(like));
+ evbuffer_remove(cs->evb, hate, sizeof(hate));
+#ifdef UTMPLOG
+ if(logfp) {
+ int x=-3;
+ fwrite(&x, sizeof(x), 1, logfp);
+ fwrite(&uid, sizeof(uid), 1, logfp);
+ fwrite(&index, sizeof(index), 1, logfp);
+ fwrite(like, sizeof(like), 1, logfp);
+ fwrite(hate, sizeof(hate), 1, logfp);
+ }
+#endif
+
+ utmplogin(uid, index, like, hate);
+ nfs=genfriendlist(uid, index, fs, MAX_FS);
+ res=0;
+#ifdef NOFLOODING
+ res=action_frequently(uid);
+#endif
+ evbuffer_drain(cs->evb, 2147483647);
+ evbuffer_add(cs->evb, &res, sizeof(res));
+ evbuffer_add(cs->evb, &nfs, sizeof(nfs));
+ evbuffer_add(cs->evb, fs, sizeof(ocfs_t) * nfs);
+}
+
+void showstat(void)
+{
+ clock_t now_clock=clock();
+ time_t now_time=time(0);
+
+ time_t used_time=now_time-begin_time;
+ clock_t used_clock=now_clock-begin_clock;
+
+ printf("%.24s : real %.0f cpu %.2f : %d login %d flood, %.2f login/sec, %.2f%% load.\n",
+ ctime(&now_time), (double)used_time, (double)used_clock/CLOCKS_PER_SEC,
+ count_login, count_flooding,
+ (double)count_login/used_time, (double)used_clock/CLOCKS_PER_SEC/used_time*100);
+
+ begin_time=now_time;
+ begin_clock=now_clock;
+ count_login=0;
+ count_flooding=0;
+}
+
+enum {
+ FSM_ENTER,
+ FSM_SYNC,
+ FSM_LOGIN,
+ FSM_WRITEBACK,
+ FSM_EXIT
+};
+
+static int firstsync=0;
+
+struct timeval tv = {5, 0};
+struct event ev;
+int clients = 0;
+
+#define READ_BLOCK 1024
+#define MAX_CLIENTS 10
+
+void connection_client(int cfd, short event, void *arg)
+{
+ struct client_state *cs = arg;
+ int cmd, break_out = 0;
+ int uid = 0, index = 0;
+
+ // ignore clients that timeout
+ if (event & EV_TIMEOUT)
+ cs->state = FSM_EXIT;
+
+ if (event & EV_READ) {
+ if (cs->state != FSM_ENTER) {
+ if (evbuffer_read(cs->evb, cfd, READ_BLOCK) < 0)
+ cs->state = FSM_EXIT;
+ }
+ else {
+ if (evbuffer_read(cs->evb, cfd, 4) < 0)
+ cs->state = FSM_EXIT;
+ }
+ }
+
+ while (!break_out) {
+ switch (cs->state) {
+ case FSM_ENTER:
+ if (EVBUFFER_LENGTH(cs->evb) < sizeof(int)) {
+ break_out = 1;
+ break;
+ }
+ evbuffer_remove(cs->evb, &cmd, sizeof(cmd));
+ if (cmd == -1)
+ cs->state = FSM_SYNC;
+ else if (cmd == -2) {
+ fcntl(cfd, F_SETFL, fcntl(cfd, F_GETFL, 0) | O_NONBLOCK);
+ cs->state = FSM_LOGIN;
+ }
+ else if (cmd >= 0)
+ cs->state = FSM_EXIT;
+ else {
+ printf("unknown cmd=%d\n",cmd);
+ cs->state = FSM_EXIT;
+ }
+ break;
+ case FSM_SYNC:
+ syncutmp(cfd);
+ firstsync = 1;
+ cs->state = FSM_EXIT;
+ break;
+ case FSM_LOGIN:
+ if (firstsync) {
+ if (EVBUFFER_LENGTH(cs->evb) < (2 + MAX_FRIEND + MAX_REJECT) * sizeof(int)) {
+ break_out = 1;
+ break;
+ }
+ evbuffer_remove(cs->evb, &index, sizeof(index));
+ evbuffer_remove(cs->evb, &uid, sizeof(uid));
+ if (index >= USHM_SIZE) {
+ fprintf(stderr, "bad index=%d\n", index);
+ cs->state = FSM_EXIT;
+ break;
+ }
+ count_login++;
+ processlogin(cs, uid, index);
+ if (count_login >= 4000 || (time(NULL) - begin_time) > 30*60)
+ showstat();
+ cs->state = FSM_WRITEBACK;
+ break_out = 1;
+ }
+ else
+ cs->state = FSM_EXIT;
+ break;
+ case FSM_WRITEBACK:
+ if (event & EV_WRITE)
+ if (evbuffer_write(cs->evb, cfd) <= 0 && EVBUFFER_LENGTH(cs->evb) > 0)
+ break_out = 1;
+ if (EVBUFFER_LENGTH(cs->evb) == 0)
+ cs->state = FSM_EXIT;
+ break;
+ case FSM_EXIT:
+ if (clients == MAX_CLIENTS)
+ event_add(&ev, NULL);
+ close(cfd);
+ evbuffer_free(cs->evb);
+ free(cs);
+ clients--;
+ return;
+ break;
+ }
+ }
+ if (cs->state == FSM_WRITEBACK)
+ event_set(&cs->ev, cfd, EV_WRITE, (void *) connection_client, cs);
+ event_add(&cs->ev, &tv);
+}
+
+void connection_accept(int fd, short event, void *arg)
+{
+ struct sockaddr_in clientaddr;
+ socklen_t len = sizeof(clientaddr);
+ int cfd;
+
+ if ((cfd = accept(fd, (struct sockaddr *)&clientaddr, &len)) < 0 )
+ return;
+
+ struct client_state *cs = calloc(1, sizeof(struct client_state));
+ cs->state = FSM_ENTER;
+ cs->evb = evbuffer_new();
+
+ event_set(&cs->ev, cfd, EV_READ, (void *) connection_client, cs);
+ event_add(&cs->ev, &tv);
+ clients++;
+
+ if (clients >= MAX_CLIENTS) {
+ event_del(&ev);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int ch, port = 5120, sfd;
+ char *iface_ip = NULL;
+
+#ifdef UTMPLOG
+ logfp = fopen("utmp.log","a");
+ if(logfp && ftell(logfp)> 500*(1<<20)) {
+ fclose(logfp);
+ logfp=NULL;
+ }
+#endif
+
+ Signal(SIGPIPE, SIG_IGN);
+ while( (ch = getopt(argc, argv, "p:i:h")) != -1 )
+ switch( ch ){
+ case 'p':
+ port = atoi(optarg);
+ break;
+ case 'i':
+ iface_ip = optarg;
+ break;
+ case 'h':
+ default:
+ fprintf(stderr, "usage: utmpserver [-i interface_ip] [-p port]\n");
+ return 1;
+ }
+
+ if( (sfd = tobind(iface_ip, port)) < 0 )
+ return 1;
+
+ event_init();
+ event_set(&ev, sfd, EV_READ | EV_PERSIST, connection_accept, &ev);
+ event_add(&ev, NULL);
+ event_dispatch();
+ return 0;
+}
diff --git a/pttbbs/cacheserver/utmpsync.c b/pttbbs/cacheserver/utmpsync.c
new file mode 100644
index 00000000..69d1c623
--- /dev/null
+++ b/pttbbs/cacheserver/utmpsync.c
@@ -0,0 +1,27 @@
+/* $Id$ */
+#include "bbs.h"
+#include <err.h>
+
+extern SHM_t *SHM;
+
+int main(int argc, char **argv)
+{
+ int sfd, index, i;
+ attach_SHM();
+ if( (sfd = toconnect(OUTTACACHEHOST, OUTTACACHEPORT)) < 0 ) {
+ printf("connect fail\n");
+ return 1;
+ }
+
+ index = -1;
+ towrite(sfd, &index, sizeof(index));
+ for( i = 0 ; i < USHM_SIZE ; ++i )
+ if( towrite(sfd, &SHM->uinfo[i].uid, sizeof(SHM->uinfo[i].uid)) < 0 ||
+ towrite(sfd, SHM->uinfo[i].myfriend,
+ sizeof(SHM->uinfo[i].myfriend)) < 0 ||
+ towrite(sfd, SHM->uinfo[i].reject,
+ sizeof(SHM->uinfo[i].reject)) < 0 ){
+ fprintf(stderr, "sync error %d\n", i);
+ }
+ return 0;
+}
diff --git a/pttbbs/docs/ADVANCE b/pttbbs/docs/ADVANCE
new file mode 100644
index 00000000..6c40d53a
--- /dev/null
+++ b/pttbbs/docs/ADVANCE
@@ -0,0 +1,66 @@
+我們在這個版本裡面加入了一些好玩的東西,
+最主要是希望可以偷懶做一些事情.
+
+bbsctl
+------
+bbsctl是一個很特別的程式, 安裝有點複雜.
+ 1.先用 bbsadm 的權限在 util/下 make bbsctl
+ 2.改成 root 的權限在 util/下 make installbbsctl
+如此會把 bbsctl 安裝到 $(BBSHOME)/bin/bbsctl下.
+我們會建議您將 bbsctl 所在目錄加進您的 path 裡面,
+或是建立 symbolic link把 bbsctl 放到一個有設 path 的目錄
+ (像是 /usr/local/bin 將是一個符合 FreeBSD程式置放路徑的目錄)
+您可以用 ln -s $(BBSHOME)/bbs/bbsctl /usr/local/bin/bbsctl
+ (其中 $(BBSHOME) 請用您所設的 bbs路徑代替, 如 /home/bbs)
+如此您將可以在任何一個目錄下使用 bbsctl
+
+
+!!請注意, bbsctl 是有 root setuid的, 這可能會有安全上的問題!!
+
+
+其中 bbsctl 設計是只有該帳號有加入 bbsadm 這個 group的人才能使用.
+ (關於如何設定 group請見 FAQ)
+您應該至少要將 bbs和 bbsadm 這兩個帳號加入 bbsadm 這個 group
+
+
+您可以直接打 bbsctl 來看有什麼功能.
+主要的功能說明如下:
+1.bbsctl start
+ 用於啟動 mbbsd (須先跑 shmctl init)
+ 以往因為要 bind port 23 須要 root 所以要手動 su ,
+ 不過現在有 bbsctl with setuid ,
+ 請直接使用 bbsctl start , 即可將 mbbsd跑起來.
+2.bbsctl stop
+ 會將所有正在 listen 的 mbbsd都砍掉
+3.bbsctl restart
+ 即 bbsctl stop; bbsctl start
+ 主要是如果程式更新的話, 可以透過這個來直接重跑.
+4.bbsctl bbsadm
+ 若該使用者有加入 bbsadm 群組, 則同於直接 su 成 bbsadm
+ (不須密碼)
+ 如此您可以將某些有管理權限的使用者,
+ 透過 /etc/group 放進 bbsadm 的群組,
+ 讓她們在 login後可以直接用 bbsctl bbsadm來換成 bbsadm 的權限.
+
+
+
+shmctl
+------
+這隻程式主要是外部用來輔助 shared-memory正常運作的.
+1.shmctl utmpfix
+ 將 shared-memory中 utmp 不正確的 record 清掉.
+ 建議至少每小時跑一次.
+ 另外可以用這個來外部設定 idle 多久後就踢人.
+
+Makefile of mbbsd/
+------------------
+1.make all
+ 自動加上 -O 的最佳化參數
+2.make DEBUG=1
+ 一般用來 debug的情況下會用到,
+ 會開啟一些 DEBUG的程式碼,
+ gcc不用 -O 而改加上 -g ,
+ menu title 顯示目前的 pid,
+ 以方便用 gdb來 attach.
+3.make NO_FORK=1
+ 這通常都是很特別的情況才會用到. \ No newline at end of file
diff --git a/pttbbs/docs/ANCESTOR b/pttbbs/docs/ANCESTOR
new file mode 100644
index 00000000..1683c8fb
--- /dev/null
+++ b/pttbbs/docs/ANCESTOR
@@ -0,0 +1,24 @@
+Pirate Bulletin Board System Version: 1.00
+Copyright (C) 1990 Edward A. Luke
+
+Eagles Bulletin Board System Version: 2.00-3.00
+Copyright (C) 1992, 1993, 1994 Raymond R. Rocker, Dominic B. Tynes
+ Guy T. Vega
+
+Phoenix Bulletin Board System Version: 3.00-4.0
+Copyright (C) 1993, 1994 Ming-Feng Chen, Ji-Tzay Yang
+ Tsun-Yi Wen
+
+秘密情人資訊站 Version: 3.1-4.0
+Copyright (C) 1994 簡顯鑑, 劉佳峰
+
+MapleBridge Bulletin Board System Version: 2.36
+Copyright (C) 1994-1995 Jeng-Hermes Lin, Hung-Pin Chen
+ SoC, Xshadow
+
+SunOfBeach Bulletin Board System Version: 0.22
+Copyleft ($) 1996 Kaede, woju
+
+Ptt Bulletin Board System Ptt, Jaky, Action, Heat, Dunk,
+批踢踢實業坊 CharlieL, DickG, DavidYu,
+ in2, kcwu, victor \ No newline at end of file
diff --git a/pttbbs/docs/CHANGE b/pttbbs/docs/CHANGE
new file mode 100644
index 00000000..2ddb6869
--- /dev/null
+++ b/pttbbs/docs/CHANGE
@@ -0,0 +1,76 @@
+Version 1.0.2 2001/07/17
+
+* 新增 util/merge_passwd
+* 新增 util/merge_board
+* 修正 load fromcache 超過 300 行會 segfault 的 bug
+* 修正來源字串在 FreeBSD 下會有亂碼的問題
+* 修正 mbbsd 中 select() 不好的用法
+* 修正 more.c 中的安全問題 (by kcwu)
+
+--------------------------------------------------
+Version 1.0.1 2001/04/26
+
+* 將文件搬至 docs 目錄下
+* 修改 config.h 中的 MAX_ACTIVE, MAX_CPULOAD
+ 並加入 FORCE_PROCESS_REGISTER_FORM 的選項
+* 新增修復遺失看板的功能 (press 'R')
+* 修正訊息的錯誤
+* 修正幽靈水球
+* 移除在使用者名單按'N'修改暱稱的功能, 該功能可能有bug存在
+* 新增 util/shmsweep
+
+--------------------------------------------------
+Version 1.0.0 2000/09/16
+
+* 修正 Select 看板的 bug
+* 新增 Reaper, 刪除過期帳號
+* 新增 chickens 到 sample 中
+* 新增 initbbs 的功能: 初始化動態看板, 範本精靈
+* 新增 FAQ
+
+--------------------------------------------------
+Version 0.9.7 2000/08/31
+
+* 新增 tunepasswd
+* 修正 innbbsd 的 bug
+* xchatd 運作正常
+* 整合 buildir
+* 修正 Makefile 在某些 Linux 上無法正常工作的 bug
+* 在 sample 的目錄下打 make install 即可自動安裝 sample 設定檔
+
+--------------------------------------------------
+Version 0.9.6 2000/06/28
+
+* 修正使用暫存檔會當掉的 bug
+* 整合 FreeBSD 和 Linux 的 Makefile, 不需要再手動修改
+* 把 pttbbs.conf 從 include 搬到 pttsrc 下
+* 新增 outmail 取代 bmda
+* 整合 xchatd (未測試)
+* 整合 innbbsd (未測試)
+
+--------------------------------------------------
+Version 0.9.5 2000/06/07
+
+* 修正在 Linux 無法 talk 的 bug
+* 如果看板列表是空的的話, 自動進入開板功能
+* 新增 initbbs 工具程式
+* 新增 BBSHOME environment variable 的使用 (見 INSTALL)
+* 搬移部份設定到 include/pttbbs.conf
+* 修正在FreeBSD上無法取得 swapinfo 的 bug
+
+--------------------------------------------------
+Version 0.9.4 2000/06/03
+
+* 把程式中有用到 ncurses 的地方移除, 不再需要 -ltermcap, -lncurses
+* 修正一個造成無窮迴圈的 Bug
+* 移除以使用者名稱搜尋全站文章的功能
+
+--------------------------------------------------
+Version 0.9.3 2000/05/23
+
+* 修正在 Linux 上 Makefile 的問題
+
+--------------------------------------------------
+Version 0.9.2 2000/05/20
+
+* port 到 Linux 上
diff --git a/pttbbs/docs/DONATE b/pttbbs/docs/DONATE
new file mode 100644
index 00000000..a37c1d06
--- /dev/null
+++ b/pttbbs/docs/DONATE
@@ -0,0 +1,27 @@
+這篇文章描述關於贊助 PttBBS . 文章的版號及最後編修時間是:
+$Id$
+
+Donate to PttBBS!
+ PttBBS 是免費自由軟體, 我們透過這套系統(正如您手上這一份), 在台大
+資訊系裡面架起了批踢踢實業坊以及批踢踢兔, 直到今日 Ptt及 Ptt2 引起
+不錯的回響, 而 Ptt更在日前站上了同時服務一萬人的里程碑, 而這份服務
+也是完全不收取任何費用的.
+我們希望所有的使用者, 不論您是直接連上 Ptt/Ptt2 , 或是透過本系統自
+行架設站台, 都可以享受在其中; 事實上, 我們花了許多時間, 精力, 甚至
+金錢, 用在研發改善, 以及提供服務. 而這一份系統就含有超過三萬行的程
+式碼, 為了要讓 Ptt/Ptt2 可以營運, 就用掉超過十顆 SCSI 硬碟, 再加上
+無數人的心血.
+
+對於我們的貢獻, 或許有人願意向我們說聲感謝, 而這邊有一個方便的方式
+, 可以讓您表達對我們的謝意.
+
+我們歡迎您提供贊助給我們, 不論是提供金錢上的贊助, 或是設備上的贊助
+ (我們建議是贊助設備, 因為錢總是帶來麻煩的事情 :( ) . 而您的贊助我
+們將全數使用在 Ptt/Ptt2 上面.
+我們主要須要多數的硬碟以提供存放使用者資料, 由於 SCSI 的硬碟得以同
+時讀寫, 越多顆硬碟可以分散原來的負荷. 我們須要的規格是,
+Ultra SCSI 160 (或 320) , 68 pins , 4MB 或以上的 cache, 10000rpm
+或以上, 容量 18GB 或以上. 或者是我們希望可以整個更新機器, 須要
+ cpu Xeon 2.0G以上 1~2 , 記憶體 6GB或以上.
+
+詳細的情況, 請和當時的系統站長聯絡^^
diff --git a/pttbbs/docs/FAQ b/pttbbs/docs/FAQ
new file mode 100644
index 00000000..9b3bbfcd
--- /dev/null
+++ b/pttbbs/docs/FAQ
@@ -0,0 +1,165 @@
+這篇文章描述 PttBBS 中常被問到的問題及解決方式. 文章的版號及最後編修時間是:
+$Id$
+
+ 1. PttBBS的討論區
+ 2. 在 Linux底下出現 Makefile 錯誤.
+ 3. 加大 shared-memory
+ 4. 設定 group
+ 5. 五子棋盤沒有出現
+ 6. sendmail.cf要改的地方(參考用) (by DavidYu)
+ 7. /usr/libexec/elf/ld: cannot find -liconv
+ 或 iconv.h: No such file or directory
+ 8. /usr/libexec/elf/ld: cannot find -lhz
+ 9. 如何讓用 ssh方式進 bbs不用密碼
+------------------------------------------------------------------------------
+1.PttBBS的討論區
+位於批踢踢實業坊 (telnet://ptt.cc) 的 PttCurrent 看板.
+
+------------------------------------------------------------------------------
+2.在 Linux底下出現 Makefile 錯誤.
+
+使用 Linux, 在 compile的時候出如 Makefile:20: *** missing separator. Stop.
+請安裝 pmake, 並將 make alias 成 pmake .
+如: apt-get install pmake
+ alias make pmake
+
+注意: 如果你 pmake 看到 "Makefile", line 9: Missing dependency operator
+是因為裝了 pmake 2.x (Debian 目前裝的是 1.9x, 而 Mandrake 就是用 2.x)。
+PMake 2.x 把 .if 改成 #if 的格式,這會造成舊的檔案都失效,而且雖然有提供
+-Z . 可用 . 代替 # ,但 system.mk 等系統檔仍為 # 所以會爛。
+
+PttBBS 會不會改成 PMake v2 的格式呢? 因為 BSD 系統都沒變,所以可能不會改。
+
+解決方法有兩種
+(1) 請裝 pmake 1.x ,簡單直接的作法
+(2) 不過有時候你就是不幸要用 pmake 2.x,所以有個小程式可以幫你忙。
+ 在 pttbbs/ 目錄下執行 util/pmakev2.sh
+ 它會自動轉換成 PMake v2 的格式。
+
+------------------------------------------------------------------------------
+3.加大 shared-memory
+請在 /etc/sysctl.conf 裡面加入
+in FreeBSD:
+ kern.ipc.shmmax=104857600
+ kern.ipc.shmall=25600
+in Linux:
+ kernel.shmmax=104857600
+ kernel.shmall=25600
+這兩個數值可視須要調整,
+在重新開機後會生效.
+
+------------------------------------------------------------------------------
+4.設定 group
+以 FreeBSD為例, 您須要更動 /etc/group 這個檔案,
+ /etc/group 的語法是:
+ group:passwd:gid:member
+
+ group是該 group的名字,
+ passwd一般情況下不設, 以 *代替,
+ gid 為一個十進位的數字, 最好不要和其他的重複,
+ member為哪些人有在該群組內, 以逗號 (,)隔開.
+詳細用法請見group(5)
+例如您要將 aaa, bbb 加入 bbsadm 這個 group, 您可能會用:
+ bbsadm:*:9876:aaa,bbb
+在設定完成後, 原來已經登入的使用者須要重新登入,
+才會重新載入至新設的 group內.
+您可以參考 group(5)
+
+------------------------------------------------------------------------------
+5.五子棋盤沒有出現
+請將 sample/etc/@five 拷貝到 ~bbs/etc/ 即可.
+
+------------------------------------------------------------------------------
+6. sendmail.cf要改的地方(參考用) (by DavidYu)
+
+要注意,tab和space不能混用
+
+######################################
+### Ruleset 0 -- Parse Address ###
+######################################
+S0
+
+R$* $: $>Parse0 $1 initial parsing
+R<@> $#local $: <@> special case error msgs
+R$* $: $>98 $1 handle local hacks
+R$+.bbs < @ $=w .> $#bbsmail $: $1 bbs mail gateway
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+R$* $: $>Parse1 $1 final parsing
+
+......
+
+# handle locally delivered names
+R$+.bbs $#bbsmail $:$1 bbs mail gateway
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+R$=L $#local $: @ $1 special local names
+R$+ $#local $: $1 regular local names
+
+###########################################################################
+### Ruleset 5 -- special rewriting after aliases have been expanded ###
+###########################################################################
+
+......
+
+##################################################
+### Local and Program Mailer specification ###
+##################################################
+
+##### @(#)local.m4 8.30 (Berkeley) 6/30/1998 #####
+
+Mlocal, P=/usr/libexec/mail.local, F=lsDFMAw5:/|@qSXfmnz9P, S=10/30, R=
+ T=DNS/RFC822/X-Unix,
+ A=mail.local -l
+Mprog, P=/bin/sh, F=lsDFMoqeu9, S=10/30, R=20/40, D=$z:/,
+ T=X-Unix,
+ A=sh -c $u
+Mbbsmail, P=/home/bbs/bin/bbsmail, F=lsSDFMhPu, U=bbs, S=10,R=20/40,
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ A=bbsmail $u
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+------------------------------------------------------------------------------
+7./usr/libexec/elf/ld: cannot find -liconv
+ 或 iconv.h: No such file or directory
+因為新的 innbbsd中加入了 RFC 2045 support, 須要 libiconv .
+您須先要有安裝 libiconv (/usr/ports/converters/libiconv/ in FreeBSD)
+再重新 make 即可
+
+若您已經安裝了 libiconv 不過還是找不到,
+請將該 lib所在的 path (/usr/local/lib in FreeBSD)
+加到 innbbsd/Makefile 的 LDFLAGS 中 (line 43)
+ LDFLAGS+= -liconv
+改成:
+ LDFLAGS+= -liconv -L/usr/local/lib
+
+在 Linux 還境下 libiconv 被包含在 libc 中, 所以 -liconv 是不需要的
+請將 innbbsd/Makefile 的 LDFLAGS (line 43)
+ LDFLAGS+= -liconv
+刪除
+
+------------------------------------------------------------------------------
+8./usr/libexec/elf/ld: cannot find -lhz
+Ptt 支援繁體中文轉簡體中文與 UTF-8 的功能
+若您開啟了這個選項 (#define CONVERT)
+請安裝 autoconvert
+ (/usr/ports/chinese/autoconvert in FreeBSD
+ package libhz0 in Debian/Linux )
+
+若不想開啟此選項 請修改 pttbbs.mk (line 10)
+ PTT_LIBS= -lcrypt -lhz
+改成:
+ PTT_LIBS= -lcrypt
+
+------------------------------------------------------------------------------
+ 9. 如何讓用 ssh方式進 bbs不用密碼
+先在 /etc/ssh/sshd_config 中把
+ #PermitEmptyPasswords no
+改成
+ PermitEmptyPasswords yes
+
+若您使用 FreeBSD 4.x, 請改 /etc/pam.conf, 在
+sshd auth required pam_unix.so try_first_pass
+這行最後面再加上 nullok
+
+若您使用 FreeBSD 5.x, 請改 /etc/pam.d/sshd, 在
+auth required pam_unix.so no_warn try_first_pass
+這行最後面再加上 nullok
diff --git a/pttbbs/docs/FAQ.old b/pttbbs/docs/FAQ.old
new file mode 100644
index 00000000..bc2904a0
--- /dev/null
+++ b/pttbbs/docs/FAQ.old
@@ -0,0 +1,146 @@
+ 作者 DavidYu (^^Y) 看板 PttSrc
+ 標題 Re: 安裝完連線時.....
+ 時間 Thu May 18 15:08:04 2000
+───────────────────────────────────────
+
+※ 引述《JamesCheng (網球比排球好玩啦!)》之銘言:
+: 我在安裝完之後 用root執行 mbbsd 23
+: 連線看出現
+: Escape character is '^]'.
+: 【物治樂園】◎ 物治第二站 ◎(pt.mc.ntu.edu.tw)
+: 調幅(140.112.122.44) 系統負荷 0.00 0.01 0.00
+: Connection closed by foreign host.
+: 然後系統出現下列訊息
+: [shmget error] key = 8ab
+: errno = 12: Cannot allocate memory
+: 這是何原因呢?
+shared memory要加大
+在kernel config file中加一行
+options SHMMAXPGS=4100
+重新make kernel
+
+--
+※ 發信站: 批踢踢實業坊(ptt.twbbs.org)
+◆ From: oio.m6.ntu.edu.t
+
+ 作者 DavidYu (Do it YOURSELF!) 看板 PttSrc
+ 標題 Re: 請問一下...
+ 時間 Sat May 27 18:41:39 2000
+───────────────────────────────────────
+
+※ 引述《clifflu ( 煩呀煩呀煩~~~)》之銘言:
+: ※ 引述《hscat (復站了!)》之銘言:
+: : 不過我有一個小小的問題
+: : 能不能指導一下...想要開新的版應該從哪邊開呢?
+: 主選單下的 (0)Admin ??
+: : 因為我實在找不到怎麼開新看板...:~~~~...
+不不不..
+開板要從分類看板的地方開,這部份有點小小的複雜,
+和以往的版本不一樣,我來簡單說明一下:
+
+首先介紹看板.
+看板有兩種, 一般看板和群組看板. 如果用檔案系統的架構來看的話,
+一般看板如同 file, 而群組看板就是 directory
+(在所有看板列表的地方只會出現一般看板)
+
+一個看板有UID及GID這兩個屬性. UID是unique的,從1開始分配,系統在開板時會
+菾佧llocate一個. 而GID是指定這個看板在哪一個群組看板之下(看板是樹狀架構的)
+所以如果有一個群組看板的UID是x, 則GID為x的看板都會擺在該群組看板之下
+而如果看板GID為0(保留的UID)的話,該看板就會出現在一開始從主選單進CLASS的
+那個地方
+
+至於開板的方法.
+首先從CLASS進到你想要開新板的類別下,然後按大B設定
+如此新開的板就會自動assign GID 到那個類別下
+
+如果要般動一個已經開好的板到其他類別的話
+先找出那個類別(群組看板)的UID, 按大E可以看到
+然後把要搬動的板的GID設成那個UID及可
+
+值得注意的是, 如果把某個使用者設成某群組看板的板主的話
+則當他從CLASS進到那個群組時會獲得小組長的權限(可以開板,設定看板等等)
+要特別小心
+
+比較麻煩的地方:
+一個新開的群組裡面並沒有任何看板,所以無法從選單進去裡面開板
+這時候就需要在可開板的地方先開個板,然後再把那個板的GID設過去即可
+(這也是為什麼需要一個initial的.BOARDS的原因 :p)
+
+--
+※ 發信站: 批踢踢實業坊(ptt.twbbs.org)
+◆ From: meow.cc.ntu.edu.
+
+ 作者 DavidYu (軱~~~~~~~~~~) 看板 PttSrc
+ 標題 sendmail.cf要改的地方(參考用)
+ 時間 Sun Jul 9 09:39:53 2000
+───────────────────────────────────────
+
+有顏色的是要加上去的
+要注意,tab和space不能混用
+
+######################################
+### Ruleset 0 -- Parse Address ###
+######################################
+S0
+
+R$* $: $>Parse0 $1 initial parsing
+R<@> $#local $: <@> special case error msgs
+R$* $: $>98 $1 handle local hacks
+R$+.bbs < @ $=w .> $#bbsmail $: $1 bbs mail gateway
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+R$* $: $>Parse1 $1 final parsing
+
+......
+
+# handle locally delivered names
+R$+.bbs $#bbsmail $:$1 bbs mail gateway
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+R$=L $#local $: @ $1 special local names
+R$+ $#local $: $1 regular local names
+
+###########################################################################
+### Ruleset 5 -- special rewriting after aliases have been expanded ###
+###########################################################################
+
+......
+
+##################################################
+### Local and Program Mailer specification ###
+##################################################
+
+##### @(#)local.m4 8.30 (Berkeley) 6/30/1998 #####
+
+Mlocal, P=/usr/libexec/mail.local, F=lsDFMAw5:/|@qSXfmnz9P, S=10/30, R=
+ T=DNS/RFC822/X-Unix,
+ A=mail.local -l
+Mprog, P=/bin/sh, F=lsDFMoqeu9, S=10/30, R=20/40, D=$z:/,
+ T=X-Unix,
+ A=sh -c $u
+Mbbsmail, P=/home/bbs/bin/bbsmail, F=lsSDFMhPu, U=bbs, S=10,R=20/40,
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ A=bbsmail $u
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+--
+※ 發信站: 批踢踢實業坊(ptt.twbbs.org)
+◆ From: oio.m6.ntu.edu.t
+
+ 作者 DavidYu (鴰~~~~~~~~~~~~~~) 看板 PttSrc
+ 標題 [anno] tunepasswd
+ 時間 Sat Jul 29 09:18:06 2000
+───────────────────────────────────────
+
+新增了一個 tunepasswd 的程式
+
+如果你在 .PASSWD 建立後又改過了 MAX_USERS
+
+你必須要執行 tunepasswd 一次來修改 .PASSWD 檔的大小
+
+執行的時候不可以有任何BBS的程式在執行
+
+shared memory 也應該要清除, 否則會有不預期的錯誤發生
+
+執行 tunepasswd 會產生一個原來的備份檔 .PASSWD~
+
+--
+※ 發信站: 批踢踢實業坊(ptt.twbbs.org)
+◆ From: oio.m6.ntu.edu.t
diff --git a/pttbbs/docs/INSTALL b/pttbbs/docs/INSTALL
new file mode 100644
index 00000000..83f87c2f
--- /dev/null
+++ b/pttbbs/docs/INSTALL
@@ -0,0 +1,128 @@
+這篇文件說明快速安裝的方法, 文章的版號及最後編修時間是:
+$Id$
+
+==============
+以 root 的權限
+==============
+
+ 1. 打 vipw, 加入底下兩行
+
+ bbs::9999:99::0:0:Ptt BBS:/home/bbs:/home/bbs/bin/bbsrf
+ bbsadm::9999:99::0:0:Ptt BBS:/home/bbs:/bin/csh
+
+ 2. 執行 passwd bbsadm 設 bbsadm的密碼
+ 3. 在 /etc/group 中加入一行
+
+ bbs:*:99:
+
+ 4. 執行 mkdir /home/bbs
+ 5. 執行 chown bbs:bbs /home/bbs
+ 6. 執行 chmod 700 /home/bbs
+
+====================
+以下用 bbsadm 的身份
+====================
+
+ 7. 您可以透過下列方式下載到本站的原始程式碼:
+ a.置於 https://OpenSVN.csie.org/pttbbs/trunk/
+ 這是目前主要開發的版本, 也是批踢踢實業坊和批踢踢兔兩個站台
+ 所使用的版本. 在這個版本中含有最新的程式, 但是可能會不穩定,
+ 有 bug, 甚至和之前的版本不相容. 建議您有基礎的程式能力, 並
+ 隨時注意 PttCurrent 看板 (在 telnet://ptt.cc中, 或可以使用
+ 網頁版本 http://www.ptt.cc/bbs/PttCurrent/index.html) , 尤
+ 其當您在更新程式的時候.
+ a)至 http://fs.ptt.cc 抓得每日自動製作的 snapshot ,
+ 通常檔名長的會像是 pttbbs-DATE.tar.gz
+ 再解開該檔即可: tar zxvf pttbbs-DATE.tar.gz
+
+ b)直接抓取 http://OpenSVN.csie.org/pttbbs/trunk/pttbbs下的所有資料.
+ 如 wget -r -np http://OpenSVN.csie.org/pttbbs/trunk/pttbbs
+
+ c)透過 subversion 聯繫 pttbbs version control repository(註4)
+ 再執行 svn checkout http://OpenSVN.csie.org/pttbbs/trunk/pttbbs
+
+ 其中不論您以方式 a, b 取得源碼, 您都可以在該目錄下直接透過 svn update
+ 更新至 pttbbs 最新的源碼. 如:
+ cd /home/bbs/pttbbs; svn update
+ (須先安裝 subversion, 見註4)
+
+ 8. 切換到 pttbbs 的目錄下 ( cd ~bbs/pttbbs )
+ 9. 如果您的 pttbbs.conf並不存在, 可以拷一份預設的來用:
+ cp sample/pttbbs.conf pttbbs.conf
+ 10.依據您的須求, 修改 pttbbs.conf
+ 請注意, 預設的 pttbbs.conf是給相當大規模的 bbs用的.
+ 通常您須要進行修改以符合您的須求.
+
+ * 如果您是用 Linux系統, 請先安裝 pmake, 然後將 make alias 成 pmake *
+ 11.在編譯 pttbbs 之前, 您需要先裝好 libiconv, libhz, pgp
+ 如果您使用的是 FreeBSD, 可以直接透過 ports安裝:
+ cd /usr/ports/converters/libiconv; make install
+ cd /usr/ports/chinese/autoconvert; make install
+ cd /usr/ports/security/pgp; make install
+ 12. (在 ~bbs/pttbbs 下) 執行 make BBSHOME=/home/bbs all install
+ 13.如果是新架起來的站, 請執行 cd sample; make install
+ 14.執行 cd /home/bbs; bin/initbbs -DoIt
+
+其中第 13 以及 14 步是給第一次安裝的時候使用的,
+如果您的 BBS中已經有資料了, 請務必不要執行這兩個步驟.
+假如一切都很順利的話, 這樣子大概就安裝完了, 接下來是啟動 bbs 的部份.
+
+ 15.執行 bin/shmctl init (*註1, *註2)
+ 16.用 root 執行 bin/mbbsd 23 (註2)
+
+ 17.測試整個系統是否完成:
+ telnet localhost 23 看看
+ new 一個帳號叫SYSOP, 然後 logout再 login, 這樣子就會擁有站長權限囉~
+ 再 new一個帳號叫 guest , 這樣子別人就可以用 guest 參觀你的站了
+ 開板, 開群組, 以及搬動群組的方法請看 PttSrc 板的文摘
+ 18.新功能請參考 ADVANCE
+ 19.為了讓開機的時候會自己把 bbs (以及所須要的程式) 跑起來,
+ 若您使用 FreeBSD, 您可以將 sample/pttbbs.sh 拷貝到 /usr/local/etc/rc.d/
+ 之後您可以重新開機試試看是不是在重開後可以正常運作 :)
+ 20. pttbbs 須要定時執行一些小 tool 來維持系統正常運作,
+ reload cache, 備份, 特殊外部程式等等.
+ 請用 bbs的權限, 執行 crontab -e , 內容請參照 sample/crontab
+ (在大部份的情況下, 直接將 sample/crontab 內容拷備進去即可)
+ 21.在某些情況下, pttbbs會須要一些其他的程式來輔助.
+ 下面列出所須要的程式, 預設的路徑, 以及在 FreeBSD ports內對映的路徑
+ tar /usr/bin/tar FreeBSD default install
+ gzip /usr/bin/gzip FreeBSD default install
+ rm /bin/rm FreeBSD default install
+ perl /usr/bin/perl /usr/ports/lang/perl5.8/
+ mutt /usr/local/bin/mutt /usr/ports/mail/mutt/
+ lynx /usr/local/bin/lynx /usr/ports/www/lynx/
+
+ 我們大部份的 perl script會用到 LocalVars.pm , 該檔案內負責定義
+ 各別機器的設定 (如外部程式的路徑) . 請將該檔拷份一份至 ~/bin/
+ 下, 並修正該檔以符合貴環境的設定.
+
+ 另外列出我們 perl script會用到的 perl module, 以 FreeBSD ports相對目錄.
+ Mail::Sender /usr/ports/mail/p5-Mail-Sender/
+ IO::All /usr/ports/devel/p5-IO-All/
+ 22.若您有安裝 Blog 相關功能, 您需要先產生一個預設的 Blog 目錄
+ 您可以參考批踢踢兔(telnet://ptt2.cc), Blog看板下的 Blog.Default 目錄,
+ 並且拷貝一份或透過 symbolic link的方式放到 etc/Blog.Default.
+ 同時將 sample/README.BLOG 拷貝到 etc/ 下.
+
+註:
+1. 這個程式是在 initial shared memory 用的, 只有開機後第一次執行, 或是
+ 你手動清除 shm 後才需要執行
+
+2. 如果您跑了 shmctl init 或 uhash_loader 並沒有看到
+ total XXXXX names loaded.
+ 而是看到其他的錯誤訊息
+ (例如 [shmget error] key = 4cc, errno = 22: Invalid argument)
+ 可能是您作業系統 shared-memory 上限不足,
+ 請參考 FAQ中加大 shared-memory 部份的說明.
+
+3. bin/mbbsd 23 是指定要 bind 23 port, 依照 UNIX 規定, 1024 以下的 port
+ 須要有 root 權限, 所以如果要 bind 23 port 的話就要用 root 去執行,
+ 3000 port 則不需要.
+ 請參考 docs/ADVANCE , 安裝好 bbsctl ,
+ 之後您就可以透過 bbsctl 直接在其他權限下 (如 bbsadm )
+ 來啟動可以 bind port 23 的 mbbsd.
+4. 您必須先安裝 subversion (請參考官方網站 http://subversion.tigris.org/ )
+ 在 FreeBSD底下, 直接使用 ports 安裝即可:
+ cd /usr/ports/devel/subversion/; make all install
+ 其他作業系統請參考 http://subversion.tigris.org/project_packages.html
+ 或相關文件.
diff --git a/pttbbs/docs/brc.txt b/pttbbs/docs/brc.txt
new file mode 100644
index 00000000..315a1fbc
--- /dev/null
+++ b/pttbbs/docs/brc.txt
@@ -0,0 +1,126 @@
+
+ BRC documentation by scw 08/05/2003
+ 06/12/2007 revised by kcwu
+
+源起:
+ 這篇文章主要是介紹 brc_* 的函式,這組函式是 pttbbs 用來紀錄文章已讀/未讀
+ 的工具,但因為內部的儲存方式十分 tricky 連帶使的內容相當難懂。為了重現及修正
+ 其中的一個 bug,筆者有幸弄清了其中運作方式,並為其撰寫說明,希望對管理者有幫
+ 助。
+
+什麼人該看這篇文章?
+ 1. pttbbs 的系統管理者。如果您要對這部份進行修改或抓蟲,希望這篇文章能對
+ 您有所助益。
+ 2. 想要研究這種用極少空間記下極大資訊的方法的人。
+
+BRC 是什麼?如何運作?
+ brc_* 是定義在 pttbbs/mbbsd/board.c 中的一組函式,負責紀錄文章已讀/未讀,
+ 它的特點是用的空間極少。可以在 24k 以內的空間記下一個人在全站的文章已讀/
+ 未讀。當然,這樣的方法不可能真正完美,但是對於使用上已經足夠了。為什麼說是不
+ 完美呢?這跟紀錄的儲存方式有關。
+ 紀錄檔在 home/[first charactor of id]/[id]/.brc2。檔案格式如下:
+
+ FILE := RECORDS ;
+ RECORDS := RECORDS RECORD | ;
+ RECORD := BRC_BID BRC_DATA ;
+ BRC_DATA := BRC_NUM BRC_LIST ;
+ BRC_LIST := NUM NUM ... NUM ; (共 BRC_NUM 個 NUM)
+ BRC_BID 是 board bid, sizeof(brcbid_t)=2 bytes.
+ BRC_NUM 是對這個板的儲存量,sizeof(brcnbrd_t)=2 bytes 以 binary 方式儲存,其值 <= MAX_NUM (80)
+ BRC_LIST 是對這個板的紀錄,剛好有 BRC_NUM 個 sizeof(time4_t)=4 bytes integers。
+ 另外在 24576 bytes (#define BRC_MAXSIZE 24576) 之外的資料不會被用到。
+
+ 在下面會看到,BRC_BID 跟 BRC_NUM 跟 BRC_LIST 都會放在相應的變數中, brc_currbid & brc_num & brc_list 。
+ 判定一個檔案是否已經讀過的方法是在 brc_list 中搜尋檔案建立的時間,也就是
+ 檔名 M.xxxxxxxxxx.A.yyy 中 xxxxxxxxx 的那個數字。如果這個數字有在 brc_list 中
+ 出現就是已讀,要不如果 brc_list 中所有的數字都比這個檔案的建立時間大(也就是
+ 這個檔案的建立時間在所有 brc_list 中的時間點之前)也是已讀,最後為了節省空間
+ 還有一個判定(其實這個判定是第一個做的),如果檔案建立時間在 login 時間的一年
+ 之前,一律是已讀。
+ 這樣可以看出為什麼這個方法不是真正完美但是已經足夠。不完美的原因有三個:
+ 首先, brc_num <= 80 也就是 brc_list 最多存八十個數,這表示除了很久以前的文章
+ 外,只會有八十篇是已讀的。第二就是所有一年前的文章都會被判為已讀。最後,如果一
+ 個人看的板太多,讓 .brc2 大小超過 BRC_MAXSIZE 有些板的紀錄就會不見( 24576
+ bytes 最少可以存 73 個板的資料,這還是用全部板 brc_num 都是 80 計算的)。但這
+ 三個小缺點影響應該不大吧?
+
+BRC 實作
+
+ interface: (in proto.h)
+
+ int brc_initialize();
+ void brc_finalize();
+
+ int brc_unread(int bid, char *fname, int bnum, int *blist);
+ 判斷一篇文章是否已讀。
+ 傳入值:文章檔名 (fname) 以及 brc_num (bnum) 和 brc_list (blist)。
+ 傳回值:如果由 bnum 和 blist 判斷本篇文章未讀傳回 1。
+ 否則傳回 0。
+ 額外效果:無。
+
+ int brc_initial_board(char *boardname);
+ 初始化在一個板的已讀未讀狀態。
+ 傳入值:要初始化的板名。
+ 傳回值:若找到之前的紀錄傳回新的 brc_num,否則傳回 0。
+ 額外效果:如果傳入的看板就是目前看板會直接傳回 brc_num, 不做別的事。否則
+ 本函式會先將目前的 brc data 寫回 brc_buf 中,更改 currboard ,取得
+ currbid 和 currbrdattr 後再讀取並更新 brc_num 及 brc_list。如果在使用者
+ 的 brc_buf 中沒有關於這個板的紀錄,會設定 brc_num = 1,brc_list[0] = 1
+ 並傳回 0。
+
+ void brc_update();
+ 將目前的 brc data 寫入 brc_buf 中。
+ 額外效果:如果 brc data 未被更改或使用者權限不足則不會有動作。
+
+ void brc_addlist(char *fname);
+ 將文章標示為已讀。使用前需先 brc_initial_board()
+ 傳入值:要標示為已讀的文章檔名。
+
+ constant definition:
+
+ #define BRC_MAXSIZE 24576
+ .brc2 的有效大小。
+
+ #define BRC_MAXNUM 80
+ brc_num 的最大值。
+
+ private variables: (in board.c)
+
+ static time_t brc_expire_time;
+ brc_list 中值的下限,時間在此之前的一律當作已讀。會在 init_brdbuf 中被設
+ 定為 login_start_time - 365 * 86400。
+
+ static char brc_buf[BRC_MAXSIZE];
+ 呼叫 read_brc_buf 後 .brc2 的前 BRC_MAXSIZE bytes 會被置入這個 buffer 中。
+
+ static int brc_size;
+ 呼叫 read_brc_buf 後 brc_buf 中的有效字元數。
+
+ static int brc_changed = 0;
+ 從上次讀取 .brc2 到當時為止,brc_num 與 brc_list 是否改變過。
+
+ static int brc_currbid;
+
+ static int brc_num;
+ brc_list 中的有效數字個數。
+
+ static int brc_list[BRC_MAXNUM];
+ 已讀文章的存檔時間。
+
+
+ static void read_brc_buf();
+ 從 .brc2 中讀取最多 BRC_MAXSIZE bytes 並存入 brc_buf 中,將存入的字元
+ 數存在 brc_size 中。
+
+ static char * brc_putrecord(char *ptr, char *endp, brcbid_t bid, brcnbrd_t num, const time4_t *list);
+ 與 brc_getrecord() 的作用正好相反,將資料寫入 puffer 中。
+ 傳入值:ptr 指向要寫入的 buffer,bid, num, list 分別是要寫入的資料。
+ 傳回值:指向寫入的 record 下一個字元的指標。
+ 額外效果:若資料是合法的 (num > 0 && list[0] > brc_expire_time) 且空間足夠,
+ 資料會被寫入 ptr, endp 之間。
+
+ static int brc_unread_time(time_t ftime, int bnum, int *blist);
+ 跟 brc_unread() 類似,只是傳入的是檔案建立的時間。
+ 傳入值:文章的建立時間 (ftime) 及 brc_num (bnum) 和 brc_list (blist)。
+ 傳回值:如果由 bnum 和 blist 判斷本篇文章未讀傳回 1。
+ 否則傳回 0。
diff --git a/pttbbs/docs/fav.txt b/pttbbs/docs/fav.txt
new file mode 100644
index 00000000..32a616cc
--- /dev/null
+++ b/pttbbs/docs/fav.txt
@@ -0,0 +1,41 @@
+Favorite ver.4
+
+Feature
+=======
+•重寫、整個架構改變
+•folding
+
+Structure
+=========
+ fav4 的主要架構如下:
+
+•fav_t
+ 進入我的最愛時,看到的東西就是根據 fav_t 生出來的。
+ 裡面紀錄者,這一個 level 中有多少個看板、目錄、分隔線。(favh)
+
+•fav_type_t
+ 這算是架在以下三個東西之上的介面,等於是將他們視為同一種東西,方便之後的存取。
+ 用一個 void * 指標指向某塊記憶體,存取時可透過 type 變數來得知正確的型態。
+
+•fav_board_t
+ 紀錄了 bid 及上次拜訪時間。
+
+•fav_line_t
+ 紀錄了 lid
+
+•fav_folder_t
+ 紀錄了 fid 及可自訂的名稱。
+
+•fav.c 中以 cast_(board|line|folder)_t() 來將一個 fav_type_t 轉為正確的型態。
+
+Policy
+======
+•為了避免過度的資料搬移,當將一個 item 從我的最愛中移除時,只將他的 FAVH_FAV
+ flag 移除。而沒有這個 flag 的 item 也不被視為我的最愛。
+
+•我的最愛中,沒設 FAVH_FAV 的資料,將在某些時候,如寫入檔案時,呼叫
+ rebuild_fav 清除乾淨。
+
+•站長搬移看板所用的 t ,因為不能只存在 nbrd 裡面,又不然再弄出額外的空間,
+ 所以當站長不在我的最愛按了 t ,會把這個記錄暫存在 fav 中(FAVH_ADM_TAG == 1,
+ FAVH_FAV == 0)。
diff --git a/pttbbs/docs/keeploc_t.txt b/pttbbs/docs/keeploc_t.txt
new file mode 100644
index 00000000..fc98a2db
--- /dev/null
+++ b/pttbbs/docs/keeploc_t.txt
@@ -0,0 +1,22 @@
+typedef struct keeploc_t {
+ char *key;
+ int top_ln;
+ int crs_ln;
+ struct keeploc_t *next;
+} keeploc_t;
+
+keeploc_t 是一個 linked list,紀錄不同的 key(字串)所對應到的游標位置。
+
+
+
+keeploc_t *getkeep(char *s, int def_topline, int def_cursline);
+
+ 給定一個字串 s 當 key(不存在則新增),傳回對應的 keeploc_t。
+
+ 1.如果 def_cursline >= 0,表示要從目前的 keeplist 中找出 key s 所對應到的 keeploc_t 並傳回。
+ 如果該筆記錄的 curse position 不合法,則會先設為 1。沒找到則依下面的動作
+ 新增。
+
+ 2.如果 def_cursline < 0,代表要新增一筆記錄。
+ 將 def_cursline 取絕對值。然後把參數中的 def_* 填入新記錄中。
+ 傳回這筆新記錄。
diff --git a/pttbbs/docs/proto/Makefile b/pttbbs/docs/proto/Makefile
new file mode 100644
index 00000000..63086b38
--- /dev/null
+++ b/pttbbs/docs/proto/Makefile
@@ -0,0 +1,18 @@
+
+#.SUFFIXES: .c .txt
+
+# XXX 瘝瘥頛憟賜雿瘜?
+# 湔亦 .c.txt 憟賢餌桀桅瘝 xxx.c @_@
+
+NAME!= cd ../../mbbsd/ && ls *.c | sed -e "s/\.c//"
+SRC!= cd ../../mbbsd/ && ls *.c | sed -e "s/\.c/.txt/"
+
+all: $(SRC)
+
+.for fn in $(NAME)
+$(fn).txt: ../../mbbsd/$(fn).c
+ ./cdoc ../../mbbsd/$(fn).c > $(fn).txt
+.endfor
+
+clean:
+ rm -f *.txt
diff --git a/pttbbs/docs/proto/README b/pttbbs/docs/proto/README
new file mode 100644
index 00000000..8b7d2b58
--- /dev/null
+++ b/pttbbs/docs/proto/README
@@ -0,0 +1,24 @@
+Introduction
+============
+
+這個程式可以從 C 的程式碼中,列出所有 statis/non-static 的 functions。
+
+每個 function 前面可以有 javadoc-style 的註解。範例如下:
+
+/**
+ * Function of the function func.
+ * @param x variable, if there are more than one @-style description, it
+ * will end with the next @-style description.
+ * @return void
+ */
+void func(int x)
+{
+ ...
+}
+
+如果有註解之後沒有接 function 的話,會原封不動丟出來。
+
+Usage
+=====
+ make 產生 documents
+ make clean 清除 documents
diff --git a/pttbbs/docs/proto/cdoc b/pttbbs/docs/proto/cdoc
new file mode 100755
index 00000000..60f29a50
--- /dev/null
+++ b/pttbbs/docs/proto/cdoc
@@ -0,0 +1,133 @@
+#!/usr/bin/perl
+
+# follow javadoc
+# @see http://java.sun.com/j2se/javadoc/writingdoccomments/
+
+use strict;
+
+=cut
+
+這個程式可以從 C 的程式碼中,列出所有 statis/non-static 的 functions。
+
+每個 function 前面可以有 javadoc-style 的註解。範例如下:
+
+/**
+ * Function of the function func.
+ * @param x variable, if there are more than one @-style description, it
+ * will end with the next @-style description.
+ * @return void
+ */
+void func(int x)
+{
+ ...
+}
+
+如果有註解之後沒有接 function 的話,會原封不動丟出來。
+
+=cut
+
+my $content;
+
+foreach my $f (@ARGV) {
+ makedoc($f);
+}
+
+
+sub grep_desc
+{
+ my @buffer = ();
+ my $name = '\b\w+\b';
+ my $type = '\b (?: (?:struct|unsigned) \s+)? \w+\b (?: \s*\*\s* | \s+)';
+ my $sentence = '.*';
+
+ my $one_desc = "$name\\s+$sentence";
+ my $desc_head = "\\/ \\* \\* \n";
+ my $desc_tail = "\\s* \\* \\/ \n";
+ my $desc_line = "\\s* \\* .* \n";
+ my $paramdesc = "\@param\\s+ $one_desc (?:\n$desc_line)*";
+ my $returndesc = "\@return\\s+ $sentence (?:\n$desc_line)*";
+ my $seedesc = "\@see\\s+ $sentence (?:\n$desc_line)*";
+ my $desc = "$desc_head(?:$desc_line)*$desc_tail";
+
+ my $modifier = '(?: static | inline)\s+';
+ my $one_param = "$type \\s* $name";
+ my $more_param = ",\\s* $one_param";
+ my $param = "(?: $one_param(?:$more_param)* | void )?";
+ my $func_proto = "(?:$modifier)* $type \\s* $name\\($param\\)";
+
+ my $pattern = "(?: ($desc)|($func_proto)[\n\\s]*{ )";
+ my $out;
+
+ if ($content =~ s/^([.\n]*)$pattern//mox) {
+ $1 and push @buffer, {type => 'garbage', data => undef};
+ if ($2) {
+ $out = $2;
+ $out =~ s#^/\*\*##;
+ $out =~ s#\s*\*/$##;
+ $out =~ s/^\s*\* ?/ /mg;
+ push @buffer, { type => 'comment', data => $out };
+ }
+ elsif ($3) {
+ $out = $3;
+ $out =~ s/\n/ /g;
+ $out =~ s/\s+/ /g;
+ $out .= ";\n";
+ push @buffer, { type => 'function', data => $out };
+ }
+ else {
+ die;
+ }
+ }
+ return @buffer;
+}
+
+sub makedoc
+{
+ my $file = shift @_;
+ open SRC, "<$file";
+ $content = join "",<SRC>;
+ close SRC;
+
+ # just to break them up to avoid vim's misunderstanding
+ print "// vim".":ft=c\n\n";
+
+ $content =~ s#/\*[^*].*?\*/##sg;
+ my @buffer = ();
+
+ while (my @b = grep_desc()) {
+ push @buffer, @b;
+ shift @buffer while ($buffer[0]->{type} eq 'garbage');
+ last unless @buffer;
+
+ if (@buffer == 3) {
+ if ($buffer[1]->{type} eq 'garbage') {
+ $_ = shift @buffer;
+ shift @buffer;
+ unshift @buffer, $_;
+ }
+ }
+ if (@buffer == 2) {
+ if ($buffer[0]->{type} eq 'function') {
+ $_ = shift @buffer;
+ print $/, $_->{data};
+ }
+ elsif ($buffer[0]->{type} eq 'comment') {
+ if ($buffer[1]->{type} eq 'comment') {
+ $_ = shift @buffer;
+ print $_->{data}, $/;
+ }
+ else {
+ print $/, $buffer[1]->{data}, $buffer[0]->{data}, $/;
+ undef @buffer;
+ }
+ }
+ }
+ if (@buffer == 1) {
+ if ($buffer[0]->{type} eq 'function') {
+ $_ = shift @buffer;
+ print $/, $_->{data};
+ }
+ }
+
+ }
+}
diff --git a/pttbbs/docs/z6ibbs.1.txt b/pttbbs/docs/z6ibbs.1.txt
new file mode 100644
index 00000000..6ff5f0d6
--- /dev/null
+++ b/pttbbs/docs/z6ibbs.1.txt
@@ -0,0 +1,122 @@
+作者: in2 (中秋玉兔 *^^*) 看板: in2
+標題: [隨意] 關於架設一個 z6ibbs (硬體篇)
+時間: Mon Oct 21 14:17:31 2002
+
+唔, 只是隨手寫寫東西,
+如果下面有提到任何價格那都是僅供參考,
+並不代表妳可以用這個價格買到東西
+ (當然妳可能買到比較便宜就是了 A_AY )
+所有的價格以 k為單位, 即中文的 仟 .
+
+也許我會拿 Ptt/Ptt2 來做些例子 ;p
+當然我並不會告訴妳現在到底是用什麼 :P
+不過也許我會偷偷告訴妳一些好玩的東西 ;p
+
+第一個先說到 CPU.
+就一般的情況來說, 只要程式寫的有一點好
+ (尤其是 userlist 不要惡搞的話)
+通常不會要到很高,
+一般的 PIII-class 等級應該就是可以動了,
+當然妳可以考慮用 Xeon幾顆, 再打開 hyper-threading....
+ Xeon 的價格差不多是 10k (2.4G, socket 603)
+據說醬子跑 setiathome 相當快 A_AY
+事實上我並不會告訴妳 Ptt是用什麼樣的 CPU,
+不過我可以偷偷告訴妳,
+我們不過用了 PIII-class 某某兩顆,
+ clock並沒有很快, 據不知道可不可靠的消息,
+她們是不到 1G Hz的 :X
+
+說到 memory 的話, 就是大家心中的痛了 Q_Q
+就我所知道目前沒有在怕記憶體的,
+只有小旭旭長輩的 天火BBS .
+就我手邊的兩台機器, 都幾乎是卡死在 ram不夠.
+並且, 一般的 motherboard都不能支援到很高的 ram,
+或是她雖然可以支援到很高, 可是她的 bank 數並不夠.
+像有些 motherboard雖然可以插到 4G ,
+但是只有 4 banks. 也就是妳須要一條 1G 的 ram x 4才能達到.
+再者, 到夠高級的 motherboard通常要求 ram要是 registered 的.
+有 registered 的 ram比一般用的 non-registered 還要貴的多.
+前陣子問到的是 512MB 5~7k , 1GB 一條要 13k.
+這真的是:
+ 平平是 ram, 成份不同, 效果也不同 XD
+以我們自己估算的話,
+差不多 1G 可以撐到 2k+人 (同時在線上)
+當然實際值比這個還要再多一點,
+我覺得再上去都已經是讓系統死命的在跑,
+我自然是不會告訴妳現在 Ptt是用多少記憶體在跑,
+不過可以偷偷告訴妳, 事實上和妳偷猜的應該差不多, 或者是少點多點.
+
+大概考慮了 cpu和 ram後, 就可以來找一塊合適的 motherboard了.
+個人滿喜歡 tyan 的 (http://www.tyan.com)
+ (呃, 好像有點打廣告 ^^")
+ tyan 有生產不少高級板子 (請往 Thunder系列走)
+像是可以插至少兩顆 processor(s), dual-LAN, SCSI onboard,
+價格差不多都是至少 15k, 一般所用的 chipset應該 FreeBSD都有 support .
+當然, 妳最好在刷卡前確認好價錢以及是不是有支援.
+請去買一張樂透以及把板子拿去換另一個型號或退貨,
+如果妳運氣好到買的那張剛剛好是 FreeBSD沒有支援的.
+ tyan 的高級板, 像是這一張:
+ http://www.tyan.com/products/html/thundergche.html
+Intel Xeon MP x 4, MAX 24GB DDR ram, dual LAN, SCSI onboard ....
+motherboard 算 20k, CPU 一顆算 10k, 再加 24GB DDR算 13k/GB
+差不多 400k 左右就可以買的到全套,
+這應該是叫她送一個好機殼外加一大堆風扇, power supply(s).
+買了這張之後, 一打 top看到的前 8個 process(es) 應該都是 setiathome A_AY
+當然, 我並打算要告訴妳我們現在用哪一張,
+也不會偷偷告訴妳我們現在用哪一張 -_-!#
+
+硬碟算是 bbs滿主要的一個地方.
+這個時候就要套 D某, 某K 大大的一句話:
+ IDE 硬碟是什麼? 沒聽過... 拿去丟掉吧 -_-
+是的, SCSI 硬碟是最佳的選擇,
+ SCSI 硬碟沒有幾家有在做, 所以也不用急 ;p
+價格差不多是 9G 6k, 18G 8k, 32G 16k 左右.
+就我自己的經驗, 硬碟通常不用很大, 不過越多越好~ ;p
+因為 SCSI 硬碟基本上各顆間可以平行存取,
+所以越多顆理論上可以越快一點,
+而一般來說 bbs都會卡在 io 速度不夠快.
+IDE 硬碟則是拿來開機和備份的好東西,
+便宜又大碗, 反正壞了就丟, 也不怎麼貴 -_-!#
+或許有人會很有錢的用 hardware-raid A_AY,
+又或是用 vinum把她整個弄成一大塊 ;p
+不過個人還是習慣切很多塊自己搞 ;x
+ (事實上是因為沒錢買硬碟 raid :X )
+ vinum好像又出過一些小問題, 所以就自己惡搞了 ;o
+另外一般情況, 市面上只能買到可以接四個 SCSI 裝置的排線.
+雖然一個 SCSI bus 理論上可以接 15 (還是 16 ? ) 個 SCSI 裝置,
+不過超過四個的話就要訂做了.
+如果有人去訂一條可以插十五個裝置線的話, 麻煩借我看一下 A_AY
+我自然也不會告訴妳 Ptt現在有幾顆硬碟在上面,
+不過可以偷偷告訴妳, 在今年內, 最少有四顆, 最多有九顆硬碟同時在上面.
+
+其他剩下的就是很次要的了,
+像是要準備很大的殼子以塞進這麼多硬碟 @_@;;;
+另外裝置一多的時候, 一個小小的 power可能會不夠力,
+於是也許妳要用兩個 (以上) 的 power supply .
+幸運的, tyan 有不少 motherboard都可以插兩個 power supply上去.
+另外還要注意散熱, 事實上當買完上面那些東西,
+風扇大概都可以直接叫她送了~ ;p
+比較要緊的是也許要為硬碟加個風扇 ;d
+以免像某大一樣把硬碟測到燒掉 ;ppp
+另外, 有不少 motherboard都是直接音效和顯示內建的.
+自然我還是不打算告訴妳我們現在用的 onboard 音效和顯示卡到底是哪個 chip.
+不過我可以偷偷告訴妳,
+ 也許我哪天心情好的時候, 實驗室裡的人會聽到 ptt正在放 mp3 -__-!#
+
+我自然不會告訴妳現在 ptt or ptt2到底花掉了多少錢 @_@;;;
+不過我可以偷偷告訴妳:
+
+ 我好窮喔 >O<
+
+--
+  ▂▅▇▇▅   裺嘵○ 摃丐丐片    ▃ ◢◢ 
+  ▊ /    摃片裐朅 遉丐丐片歈 歈稙裐遉丐片  ◢ ▃  ██ 
+  ▎ ◤◢█◤  裺稙潁矙 聝丐丐片/裐歈朅裐═ ◥◤▌◢◢◢ 
+  ▌ ▎◣ ◢▎ 矙矙矙矙 ══煍片/ 矙僓朅禊歈屣片  ◢ ◣  
+  ◣        潁 ═嘵嘵 矙矙裺朅潁裺屣丑  _ '_ ' _  
+ ◢█\ ‾◤    裺潁  ◥ ▼ \◤ 
+
+--
+※ 發信站: 新批踢踢(ptt2.cc)
+◆ From: 140.112.30.143
+→ windsheep:好好笑喔...借我偷回家放..@_@s 推140.112.182.151 10/21
diff --git a/pttbbs/docs/z6ibbs.2.txt b/pttbbs/docs/z6ibbs.2.txt
new file mode 100644
index 00000000..1a704c6d
--- /dev/null
+++ b/pttbbs/docs/z6ibbs.2.txt
@@ -0,0 +1,159 @@
+作者: in2 (中秋玉兔 *^^*) 看板: in2
+標題: [隨意] 關於架設一個 z6ibbs (隨筆篇)
+時間: Tue Oct 22 21:59:12 2002
+
+只是剛剛好想到一些事情, 隨手寫了下來 ;d
+不過我想某些東西, 也許可以提供大家在某些地方上面用.
+
+我們先來說整個系統的架構.
+尤其像是 bbs這種不是很容易分散到多台機器跑的系統,
+ (也就是要用一台強力的機器硬撐的)
+最好要再多準備一台電腦,
+幫忙把一些其餘的服務分掉.
+像是郵件 (尤其加上擋信) , 轉信, make world 以及一些後面會描述到的東西.
+我們姑且將這台機器稱為後援機好了,
+他主要的目的只是想要幫前端機分掉一點事情,
+8像是郵件, 就由後援機去對外收 (並進行擋信) 以及替前端機發信.
+好處是, 信件並不是太須要即時 (妳大概不會要求按下 send 後 100ms內要送到)
+可是有的時候可能會花掉大量的資源,
+像是要擋信, 或是和對方網路上的問題一直重送,
+又或是 queue一大堆信件在硬碟裡面, 每過一些時候又一直要 retry一次.
+又或是妳大概不會想要在一台 load > 30的機器上 make world or ports
+於是我們在前端機旁加一台後援, 讓這種煩人的事情由後援機去做,
+於是前端機就可以更 focus在他的服務上.
+像這種情況, 兩台機器最好串在很近的地方,
+像是同一個 switch 上, 又或是直接用 cross-over 線互聯.
+
+要怎麼來備份一個足夠龐大又會不斷亂長的系統?
+我想應該不會有人想要把 bbs丟到 cvs裡面, 雖然 cvs很好用 :X
+bbs 一般可以考慮直接把她整個 tar + gz 起來,
+就算爆炸了要還原應該也不會算是什麼大困難.
+好像有某長輩是用 dump 的 @_@;;;;
+備份最好兩地備份,
+當然妳可以考慮直接備份在妳的後援機上,
+這決對會比妳備份在前端機要好的太多了.
+當然若妳可以再備份到另外一地 (像是備份到美國)
+醬子會大大增加爆炸時的損失.
+
+一般應該是會把 bbs架在 i386 上,
+自然用的是 FreeBSD或是 Solaris(i386)
+我當然不會告訴妳我們用的是什麼,
+不過據說我們頗崇尚 berkeley A_AY
+另外我還可以偷偷的告訴妳一些小道消息,
+像是據說某間很大的動物園, 她們好像也是用 FreeBSD ,
+某個喀喀城市, 用的也是 FreeBSD,
+另外有一個神祕據說叫做天火的 bbs, 用的是 Solaris(i386)
+曾經有位 I零 大大, 說他都直接跟客戶說:
+ Linux, no tech support
+因為他不會用 Linux, 然後如果人家硬要用的話,
+他就會拿 RedHat 光碟把他 install all :PP
+ (要用什麼是個人信仰, 請不要找我來吵架 ;p )
+
+在 FreeBSD分成三個主要的 branch(es): stable, release, current,
+除非妳心臟很夠力或很年輕或是夠大,
+一般情況下並不會拿 current的版本在正式的 server 上玩.
+雖然 current有些強力的功能, 不過正如 handbook 說的:
+21.2.1.3 What Is FreeBSD-CURRENT Not?
+1. A fast-track to getting pre-release bits because you heard there
+ is some cool new feature in there and you want to be the first on
+ your block to have it. Being the first on the block to get the new
+ feature means that you're the first on the block to get the new bugs.
+2. A quick way of getting bug fixes. Any given version of FreeBSD-CURRENT
+ is just as likely to introduce new bugs as to fix existing ones.
+ (引自 http://www.freebsd.org/doc/en_US.ISO8859-1/
+ books/handbook/current-stable.html )
+不過不管你用哪一個, 記得時時要注意一下,
+看看有沒有什麼 security hole又被爆破.
+通常 follow release 是比較保險的做法,
+不過這項事情好像在最近並不成立.
+我自然不會告訴妳我到底是不是偷偷昇級到 current了 A"A
+
+關於 security 上的問題,
+一般的通訊顯然應該要走 ssh一類的通道,
+而至於 telnetd, rsh 等等, 請直接拿去馬桶沖掉 @_@;;;;
+另外建議設簡單的 firewall ,
+在發生火災的時候會有比較高的存活率~ ;d
+通常至少會有
+ allow ip from any to any via lo0
+ deny ip from any to 127.0.0.0/8
+ deny ip from 127.0.0.0/8 to any
+就正常的情況下來說, 127.0.0.0/8 應該是不可能會從非 lo0來.
+而對於 < 1024 的 resvered port ,
+通常會只 allow幾個有開 service的 port , 其他全擋.
+像是:
+ allow tcp from any to me 22,23,25 in
+ deny tcp from any to me 1-1023 in
+若機器並不須要提供大家 ssh的 login
+ (像是妳的 bbs只開放 port 23, 並沒有開放由 ssh轉到 bbs)
+事實上可以考慮直接把 port 22 (ssh)設死在只有某個 ip 可以連進來,
+像是上面改成:
+ allow tcp from 140.112.30.142 to me 22 in
+ # 只給 140.112.30.142 連到本機 port 22
+ allow tcp from any to me 23,25 in
+ # 大家都可以連本機 port 23,25
+ deny tcp from any to me 1-1023 in
+ # 其他每個 port 的 tcp都不給連
+醬子就算 ssh暴出 security hole ,
+只要唯一(s) 可以連到的幾台機器都沒有被攻破,
+或是跟本就沒有人知道只有那一(s) 台電腦可以連到該台的 port 22 ,
+至少又比大家都可以連到妳的 ssh安全許多.
+
+我相信在一般情況下,
+或多或少妳都會碰到那種非到 console解決的事情不可,
+像是 fsck 失敗了, console提示要妳打入 root's passsword ,
+然後問妳 shell, 要妳手動 fsck ,
+通常發生這種 (或其他) 的情況的時候,
+網路並還沒有起來, 妳並是 ping 不到, 更別說要 ssh進去了.
+如果這個時候妳妳人就在機器旁的話自然還好,
+不過如果妳人不在機器旁 (ex妳在北京, 機器在台灣) 呢?
+我建議如果妳有這種考量 or 吃過大苦頭 or 不想吃大苦頭的,
+最好使用 serial console.
+ serial console 差不多是把 console的訊息,
+透過 serial (也就是 com port) 導到另外一台機器上 (假設接到後援機)
+啟動 serial console 是在相當早的,
+在 BIOS 等等開機程序完成把 loader 載進來後,
+就會把所有的 console訊息導到 serial 上
+ (這部份我並不是很確定, 若有錯誤請指正 ^^" )
+也就是, serial console 可以看到完整的開機畫面,
+就算是 kernel panic , 也可以在 serial console 進 kdb.
+於是, 一旦前端機 crash掉, 連網路都 ping 不到的時候,
+只要這個時候後援機還活著, 就可以透過網路連進後援機,
+透過 serial console 看看前端機目前的情況,
+在大多數的情況下, 透過 serial console 都可以把問題解決掉.
+ (當然, 如果像是硬碟爆炸, CPU燒掉的話, 顯然就沒辦法了 ^^" )
+我會建議在後援機開隻 screen 把前端機的 console ALWAYS attach.
+如此一旦出問題的話, 妳可以看到最後到底發生什麼事情.
+
+自然也許有朋友會有疑問, 如果後援機 crash的話怎麼辦 @"@
+唔, 我們當然只會在後援機上找外星人和做一些簡單的事情,
+一般的情況下醬子要把機器搞爆應該不是很容易的.
+當然妳可以把後援機的 console接到前端機上,
+醬如果後援機 crash而前端機沒有的話就可以用的到.....
+雖然這應該是很奇怪的事情 -____-!#
+
+如果 serial console 還是不能解決妳的問題的話,
+也許妳可以考慮接一個遠端 reboot 的裝置.
+我說的是妳可以下指令給後援機,
+當她去 short前端機 motherboard上的 reset pin(s)
+醬子當然是很暴力的做法,
+不過在某些情況下確實有醬子惡搞的必要就是了~ ;p
+我當然不會告訴妳我們到底有沒有遠端 reboot 的系統,
+不過我可以偷偷告訴妳, 我們有用 serial console.
+
+唔 @"@
+我當然不會把這篇寫的很好笑或是一點都一正經,
+不過我可以偷偷告訴妳,
+竟然有人說我上次寫的那篇很好玩 -_______________-!#
+
+--
+  ▂▅▇▇▅   裺嘵○ 摃丐丐片    ▃ ◢◢ 
+  ▊ /    摃片裐朅 遉丐丐片歈 歈稙裐遉丐片  ◢ ▃  ██ 
+  ▎ ◤◢█◤  裺稙潁矙 聝丐丐片/裐歈朅裐═ ◥◤▌◢◢◢ 
+  ▌ ▎◣ ◢▎ 矙矙矙矙 ══煍片/ 矙僓朅禊歈屣片  ◢ ◣  
+  ◣        潁 ═嘵嘵 矙矙裺朅潁裺屣丑  _ '_ ' _  
+ ◢█\ ‾◤    裺潁  ◥ ▼ \◤ 
+
+--
+※ 發信站: 新批踢踢(ptt2.cc)
+◆ From: 140.112.30.143
+→ windsheep:看到最後才發現好像比第一篇還好笑..^^ 推140.112.182.151 10/22
diff --git a/pttbbs/include/ansi.h b/pttbbs/include/ansi.h
new file mode 100644
index 00000000..faa3b0f8
--- /dev/null
+++ b/pttbbs/include/ansi.h
@@ -0,0 +1,31 @@
+/* $Id */
+#ifndef INCLUDE_ANSI_H
+#define INCLUDE_ANSI_H
+
+/* This is a more elegant style which is first introduced by pmore(piaip)
+ * and then promoted by Rong-en Fan (rafan). */
+
+/* to help pmore know that we've already included this. */
+#define PMORE_STYLE_ANSI
+
+// Escapes.
+#define ESC_NUM (0x1b)
+#define ESC_STR "\x1b"
+#define ESC_CHR '\x1b'
+
+// Common ANSI commands.
+#define ANSI_RESET ESC_STR "[m"
+#define ANSI_COLOR(x) ESC_STR "[" #x "m"
+#define ANSI_MOVETO(y,x) ESC_STR "[" #y ";" #x "H"
+#define ANSI_CLRTOEND ESC_STR "[K"
+
+#define ANSI_SAVEPOS ESC_STR "[s"
+#define ANSI_RESTOREPOS ESC_STR "[u"
+
+#define ANSI_IN_ESCAPE(x) (((x) >= '0' && (x) <= '9') || \
+ (x) == ';' || (x) == ',' || (x) == '[')
+
+#endif /* INCLUDE_ANSI_H */
+
+/* vim:sw=4
+ */
diff --git a/pttbbs/include/assess.h b/pttbbs/include/assess.h
new file mode 100644
index 00000000..f6963370
--- /dev/null
+++ b/pttbbs/include/assess.h
@@ -0,0 +1,6 @@
+#define SALE_MAXVALUE 255
+
+#define GOODPOST 1
+#define BADPOST 2
+#define GOODSALE 3
+#define BADSALE 4
diff --git a/pttbbs/include/bbs.h b/pttbbs/include/bbs.h
new file mode 100644
index 00000000..43352242
--- /dev/null
+++ b/pttbbs/include/bbs.h
@@ -0,0 +1,74 @@
+/* $id$ */
+
+#ifndef INCLUDE_BBS_H
+#define INCLUDE_BBS_H
+
+#include "osdep.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <errno.h>
+#include <netdb.h>
+#include <time.h>
+#include <ctype.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/telnet.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/mman.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <sys/sem.h>
+#include <sys/msg.h>
+
+/* our header */
+#include "config.h"
+#ifdef TIMET64
+typedef uint32_t time4_t;
+#else
+typedef time_t time4_t;
+#endif
+#include "ansi.h"
+#include "statistic.h"
+#include "pttstruct.h"
+#include "fav.h"
+#include "common.h"
+#include "perm.h"
+#include "modes.h"
+#include "chess.h"
+#include "proto.h"
+#include "fnv_hash.h"
+
+#ifdef ASSESS
+ #include "assess.h"
+#endif
+
+#ifdef CONVERT
+ #include "convert.h"
+#endif
+
+#ifdef _UTIL_C_
+ #include "util.h"
+#endif
+
+#ifndef INCLUDE_VAR_H
+ #include "var.h"
+#endif
+
+#endif /* INCLUDE_BBS_H */
diff --git a/pttbbs/include/chc.h b/pttbbs/include/chc.h
new file mode 100644
index 00000000..8b7fb23b
--- /dev/null
+++ b/pttbbs/include/chc.h
@@ -0,0 +1,21 @@
+
+#define CHE_O(c) ((c) >> 3)
+#define CHE_P(c) ((c) & 7)
+#define CHE(a, b) ((a) | ((b) << 3))
+/* TODO let user flip chessboard */
+#define REDDOWN(info) ((info)->myturn==RED)
+#define RTL(info, x) (REDDOWN(info)?((x)-3)/2:BRD_ROW-1-((x)-3)/2)
+#define CTL(info, x) (REDDOWN(info)?(x):BRD_COL-1-(x))
+#define LTR(info, x) (((REDDOWN(info)?(x):BRD_ROW-1-(x)) * 2) + 3)
+
+#define BLACK_COLOR ANSI_COLOR(1;36)
+#define RED_COLOR ANSI_COLOR(1;31)
+#define BLACK_REVERSE ANSI_COLOR(1;37;46)
+#define RED_REVERSE ANSI_COLOR(1;37;41)
+
+#define BRD_ROW 10
+#define BRD_COL 9
+
+typedef int board_t[BRD_ROW][BRD_COL];
+typedef int (*board_p)[BRD_COL];
+
diff --git a/pttbbs/include/chess.h b/pttbbs/include/chess.h
new file mode 100644
index 00000000..e1a50777
--- /dev/null
+++ b/pttbbs/include/chess.h
@@ -0,0 +1,201 @@
+/* $Id$ */
+
+#ifndef INCLUDE_CHESS_H
+#define INCLUDE_CHESS_H
+
+#define CHESS_DRAWING_TIME_ROW 128
+#define CHESS_DRAWING_TURN_ROW 129
+#define CHESS_DRAWING_WARN_ROW 130
+#define CHESS_DRAWING_STEP_ROW 131
+#define CHESS_DRAWING_HELP_ROW 132
+
+#define CHESS_PHOTO_LINE 15
+#define CHESS_PHOTO_COLUMN (256 + 25)
+
+struct ChessBroadcastList;
+struct ChessActions;
+struct ChessConstants;
+
+#define const
+#define static
+#define private
+
+typedef struct {
+ char userid[IDLEN + 1];
+ int win;
+ int lose;
+ int tie;
+ unsigned short rating;
+ unsigned short orig_rating; // 原始 rating, 因為遊戲開始先算輸一場, rating 值就跑掉了
+} ChessUser;
+
+private typedef struct {
+ int used;
+ int size;
+ void *body;
+} ChessHistory;
+
+/* 棋類觀戰
+ *
+ * 雙人對戰時,雙方都會有一個 broadcast_list 的 linked-list,紀錄著每下一
+ * 步棋,必須將這個訊息丟給那些人(sock)。
+ * 每當一個觀棋者加入(觀棋可以從紅方或黑方的觀點進行),其中一方的下棋者
+ * 的 broadcast_list 就會多一筆記錄,之後就會將下的或收到對方下的每一步棋
+ * 傳給 broadcast_list 中所有需要的人,達到觀棋的效果。
+ */
+private typedef struct ChessBroadcastListNode {
+ int sock;
+ struct ChessBroadcastListNode *next;
+} ChessBroadcastListNode;
+
+private typedef struct ChessBroadcastList {
+ struct ChessBroadcastListNode head; /* dummy node */
+} ChessBroadcastList;
+
+
+typedef struct {
+ int limit_hand;
+ int limit_time;
+ int free_time;
+ enum {
+ CHESS_TIMEMODE_MULTIHAND, /* 限時限步 */
+ CHESS_TIMEMODE_COUNTING /* 讀秒 */
+ } time_mode;
+} ChessTimeLimit;
+
+typedef enum {
+ CHESS_MODE_VERSUS, /* 對奕 */
+ CHESS_MODE_WATCH, /* 觀棋 */
+ CHESS_MODE_PERSONAL, /* 打譜 */
+ CHESS_MODE_REPLAY /* 看譜 */
+} ChessGameMode;
+
+typedef enum {
+ CHESS_RESULT_CONTINUE,
+ CHESS_RESULT_WIN,
+ CHESS_RESULT_LOST,
+ CHESS_RESULT_TIE,
+ CHESS_RESULT_END /* watching or replaying */
+} ChessGameResult;
+
+typedef struct ChessInfo {
+ private const static
+ struct ChessActions* actions; /* vtable */
+ private const static
+ struct ChessConstants* constants;
+
+ rc_t cursor;
+
+ /* 計時用, [0] = mine, [1] = his */
+ int lefttime[2];
+ int lefthand[2]; /* 限時限步時用, = 0 表為自由時間或非限時限步模式 */
+ const ChessTimeLimit* timelimit;
+
+ const ChessGameMode mode;
+ const ChessUser user1;
+ const ChessUser user2;
+ const char myturn; /* 我方顏色 */
+
+ char turn;
+ char pass[2];
+ char warnmsg[64];
+ char last_movestr[36];
+ char *photo;
+
+ void *board;
+ void *tag;
+
+ private int sock;
+ private ChessHistory history;
+ private ChessBroadcastList broadcast_list;
+ private ChessGameResult (*play_func[2])(struct ChessInfo* info);
+
+ private int current_step; /* used by watch and replay */
+ private char step_tmp[0];
+} ChessInfo;
+
+#undef const
+#undef static
+#undef private
+
+typedef struct ChessActions {
+ /* initial */
+ void (*init_user) (const userinfo_t* uinfo, ChessUser* user);
+ void (*init_user_rec)(const userec_t* uinfo, ChessUser* user);
+ void (*init_board) (void* board);
+
+ /* playing */
+ void (*drawline) (const ChessInfo* info, int line);
+ void (*movecur) (int r, int c);
+ int (*prepare_play)(ChessInfo* info);
+ int (*process_key) (ChessInfo* info, int key, ChessGameResult* result);
+ int (*select) (ChessInfo* info, rc_t location,
+ ChessGameResult* result);
+ void (*prepare_step)(ChessInfo* info, const void* step);
+ ChessGameResult (*apply_step)(void* board, const void* step);
+ void (*drawstep) (ChessInfo* info, const void* step);
+ ChessGameResult (*post_game)(ChessInfo* info);
+
+ /* ending */
+ void (*gameend) (ChessInfo* info, ChessGameResult result);
+ void (*genlog) (ChessInfo* info, FILE* fp, ChessGameResult result);
+} ChessActions;
+
+typedef struct ChessConstants {
+ int step_entry_size;
+ int traditional_timeout;
+ int board_height;
+ int board_width;
+ int pass_is_step;
+ const char *chess_name;
+ const char *photo_file_name;
+ const char *log_board;
+ const char *turn_color[2];
+ const char *turn_str[2];
+} ChessConstants;
+
+typedef enum {
+ CHESS_STEP_NORMAL, CHESS_STEP_PASS,
+ CHESS_STEP_DROP, CHESS_STEP_FAILURE,
+ CHESS_STEP_SPECIAL, /* chesses' special steps */
+ CHESS_STEP_NOP, /* for wake up */
+
+ CHESS_STEP_TIE, /* tie request */
+ CHESS_STEP_TIE_ACC, /* accept */
+ CHESS_STEP_TIE_REJ, /* reject */
+
+ CHESS_STEP_UNDO, /* undo request */
+ CHESS_STEP_UNDO_ACC, /* accept */
+ CHESS_STEP_UNDO_REJ, /* reject */
+} ChessStepType;
+
+
+int ChessTimeCountDown(ChessInfo* info, int who, int length);
+void ChessStepMade(ChessInfo* info, int who);
+
+ChessStepType ChessStepReceive(ChessInfo* info, void* step); /* should this be public? */
+int ChessStepSend(ChessInfo* info, const void* step);
+int ChessMessageSend(ChessInfo* info, ChessStepType type);
+
+void ChessHistoryAppend(ChessInfo* info, void* step);
+const void* ChessHistoryRetrieve(ChessInfo* info, int n);
+
+void ChessPlay(ChessInfo* info);
+int ChessStartGame(char func_char, int sig, const char* title);
+int ChessWatchGame(void (*play)(int, ChessGameMode),
+ int game, const char* title);
+int ChessReplayGame(const char* fname);
+
+ChessInfo* NewChessInfo(const ChessActions* actions,
+ const ChessConstants* constants, int sock, ChessGameMode mode);
+void DeleteChessInfo(ChessInfo* info);
+
+void ChessEstablishRequest(int sock);
+void ChessAcceptingRequest(int sock);
+void ChessShowRequest(void);
+
+void ChessRedraw(const ChessInfo* info);
+void ChessDrawLine(const ChessInfo* info, int line);
+void ChessDrawExtraInfo(const ChessInfo* info, int line, int space);
+
+#endif /* INCLUDE_CHESS_H */
diff --git a/pttbbs/include/common.h b/pttbbs/include/common.h
new file mode 100644
index 00000000..9e7d7b23
--- /dev/null
+++ b/pttbbs/include/common.h
@@ -0,0 +1,253 @@
+/* $Id$ */
+#ifndef INCLUDE_COMMON_H
+#define INCLUDE_COMMON_H
+
+#define STR_GUEST "guest"
+#define DEFAULT_BOARD str_sysop
+
+#define FN_PASSWD BBSHOME "/.PASSWDS" /* User records */
+#define FN_USSONG "ussong" /* 點歌統計 */
+#define FN_POST_NOTE "post.note" /* po文章備忘錄 */
+#define FN_POST_BID "post.bid"
+#define FN_MONEY "etc/money"
+#define FN_OVERRIDES "overrides"
+#define FN_REJECT "reject"
+#define FN_WATER "water"
+#define FN_CANVOTE "can_vote"
+#define FN_VISABLE "visable"
+#define FN_USIES "usies" /* BBS log */
+#define FN_BOARD ".BRD" /* board list */
+#define FN_USEBOARD "usboard" /* 看板統計 */
+#define FN_NOTE_ANS "note.ans"
+#define FN_TOPSONG "etc/topsong"
+#define FN_OVERRIDES "overrides"
+#define FN_TICKET "ticket"
+#define FN_TICKET_END "ticket.end"
+#define FN_TICKET_LOCK "ticket.end.lock"
+#define FN_TICKET_ITEMS "ticket.items"
+#define FN_TICKET_RECORD "ticket.data"
+#define FN_TICKET_USER "ticket.user"
+#define FN_TICKET_OUTCOME "ticket.outcome"
+#define FN_TICKET_BRDLIST "boardlist"
+#define FN_BRDLISTHELP "etc/boardlist.help"
+#define FN_BOARDHELP "etc/board.help"
+
+#define MSG_DEL_CANCEL "取消刪除"
+#define MSG_CLOAKED "哈哈!我隱形了!看不到勒... :P"
+#define MSG_UNCLOAK "我要重現江湖了...."
+#define MSG_BIG_BOY "我是大帥哥! ^o^Y"
+#define MSG_BIG_GIRL "世紀大美女 *^-^*"
+#define MSG_LITTLE_BOY "我是底迪啦... =)"
+#define MSG_LITTLE_GIRL "最可愛的美眉! :>"
+#define MSG_MAN "麥當勞叔叔 (^O^)"
+#define MSG_WOMAN "叫我小阿姨!! /:>"
+#define MSG_PLANT "植物也有性別喔.."
+#define MSG_MIME "礦物總沒性別了吧"
+#define MSG_PASSWD "請輸入您的密碼: "
+
+#define MSG_CLOAKED "哈哈!我隱形了!看不到勒... :P"
+#define MSG_UNCLOAK "我要重現江湖了...."
+
+#define MSG_WORKING "處理中,請稍候..."
+
+#define MSG_CANCEL "取消。"
+#define MSG_USR_LEFT "使用者已經離開了"
+#define MSG_NOBODY "目前無人上線"
+
+#define MSG_DEL_OK "刪除完畢"
+#define MSG_DEL_CANCEL "取消刪除"
+#define MSG_DEL_ERROR "刪除錯誤"
+#define MSG_DEL_NY "請確定刪除(Y/N)?[N] "
+
+#define MSG_FWD_OK "文章轉寄完成!"
+#define MSG_FWD_ERR1 "轉寄失誤: 系統錯誤 system error"
+#define MSG_FWD_ERR2 "轉寄失誤: 位址錯誤 address error"
+
+#define MSG_SURE_NY "請您確定(Y/N)?[N] "
+#define MSG_SURE_YN "請您確定(Y/N)?[Y] "
+
+#define MSG_BID "請輸入看板名稱:"
+#define MSG_UID "請輸入使用者代號:"
+#define MSG_PASSWD "請輸入您的密碼: "
+
+#define MSG_BIG_BOY "我是大帥哥! ^o^Y"
+#define MSG_BIG_GIRL "世紀大美女 *^-^*"
+#define MSG_LITTLE_BOY "我是底迪啦... =)"
+#define MSG_LITTLE_GIRL "最可愛的美眉! :>"
+#define MSG_MAN "麥當勞叔叔 (^O^)"
+#define MSG_WOMAN "叫我小阿姨!! /:>"
+#define MSG_PLANT "植物也有性別喔.."
+#define MSG_MIME "礦物總沒性別了吧"
+
+#define ERR_BOARD_OPEN ".BOARD 開啟錯誤"
+#define ERR_BOARD_UPDATE ".BOARD 更新有誤"
+#define ERR_PASSWD_OPEN ".PASSWDS 開啟錯誤"
+
+#define ERR_BID "你搞錯了啦!沒有這個板喔!"
+#define ERR_UID "這裡沒有這個人啦!"
+#define ERR_PASSWD "密碼不對喔!你有沒有冒用人家的名字啊?"
+#define ERR_FILENAME "檔名不合法!"
+
+#define STR_AUTHOR1 "作者:"
+#define STR_AUTHOR2 "發信人:"
+#define STR_POST1 "看板:"
+#define STR_POST2 "站內:"
+
+/* LONG MESSAGES */
+#define MSG_SELECT_BOARD ANSI_COLOR(7) "【 選擇看板 】" ANSI_RESET "\n" \
+ "請輸入看板名稱(按空白鍵自動搜尋):"
+
+#define MSG_POSTER_LEN (78)
+#define MSG_POSTER ANSI_COLOR(34;46) " 文章選讀 "\
+ ANSI_COLOR(31;47) " (y)" ANSI_COLOR(30) "回信"\
+ ANSI_COLOR(31) "(X)" ANSI_COLOR(30) "推文"\
+ ANSI_COLOR(31) "(x)" ANSI_COLOR(30) "轉錄 "\
+ ANSI_COLOR(31) "(=[]<>)" ANSI_COLOR(30) "相關主題 "\
+ ANSI_COLOR(31) "(/?a)" ANSI_COLOR(30) "搜尋標題/作者 "\
+ ANSI_COLOR(31) "(V)" ANSI_COLOR(30) "投票 "\
+ ""
+#define MSG_MAILER_LEN (78)
+#define MSG_MAILER \
+ ANSI_COLOR(34;46) " 鴻雁往返 " \
+ ANSI_COLOR(31;47) " (R)" ANSI_COLOR(30) "回信" \
+ ANSI_COLOR(31) "(x)" ANSI_COLOR(30) "轉寄" \
+ ANSI_COLOR(31) "(y)" ANSI_COLOR(30) "回群組信 " \
+ ANSI_COLOR(31) "(D)" ANSI_COLOR(30) "刪除 " \
+ ANSI_COLOR(31) "(c)" ANSI_COLOR(30) "收入信件夾" \
+ ANSI_COLOR(31) "(z)" ANSI_COLOR(30) "信件夾 " \
+ ANSI_COLOR(31) "←[q]" ANSI_COLOR(30) "離開 " \
+ ""
+
+#define MSG_SHORTULIST ANSI_COLOR(7) \
+ "使用者代號 目前狀態 │使用者代號 目前狀態 │"\
+ "使用者代號 目前狀態 " ANSI_RESET ANSI_CLRTOEND
+
+#define MSG_SEPERATOR "\
+───────────────────────────────────────"
+
+/* Flags to getdata input function */
+#define NOECHO 0
+#define DOECHO 1
+#define LCECHO 2
+
+#define YEA 1 /* Booleans (Yep, for true and false) */
+#define NA 0
+
+#define EQUSTR 0 /* for strcmp */
+
+/* 好友關係 */
+#define IRH 1 /* I reject him. */
+#define HRM 2 /* He reject me. */
+#define IBH 4 /* I am board friend of him. */
+#define IFH 8 /* He is one of my friends. */
+#define HFM 16 /* I am one of his friends. */
+#define ST_FRIEND (IBH | IFH | HFM)
+#define ST_REJECT (IRH | HRM)
+
+/* 鍵盤設定 */
+#define KEY_TAB 9
+#define KEY_ESC 27
+#define KEY_UP 0x0101
+#define KEY_DOWN 0x0102
+#define KEY_RIGHT 0x0103
+#define KEY_LEFT 0x0104
+#define KEY_STAB 0x0105 /* shift-tab */
+#define KEY_HOME 0x0201
+#define KEY_INS 0x0202
+#define KEY_DEL 0x0203
+#define KEY_END 0x0204
+#define KEY_PGUP 0x0205
+#define KEY_PGDN 0x0206
+#define KEY_F1 0x0301
+#define KEY_F2 0x0302
+#define KEY_F3 0x0303
+#define KEY_F4 0x0304
+#define KEY_F5 0x0305
+#define KEY_F6 0x0306
+#define KEY_F7 0x0307
+#define KEY_F8 0x0308
+#define KEY_F9 0x0309
+#define KEY_F10 0x030A
+#define KEY_F11 0x030B
+#define KEY_F12 0x030C
+#define KEY_UNKNOWN 0x0FFF /* unknown sequence */
+
+#define QCAST int (*)(const void *, const void *)
+#define Ctrl(c) (c & 037)
+#define chartoupper(c) ((c >= 'a' && c <= 'z') ? c+'A'-'a' : c)
+
+#define LEN_AUTHOR1 5
+#define LEN_AUTHOR2 7
+
+/* the title of article will be truncate to PROPER_TITLE_LEN */
+#define PROPER_TITLE_LEN 42
+
+
+/* ----------------------------------------------------- */
+/* 水球模式 邊界定義 */
+/* ----------------------------------------------------- */
+#define WB_OFO_USER_TOP 7
+#define WB_OFO_USER_BOTTOM 11
+#define WB_OFO_USER_HEIGHT ((WB_OFO_MSG_BOTTOM) - (WB_OFO_MSG_TOP) + 1)
+#define WB_OFO_USER_LEFT 28
+#define WB_OFO_MSG_TOP 15
+#define WB_OFO_MSG_BOTTOM 22
+#define WB_OFO_MSG_LEFT 4
+
+
+/* ----------------------------------------------------- */
+/* 群組名單模式 Ptt */
+/* ----------------------------------------------------- */
+#define FRIEND_OVERRIDE 0
+#define FRIEND_REJECT 1
+#define FRIEND_ALOHA 2
+#define FRIEND_POST 3
+#define FRIEND_SPECIAL 4
+#define FRIEND_CANVOTE 5
+#define BOARD_WATER 6
+#define BOARD_VISABLE 7
+
+#define LOCK_THIS 1 // lock這線不能重複玩
+#define LOCK_MULTI 2 // lock所有線不能重複玩
+
+#define I_TIMEOUT (-2) /* Used for the getchar routine select call */
+#define I_OTHERDATA (-333) /* interface, (-3) will conflict with chinese */
+
+#define MAX_MODES 127
+
+#ifndef MIN
+#define MIN(a,b) (((a)<(b))?(a):(b))
+#endif
+#ifndef MAX
+#define MAX(a,b) (((a)>(b))?(a):(b))
+#endif
+
+#define toSTR(x) __toSTR(x)
+#define __toSTR(x) #x
+
+#define char_lower(c) ((c >= 'A' && c <= 'Z') ? c|32 : c)
+
+#define STR_CURSOR "●"
+#define STR_UNCUR " "
+
+#define NOTREPLYING -1
+#define REPLYING 0
+#define RECVINREPLYING 1
+
+#define LOG_CREAT 1
+#define LOG_VF 2
+
+
+#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
+ #define __builtin_expect(exp,c) (exp)
+
+#endif
+
+#define likely(x) __builtin_expect(!!(x), 1)
+#define unlikely(x) __builtin_expect(!!(x), 0)
+
+#ifndef SHM_HUGETLB
+#define SHM_HUGETLB 04000 /* segment is mapped via hugetlb */
+#endif
+
+#endif
diff --git a/pttbbs/include/config.h b/pttbbs/include/config.h
new file mode 100644
index 00000000..9cf75555
--- /dev/null
+++ b/pttbbs/include/config.h
@@ -0,0 +1,272 @@
+/* $Id$ */
+#ifndef INCLUDE_CONFIG_H
+#define INCLUDE_CONFIG_H
+
+#include <syslog.h>
+#include "../pttbbs.conf"
+
+#define BBSPROG BBSHOME "/bin/mbbsd" /* 主程式 */
+#define BAN_FILE "BAN" /* 關站通告檔 */
+#define LOAD_FILE "/proc/loadavg" /* for Linux */
+
+#ifndef BBSUSER
+#define BBSUSER "bbs"
+#endif
+
+#ifndef BBSUID
+#define BBSUID (9999)
+#endif
+
+#ifndef BBSGID
+#define BBSGID (99)
+#endif
+
+#ifndef RELAY_SERVER_IP /* 寄站外信的 mail server */
+#define RELAY_SERVER_IP "127.0.0.1"
+#endif
+
+#ifndef MAX_USERS /* 最高註冊人數 */
+#define MAX_USERS (150000)
+#endif
+
+#ifndef MAX_ACTIVE
+#define MAX_ACTIVE (1024) /* 最多同時上站人數 */
+#endif
+
+#ifndef MAX_GUEST
+#define MAX_GUEST (100) /* 最多 guest 上站人數 */
+#endif
+
+#ifndef MAX_CPULOAD
+#define MAX_CPULOAD (70) /* CPU 最高load */
+#endif
+
+#ifndef MAX_LANG
+#define MAX_LANG (1) /* 最多使用語言 */
+#endif
+
+#ifndef MAX_STRING
+#define MAX_STRING (8000) /* 系統最多使用字串 */
+#endif
+
+#ifndef MAX_POST_MONEY /* 發表文章稿費的上限 */
+#define MAX_POST_MONEY (100)
+#endif
+
+#ifndef MAX_CHICKEN_MONEY /* 養雞場獲利上限 */
+#define MAX_CHICKEN_MONEY (100)
+#endif
+
+#ifndef MAX_GUEST_LIFE /* 最長未認證使用者保留時間(秒) */
+#define MAX_GUEST_LIFE (3 * 24 * 60 * 60)
+#endif
+
+#ifndef MAX_EDIT_LINE
+#define MAX_EDIT_LINE 2048 /* 文章最長編輯長度 */
+#endif
+
+#ifndef MAX_LIFE /* 最長使用者保留時間(秒) */
+#define MAX_LIFE (120 * 24 * 60 * 60)
+#endif
+
+#ifndef MAX_FROM
+#define MAX_FROM (300) /* 最多故鄉數 */
+#endif
+
+#ifndef THREAD_SEARCH_RANGE
+#define THREAD_SEARCH_RANGE (500)
+#endif
+
+#ifndef HAVE_JCEE /* 大學聯考查榜系統 */
+#define HAVE_JCEE 0
+#endif
+
+#ifndef MEM_CHECK
+#define MEM_CHECK 0x98761234
+#endif
+
+#ifndef FOREIGN_REG_DAY /* 外籍使用者試用日期上限 */
+#define FOREIGN_REG_DAY 30
+#endif
+
+#ifndef HAVE_FREECLOAK
+#define HAVE_FREECLOAK 0
+#endif
+
+#ifndef FORCE_PROCESS_REGISTER_FORM
+#define FORCE_PROCESS_REGISTER_FORM 0
+#endif
+
+#ifndef TITLE_COLOR
+#define TITLE_COLOR ANSI_COLOR(0;1;37;46)
+#endif
+
+#ifndef SYSLOG_FACILITY
+#define SYSLOG_FACILITY LOG_LOCAL0
+#endif
+
+#ifndef TAR_PATH
+#define TAR_PATH "tar"
+#endif
+
+#ifndef MUTT_PATH
+#define MUTT_PATH "mutt"
+#endif
+
+#ifndef HBFLexpire
+#define HBFLexpire (432000) /* 5 days */
+#endif
+
+#ifndef MAXPATHLEN
+#define MAXPATHLEN (256)
+#endif
+
+#ifndef PATHLEN
+#define PATHLEN (256)
+#endif
+
+#ifndef MAX_BOARD
+#define MAX_BOARD (8192) /* 最大開板個數 */
+#endif
+
+#ifndef MAX_EXKEEPMAIL
+#define MAX_EXKEEPMAIL (1000) /* 最多信箱加大多少封 */
+#endif
+
+#ifndef OVERLOADBLOCKFDS
+#define OVERLOADBLOCKFDS (0) /* 超載後會保留這麼多個 fd */
+#endif
+
+#ifndef HOTBOARDCACHE
+#define HOTBOARDCACHE (0) /* 熱門看板快取 */
+#endif
+
+#ifndef INNTIMEZONE
+#define INNTIMEZONE "+0000 (UTC)"
+#endif
+
+#ifndef ADD_EXMAILBOX
+#define ADD_EXMAILBOX 0 /* 贈送信箱 */
+#endif
+
+#ifndef HASH_BITS
+#define HASH_BITS 16 /* userid->uid hashing bits */
+#endif
+
+/* more.c 中文章頁數上限(lines/22), +4 for safe */
+#define MAX_PAGES (MAX_EDIT_LINE / 22 + 4)
+
+/* 以下還未整理 */
+#define MAX_FRIEND (256) /* 載入 cache 之最多朋友數目 */
+#define MAX_REJECT (32) /* 載入 cache 之最多壞人數目 */
+#define MAX_MSGS (10) /* 水球(熱訊)忍耐上限 */
+#define MAX_MOVIE (500) /* 最多動態看板數 */
+#define MAX_MOVIE_SECTION (10) /* 最多動態看板類別 */
+#define MAX_ITEMS (1000) /* 一個目錄最多有幾項 */
+#define MAX_HISTORY (12) /* 動態看板保持 12 筆歷史記錄 */
+#define MAX_CROSSNUM (9) /* 最多crosspost次數 */
+#define MAX_QUERYLINES (16) /* 顯示 Query/Plan 訊息最大行數 */
+#define MAX_LOGIN_INFO (128) /* 最多上線通知人數 */
+#define MAX_POST_INFO (32) /* 最多新文章通知人數 */
+#define MAX_NAMELIST (128) /* 最多其他特別名單人數 */
+#define MAX_KEEPMAIL (200) /* 最多保留幾封 MAIL? */
+#define MAX_NOTE (20) /* 最多保留幾篇留言? */
+#define MAX_SIGLINES (6) /* 簽名檔引入最大行數 */
+#define MAX_CROSSNUM (9) /* 最多crosspost次數 */
+#define MAX_REVIEW (7) /* 最多水球回顧 */
+#define NUMVIEWFILE (14) /* 進站畫面最多數 */
+#define MAX_SWAPUSED (0.7) /* SWAP最高使用率 */
+#define LOGINATTEMPTS (3) /* 最大進站失誤次數 */
+#define WHERE /* 是否有故鄉功能 */
+#undef LOG_BOARD /* 看板是否log */
+
+
+#define LOGINASNEW /* 採用上站申請帳號制度 */
+#define NO_WATER_POST /* 防止BlahBlah式灌水 */
+#define USE_BSMTP /* 使用opus的BSMTP 寄收信? */
+#define HAVE_ANONYMOUS /* 提供 Anonymous 板 */
+#undef POSTNOTIFY /* 新文章通知功能 */
+#define INTERNET_EMAIL /* 支援 InterNet Email 功能(含 Forward) */
+#define HAVE_ORIGIN /* 顯示 author 來自何處 */
+#undef HAVE_MAILCLEAN /* 清理所有使用者個人信箱 */
+#undef HAVE_SUICIDE /* 提供使用者自殺功能 */
+#undef HAVE_REPORT /* 系統追蹤報告 */
+#undef HAVE_INFO /* 顯示程式版本說明 */
+#undef HAVE_LICENSE /* 顯示 GNU 版權畫面 */
+#define FAST_LOGIN /* Login 不檢查遠端使用者 */
+#define HAVE_CAL /* 提供計算機 */
+#undef HAVE_REPORT /* 系統追蹤報告 */
+#undef NEWUSER_LIMIT /* 新手上路的三天限制 */
+#undef HAVE_X_BOARDS
+
+#define USE_LYNX /* 使用外部lynx dump ? */
+#undef USE_PROXY
+#ifdef USE_PROXY
+#define PROXYSERVER "140.112.28.165"
+#define PROXYPORT 3128
+#endif
+#define LOCAL_PROXY /* 是否開啟local 的proxy */
+#ifdef LOCAL_PROXY
+#define HPROXYDAY 1 /* local的proxy refresh天數 */
+#endif
+
+#define SHOWMIND /* 看見心情 */
+#define SHOWUID /* 看見使用者 UID */
+#define SHOWBOARD /* 看見使用者看板 */
+#define SHOWPID /* 看見使用者 PID */
+
+#define DOTIMEOUT
+#ifdef DOTIMEOUT
+#define IDLE_TIMEOUT (43200) /* 一般情況之 timeout (12hr) */
+#define MONITOR_TIMEOUT (20*60) /* monitor 時之 timeout */
+#define SHOW_IDLE_TIME /* 顯示閒置時間 */
+#endif
+
+#define SEM_ENTER -1 /* enter semaphore */
+#define SEM_LEAVE 1 /* leave semaphore */
+#define SEM_RESET 0 /* reset semaphore */
+
+#define MAGIC_KEY 1234 /* 身分認證信函編碼 */
+
+#define SHM_KEY 1228
+#if 0
+#define BRDSHM_KEY 1208
+#define UHASH_KEY 1218 /* userid->uid hash */
+#define UTMPSHM_KEY 2221
+#define PTTSHM_KEY 1220 /* 動態看板 , 節日 */
+#define FROMSHM_KEY 1223 /* whereis, 最多使用者 */
+#endif
+
+#define BRDSEM_KEY 2005 /* semaphore key */
+#define PTTSEM_KEY 2000 /* semaphore key */
+#define FROMSEM_KEY 2003 /* semaphore key */
+#define PASSWDSEM_KEY 2010
+
+#define NEW_CHATPORT 3838
+#define CHATPORT 5722
+
+#define MAX_ROOM 16 /* 最多有幾間包廂? */
+
+#define EXIT_LOGOUT 0
+#define EXIT_LOSTCONN -1
+#define EXIT_CLIERROR -2
+#define EXIT_TIMEDOUT -3
+#define EXIT_KICK -4
+
+#define CHAT_LOGIN_OK "OK"
+#define CHAT_LOGIN_EXISTS "EX"
+#define CHAT_LOGIN_INVALID "IN"
+#define CHAT_LOGIN_BOGUS "BG"
+#define BADCIDCHARS " *" /* Chat Room 中禁用於 nick 的字元 */
+
+#define ALLPOST "ALLPOST"
+#define ALLHIDPOST "ALLHIDPOST"
+
+#define MAXTAGS 256
+#define BRC_STRLEN 15 /* Length of board name */
+#define BRC_MAXSIZE 24576
+#define BRC_MAXNUM 80
+
+#define WRAPMARGIN (511)
+
+#endif
diff --git a/pttbbs/include/convert.h b/pttbbs/include/convert.h
new file mode 100644
index 00000000..cafa08b7
--- /dev/null
+++ b/pttbbs/include/convert.h
@@ -0,0 +1,12 @@
+/* $Id: convert.h 1374 2003-11-27 14:11:40Z victor $ */
+
+#ifdef CONVERT
+
+#define CONV_NORMAL 0
+#define CONV_GB 1
+#define CONV_UTF8 2
+
+typedef ssize_t (*read_write_type)(int, void *, size_t);
+typedef ssize_t (*convert_type)(void *, ssize_t);
+
+#endif
diff --git a/pttbbs/include/fav.h b/pttbbs/include/fav.h
new file mode 100644
index 00000000..dea3cbba
--- /dev/null
+++ b/pttbbs/include/fav.h
@@ -0,0 +1,64 @@
+
+#define FAV_VERSION 3363
+
+#define FAVT_BOARD 1
+#define FAVT_FOLDER 2
+#define FAVT_LINE 3
+
+#define FAVH_FAV 1
+#define FAVH_TAG 2
+#define FAVH_UNREAD 4
+#define FAVH_ADM_TAG 8
+/* 站長用 t 來管理 (eg.搬移) 看板時 舊的作法是把這些 tag 起來的看板
+ * 記錄在 fav 裡面。為了不再多花其他記憶體,這邊繼續沿用。*/
+
+#define FALSE 0
+#define TRUE 1
+#define EXCH 2
+
+#define FAV_PRE_ALLOC 8
+#define FAV_MAXDEPTH 5
+#define MAX_FAV 1024
+#define MAX_LINE 64
+#define MAX_FOLDER 64
+#define NEW_FAV_THRESHOLD 12 /* half page */
+
+#define FAV ".fav"
+#define FAV4 ".fav4"
+#define FAVNB ".favnb"
+
+typedef struct {
+ char type;
+ char attr;
+ /* *fp could be *fav_board_t or *fav_folder_t. */
+ void *fp;
+} fav_type_t;
+
+typedef struct {
+ short nAllocs;
+ short DataTail; /* the tail of item list that user
+ have ever used */
+ short nBoards; /* number of the boards */
+ char nLines; /* number of the lines */
+ char nFolders; /* number of the folders */
+ char lineID; /* current max line id */
+ char folderID; /* current max folder id */
+
+ fav_type_t *favh; /* record of boards/folders */
+} fav_t;
+
+typedef struct {
+ int bid;
+ time4_t lastvisit; /* UNUSED */
+ char attr;
+} fav_board_t;
+
+typedef struct {
+ char fid;
+ char title[BTLEN + 1];
+ fav_t *this_folder;
+} fav_folder_t;
+
+typedef struct {
+ char lid;
+} fav_line_t;
diff --git a/pttbbs/include/fnv_hash.h b/pttbbs/include/fnv_hash.h
new file mode 100644
index 00000000..837fd66c
--- /dev/null
+++ b/pttbbs/include/fnv_hash.h
@@ -0,0 +1,112 @@
+#ifndef _FNV_HASH_H_
+#define _FNV_HASH_H_
+/*
+ * Fowler / Noll / Vo Hash (FNV Hash)
+ * http://www.isthe.com/chongo/tech/comp/fnv/
+ *
+ * This is an implementation of the algorithms posted above.
+ * This file is placed in the public domain by Peter Wemm.
+ *
+ * $FreeBSD: src/sys/sys/fnv_hash.h,v 1.2 2001/03/20 02:10:18 peter Exp $
+ */
+
+typedef unsigned int Fnv32_t;
+typedef unsigned long long Fnv64_t;
+
+#define FNV1_32_INIT ((Fnv32_t) 33554467UL)
+#define FNV1_64_INIT ((Fnv64_t) 0xcbf29ce484222325ULL)
+
+#define FNV_32_PRIME ((Fnv32_t) 0x01000193UL)
+#define FNV_64_PRIME ((Fnv64_t) 0x100000001b3ULL)
+
+static __inline Fnv32_t
+fnv_32_buf(const void *buf, size_t len, Fnv32_t hval)
+{
+ const unsigned char *s = (const unsigned char *)buf;
+
+ while (len-- != 0) {
+ hval *= FNV_32_PRIME;
+ hval ^= *s++;
+ }
+ return hval;
+}
+
+static __inline Fnv32_t
+fnv_32_str(const char *str, Fnv32_t hval)
+{
+ const unsigned char *s = (const unsigned char *)str;
+ Fnv32_t c;
+
+ while ((c = *s++) != 0) {
+ hval *= FNV_32_PRIME;
+ hval ^= c;
+ }
+ return hval;
+}
+
+static __inline Fnv32_t
+fnv1a_32_str(const char *str, Fnv32_t hval)
+{
+ const unsigned char *s = (const unsigned char *)str;
+ Fnv32_t c;
+
+ while ((c = *s++) != 0) {
+ hval ^= c;
+ hval *= FNV_32_PRIME;
+ }
+ return hval;
+}
+
+static __inline Fnv32_t
+fnv1a_32_strcase(const char *str, Fnv32_t hval)
+{
+ const unsigned char *s = (const unsigned char *)str;
+ Fnv32_t c;
+
+ while ((c = *s++) != 0) {
+ hval ^= toupper(c);
+ hval *= FNV_32_PRIME;
+ }
+ return hval;
+}
+
+static __inline Fnv64_t
+fnv_64_buf(const void *buf, size_t len, Fnv64_t hval)
+{
+ const unsigned char *s = (const unsigned char *)buf;
+
+ while (len-- != 0) {
+ hval *= FNV_64_PRIME;
+ hval ^= *s++;
+ }
+ return hval;
+}
+
+static __inline Fnv64_t
+fnv_64_str(const char *str, Fnv64_t hval)
+{
+ const unsigned char *s = (const unsigned char *)str;
+ Fnv64_t c;
+
+ while ((c = *s++) != 0) {
+ hval *= FNV_64_PRIME;
+ hval ^= c;
+ }
+ return hval;
+}
+
+static __inline Fnv64_t
+fnv1a_64_strcase(const char *str, Fnv64_t hval)
+{
+ const unsigned char *s = (const unsigned char *)str;
+ Fnv64_t c;
+
+ while ((c = *s++) != 0) {
+ hval ^= toupper(c);
+ hval *= FNV_64_PRIME;
+ }
+ return hval;
+}
+
+#define FNV1A_CHAR(c,hval) do { hval^=(unsigned char)c; hval*=FNV_32_PRIME; } while(0)
+#endif
diff --git a/pttbbs/include/fpg.h b/pttbbs/include/fpg.h
new file mode 100644
index 00000000..13045dae
--- /dev/null
+++ b/pttbbs/include/fpg.h
@@ -0,0 +1,73 @@
+#define STRLEN 80 /* Length of most string data */
+#define BRC_STRLEN 15 /* Length of boardname */
+#define BTLEN 48 /* Length of board title */
+#define NAMELEN 40 /* Length of username/realname */
+//#define FNLEN 33 /* Length of filename */
+// /* XXX Ptt 說這裡有bug*/
+#define IDLEN 12 /* Length of bid/uid */
+#define PASSLEN 14 /* Length of encrypted passwd field */
+#define REGLEN 38 /* Length of registration data */
+
+
+
+typedef unsigned char uschar; /* length = 1 */
+typedef unsigned short ushort; /* length = 2 */
+typedef unsigned long uslong; /* length = 4 */
+typedef unsigned int usint; /* length = 4 */
+
+/* ----------------------------------------------------- */
+/* .PASSWDS struct : 512 bytes */
+/* ----------------------------------------------------- */
+struct sobuserec
+{
+ char userid[IDLEN + 1]; /* 使用者名稱 13 bytes */
+ char realname[20]; /* 真實姓名 20 bytes */
+ char username[24]; /* 暱稱 24 bytes */
+ char passwd[PASSLEN]; /* 密碼 14 bytes */
+ uschar uflag; /* 使用者選項 1 byte */
+ usint userlevel; /* 使用者權限 4 bytes */
+ ushort numlogins; /* 上站次數 2 bytes */
+ ushort numposts; /* POST次數 2 bytes */
+ time4_t firstlogin; /* 註冊時間 4 bytes */
+ time4_t lastlogin; /* 前次上站 4 bytes */
+ char lasthost[24]; /* 上站地點 24 bytes */
+ char vhost[24]; /* 虛擬網址 24 bytes */
+ char email[50]; /* E-MAIL 50 bytes */
+ char address[50]; /* 地址 50 bytes */
+ char justify[REGLEN + 1]; /* 註冊資料 39 bytes */
+ uschar month; /* 出生月份 1 byte */
+ uschar day; /* 出生日期 1 byte */
+ uschar year; /* 出生年份 1 byte */
+ uschar sex; /* 性別 1 byte */
+ uschar state; /* 狀態?? 1 byte */
+ usint habit; /* 喜好設定 4 bytes */
+ uschar pager; /* 心情顏色 1 bytes */
+ uschar invisible; /* 隱身模式 1 bytes */
+ usint exmailbox; /* 信箱封數 4 bytes */
+ usint exmailboxk; /* 信箱K數 4 bytes */
+ usint toquery; /* 好奇度 4 bytes */
+ usint bequery; /* 人氣度 4 bytes */
+ char toqid[IDLEN + 1]; /* 前次查誰 13 bytes */
+ char beqid[IDLEN + 1]; /* 前次被誰查 13 bytes */
+ uslong totaltime; /* 上線總時數 8 bytes */
+ usint sendmsg; /* 發訊息次數 4 bytes */
+ usint receivemsg; /* 收訊息次數 4 bytes */
+ usint goldmoney; /* 風塵金幣 8 bytes */
+ usint silvermoney; /* 銀幣 8 bytes */
+ usint exp; /* 經驗值 8 bytes */
+ time4_t dtime; /* 存款時間 4 bytes */
+ int scoretimes; /* 評分次數 4 bytes */
+ uschar rtimes; /* 填註冊單次數 1 bytes */
+ int award; /* 獎懲判斷 4 bytes */
+ int pagermode; /* 呼叫器門號 4 bytes */
+ char pagernum[7]; /* 呼叫器號碼 7 bytes */
+ char feeling[5]; /* 心情指數 5 bytes */
+ char title[20]; /* 稱謂(封號) 20 bytes */
+ usint five_win;
+ usint five_lost;
+ usint five_draw;
+ char pad[91]; /* 空著填滿至512用 */
+};
+
+typedef struct sobuserec sobuserec;
+
diff --git a/pttbbs/include/gomo.h b/pttbbs/include/gomo.h
new file mode 100644
index 00000000..85b9d276
--- /dev/null
+++ b/pttbbs/include/gomo.h
@@ -0,0 +1,1970 @@
+/* $Id$ */
+
+#ifndef _INCLUDE_GOMO_H
+#define _INCLUDE_GOMO_H
+
+#define BBLANK (-1) /* 空白 */
+#define BWHITE (0) /* 白子, 後手 */
+#define BBLACK (1) /* 黑子, 先手 */
+#define MAX_TIME (300) /*最長idle秒數*/
+#ifndef BRDSIZ
+#define BRDSIZ (15) /* 棋盤單邊大小 */
+#endif
+
+/*
+ 0 0 0 = #@# : len= 3 : NO 00 NO
+ 1 1 0 = #_@# : len= 4 : NO 00 NO
+ 2 1 1 = #_@_# : len= 5 : NO 00 NO
+ 3 2 0 = #O@# : len= 4 : NO 00 NO
+ 4 2 1 = #O@_# : len= 5 : NO 00 NO
+ 5 2 2 = #O@O# : len= 5 : NO 00 NO
+ 6 3 0 = #__@# : len= 5 : NO 00 NO
+ 7 3 1 = #__@_# : len= 6 : NO 00 NO
+ 8 3 2 = #__@O# : len= 6 : NO 00 NO
+ 9 3 3 = #__@__# : len= 7 : NO 00 NO
+ 10 4 0 = #_O@# : len= 5 : NO 00 NO
+ 11 4 1 = #_O@_# : len= 6 : NO 00 NO
+ 12 4 2 = #_O@O# : len= 6 : NO 00 NO
+ 13 4 3 = #_O@__# : len= 7 : NO 00 NO
+ 14 4 4 = #_O@O_# : len= 7 : NO 00 NO
+ 15 5 0 = #O_@# : len= 5 : NO 00 NO
+ 16 5 1 = #O_@_# : len= 6 : NO 00 NO
+ 17 5 2 = #O_@O# : len= 6 : NO 00 NO
+ 18 5 3 = #O_@__# : len= 7 : NO 00 NO
+ 19 5 4 = #O_@O_# : len= 7 : NO 00 NO
+ 20 5 5 = #O_@_O# : len= 7 : NO 00 NO
+ 21 6 0 = #OO@# : len= 5 : NO 00 NO
+ 22 6 1 = #OO@_# : len= 6 : NO 00 NO
+ 23 6 2 = #OO@O# : len= 6 : NO 00 NO
+ 24 6 3 = #OO@__# : len= 7 : NO 00 NO
+ 25 6 4 = #OO@O_# : len= 7 : S4 00 S4
+ 26 6 5 = #OO@_O# : len= 7 : D4 00 D4
+ 27 6 6 = #OO@OO# : len= 7 : L5 00 L5
+ 28 7 0 = #___@# : len= 6 : NO 00 NO
+ 29 7 1 = #___@_# : len= 7 : NO 00 NO
+ 30 7 2 = #___@O# : len= 7 : NO 00 NO
+ 31 7 3 = #___@__# : len= 8 : NO 00 NO
+ 32 7 4 = #___@O_# : len= 8 : NO 00 NO
+ 33 7 5 = #___@_O# : len= 8 : NO 00 NO
+ 34 7 6 = #___@OO# : len= 8 : NO 00 NO
+ 35 7 7 = #___@___# : len= 9 : NO 00 NO
+ 36 8 0 = #__O@# : len= 6 : NO 00 NO
+ 37 8 1 = #__O@_# : len= 7 : NO 00 NO
+ 38 8 2 = #__O@O# : len= 7 : NO 00 NO
+ 39 8 3 = #__O@__# : len= 8 : NO 00 NO
+ 40 8 4 = #__O@O_# : len= 8 : H3 22 H3
+ 41 8 5 = #__O@_O# : len= 8 : NO 00 NO
+ 42 8 6 = #__O@OO# : len= 8 : S4 00 S4
+ 43 8 7 = #__O@___# : len= 9 : NO 00 NO
+ 44 8 8 = #__O@O__# : len= 9 : H3 22 H3
+ 45 9 0 = #_O_@# : len= 6 : NO 00 NO
+ 46 9 1 = #_O_@_# : len= 7 : NO 00 NO
+ 47 9 2 = #_O_@O# : len= 7 : NO 00 NO
+ 48 9 3 = #_O_@__# : len= 8 : NO 00 NO
+ 49 9 4 = #_O_@O_# : len= 8 : D3 10 D3
+ 50 9 5 = #_O_@_O# : len= 8 : NO 00 NO
+ 51 9 6 = #_O_@OO# : len= 8 : D4 00 D4
+ 52 9 7 = #_O_@___# : len= 9 : NO 00 NO
+ 53 9 8 = #_O_@O__# : len= 9 : D3 10 D3
+ 54 9 9 = #_O_@_O_# : len= 9 : NO 00 NO
+ 55 10 0 = #_OO@# : len= 6 : NO 00 NO
+ 56 10 1 = #_OO@_# : len= 7 : NO 00 NO
+ 57 10 2 = #_OO@O# : len= 7 : S4 00 S4
+ 58 10 3 = #_OO@__# : len= 8 : H3 31 H3
+ 59 10 4 = #_OO@O_# : len= 8 : H4 00 H4
+ 60 10 5 = #_OO@_O# : len= 8 : D4 00 D4
+ 61 10 6 = #_OO@OO# : len= 8 : L5 00 L5
+ 62 10 7 = #_OO@___# : len= 9 : H3 31 H3
+ 63 10 8 = #_OO@O__# : len= 9 : H4 00 H4
+ 64 10 9 = #_OO@_O_# : len= 9 : D4 00 D4
+ 65 10 10 = #_OO@OO_# : len= 9 : L5 00 L5
+ 66 11 0 = #O__@# : len= 6 : NO 00 NO
+ 67 11 1 = #O__@_# : len= 7 : NO 00 NO
+ 68 11 2 = #O__@O# : len= 7 : NO 00 NO
+ 69 11 3 = #O__@__# : len= 8 : NO 00 NO
+ 70 11 4 = #O__@O_# : len= 8 : NO 00 NO
+ 71 11 5 = #O__@_O# : len= 8 : NO 00 NO
+ 72 11 6 = #O__@OO# : len= 8 : NO 00 NO
+ 73 11 7 = #O__@___# : len= 9 : NO 00 NO
+ 74 11 8 = #O__@O__# : len= 9 : NO 00 NO
+ 75 11 9 = #O__@_O_# : len= 9 : NO 00 NO
+ 76 11 10 = #O__@OO_# : len= 9 : NO 00 H3
+ 77 11 11 = #O__@__O# : len= 9 : NO 00 NO
+ 78 12 0 = #O_O@# : len= 6 : NO 00 NO
+ 79 12 1 = #O_O@_# : len= 7 : NO 00 NO
+ 80 12 2 = #O_O@O# : len= 7 : D4 00 D4
+ 81 12 3 = #O_O@__# : len= 8 : NO 00 NO
+ 82 12 4 = #O_O@O_# : len= 8 : D4 00 D4
+ 83 12 5 = #O_O@_O# : len= 8 : NO 00 NO
+ 84 12 6 = #O_O@OO# : len= 8 : NO 00 D4
+ 85 12 7 = #O_O@___# : len= 9 : NO 00 NO
+ 86 12 8 = #O_O@O__# : len= 9 : D4 00 D4
+ 87 12 9 = #O_O@_O_# : len= 9 : NO 00 D3
+ 88 12 10 = #O_O@OO_# : len= 9 : S4 00 H4
+ 89 12 11 = #O_O@__O# : len= 9 : NO 00 NO
+ 90 12 12 = #O_O@O_O# : len= 9 : X4 00 X4
+ 91 13 0 = #OO_@# : len= 6 : NO 00 NO
+ 92 13 1 = #OO_@_# : len= 7 : NO 00 NO
+ 93 13 2 = #OO_@O# : len= 7 : D4 00 D4
+ 94 13 3 = #OO_@__# : len= 8 : NO 00 NO
+ 95 13 4 = #OO_@O_# : len= 8 : D4 00 D4
+ 96 13 5 = #OO_@_O# : len= 8 : NO 00 NO
+ 97 13 6 = #OO_@OO# : len= 8 : NO 00 D4
+ 98 13 7 = #OO_@___# : len= 9 : NO 00 NO
+ 99 13 8 = #OO_@O__# : len= 9 : D4 00 D4
+ 100 13 9 = #OO_@_O_# : len= 9 : NO 00 NO
+ 101 13 10 = #OO_@OO_# : len= 9 : NO 00 D4
+ 102 13 11 = #OO_@__O# : len= 9 : NO 00 NO
+ 103 13 12 = #OO_@O_O# : len= 9 : D4 00 D4
+ 104 13 13 = #OO_@_OO# : len= 9 : NO 00 NO
+ 105 14 0 = #OOO@# : len= 6 : NO 00 NO
+ 106 14 1 = #OOO@_# : len= 7 : S4 00 S4
+ 107 14 2 = #OOO@O# : len= 7 : L5 00 L5
+ 108 14 3 = #OOO@__# : len= 8 : S4 00 S4
+ 109 14 4 = #OOO@O_# : len= 8 : L5 00 L5
+ 110 14 5 = #OOO@_O# : len= 8 : NO 00 D4
+ 111 14 6 = #OOO@OO# : len= 8 : L6 00 L6
+ 112 14 7 = #OOO@___# : len= 9 : S4 00 S4
+ 113 14 8 = #OOO@O__# : len= 9 : L5 00 L5
+ 114 14 9 = #OOO@_O_# : len= 9 : NO 00 D4
+ 115 14 10 = #OOO@OO_# : len= 9 : L6 00 L6
+ 116 14 11 = #OOO@__O# : len= 9 : S4 00 S4
+ 117 14 12 = #OOO@O_O# : len= 9 : L5 00 L5
+ 118 14 13 = #OOO@_OO# : len= 9 : NO 00 D4
+ 119 14 14 = #OOO@OOO# : len= 9 : L6 00 L6
+ 120 15 0 = #____@# : len= 7 : NO 00 NO
+ 121 15 1 = #____@_# : len= 8 : NO 00 NO
+ 122 15 2 = #____@O# : len= 8 : NO 00 NO
+ 123 15 3 = #____@__# : len= 9 : NO 00 NO
+ 124 15 4 = #____@O_# : len= 9 : NO 00 NO
+ 125 15 5 = #____@_O# : len= 9 : NO 00 NO
+ 126 15 6 = #____@OO# : len= 9 : NO 00 NO
+ 127 15 7 = #____@___# : len=10 : NO 00 NO
+ 128 15 8 = #____@O__# : len=10 : NO 00 NO
+ 129 15 9 = #____@_O_# : len=10 : NO 00 NO
+ 130 15 10 = #____@OO_# : len=10 : H3 13 H3
+ 131 15 11 = #____@__O# : len=10 : NO 00 NO
+ 132 15 12 = #____@O_O# : len=10 : NO 00 NO
+ 133 15 13 = #____@_OO# : len=10 : NO 00 NO
+ 134 15 14 = #____@OOO# : len=10 : S4 00 S4
+ 135 15 15 = #____@____# : len=11 : NO 00 NO
+ 136 16 0 = #___O@# : len= 7 : NO 00 NO
+ 137 16 1 = #___O@_# : len= 8 : NO 00 NO
+ 138 16 2 = #___O@O# : len= 8 : NO 00 NO
+ 139 16 3 = #___O@__# : len= 9 : NO 00 NO
+ 140 16 4 = #___O@O_# : len= 9 : H3 22 H3
+ 141 16 5 = #___O@_O# : len= 9 : NO 00 NO
+ 142 16 6 = #___O@OO# : len= 9 : S4 00 S4
+ 143 16 7 = #___O@___# : len=10 : NO 00 NO
+ 144 16 8 = #___O@O__# : len=10 : H3 22 H3
+ 145 16 9 = #___O@_O_# : len=10 : D3 01 D3
+ 146 16 10 = #___O@OO_# : len=10 : H4 00 H4
+ 147 16 11 = #___O@__O# : len=10 : NO 00 NO
+ 148 16 12 = #___O@O_O# : len=10 : D4 00 D4
+ 149 16 13 = #___O@_OO# : len=10 : D4 00 D4
+ 150 16 14 = #___O@OOO# : len=10 : L5 00 L5
+ 151 16 15 = #___O@____# : len=11 : NO 00 NO
+ 152 16 16 = #___O@O___# : len=11 : H3 22 H3
+ 153 17 0 = #__O_@# : len= 7 : NO 00 NO
+ 154 17 1 = #__O_@_# : len= 8 : NO 00 NO
+ 155 17 2 = #__O_@O# : len= 8 : NO 00 NO
+ 156 17 3 = #__O_@__# : len= 9 : NO 00 NO
+ 157 17 4 = #__O_@O_# : len= 9 : D3 10 D3
+ 158 17 5 = #__O_@_O# : len= 9 : NO 00 NO
+ 159 17 6 = #__O_@OO# : len= 9 : D4 00 D4
+ 160 17 7 = #__O_@___# : len=10 : NO 00 NO
+ 161 17 8 = #__O_@O__# : len=10 : D3 10 D3
+ 162 17 9 = #__O_@_O_# : len=10 : NO 00 NO
+ 163 17 10 = #__O_@OO_# : len=10 : D4 00 D4
+ 164 17 11 = #__O_@__O# : len=10 : NO 00 NO
+ 165 17 12 = #__O_@O_O# : len=10 : NO 00 D3
+ 166 17 13 = #__O_@_OO# : len=10 : NO 00 NO
+ 167 17 14 = #__O_@OOO# : len=10 : NO 00 D4
+ 168 17 15 = #__O_@____# : len=11 : NO 00 NO
+ 169 17 16 = #__O_@O___# : len=11 : D3 10 D3
+ 170 17 17 = #__O_@_O__# : len=11 : NO 00 NO
+ 171 18 0 = #__OO@# : len= 7 : NO 00 NO
+ 172 18 1 = #__OO@_# : len= 8 : H3 31 H3
+ 173 18 2 = #__OO@O# : len= 8 : S4 00 S4
+ 174 18 3 = #__OO@__# : len= 9 : H3 31 H3
+ 175 18 4 = #__OO@O_# : len= 9 : H4 00 H4
+ 176 18 5 = #__OO@_O# : len= 9 : D4 00 D4
+ 177 18 6 = #__OO@OO# : len= 9 : L5 00 L5
+ 178 18 7 = #__OO@___# : len=10 : H3 31 H3
+ 179 18 8 = #__OO@O__# : len=10 : H4 00 H4
+ 180 18 9 = #__OO@_O_# : len=10 : D4 00 D4
+ 181 18 10 = #__OO@OO_# : len=10 : L5 00 L5
+ 182 18 11 = #__OO@__O# : len=10 : H3 31 H3
+ 183 18 12 = #__OO@O_O# : len=10 : S4 00 H4
+ 184 18 13 = #__OO@_OO# : len=10 : NO 00 D4
+ 185 18 14 = #__OO@OOO# : len=10 : L6 00 L6
+ 186 18 15 = #__OO@____# : len=11 : H3 31 H3
+ 187 18 16 = #__OO@O___# : len=11 : H4 00 H4
+ 188 18 17 = #__OO@_O__# : len=11 : D4 00 D4
+ 189 18 18 = #__OO@OO__# : len=11 : L5 00 L5
+ 190 19 0 = #_O__@# : len= 7 : NO 00 NO
+ 191 19 1 = #_O__@_# : len= 8 : NO 00 NO
+ 192 19 2 = #_O__@O# : len= 8 : NO 00 NO
+ 193 19 3 = #_O__@__# : len= 9 : NO 00 NO
+ 194 19 4 = #_O__@O_# : len= 9 : NO 00 NO
+ 195 19 5 = #_O__@_O# : len= 9 : NO 00 NO
+ 196 19 6 = #_O__@OO# : len= 9 : NO 00 NO
+ 197 19 7 = #_O__@___# : len=10 : NO 00 NO
+ 198 19 8 = #_O__@O__# : len=10 : NO 00 NO
+ 199 19 9 = #_O__@_O_# : len=10 : NO 00 NO
+ 200 19 10 = #_O__@OO_# : len=10 : NO 00 H3
+ 201 19 11 = #_O__@__O# : len=10 : NO 00 NO
+ 202 19 12 = #_O__@O_O# : len=10 : NO 00 NO
+ 203 19 13 = #_O__@_OO# : len=10 : NO 00 NO
+ 204 19 14 = #_O__@OOO# : len=10 : S4 00 S4
+ 205 19 15 = #_O__@____# : len=11 : NO 00 NO
+ 206 19 16 = #_O__@O___# : len=11 : NO 00 NO
+ 207 19 17 = #_O__@_O__# : len=11 : NO 00 NO
+ 208 19 18 = #_O__@OO__# : len=11 : H3 13 H3
+ 209 19 19 = #_O__@__O_# : len=11 : NO 00 NO
+ 210 20 0 = #_O_O@# : len= 7 : NO 00 NO
+ 211 20 1 = #_O_O@_# : len= 8 : D3 20 D3
+ 212 20 2 = #_O_O@O# : len= 8 : D4 00 D4
+ 213 20 3 = #_O_O@__# : len= 9 : D3 20 D3
+ 214 20 4 = #_O_O@O_# : len= 9 : D4 00 D4
+ 215 20 5 = #_O_O@_O# : len= 9 : NO 00 D3
+ 216 20 6 = #_O_O@OO# : len= 9 : NO 00 D4
+ 217 20 7 = #_O_O@___# : len=10 : D3 20 D3
+ 218 20 8 = #_O_O@O__# : len=10 : D4 00 D4
+ 219 20 9 = #_O_O@_O_# : len=10 : NO 00 D3
+ 220 20 10 = #_O_O@OO_# : len=10 : S4 00 H4
+ 221 20 11 = #_O_O@__O# : len=10 : D3 20 D3
+ 222 20 12 = #_O_O@O_O# : len=10 : X4 00 X4
+ 223 20 13 = #_O_O@_OO# : len=10 : D4 00 D4
+ 224 20 14 = #_O_O@OOO# : len=10 : L5 00 L5
+ 225 20 15 = #_O_O@____# : len=11 : D3 20 D3
+ 226 20 16 = #_O_O@O___# : len=11 : D4 00 D4
+ 227 20 17 = #_O_O@_O__# : len=11 : NO 00 D3
+ 228 20 18 = #_O_O@OO__# : len=11 : S4 00 H4
+ 229 20 19 = #_O_O@__O_# : len=11 : D3 20 D3
+ 230 20 20 = #_O_O@O_O_# : len=11 : X4 00 X4
+ 231 21 0 = #_OO_@# : len= 7 : NO 00 NO
+ 232 21 1 = #_OO_@_# : len= 8 : D3 10 D3
+ 233 21 2 = #_OO_@O# : len= 8 : D4 00 D4
+ 234 21 3 = #_OO_@__# : len= 9 : D3 10 D3
+ 235 21 4 = #_OO_@O_# : len= 9 : D4 00 D4
+ 236 21 5 = #_OO_@_O# : len= 9 : NO 00 D3
+ 237 21 6 = #_OO_@OO# : len= 9 : NO 00 D4
+ 238 21 7 = #_OO_@___# : len=10 : D3 10 D3
+ 239 21 8 = #_OO_@O__# : len=10 : D4 00 D4
+ 240 21 9 = #_OO_@_O_# : len=10 : NO 00 D3
+ 241 21 10 = #_OO_@OO_# : len=10 : NO 00 D4
+ 242 21 11 = #_OO_@__O# : len=10 : D3 10 D3
+ 243 21 12 = #_OO_@O_O# : len=10 : D4 00 D4
+ 244 21 13 = #_OO_@_OO# : len=10 : NO 00 D3
+ 245 21 14 = #_OO_@OOO# : len=10 : NO 00 D4
+ 246 21 15 = #_OO_@____# : len=11 : D3 10 D3
+ 247 21 16 = #_OO_@O___# : len=11 : D4 00 D4
+ 248 21 17 = #_OO_@_O__# : len=11 : NO 00 D3
+ 249 21 18 = #_OO_@OO__# : len=11 : NO 00 D4
+ 250 21 19 = #_OO_@__O_# : len=11 : D3 10 D3
+ 251 21 20 = #_OO_@O_O_# : len=11 : D4 00 D4
+ 252 21 21 = #_OO_@_OO_# : len=11 : NO 00 D3
+ 253 22 0 = #_OOO@# : len= 7 : S4 00 S4
+ 254 22 1 = #_OOO@_# : len= 8 : H4 00 H4
+ 255 22 2 = #_OOO@O# : len= 8 : L5 00 L5
+ 256 22 3 = #_OOO@__# : len= 9 : H4 00 H4
+ 257 22 4 = #_OOO@O_# : len= 9 : L5 00 L5
+ 258 22 5 = #_OOO@_O# : len= 9 : S4 00 H4
+ 259 22 6 = #_OOO@OO# : len= 9 : L6 00 L6
+ 260 22 7 = #_OOO@___# : len=10 : H4 00 H4
+ 261 22 8 = #_OOO@O__# : len=10 : L5 00 L5
+ 262 22 9 = #_OOO@_O_# : len=10 : S4 00 H4
+ 263 22 10 = #_OOO@OO_# : len=10 : L6 00 L6
+ 264 22 11 = #_OOO@__O# : len=10 : H4 00 H4
+ 265 22 12 = #_OOO@O_O# : len=10 : L5 00 L5
+ 266 22 13 = #_OOO@_OO# : len=10 : S4 00 H4
+ 267 22 14 = #_OOO@OOO# : len=10 : L6 00 L6
+ 268 22 15 = #_OOO@____# : len=11 : H4 00 H4
+ 269 22 16 = #_OOO@O___# : len=11 : L5 00 L5
+ 270 22 17 = #_OOO@_O__# : len=11 : S4 00 H4
+ 271 22 18 = #_OOO@OO__# : len=11 : L6 00 L6
+ 272 22 19 = #_OOO@__O_# : len=11 : H4 00 H4
+ 273 22 20 = #_OOO@O_O_# : len=11 : L5 00 L5
+ 274 22 21 = #_OOO@_OO_# : len=11 : S4 00 H4
+ 275 22 22 = #_OOO@OOO_# : len=11 : L6 00 L6
+ 276 23 0 = #O___@# : len= 7 : NO 00 NO
+ 277 23 1 = #O___@_# : len= 8 : NO 00 NO
+ 278 23 2 = #O___@O# : len= 8 : NO 00 NO
+ 279 23 3 = #O___@__# : len= 9 : NO 00 NO
+ 280 23 4 = #O___@O_# : len= 9 : NO 00 NO
+ 281 23 5 = #O___@_O# : len= 9 : NO 00 NO
+ 282 23 6 = #O___@OO# : len= 9 : NO 00 NO
+ 283 23 7 = #O___@___# : len=10 : NO 00 NO
+ 284 23 8 = #O___@O__# : len=10 : NO 00 NO
+ 285 23 9 = #O___@_O_# : len=10 : NO 00 NO
+ 286 23 10 = #O___@OO_# : len=10 : H3 13 H3
+ 287 23 11 = #O___@__O# : len=10 : NO 00 NO
+ 288 23 12 = #O___@O_O# : len=10 : NO 00 NO
+ 289 23 13 = #O___@_OO# : len=10 : NO 00 NO
+ 290 23 14 = #O___@OOO# : len=10 : S4 00 S4
+ 291 23 15 = #O___@____# : len=11 : NO 00 NO
+ 292 23 16 = #O___@O___# : len=11 : NO 00 NO
+ 293 23 17 = #O___@_O__# : len=11 : NO 00 NO
+ 294 23 18 = #O___@OO__# : len=11 : H3 13 H3
+ 295 23 19 = #O___@__O_# : len=11 : NO 00 NO
+ 296 23 20 = #O___@O_O_# : len=11 : D3 02 D3
+ 297 23 21 = #O___@_OO_# : len=11 : D3 01 D3
+ 298 23 22 = #O___@OOO_# : len=11 : H4 00 H4
+ 299 23 23 = #O___@___O# : len=11 : NO 00 NO
+ 300 24 0 = #O__O@# : len= 7 : NO 00 NO
+ 301 24 1 = #O__O@_# : len= 8 : NO 00 NO
+ 302 24 2 = #O__O@O# : len= 8 : NO 00 NO
+ 303 24 3 = #O__O@__# : len= 9 : NO 00 NO
+ 304 24 4 = #O__O@O_# : len= 9 : NO 00 H3
+ 305 24 5 = #O__O@_O# : len= 9 : NO 00 NO
+ 306 24 6 = #O__O@OO# : len= 9 : S4 00 S4
+ 307 24 7 = #O__O@___# : len=10 : NO 00 NO
+ 308 24 8 = #O__O@O__# : len=10 : H3 22 H3
+ 309 24 9 = #O__O@_O_# : len=10 : D3 01 D3
+ 310 24 10 = #O__O@OO_# : len=10 : H4 00 H4
+ 311 24 11 = #O__O@__O# : len=10 : NO 00 NO
+ 312 24 12 = #O__O@O_O# : len=10 : D4 00 D4
+ 313 24 13 = #O__O@_OO# : len=10 : D4 00 D4
+ 314 24 14 = #O__O@OOO# : len=10 : L5 00 L5
+ 315 24 15 = #O__O@____# : len=11 : NO 00 NO
+ 316 24 16 = #O__O@O___# : len=11 : H3 22 H3
+ 317 24 17 = #O__O@_O__# : len=11 : D3 01 D3
+ 318 24 18 = #O__O@OO__# : len=11 : H4 00 H4
+ 319 24 19 = #O__O@__O_# : len=11 : NO 00 NO
+ 320 24 20 = #O__O@O_O_# : len=11 : D4 00 D4
+ 321 24 21 = #O__O@_OO_# : len=11 : D4 00 D4
+ 322 24 22 = #O__O@OOO_# : len=11 : L5 00 L5
+ 323 24 23 = #O__O@___O# : len=11 : NO 00 NO
+ 324 24 24 = #O__O@O__O# : len=11 : NO 00 H3
+ 325 25 0 = #O_O_@# : len= 7 : NO 00 NO
+ 326 25 1 = #O_O_@_# : len= 8 : NO 00 NO
+ 327 25 2 = #O_O_@O# : len= 8 : NO 00 NO
+ 328 25 3 = #O_O_@__# : len= 9 : NO 00 NO
+ 329 25 4 = #O_O_@O_# : len= 9 : NO 00 D3
+ 330 25 5 = #O_O_@_O# : len= 9 : NO 00 NO
+ 331 25 6 = #O_O_@OO# : len= 9 : D4 00 D4
+ 332 25 7 = #O_O_@___# : len=10 : NO 00 NO
+ 333 25 8 = #O_O_@O__# : len=10 : NO 00 D3
+ 334 25 9 = #O_O_@_O_# : len=10 : NO 00 NO
+ 335 25 10 = #O_O_@OO_# : len=10 : D4 00 D4
+ 336 25 11 = #O_O_@__O# : len=10 : NO 00 NO
+ 337 25 12 = #O_O_@O_O# : len=10 : NO 00 D3
+ 338 25 13 = #O_O_@_OO# : len=10 : NO 00 NO
+ 339 25 14 = #O_O_@OOO# : len=10 : NO 00 D4
+ 340 25 15 = #O_O_@____# : len=11 : NO 00 NO
+ 341 25 16 = #O_O_@O___# : len=11 : NO 00 D3
+ 342 25 17 = #O_O_@_O__# : len=11 : NO 00 NO
+ 343 25 18 = #O_O_@OO__# : len=11 : D4 00 D4
+ 344 25 19 = #O_O_@__O_# : len=11 : NO 00 NO
+ 345 25 20 = #O_O_@O_O_# : len=11 : NO 00 D3
+ 346 25 21 = #O_O_@_OO_# : len=11 : NO 00 D3
+ 347 25 22 = #O_O_@OOO_# : len=11 : S4 00 H4
+ 348 25 23 = #O_O_@___O# : len=11 : NO 00 NO
+ 349 25 24 = #O_O_@O__O# : len=11 : NO 00 D3
+ 350 25 25 = #O_O_@_O_O# : len=11 : NO 00 NO
+ 351 26 0 = #O_OO@# : len= 7 : D4 00 D4
+ 352 26 1 = #O_OO@_# : len= 8 : D4 00 D4
+ 353 26 2 = #O_OO@O# : len= 8 : NO 00 D4
+ 354 26 3 = #O_OO@__# : len= 9 : D4 00 D4
+ 355 26 4 = #O_OO@O_# : len= 9 : S4 00 H4
+ 356 26 5 = #O_OO@_O# : len= 9 : X4 00 X4
+ 357 26 6 = #O_OO@OO# : len= 9 : L5 00 L5
+ 358 26 7 = #O_OO@___# : len=10 : D4 00 D4
+ 359 26 8 = #O_OO@O__# : len=10 : S4 00 H4
+ 360 26 9 = #O_OO@_O_# : len=10 : X4 00 X4
+ 361 26 10 = #O_OO@OO_# : len=10 : L5 00 L5
+ 362 26 11 = #O_OO@__O# : len=10 : D4 00 D4
+ 363 26 12 = #O_OO@O_O# : len=10 : NO 00 H4
+ 364 26 13 = #O_OO@_OO# : len=10 : D4 00 X4
+ 365 26 14 = #O_OO@OOO# : len=10 : L6 00 L6
+ 366 26 15 = #O_OO@____# : len=11 : D4 00 D4
+ 367 26 16 = #O_OO@O___# : len=11 : S4 00 H4
+ 368 26 17 = #O_OO@_O__# : len=11 : X4 00 X4
+ 369 26 18 = #O_OO@OO__# : len=11 : L5 00 L5
+ 370 26 19 = #O_OO@__O_# : len=11 : D4 00 D4
+ 371 26 20 = #O_OO@O_O_# : len=11 : NO 00 H4
+ 372 26 21 = #O_OO@_OO_# : len=11 : D4 00 X4
+ 373 26 22 = #O_OO@OOO_# : len=11 : L6 00 L6
+ 374 26 23 = #O_OO@___O# : len=11 : D4 00 D4
+ 375 26 24 = #O_OO@O__O# : len=11 : S4 00 H4
+ 376 26 25 = #O_OO@_O_O# : len=11 : X4 00 X4
+ 377 26 26 = #O_OO@OO_O# : len=11 : L5 00 L5
+ 378 27 0 = #OO__@# : len= 7 : NO 00 NO
+ 379 27 1 = #OO__@_# : len= 8 : NO 00 NO
+ 380 27 2 = #OO__@O# : len= 8 : NO 00 NO
+ 381 27 3 = #OO__@__# : len= 9 : NO 00 NO
+ 382 27 4 = #OO__@O_# : len= 9 : NO 00 NO
+ 383 27 5 = #OO__@_O# : len= 9 : NO 00 NO
+ 384 27 6 = #OO__@OO# : len= 9 : NO 00 NO
+ 385 27 7 = #OO__@___# : len=10 : NO 00 NO
+ 386 27 8 = #OO__@O__# : len=10 : NO 00 NO
+ 387 27 9 = #OO__@_O_# : len=10 : NO 00 NO
+ 388 27 10 = #OO__@OO_# : len=10 : NO 00 H3
+ 389 27 11 = #OO__@__O# : len=10 : NO 00 NO
+ 390 27 12 = #OO__@O_O# : len=10 : NO 00 NO
+ 391 27 13 = #OO__@_OO# : len=10 : NO 00 NO
+ 392 27 14 = #OO__@OOO# : len=10 : S4 00 S4
+ 393 27 15 = #OO__@____# : len=11 : NO 00 NO
+ 394 27 16 = #OO__@O___# : len=11 : NO 00 NO
+ 395 27 17 = #OO__@_O__# : len=11 : NO 00 NO
+ 396 27 18 = #OO__@OO__# : len=11 : H3 13 H3
+ 397 27 19 = #OO__@__O_# : len=11 : NO 00 NO
+ 398 27 20 = #OO__@O_O_# : len=11 : D3 02 D3
+ 399 27 21 = #OO__@_OO_# : len=11 : D3 01 D3
+ 400 27 22 = #OO__@OOO_# : len=11 : H4 00 H4
+ 401 27 23 = #OO__@___O# : len=11 : NO 00 NO
+ 402 27 24 = #OO__@O__O# : len=11 : NO 00 NO
+ 403 27 25 = #OO__@_O_O# : len=11 : NO 00 NO
+ 404 27 26 = #OO__@OO_O# : len=11 : D4 00 D4
+ 405 27 27 = #OO__@__OO# : len=11 : NO 00 NO
+ 406 28 0 = #OO_O@# : len= 7 : D4 00 D4
+ 407 28 1 = #OO_O@_# : len= 8 : D4 00 D4
+ 408 28 2 = #OO_O@O# : len= 8 : NO 00 D4
+ 409 28 3 = #OO_O@__# : len= 9 : D4 00 D4
+ 410 28 4 = #OO_O@O_# : len= 9 : NO 00 D4
+ 411 28 5 = #OO_O@_O# : len= 9 : D4 00 D4
+ 412 28 6 = #OO_O@OO# : len= 9 : NO 00 D4
+ 413 28 7 = #OO_O@___# : len=10 : D4 00 D4
+ 414 28 8 = #OO_O@O__# : len=10 : NO 00 D4
+ 415 28 9 = #OO_O@_O_# : len=10 : D4 00 D4
+ 416 28 10 = #OO_O@OO_# : len=10 : S4 00 H4
+ 417 28 11 = #OO_O@__O# : len=10 : D4 00 D4
+ 418 28 12 = #OO_O@O_O# : len=10 : D4 00 X4
+ 419 28 13 = #OO_O@_OO# : len=10 : X4 00 X4
+ 420 28 14 = #OO_O@OOO# : len=10 : L5 00 L5
+ 421 28 15 = #OO_O@____# : len=11 : D4 00 D4
+ 422 28 16 = #OO_O@O___# : len=11 : NO 00 D4
+ 423 28 17 = #OO_O@_O__# : len=11 : D4 00 D4
+ 424 28 18 = #OO_O@OO__# : len=11 : S4 00 H4
+ 425 28 19 = #OO_O@__O_# : len=11 : D4 00 D4
+ 426 28 20 = #OO_O@O_O_# : len=11 : D4 00 X4
+ 427 28 21 = #OO_O@_OO_# : len=11 : X4 00 X4
+ 428 28 22 = #OO_O@OOO_# : len=11 : L5 00 L5
+ 429 28 23 = #OO_O@___O# : len=11 : D4 00 D4
+ 430 28 24 = #OO_O@O__O# : len=11 : NO 00 D4
+ 431 28 25 = #OO_O@_O_O# : len=11 : D4 00 D4
+ 432 28 26 = #OO_O@OO_O# : len=11 : NO 00 H4
+ 433 28 27 = #OO_O@__OO# : len=11 : D4 00 D4
+ 434 28 28 = #OO_O@O_OO# : len=11 : NO 00 X4
+ 435 29 0 = #OOO_@# : len= 7 : D4 00 D4
+ 436 29 1 = #OOO_@_# : len= 8 : D4 00 D4
+ 437 29 2 = #OOO_@O# : len= 8 : NO 00 D4
+ 438 29 3 = #OOO_@__# : len= 9 : D4 00 D4
+ 439 29 4 = #OOO_@O_# : len= 9 : NO 00 D4
+ 440 29 5 = #OOO_@_O# : len= 9 : D4 00 D4
+ 441 29 6 = #OOO_@OO# : len= 9 : NO 00 D4
+ 442 29 7 = #OOO_@___# : len=10 : D4 00 D4
+ 443 29 8 = #OOO_@O__# : len=10 : NO 00 D4
+ 444 29 9 = #OOO_@_O_# : len=10 : D4 00 D4
+ 445 29 10 = #OOO_@OO_# : len=10 : NO 00 D4
+ 446 29 11 = #OOO_@__O# : len=10 : D4 00 D4
+ 447 29 12 = #OOO_@O_O# : len=10 : NO 00 D4
+ 448 29 13 = #OOO_@_OO# : len=10 : D4 00 D4
+ 449 29 14 = #OOO_@OOO# : len=10 : NO 00 D4
+ 450 29 15 = #OOO_@____# : len=11 : D4 00 D4
+ 451 29 16 = #OOO_@O___# : len=11 : NO 00 D4
+ 452 29 17 = #OOO_@_O__# : len=11 : D4 00 D4
+ 453 29 18 = #OOO_@OO__# : len=11 : NO 00 D4
+ 454 29 19 = #OOO_@__O_# : len=11 : D4 00 D4
+ 455 29 20 = #OOO_@O_O_# : len=11 : NO 00 D4
+ 456 29 21 = #OOO_@_OO_# : len=11 : D4 00 D4
+ 457 29 22 = #OOO_@OOO_# : len=11 : S4 00 H4
+ 458 29 23 = #OOO_@___O# : len=11 : D4 00 D4
+ 459 29 24 = #OOO_@O__O# : len=11 : NO 00 D4
+ 460 29 25 = #OOO_@_O_O# : len=11 : D4 00 D4
+ 461 29 26 = #OOO_@OO_O# : len=11 : D4 00 X4
+ 462 29 27 = #OOO_@__OO# : len=11 : D4 00 D4
+ 463 29 28 = #OOO_@O_OO# : len=11 : D4 00 X4
+ 464 29 29 = #OOO_@_OOO# : len=11 : X4 00 X4
+ 465 30 0 = #OOOO@# : len= 7 : L5 00 L5
+ 466 30 1 = #OOOO@_# : len= 8 : L5 00 L5
+ 467 30 2 = #OOOO@O# : len= 8 : L6 00 L6
+ 468 30 3 = #OOOO@__# : len= 9 : L5 00 L5
+ 469 30 4 = #OOOO@O_# : len= 9 : L6 00 L6
+ 470 30 5 = #OOOO@_O# : len= 9 : L5 00 L5
+ 471 30 6 = #OOOO@OO# : len= 9 : L6 00 L6
+ 472 30 7 = #OOOO@___# : len=10 : L5 00 L5
+ 473 30 8 = #OOOO@O__# : len=10 : L6 00 L6
+ 474 30 9 = #OOOO@_O_# : len=10 : L5 00 L5
+ 475 30 10 = #OOOO@OO_# : len=10 : L6 00 L6
+ 476 30 11 = #OOOO@__O# : len=10 : L5 00 L5
+ 477 30 12 = #OOOO@O_O# : len=10 : L6 00 L6
+ 478 30 13 = #OOOO@_OO# : len=10 : L5 00 L5
+ 479 30 14 = #OOOO@OOO# : len=10 : L6 00 L6
+ 480 30 15 = #OOOO@____# : len=11 : L5 00 L5
+ 481 30 16 = #OOOO@O___# : len=11 : L6 00 L6
+ 482 30 17 = #OOOO@_O__# : len=11 : L5 00 L5
+ 483 30 18 = #OOOO@OO__# : len=11 : L6 00 L6
+ 484 30 19 = #OOOO@__O_# : len=11 : L5 00 L5
+ 485 30 20 = #OOOO@O_O_# : len=11 : L6 00 L6
+ 486 30 21 = #OOOO@_OO_# : len=11 : L5 00 L5
+ 487 30 22 = #OOOO@OOO_# : len=11 : L6 00 L6
+ 488 30 23 = #OOOO@___O# : len=11 : L5 00 L5
+ 489 30 24 = #OOOO@O__O# : len=11 : L6 00 L6
+ 490 30 25 = #OOOO@_O_O# : len=11 : L5 00 L5
+ 491 30 26 = #OOOO@OO_O# : len=11 : L6 00 L6
+ 492 30 27 = #OOOO@__OO# : len=11 : L5 00 L5
+ 493 30 28 = #OOOO@O_OO# : len=11 : L6 00 L6
+ 494 30 29 = #OOOO@_OOO# : len=11 : L5 00 L5
+ 495 30 30 = #OOOO@OOOO# : len=11 : L6 00 L6
+ 496 31 0 = _____@# : len= 7 : NO 00 NO
+ 497 31 1 = _____@_# : len= 8 : NO 00 NO
+ 498 31 2 = _____@O# : len= 8 : NO 00 NO
+ 499 31 3 = _____@__# : len= 9 : NO 00 NO
+ 500 31 4 = _____@O_# : len= 9 : NO 00 NO
+ 501 31 5 = _____@_O# : len= 9 : NO 00 NO
+ 502 31 6 = _____@OO# : len= 9 : NO 00 NO
+ 503 31 7 = _____@___# : len=10 : NO 00 NO
+ 504 31 8 = _____@O__# : len=10 : NO 00 NO
+ 505 31 9 = _____@_O_# : len=10 : NO 00 NO
+ 506 31 10 = _____@OO_# : len=10 : H3 13 H3
+ 507 31 11 = _____@__O# : len=10 : NO 00 NO
+ 508 31 12 = _____@O_O# : len=10 : NO 00 NO
+ 509 31 13 = _____@_OO# : len=10 : NO 00 NO
+ 510 31 14 = _____@OOO# : len=10 : S4 00 S4
+ 511 31 15 = _____@____# : len=11 : NO 00 NO
+ 512 31 16 = _____@O___# : len=11 : NO 00 NO
+ 513 31 17 = _____@_O__# : len=11 : NO 00 NO
+ 514 31 18 = _____@OO__# : len=11 : H3 13 H3
+ 515 31 19 = _____@__O_# : len=11 : NO 00 NO
+ 516 31 20 = _____@O_O_# : len=11 : D3 02 D3
+ 517 31 21 = _____@_OO_# : len=11 : D3 01 D3
+ 518 31 22 = _____@OOO_# : len=11 : H4 00 H4
+ 519 31 23 = _____@___O# : len=11 : NO 00 NO
+ 520 31 24 = _____@O__O# : len=11 : NO 00 NO
+ 521 31 25 = _____@_O_O# : len=11 : NO 00 NO
+ 522 31 26 = _____@OO_O# : len=11 : D4 00 D4
+ 523 31 27 = _____@__OO# : len=11 : NO 00 NO
+ 524 31 28 = _____@O_OO# : len=11 : D4 00 D4
+ 525 31 29 = _____@_OOO# : len=11 : D4 00 D4
+ 526 31 30 = _____@OOOO# : len=11 : L5 00 L5
+ 527 31 31 = _____@_____ : len=11 : NO 00 NO
+ 528 32 0 = ____O@# : len= 7 : NO 00 NO
+ 529 32 1 = ____O@_# : len= 8 : NO 00 NO
+ 530 32 2 = ____O@O# : len= 8 : NO 00 NO
+ 531 32 3 = ____O@__# : len= 9 : NO 00 NO
+ 532 32 4 = ____O@O_# : len= 9 : H3 22 H3
+ 533 32 5 = ____O@_O# : len= 9 : NO 00 NO
+ 534 32 6 = ____O@OO# : len= 9 : S4 00 S4
+ 535 32 7 = ____O@___# : len=10 : NO 00 NO
+ 536 32 8 = ____O@O__# : len=10 : H3 22 H3
+ 537 32 9 = ____O@_O_# : len=10 : D3 01 D3
+ 538 32 10 = ____O@OO_# : len=10 : H4 00 H4
+ 539 32 11 = ____O@__O# : len=10 : NO 00 NO
+ 540 32 12 = ____O@O_O# : len=10 : D4 00 D4
+ 541 32 13 = ____O@_OO# : len=10 : D4 00 D4
+ 542 32 14 = ____O@OOO# : len=10 : L5 00 L5
+ 543 32 15 = ____O@____# : len=11 : NO 00 NO
+ 544 32 16 = ____O@O___# : len=11 : H3 22 H3
+ 545 32 17 = ____O@_O__# : len=11 : D3 01 D3
+ 546 32 18 = ____O@OO__# : len=11 : H4 00 H4
+ 547 32 19 = ____O@__O_# : len=11 : NO 00 NO
+ 548 32 20 = ____O@O_O_# : len=11 : D4 00 D4
+ 549 32 21 = ____O@_OO_# : len=11 : D4 00 D4
+ 550 32 22 = ____O@OOO_# : len=11 : L5 00 L5
+ 551 32 23 = ____O@___O# : len=11 : NO 00 NO
+ 552 32 24 = ____O@O__O# : len=11 : H3 22 H3
+ 553 32 25 = ____O@_O_O# : len=11 : NO 00 D3
+ 554 32 26 = ____O@OO_O# : len=11 : S4 00 H4
+ 555 32 27 = ____O@__OO# : len=11 : NO 00 NO
+ 556 32 28 = ____O@O_OO# : len=11 : NO 00 D4
+ 557 32 29 = ____O@_OOO# : len=11 : NO 00 D4
+ 558 32 30 = ____O@OOOO# : len=11 : L6 00 L6
+ 559 32 31 = ____O@_____ : len=11 : NO 00 NO
+ 560 32 32 = ____O@O____ : len=11 : H3 22 H3
+ 561 33 0 = ___O_@# : len= 7 : NO 00 NO
+ 562 33 1 = ___O_@_# : len= 8 : NO 00 NO
+ 563 33 2 = ___O_@O# : len= 8 : NO 00 NO
+ 564 33 3 = ___O_@__# : len= 9 : NO 00 NO
+ 565 33 4 = ___O_@O_# : len= 9 : D3 10 D3
+ 566 33 5 = ___O_@_O# : len= 9 : NO 00 NO
+ 567 33 6 = ___O_@OO# : len= 9 : D4 00 D4
+ 568 33 7 = ___O_@___# : len=10 : NO 00 NO
+ 569 33 8 = ___O_@O__# : len=10 : D3 10 D3
+ 570 33 9 = ___O_@_O_# : len=10 : NO 00 NO
+ 571 33 10 = ___O_@OO_# : len=10 : D4 00 D4
+ 572 33 11 = ___O_@__O# : len=10 : NO 00 NO
+ 573 33 12 = ___O_@O_O# : len=10 : NO 00 D3
+ 574 33 13 = ___O_@_OO# : len=10 : NO 00 NO
+ 575 33 14 = ___O_@OOO# : len=10 : NO 00 D4
+ 576 33 15 = ___O_@____# : len=11 : NO 00 NO
+ 577 33 16 = ___O_@O___# : len=11 : D3 10 D3
+ 578 33 17 = ___O_@_O__# : len=11 : NO 00 NO
+ 579 33 18 = ___O_@OO__# : len=11 : D4 00 D4
+ 580 33 19 = ___O_@__O_# : len=11 : NO 00 NO
+ 581 33 20 = ___O_@O_O_# : len=11 : NO 00 D3
+ 582 33 21 = ___O_@_OO_# : len=11 : NO 00 D3
+ 583 33 22 = ___O_@OOO_# : len=11 : S4 00 H4
+ 584 33 23 = ___O_@___O# : len=11 : NO 00 NO
+ 585 33 24 = ___O_@O__O# : len=11 : D3 10 D3
+ 586 33 25 = ___O_@_O_O# : len=11 : NO 00 NO
+ 587 33 26 = ___O_@OO_O# : len=11 : X4 00 X4
+ 588 33 27 = ___O_@__OO# : len=11 : NO 00 NO
+ 589 33 28 = ___O_@O_OO# : len=11 : D4 00 D4
+ 590 33 29 = ___O_@_OOO# : len=11 : D4 00 D4
+ 591 33 30 = ___O_@OOOO# : len=11 : L5 00 L5
+ 592 33 31 = ___O_@_____ : len=11 : NO 00 NO
+ 593 33 32 = ___O_@O____ : len=11 : D3 10 D3
+ 594 33 33 = ___O_@_O___ : len=11 : NO 00 NO
+ 595 34 0 = ___OO@# : len= 7 : NO 00 NO
+ 596 34 1 = ___OO@_# : len= 8 : H3 31 H3
+ 597 34 2 = ___OO@O# : len= 8 : S4 00 S4
+ 598 34 3 = ___OO@__# : len= 9 : H3 31 H3
+ 599 34 4 = ___OO@O_# : len= 9 : H4 00 H4
+ 600 34 5 = ___OO@_O# : len= 9 : D4 00 D4
+ 601 34 6 = ___OO@OO# : len= 9 : L5 00 L5
+ 602 34 7 = ___OO@___# : len=10 : H3 31 H3
+ 603 34 8 = ___OO@O__# : len=10 : H4 00 H4
+ 604 34 9 = ___OO@_O_# : len=10 : D4 00 D4
+ 605 34 10 = ___OO@OO_# : len=10 : L5 00 L5
+ 606 34 11 = ___OO@__O# : len=10 : H3 31 H3
+ 607 34 12 = ___OO@O_O# : len=10 : S4 00 H4
+ 608 34 13 = ___OO@_OO# : len=10 : NO 00 D4
+ 609 34 14 = ___OO@OOO# : len=10 : L6 00 L6
+ 610 34 15 = ___OO@____# : len=11 : H3 31 H3
+ 611 34 16 = ___OO@O___# : len=11 : H4 00 H4
+ 612 34 17 = ___OO@_O__# : len=11 : D4 00 D4
+ 613 34 18 = ___OO@OO__# : len=11 : L5 00 L5
+ 614 34 19 = ___OO@__O_# : len=11 : H3 31 H3
+ 615 34 20 = ___OO@O_O_# : len=11 : S4 00 H4
+ 616 34 21 = ___OO@_OO_# : len=11 : NO 00 D4
+ 617 34 22 = ___OO@OOO_# : len=11 : L6 00 L6
+ 618 34 23 = ___OO@___O# : len=11 : H3 31 H3
+ 619 34 24 = ___OO@O__O# : len=11 : H4 00 H4
+ 620 34 25 = ___OO@_O_O# : len=11 : D4 00 D4
+ 621 34 26 = ___OO@OO_O# : len=11 : L5 00 L5
+ 622 34 27 = ___OO@__OO# : len=11 : H3 31 H3
+ 623 34 28 = ___OO@O_OO# : len=11 : S4 00 H4
+ 624 34 29 = ___OO@_OOO# : len=11 : NO 00 D4
+ 625 34 30 = ___OO@OOOO# : len=11 : L6 00 L6
+ 626 34 31 = ___OO@_____ : len=11 : H3 31 H3
+ 627 34 32 = ___OO@O____ : len=11 : H4 00 H4
+ 628 34 33 = ___OO@_O___ : len=11 : D4 00 D4
+ 629 34 34 = ___OO@OO___ : len=11 : L5 00 L5
+ 630 35 0 = __O__@# : len= 7 : NO 00 NO
+ 631 35 1 = __O__@_# : len= 8 : NO 00 NO
+ 632 35 2 = __O__@O# : len= 8 : NO 00 NO
+ 633 35 3 = __O__@__# : len= 9 : NO 00 NO
+ 634 35 4 = __O__@O_# : len= 9 : NO 00 NO
+ 635 35 5 = __O__@_O# : len= 9 : NO 00 NO
+ 636 35 6 = __O__@OO# : len= 9 : NO 00 NO
+ 637 35 7 = __O__@___# : len=10 : NO 00 NO
+ 638 35 8 = __O__@O__# : len=10 : NO 00 NO
+ 639 35 9 = __O__@_O_# : len=10 : NO 00 NO
+ 640 35 10 = __O__@OO_# : len=10 : NO 00 H3
+ 641 35 11 = __O__@__O# : len=10 : NO 00 NO
+ 642 35 12 = __O__@O_O# : len=10 : NO 00 NO
+ 643 35 13 = __O__@_OO# : len=10 : NO 00 NO
+ 644 35 14 = __O__@OOO# : len=10 : S4 00 S4
+ 645 35 15 = __O__@____# : len=11 : NO 00 NO
+ 646 35 16 = __O__@O___# : len=11 : NO 00 NO
+ 647 35 17 = __O__@_O__# : len=11 : NO 00 NO
+ 648 35 18 = __O__@OO__# : len=11 : H3 13 H3
+ 649 35 19 = __O__@__O_# : len=11 : NO 00 NO
+ 650 35 20 = __O__@O_O_# : len=11 : D3 02 D3
+ 651 35 21 = __O__@_OO_# : len=11 : D3 01 D3
+ 652 35 22 = __O__@OOO_# : len=11 : H4 00 H4
+ 653 35 23 = __O__@___O# : len=11 : NO 00 NO
+ 654 35 24 = __O__@O__O# : len=11 : NO 00 NO
+ 655 35 25 = __O__@_O_O# : len=11 : NO 00 NO
+ 656 35 26 = __O__@OO_O# : len=11 : D4 00 D4
+ 657 35 27 = __O__@__OO# : len=11 : NO 00 NO
+ 658 35 28 = __O__@O_OO# : len=11 : D4 00 D4
+ 659 35 29 = __O__@_OOO# : len=11 : D4 00 D4
+ 660 35 30 = __O__@OOOO# : len=11 : L5 00 L5
+ 661 35 31 = __O__@_____ : len=11 : NO 00 NO
+ 662 35 32 = __O__@O____ : len=11 : NO 00 NO
+ 663 35 33 = __O__@_O___ : len=11 : NO 00 NO
+ 664 35 34 = __O__@OO___ : len=11 : H3 13 H3
+ 665 35 35 = __O__@__O__ : len=11 : NO 00 NO
+ 666 36 0 = __O_O@# : len= 7 : NO 00 NO
+ 667 36 1 = __O_O@_# : len= 8 : D3 20 D3
+ 668 36 2 = __O_O@O# : len= 8 : D4 00 D4
+ 669 36 3 = __O_O@__# : len= 9 : D3 20 D3
+ 670 36 4 = __O_O@O_# : len= 9 : D4 00 D4
+ 671 36 5 = __O_O@_O# : len= 9 : NO 00 D3
+ 672 36 6 = __O_O@OO# : len= 9 : NO 00 D4
+ 673 36 7 = __O_O@___# : len=10 : D3 20 D3
+ 674 36 8 = __O_O@O__# : len=10 : D4 00 D4
+ 675 36 9 = __O_O@_O_# : len=10 : NO 00 D3
+ 676 36 10 = __O_O@OO_# : len=10 : S4 00 H4
+ 677 36 11 = __O_O@__O# : len=10 : D3 20 D3
+ 678 36 12 = __O_O@O_O# : len=10 : X4 00 X4
+ 679 36 13 = __O_O@_OO# : len=10 : D4 00 D4
+ 680 36 14 = __O_O@OOO# : len=10 : L5 00 L5
+ 681 36 15 = __O_O@____# : len=11 : D3 20 D3
+ 682 36 16 = __O_O@O___# : len=11 : D4 00 D4
+ 683 36 17 = __O_O@_O__# : len=11 : NO 00 D3
+ 684 36 18 = __O_O@OO__# : len=11 : S4 00 H4
+ 685 36 19 = __O_O@__O_# : len=11 : D3 20 D3
+ 686 36 20 = __O_O@O_O_# : len=11 : X4 00 X4
+ 687 36 21 = __O_O@_OO_# : len=11 : D4 00 D4
+ 688 36 22 = __O_O@OOO_# : len=11 : L5 00 L5
+ 689 36 23 = __O_O@___O# : len=11 : D3 20 D3
+ 690 36 24 = __O_O@O__O# : len=11 : D4 00 D4
+ 691 36 25 = __O_O@_O_O# : len=11 : NO 00 D3
+ 692 36 26 = __O_O@OO_O# : len=11 : NO 00 H4
+ 693 36 27 = __O_O@__OO# : len=11 : D3 20 D3
+ 694 36 28 = __O_O@O_OO# : len=11 : D4 00 X4
+ 695 36 29 = __O_O@_OOO# : len=11 : NO 00 D4
+ 696 36 30 = __O_O@OOOO# : len=11 : L6 00 L6
+ 697 36 31 = __O_O@_____ : len=11 : D3 20 D3
+ 698 36 32 = __O_O@O____ : len=11 : D4 00 D4
+ 699 36 33 = __O_O@_O___ : len=11 : NO 00 D3
+ 700 36 34 = __O_O@OO___ : len=11 : S4 00 H4
+ 701 36 35 = __O_O@__O__ : len=11 : D3 20 D3
+ 702 36 36 = __O_O@O_O__ : len=11 : X4 00 X4
+ 703 37 0 = __OO_@# : len= 7 : NO 00 NO
+ 704 37 1 = __OO_@_# : len= 8 : D3 10 D3
+ 705 37 2 = __OO_@O# : len= 8 : D4 00 D4
+ 706 37 3 = __OO_@__# : len= 9 : D3 10 D3
+ 707 37 4 = __OO_@O_# : len= 9 : D4 00 D4
+ 708 37 5 = __OO_@_O# : len= 9 : NO 00 D3
+ 709 37 6 = __OO_@OO# : len= 9 : NO 00 D4
+ 710 37 7 = __OO_@___# : len=10 : D3 10 D3
+ 711 37 8 = __OO_@O__# : len=10 : D4 00 D4
+ 712 37 9 = __OO_@_O_# : len=10 : NO 00 D3
+ 713 37 10 = __OO_@OO_# : len=10 : NO 00 D4
+ 714 37 11 = __OO_@__O# : len=10 : D3 10 D3
+ 715 37 12 = __OO_@O_O# : len=10 : D4 00 D4
+ 716 37 13 = __OO_@_OO# : len=10 : NO 00 D3
+ 717 37 14 = __OO_@OOO# : len=10 : NO 00 D4
+ 718 37 15 = __OO_@____# : len=11 : D3 10 D3
+ 719 37 16 = __OO_@O___# : len=11 : D4 00 D4
+ 720 37 17 = __OO_@_O__# : len=11 : NO 00 D3
+ 721 37 18 = __OO_@OO__# : len=11 : NO 00 D4
+ 722 37 19 = __OO_@__O_# : len=11 : D3 10 D3
+ 723 37 20 = __OO_@O_O_# : len=11 : D4 00 D4
+ 724 37 21 = __OO_@_OO_# : len=11 : NO 00 D3
+ 725 37 22 = __OO_@OOO_# : len=11 : S4 00 H4
+ 726 37 23 = __OO_@___O# : len=11 : D3 10 D3
+ 727 37 24 = __OO_@O__O# : len=11 : D4 00 D4
+ 728 37 25 = __OO_@_O_O# : len=11 : NO 00 D3
+ 729 37 26 = __OO_@OO_O# : len=11 : D4 00 X4
+ 730 37 27 = __OO_@__OO# : len=11 : D3 10 D3
+ 731 37 28 = __OO_@O_OO# : len=11 : X4 00 X4
+ 732 37 29 = __OO_@_OOO# : len=11 : D4 00 D4
+ 733 37 30 = __OO_@OOOO# : len=11 : L5 00 L5
+ 734 37 31 = __OO_@_____ : len=11 : D3 10 D3
+ 735 37 32 = __OO_@O____ : len=11 : D4 00 D4
+ 736 37 33 = __OO_@_O___ : len=11 : NO 00 D3
+ 737 37 34 = __OO_@OO___ : len=11 : NO 00 D4
+ 738 37 35 = __OO_@__O__ : len=11 : D3 10 D3
+ 739 37 36 = __OO_@O_O__ : len=11 : D4 00 D4
+ 740 37 37 = __OO_@_OO__ : len=11 : NO 00 D3
+ 741 38 0 = __OOO@# : len= 7 : S4 00 S4
+ 742 38 1 = __OOO@_# : len= 8 : H4 00 H4
+ 743 38 2 = __OOO@O# : len= 8 : L5 00 L5
+ 744 38 3 = __OOO@__# : len= 9 : H4 00 H4
+ 745 38 4 = __OOO@O_# : len= 9 : L5 00 L5
+ 746 38 5 = __OOO@_O# : len= 9 : S4 00 H4
+ 747 38 6 = __OOO@OO# : len= 9 : L6 00 L6
+ 748 38 7 = __OOO@___# : len=10 : H4 00 H4
+ 749 38 8 = __OOO@O__# : len=10 : L5 00 L5
+ 750 38 9 = __OOO@_O_# : len=10 : S4 00 H4
+ 751 38 10 = __OOO@OO_# : len=10 : L6 00 L6
+ 752 38 11 = __OOO@__O# : len=10 : H4 00 H4
+ 753 38 12 = __OOO@O_O# : len=10 : L5 00 L5
+ 754 38 13 = __OOO@_OO# : len=10 : S4 00 H4
+ 755 38 14 = __OOO@OOO# : len=10 : L6 00 L6
+ 756 38 15 = __OOO@____# : len=11 : H4 00 H4
+ 757 38 16 = __OOO@O___# : len=11 : L5 00 L5
+ 758 38 17 = __OOO@_O__# : len=11 : S4 00 H4
+ 759 38 18 = __OOO@OO__# : len=11 : L6 00 L6
+ 760 38 19 = __OOO@__O_# : len=11 : H4 00 H4
+ 761 38 20 = __OOO@O_O_# : len=11 : L5 00 L5
+ 762 38 21 = __OOO@_OO_# : len=11 : S4 00 H4
+ 763 38 22 = __OOO@OOO_# : len=11 : L6 00 L6
+ 764 38 23 = __OOO@___O# : len=11 : H4 00 H4
+ 765 38 24 = __OOO@O__O# : len=11 : L5 00 L5
+ 766 38 25 = __OOO@_O_O# : len=11 : S4 00 H4
+ 767 38 26 = __OOO@OO_O# : len=11 : L6 00 L6
+ 768 38 27 = __OOO@__OO# : len=11 : H4 00 H4
+ 769 38 28 = __OOO@O_OO# : len=11 : L5 00 L5
+ 770 38 29 = __OOO@_OOO# : len=11 : S4 00 H4
+ 771 38 30 = __OOO@OOOO# : len=11 : L6 00 L6
+ 772 38 31 = __OOO@_____ : len=11 : H4 00 H4
+ 773 38 32 = __OOO@O____ : len=11 : L5 00 L5
+ 774 38 33 = __OOO@_O___ : len=11 : S4 00 H4
+ 775 38 34 = __OOO@OO___ : len=11 : L6 00 L6
+ 776 38 35 = __OOO@__O__ : len=11 : H4 00 H4
+ 777 38 36 = __OOO@O_O__ : len=11 : L5 00 L5
+ 778 38 37 = __OOO@_OO__ : len=11 : S4 00 H4
+ 779 38 38 = __OOO@OOO__ : len=11 : L6 00 L6
+ 780 39 0 = _O___@# : len= 7 : NO 00 NO
+ 781 39 1 = _O___@_# : len= 8 : NO 00 NO
+ 782 39 2 = _O___@O# : len= 8 : NO 00 NO
+ 783 39 3 = _O___@__# : len= 9 : NO 00 NO
+ 784 39 4 = _O___@O_# : len= 9 : NO 00 NO
+ 785 39 5 = _O___@_O# : len= 9 : NO 00 NO
+ 786 39 6 = _O___@OO# : len= 9 : NO 00 NO
+ 787 39 7 = _O___@___# : len=10 : NO 00 NO
+ 788 39 8 = _O___@O__# : len=10 : NO 00 NO
+ 789 39 9 = _O___@_O_# : len=10 : NO 00 NO
+ 790 39 10 = _O___@OO_# : len=10 : H3 13 H3
+ 791 39 11 = _O___@__O# : len=10 : NO 00 NO
+ 792 39 12 = _O___@O_O# : len=10 : NO 00 NO
+ 793 39 13 = _O___@_OO# : len=10 : NO 00 NO
+ 794 39 14 = _O___@OOO# : len=10 : S4 00 S4
+ 795 39 15 = _O___@____# : len=11 : NO 00 NO
+ 796 39 16 = _O___@O___# : len=11 : NO 00 NO
+ 797 39 17 = _O___@_O__# : len=11 : NO 00 NO
+ 798 39 18 = _O___@OO__# : len=11 : H3 13 H3
+ 799 39 19 = _O___@__O_# : len=11 : NO 00 NO
+ 800 39 20 = _O___@O_O_# : len=11 : D3 02 D3
+ 801 39 21 = _O___@_OO_# : len=11 : D3 01 D3
+ 802 39 22 = _O___@OOO_# : len=11 : H4 00 H4
+ 803 39 23 = _O___@___O# : len=11 : NO 00 NO
+ 804 39 24 = _O___@O__O# : len=11 : NO 00 NO
+ 805 39 25 = _O___@_O_O# : len=11 : NO 00 NO
+ 806 39 26 = _O___@OO_O# : len=11 : D4 00 D4
+ 807 39 27 = _O___@__OO# : len=11 : NO 00 NO
+ 808 39 28 = _O___@O_OO# : len=11 : D4 00 D4
+ 809 39 29 = _O___@_OOO# : len=11 : D4 00 D4
+ 810 39 30 = _O___@OOOO# : len=11 : L5 00 L5
+ 811 39 31 = _O___@_____ : len=11 : NO 00 NO
+ 812 39 32 = _O___@O____ : len=11 : NO 00 NO
+ 813 39 33 = _O___@_O___ : len=11 : NO 00 NO
+ 814 39 34 = _O___@OO___ : len=11 : H3 13 H3
+ 815 39 35 = _O___@__O__ : len=11 : NO 00 NO
+ 816 39 36 = _O___@O_O__ : len=11 : D3 02 D3
+ 817 39 37 = _O___@_OO__ : len=11 : D3 01 D3
+ 818 39 38 = _O___@OOO__ : len=11 : H4 00 H4
+ 819 39 39 = _O___@___O_ : len=11 : NO 00 NO
+ 820 40 0 = _O__O@# : len= 7 : NO 00 NO
+ 821 40 1 = _O__O@_# : len= 8 : NO 00 NO
+ 822 40 2 = _O__O@O# : len= 8 : NO 00 NO
+ 823 40 3 = _O__O@__# : len= 9 : NO 00 NO
+ 824 40 4 = _O__O@O_# : len= 9 : NO 00 H3
+ 825 40 5 = _O__O@_O# : len= 9 : NO 00 NO
+ 826 40 6 = _O__O@OO# : len= 9 : S4 00 S4
+ 827 40 7 = _O__O@___# : len=10 : NO 00 NO
+ 828 40 8 = _O__O@O__# : len=10 : H3 22 H3
+ 829 40 9 = _O__O@_O_# : len=10 : D3 01 D3
+ 830 40 10 = _O__O@OO_# : len=10 : H4 00 H4
+ 831 40 11 = _O__O@__O# : len=10 : NO 00 NO
+ 832 40 12 = _O__O@O_O# : len=10 : D4 00 D4
+ 833 40 13 = _O__O@_OO# : len=10 : D4 00 D4
+ 834 40 14 = _O__O@OOO# : len=10 : L5 00 L5
+ 835 40 15 = _O__O@____# : len=11 : NO 00 NO
+ 836 40 16 = _O__O@O___# : len=11 : H3 22 H3
+ 837 40 17 = _O__O@_O__# : len=11 : D3 01 D3
+ 838 40 18 = _O__O@OO__# : len=11 : H4 00 H4
+ 839 40 19 = _O__O@__O_# : len=11 : NO 00 NO
+ 840 40 20 = _O__O@O_O_# : len=11 : D4 00 D4
+ 841 40 21 = _O__O@_OO_# : len=11 : D4 00 D4
+ 842 40 22 = _O__O@OOO_# : len=11 : L5 00 L5
+ 843 40 23 = _O__O@___O# : len=11 : NO 00 NO
+ 844 40 24 = _O__O@O__O# : len=11 : NO 00 H3
+ 845 40 25 = _O__O@_O_O# : len=11 : NO 00 D3
+ 846 40 26 = _O__O@OO_O# : len=11 : S4 00 H4
+ 847 40 27 = _O__O@__OO# : len=11 : NO 00 NO
+ 848 40 28 = _O__O@O_OO# : len=11 : NO 00 D4
+ 849 40 29 = _O__O@_OOO# : len=11 : NO 00 D4
+ 850 40 30 = _O__O@OOOO# : len=11 : L6 00 L6
+ 851 40 31 = _O__O@_____ : len=11 : NO 00 NO
+ 852 40 32 = _O__O@O____ : len=11 : H3 22 H3
+ 853 40 33 = _O__O@_O___ : len=11 : D3 01 D3
+ 854 40 34 = _O__O@OO___ : len=11 : H4 00 H4
+ 855 40 35 = _O__O@__O__ : len=11 : NO 00 NO
+ 856 40 36 = _O__O@O_O__ : len=11 : D4 00 D4
+ 857 40 37 = _O__O@_OO__ : len=11 : D4 00 D4
+ 858 40 38 = _O__O@OOO__ : len=11 : L5 00 L5
+ 859 40 39 = _O__O@___O_ : len=11 : NO 00 NO
+ 860 40 40 = _O__O@O__O_ : len=11 : NO 00 H3
+ 861 41 0 = _O_O_@# : len= 7 : NO 00 NO
+ 862 41 1 = _O_O_@_# : len= 8 : NO 00 NO
+ 863 41 2 = _O_O_@O# : len= 8 : NO 00 NO
+ 864 41 3 = _O_O_@__# : len= 9 : NO 00 NO
+ 865 41 4 = _O_O_@O_# : len= 9 : NO 00 D3
+ 866 41 5 = _O_O_@_O# : len= 9 : NO 00 NO
+ 867 41 6 = _O_O_@OO# : len= 9 : D4 00 D4
+ 868 41 7 = _O_O_@___# : len=10 : NO 00 NO
+ 869 41 8 = _O_O_@O__# : len=10 : NO 00 D3
+ 870 41 9 = _O_O_@_O_# : len=10 : NO 00 NO
+ 871 41 10 = _O_O_@OO_# : len=10 : D4 00 D4
+ 872 41 11 = _O_O_@__O# : len=10 : NO 00 NO
+ 873 41 12 = _O_O_@O_O# : len=10 : NO 00 D3
+ 874 41 13 = _O_O_@_OO# : len=10 : NO 00 NO
+ 875 41 14 = _O_O_@OOO# : len=10 : NO 00 D4
+ 876 41 15 = _O_O_@____# : len=11 : NO 00 NO
+ 877 41 16 = _O_O_@O___# : len=11 : NO 00 D3
+ 878 41 17 = _O_O_@_O__# : len=11 : NO 00 NO
+ 879 41 18 = _O_O_@OO__# : len=11 : D4 00 D4
+ 880 41 19 = _O_O_@__O_# : len=11 : NO 00 NO
+ 881 41 20 = _O_O_@O_O_# : len=11 : NO 00 D3
+ 882 41 21 = _O_O_@_OO_# : len=11 : NO 00 D3
+ 883 41 22 = _O_O_@OOO_# : len=11 : S4 00 H4
+ 884 41 23 = _O_O_@___O# : len=11 : NO 00 NO
+ 885 41 24 = _O_O_@O__O# : len=11 : NO 00 D3
+ 886 41 25 = _O_O_@_O_O# : len=11 : NO 00 NO
+ 887 41 26 = _O_O_@OO_O# : len=11 : X4 00 X4
+ 888 41 27 = _O_O_@__OO# : len=11 : NO 00 NO
+ 889 41 28 = _O_O_@O_OO# : len=11 : D4 00 D4
+ 890 41 29 = _O_O_@_OOO# : len=11 : D4 00 D4
+ 891 41 30 = _O_O_@OOOO# : len=11 : L5 00 L5
+ 892 41 31 = _O_O_@_____ : len=11 : NO 00 NO
+ 893 41 32 = _O_O_@O____ : len=11 : NO 00 D3
+ 894 41 33 = _O_O_@_O___ : len=11 : NO 00 NO
+ 895 41 34 = _O_O_@OO___ : len=11 : D4 00 D4
+ 896 41 35 = _O_O_@__O__ : len=11 : NO 00 NO
+ 897 41 36 = _O_O_@O_O__ : len=11 : NO 00 D3
+ 898 41 37 = _O_O_@_OO__ : len=11 : NO 00 D3
+ 899 41 38 = _O_O_@OOO__ : len=11 : S4 00 H4
+ 900 41 39 = _O_O_@___O_ : len=11 : NO 00 NO
+ 901 41 40 = _O_O_@O__O_ : len=11 : NO 00 D3
+ 902 41 41 = _O_O_@_O_O_ : len=11 : NO 00 NO
+ 903 42 0 = _O_OO@# : len= 7 : D4 00 D4
+ 904 42 1 = _O_OO@_# : len= 8 : D4 00 D4
+ 905 42 2 = _O_OO@O# : len= 8 : NO 00 D4
+ 906 42 3 = _O_OO@__# : len= 9 : D4 00 D4
+ 907 42 4 = _O_OO@O_# : len= 9 : S4 00 H4
+ 908 42 5 = _O_OO@_O# : len= 9 : X4 00 X4
+ 909 42 6 = _O_OO@OO# : len= 9 : L5 00 L5
+ 910 42 7 = _O_OO@___# : len=10 : D4 00 D4
+ 911 42 8 = _O_OO@O__# : len=10 : S4 00 H4
+ 912 42 9 = _O_OO@_O_# : len=10 : X4 00 X4
+ 913 42 10 = _O_OO@OO_# : len=10 : L5 00 L5
+ 914 42 11 = _O_OO@__O# : len=10 : D4 00 D4
+ 915 42 12 = _O_OO@O_O# : len=10 : NO 00 H4
+ 916 42 13 = _O_OO@_OO# : len=10 : D4 00 X4
+ 917 42 14 = _O_OO@OOO# : len=10 : L6 00 L6
+ 918 42 15 = _O_OO@____# : len=11 : D4 00 D4
+ 919 42 16 = _O_OO@O___# : len=11 : S4 00 H4
+ 920 42 17 = _O_OO@_O__# : len=11 : X4 00 X4
+ 921 42 18 = _O_OO@OO__# : len=11 : L5 00 L5
+ 922 42 19 = _O_OO@__O_# : len=11 : D4 00 D4
+ 923 42 20 = _O_OO@O_O_# : len=11 : NO 00 H4
+ 924 42 21 = _O_OO@_OO_# : len=11 : D4 00 X4
+ 925 42 22 = _O_OO@OOO_# : len=11 : L6 00 L6
+ 926 42 23 = _O_OO@___O# : len=11 : D4 00 D4
+ 927 42 24 = _O_OO@O__O# : len=11 : S4 00 H4
+ 928 42 25 = _O_OO@_O_O# : len=11 : X4 00 X4
+ 929 42 26 = _O_OO@OO_O# : len=11 : L5 00 L5
+ 930 42 27 = _O_OO@__OO# : len=11 : D4 00 D4
+ 931 42 28 = _O_OO@O_OO# : len=11 : NO 00 H4
+ 932 42 29 = _O_OO@_OOO# : len=11 : D4 00 X4
+ 933 42 30 = _O_OO@OOOO# : len=11 : L6 00 L6
+ 934 42 31 = _O_OO@_____ : len=11 : D4 00 D4
+ 935 42 32 = _O_OO@O____ : len=11 : S4 00 H4
+ 936 42 33 = _O_OO@_O___ : len=11 : X4 00 X4
+ 937 42 34 = _O_OO@OO___ : len=11 : L5 00 L5
+ 938 42 35 = _O_OO@__O__ : len=11 : D4 00 D4
+ 939 42 36 = _O_OO@O_O__ : len=11 : NO 00 H4
+ 940 42 37 = _O_OO@_OO__ : len=11 : D4 00 X4
+ 941 42 38 = _O_OO@OOO__ : len=11 : L6 00 L6
+ 942 42 39 = _O_OO@___O_ : len=11 : D4 00 D4
+ 943 42 40 = _O_OO@O__O_ : len=11 : S4 00 H4
+ 944 42 41 = _O_OO@_O_O_ : len=11 : X4 00 X4
+ 945 42 42 = _O_OO@OO_O_ : len=11 : L5 00 L5
+ 946 43 0 = _OO__@# : len= 7 : NO 00 NO
+ 947 43 1 = _OO__@_# : len= 8 : NO 00 NO
+ 948 43 2 = _OO__@O# : len= 8 : NO 00 NO
+ 949 43 3 = _OO__@__# : len= 9 : NO 00 NO
+ 950 43 4 = _OO__@O_# : len= 9 : NO 00 NO
+ 951 43 5 = _OO__@_O# : len= 9 : NO 00 NO
+ 952 43 6 = _OO__@OO# : len= 9 : NO 00 NO
+ 953 43 7 = _OO__@___# : len=10 : NO 00 NO
+ 954 43 8 = _OO__@O__# : len=10 : NO 00 NO
+ 955 43 9 = _OO__@_O_# : len=10 : NO 00 NO
+ 956 43 10 = _OO__@OO_# : len=10 : NO 00 H3
+ 957 43 11 = _OO__@__O# : len=10 : NO 00 NO
+ 958 43 12 = _OO__@O_O# : len=10 : NO 00 NO
+ 959 43 13 = _OO__@_OO# : len=10 : NO 00 NO
+ 960 43 14 = _OO__@OOO# : len=10 : S4 00 S4
+ 961 43 15 = _OO__@____# : len=11 : NO 00 NO
+ 962 43 16 = _OO__@O___# : len=11 : NO 00 NO
+ 963 43 17 = _OO__@_O__# : len=11 : NO 00 NO
+ 964 43 18 = _OO__@OO__# : len=11 : H3 13 H3
+ 965 43 19 = _OO__@__O_# : len=11 : NO 00 NO
+ 966 43 20 = _OO__@O_O_# : len=11 : D3 02 D3
+ 967 43 21 = _OO__@_OO_# : len=11 : D3 01 D3
+ 968 43 22 = _OO__@OOO_# : len=11 : H4 00 H4
+ 969 43 23 = _OO__@___O# : len=11 : NO 00 NO
+ 970 43 24 = _OO__@O__O# : len=11 : NO 00 NO
+ 971 43 25 = _OO__@_O_O# : len=11 : NO 00 NO
+ 972 43 26 = _OO__@OO_O# : len=11 : D4 00 D4
+ 973 43 27 = _OO__@__OO# : len=11 : NO 00 NO
+ 974 43 28 = _OO__@O_OO# : len=11 : D4 00 D4
+ 975 43 29 = _OO__@_OOO# : len=11 : D4 00 D4
+ 976 43 30 = _OO__@OOOO# : len=11 : L5 00 L5
+ 977 43 31 = _OO__@_____ : len=11 : NO 00 NO
+ 978 43 32 = _OO__@O____ : len=11 : NO 00 NO
+ 979 43 33 = _OO__@_O___ : len=11 : NO 00 NO
+ 980 43 34 = _OO__@OO___ : len=11 : H3 13 H3
+ 981 43 35 = _OO__@__O__ : len=11 : NO 00 NO
+ 982 43 36 = _OO__@O_O__ : len=11 : D3 02 D3
+ 983 43 37 = _OO__@_OO__ : len=11 : D3 01 D3
+ 984 43 38 = _OO__@OOO__ : len=11 : H4 00 H4
+ 985 43 39 = _OO__@___O_ : len=11 : NO 00 NO
+ 986 43 40 = _OO__@O__O_ : len=11 : NO 00 NO
+ 987 43 41 = _OO__@_O_O_ : len=11 : NO 00 NO
+ 988 43 42 = _OO__@OO_O_ : len=11 : D4 00 D4
+ 989 43 43 = _OO__@__OO_ : len=11 : NO 00 NO
+ 990 44 0 = _OO_O@# : len= 7 : D4 00 D4
+ 991 44 1 = _OO_O@_# : len= 8 : D4 00 D4
+ 992 44 2 = _OO_O@O# : len= 8 : NO 00 D4
+ 993 44 3 = _OO_O@__# : len= 9 : D4 00 D4
+ 994 44 4 = _OO_O@O_# : len= 9 : NO 00 D4
+ 995 44 5 = _OO_O@_O# : len= 9 : D4 00 D4
+ 996 44 6 = _OO_O@OO# : len= 9 : NO 00 D4
+ 997 44 7 = _OO_O@___# : len=10 : D4 00 D4
+ 998 44 8 = _OO_O@O__# : len=10 : NO 00 D4
+ 999 44 9 = _OO_O@_O_# : len=10 : D4 00 D4
+ 1000 44 10 = _OO_O@OO_# : len=10 : S4 00 H4
+ 1001 44 11 = _OO_O@__O# : len=10 : D4 00 D4
+ 1002 44 12 = _OO_O@O_O# : len=10 : D4 00 X4
+ 1003 44 13 = _OO_O@_OO# : len=10 : X4 00 X4
+ 1004 44 14 = _OO_O@OOO# : len=10 : L5 00 L5
+ 1005 44 15 = _OO_O@____# : len=11 : D4 00 D4
+ 1006 44 16 = _OO_O@O___# : len=11 : NO 00 D4
+ 1007 44 17 = _OO_O@_O__# : len=11 : D4 00 D4
+ 1008 44 18 = _OO_O@OO__# : len=11 : S4 00 H4
+ 1009 44 19 = _OO_O@__O_# : len=11 : D4 00 D4
+ 1010 44 20 = _OO_O@O_O_# : len=11 : D4 00 X4
+ 1011 44 21 = _OO_O@_OO_# : len=11 : X4 00 X4
+ 1012 44 22 = _OO_O@OOO_# : len=11 : L5 00 L5
+ 1013 44 23 = _OO_O@___O# : len=11 : D4 00 D4
+ 1014 44 24 = _OO_O@O__O# : len=11 : NO 00 D4
+ 1015 44 25 = _OO_O@_O_O# : len=11 : D4 00 D4
+ 1016 44 26 = _OO_O@OO_O# : len=11 : NO 00 H4
+ 1017 44 27 = _OO_O@__OO# : len=11 : D4 00 D4
+ 1018 44 28 = _OO_O@O_OO# : len=11 : NO 00 X4
+ 1019 44 29 = _OO_O@_OOO# : len=11 : D4 00 X4
+ 1020 44 30 = _OO_O@OOOO# : len=11 : L6 00 L6
+ 1021 44 31 = _OO_O@_____ : len=11 : D4 00 D4
+ 1022 44 32 = _OO_O@O____ : len=11 : NO 00 D4
+ 1023 44 33 = _OO_O@_O___ : len=11 : D4 00 D4
+ 1024 44 34 = _OO_O@OO___ : len=11 : S4 00 H4
+ 1025 44 35 = _OO_O@__O__ : len=11 : D4 00 D4
+ 1026 44 36 = _OO_O@O_O__ : len=11 : D4 00 X4
+ 1027 44 37 = _OO_O@_OO__ : len=11 : X4 00 X4
+ 1028 44 38 = _OO_O@OOO__ : len=11 : L5 00 L5
+ 1029 44 39 = _OO_O@___O_ : len=11 : D4 00 D4
+ 1030 44 40 = _OO_O@O__O_ : len=11 : NO 00 D4
+ 1031 44 41 = _OO_O@_O_O_ : len=11 : D4 00 D4
+ 1032 44 42 = _OO_O@OO_O_ : len=11 : NO 00 H4
+ 1033 44 43 = _OO_O@__OO_ : len=11 : D4 00 D4
+ 1034 44 44 = _OO_O@O_OO_ : len=11 : NO 00 X4
+ 1035 45 0 = _OOO_@# : len= 7 : D4 00 D4
+ 1036 45 1 = _OOO_@_# : len= 8 : D4 00 D4
+ 1037 45 2 = _OOO_@O# : len= 8 : NO 00 D4
+ 1038 45 3 = _OOO_@__# : len= 9 : D4 00 D4
+ 1039 45 4 = _OOO_@O_# : len= 9 : NO 00 D4
+ 1040 45 5 = _OOO_@_O# : len= 9 : D4 00 D4
+ 1041 45 6 = _OOO_@OO# : len= 9 : NO 00 D4
+ 1042 45 7 = _OOO_@___# : len=10 : D4 00 D4
+ 1043 45 8 = _OOO_@O__# : len=10 : NO 00 D4
+ 1044 45 9 = _OOO_@_O_# : len=10 : D4 00 D4
+ 1045 45 10 = _OOO_@OO_# : len=10 : NO 00 D4
+ 1046 45 11 = _OOO_@__O# : len=10 : D4 00 D4
+ 1047 45 12 = _OOO_@O_O# : len=10 : NO 00 D4
+ 1048 45 13 = _OOO_@_OO# : len=10 : D4 00 D4
+ 1049 45 14 = _OOO_@OOO# : len=10 : NO 00 D4
+ 1050 45 15 = _OOO_@____# : len=11 : D4 00 D4
+ 1051 45 16 = _OOO_@O___# : len=11 : NO 00 D4
+ 1052 45 17 = _OOO_@_O__# : len=11 : D4 00 D4
+ 1053 45 18 = _OOO_@OO__# : len=11 : NO 00 D4
+ 1054 45 19 = _OOO_@__O_# : len=11 : D4 00 D4
+ 1055 45 20 = _OOO_@O_O_# : len=11 : NO 00 D4
+ 1056 45 21 = _OOO_@_OO_# : len=11 : D4 00 D4
+ 1057 45 22 = _OOO_@OOO_# : len=11 : S4 00 H4
+ 1058 45 23 = _OOO_@___O# : len=11 : D4 00 D4
+ 1059 45 24 = _OOO_@O__O# : len=11 : NO 00 D4
+ 1060 45 25 = _OOO_@_O_O# : len=11 : D4 00 D4
+ 1061 45 26 = _OOO_@OO_O# : len=11 : D4 00 X4
+ 1062 45 27 = _OOO_@__OO# : len=11 : D4 00 D4
+ 1063 45 28 = _OOO_@O_OO# : len=11 : D4 00 X4
+ 1064 45 29 = _OOO_@_OOO# : len=11 : X4 00 X4
+ 1065 45 30 = _OOO_@OOOO# : len=11 : L5 00 L5
+ 1066 45 31 = _OOO_@_____ : len=11 : D4 00 D4
+ 1067 45 32 = _OOO_@O____ : len=11 : NO 00 D4
+ 1068 45 33 = _OOO_@_O___ : len=11 : D4 00 D4
+ 1069 45 34 = _OOO_@OO___ : len=11 : NO 00 D4
+ 1070 45 35 = _OOO_@__O__ : len=11 : D4 00 D4
+ 1071 45 36 = _OOO_@O_O__ : len=11 : NO 00 D4
+ 1072 45 37 = _OOO_@_OO__ : len=11 : D4 00 D4
+ 1073 45 38 = _OOO_@OOO__ : len=11 : S4 00 H4
+ 1074 45 39 = _OOO_@___O_ : len=11 : D4 00 D4
+ 1075 45 40 = _OOO_@O__O_ : len=11 : NO 00 D4
+ 1076 45 41 = _OOO_@_O_O_ : len=11 : D4 00 D4
+ 1077 45 42 = _OOO_@OO_O_ : len=11 : D4 00 X4
+ 1078 45 43 = _OOO_@__OO_ : len=11 : D4 00 D4
+ 1079 45 44 = _OOO_@O_OO_ : len=11 : D4 00 X4
+ 1080 45 45 = _OOO_@_OOO_ : len=11 : X4 00 X4
+ 1081 46 0 = _OOOO@# : len= 7 : L5 00 L5
+ 1082 46 1 = _OOOO@_# : len= 8 : L5 00 L5
+ 1083 46 2 = _OOOO@O# : len= 8 : L6 00 L6
+ 1084 46 3 = _OOOO@__# : len= 9 : L5 00 L5
+ 1085 46 4 = _OOOO@O_# : len= 9 : L6 00 L6
+ 1086 46 5 = _OOOO@_O# : len= 9 : L5 00 L5
+ 1087 46 6 = _OOOO@OO# : len= 9 : L6 00 L6
+ 1088 46 7 = _OOOO@___# : len=10 : L5 00 L5
+ 1089 46 8 = _OOOO@O__# : len=10 : L6 00 L6
+ 1090 46 9 = _OOOO@_O_# : len=10 : L5 00 L5
+ 1091 46 10 = _OOOO@OO_# : len=10 : L6 00 L6
+ 1092 46 11 = _OOOO@__O# : len=10 : L5 00 L5
+ 1093 46 12 = _OOOO@O_O# : len=10 : L6 00 L6
+ 1094 46 13 = _OOOO@_OO# : len=10 : L5 00 L5
+ 1095 46 14 = _OOOO@OOO# : len=10 : L6 00 L6
+ 1096 46 15 = _OOOO@____# : len=11 : L5 00 L5
+ 1097 46 16 = _OOOO@O___# : len=11 : L6 00 L6
+ 1098 46 17 = _OOOO@_O__# : len=11 : L5 00 L5
+ 1099 46 18 = _OOOO@OO__# : len=11 : L6 00 L6
+ 1100 46 19 = _OOOO@__O_# : len=11 : L5 00 L5
+ 1101 46 20 = _OOOO@O_O_# : len=11 : L6 00 L6
+ 1102 46 21 = _OOOO@_OO_# : len=11 : L5 00 L5
+ 1103 46 22 = _OOOO@OOO_# : len=11 : L6 00 L6
+ 1104 46 23 = _OOOO@___O# : len=11 : L5 00 L5
+ 1105 46 24 = _OOOO@O__O# : len=11 : L6 00 L6
+ 1106 46 25 = _OOOO@_O_O# : len=11 : L5 00 L5
+ 1107 46 26 = _OOOO@OO_O# : len=11 : L6 00 L6
+ 1108 46 27 = _OOOO@__OO# : len=11 : L5 00 L5
+ 1109 46 28 = _OOOO@O_OO# : len=11 : L6 00 L6
+ 1110 46 29 = _OOOO@_OOO# : len=11 : L5 00 L5
+ 1111 46 30 = _OOOO@OOOO# : len=11 : L6 00 L6
+ 1112 46 31 = _OOOO@_____ : len=11 : L5 00 L5
+ 1113 46 32 = _OOOO@O____ : len=11 : L6 00 L6
+ 1114 46 33 = _OOOO@_O___ : len=11 : L5 00 L5
+ 1115 46 34 = _OOOO@OO___ : len=11 : L6 00 L6
+ 1116 46 35 = _OOOO@__O__ : len=11 : L5 00 L5
+ 1117 46 36 = _OOOO@O_O__ : len=11 : L6 00 L6
+ 1118 46 37 = _OOOO@_OO__ : len=11 : L5 00 L5
+ 1119 46 38 = _OOOO@OOO__ : len=11 : L6 00 L6
+ 1120 46 39 = _OOOO@___O_ : len=11 : L5 00 L5
+ 1121 46 40 = _OOOO@O__O_ : len=11 : L6 00 L6
+ 1122 46 41 = _OOOO@_O_O_ : len=11 : L5 00 L5
+ 1123 46 42 = _OOOO@OO_O_ : len=11 : L6 00 L6
+ 1124 46 43 = _OOOO@__OO_ : len=11 : L5 00 L5
+ 1125 46 44 = _OOOO@O_OO_ : len=11 : L6 00 L6
+ 1126 46 45 = _OOOO@_OOO_ : len=11 : L5 00 L5
+ 1127 46 46 = _OOOO@OOOO_ : len=11 : L6 00 L6
+ 1128 47 0 = O____@# : len= 7 : NO 00 NO
+ 1129 47 1 = O____@_# : len= 8 : NO 00 NO
+ 1130 47 2 = O____@O# : len= 8 : NO 00 NO
+ 1131 47 3 = O____@__# : len= 9 : NO 00 NO
+ 1132 47 4 = O____@O_# : len= 9 : NO 00 NO
+ 1133 47 5 = O____@_O# : len= 9 : NO 00 NO
+ 1134 47 6 = O____@OO# : len= 9 : NO 00 NO
+ 1135 47 7 = O____@___# : len=10 : NO 00 NO
+ 1136 47 8 = O____@O__# : len=10 : NO 00 NO
+ 1137 47 9 = O____@_O_# : len=10 : NO 00 NO
+ 1138 47 10 = O____@OO_# : len=10 : H3 13 H3
+ 1139 47 11 = O____@__O# : len=10 : NO 00 NO
+ 1140 47 12 = O____@O_O# : len=10 : NO 00 NO
+ 1141 47 13 = O____@_OO# : len=10 : NO 00 NO
+ 1142 47 14 = O____@OOO# : len=10 : S4 00 S4
+ 1143 47 15 = O____@____# : len=11 : NO 00 NO
+ 1144 47 16 = O____@O___# : len=11 : NO 00 NO
+ 1145 47 17 = O____@_O__# : len=11 : NO 00 NO
+ 1146 47 18 = O____@OO__# : len=11 : H3 13 H3
+ 1147 47 19 = O____@__O_# : len=11 : NO 00 NO
+ 1148 47 20 = O____@O_O_# : len=11 : D3 02 D3
+ 1149 47 21 = O____@_OO_# : len=11 : D3 01 D3
+ 1150 47 22 = O____@OOO_# : len=11 : H4 00 H4
+ 1151 47 23 = O____@___O# : len=11 : NO 00 NO
+ 1152 47 24 = O____@O__O# : len=11 : NO 00 NO
+ 1153 47 25 = O____@_O_O# : len=11 : NO 00 NO
+ 1154 47 26 = O____@OO_O# : len=11 : D4 00 D4
+ 1155 47 27 = O____@__OO# : len=11 : NO 00 NO
+ 1156 47 28 = O____@O_OO# : len=11 : D4 00 D4
+ 1157 47 29 = O____@_OOO# : len=11 : D4 00 D4
+ 1158 47 30 = O____@OOOO# : len=11 : L5 00 L5
+ 1159 47 31 = O____@_____ : len=11 : NO 00 NO
+ 1160 47 32 = O____@O____ : len=11 : NO 00 NO
+ 1161 47 33 = O____@_O___ : len=11 : NO 00 NO
+ 1162 47 34 = O____@OO___ : len=11 : H3 13 H3
+ 1163 47 35 = O____@__O__ : len=11 : NO 00 NO
+ 1164 47 36 = O____@O_O__ : len=11 : D3 02 D3
+ 1165 47 37 = O____@_OO__ : len=11 : D3 01 D3
+ 1166 47 38 = O____@OOO__ : len=11 : H4 00 H4
+ 1167 47 39 = O____@___O_ : len=11 : NO 00 NO
+ 1168 47 40 = O____@O__O_ : len=11 : NO 00 NO
+ 1169 47 41 = O____@_O_O_ : len=11 : NO 00 NO
+ 1170 47 42 = O____@OO_O_ : len=11 : D4 00 D4
+ 1171 47 43 = O____@__OO_ : len=11 : NO 00 NO
+ 1172 47 44 = O____@O_OO_ : len=11 : D4 00 D4
+ 1173 47 45 = O____@_OOO_ : len=11 : D4 00 D4
+ 1174 47 46 = O____@OOOO_ : len=11 : L5 00 L5
+ 1175 47 47 = O____@____O : len=11 : NO 00 NO
+ 1176 48 0 = O___O@# : len= 7 : NO 00 NO
+ 1177 48 1 = O___O@_# : len= 8 : NO 00 NO
+ 1178 48 2 = O___O@O# : len= 8 : NO 00 NO
+ 1179 48 3 = O___O@__# : len= 9 : NO 00 NO
+ 1180 48 4 = O___O@O_# : len= 9 : H3 22 H3
+ 1181 48 5 = O___O@_O# : len= 9 : NO 00 NO
+ 1182 48 6 = O___O@OO# : len= 9 : S4 00 S4
+ 1183 48 7 = O___O@___# : len=10 : NO 00 NO
+ 1184 48 8 = O___O@O__# : len=10 : H3 22 H3
+ 1185 48 9 = O___O@_O_# : len=10 : D3 01 D3
+ 1186 48 10 = O___O@OO_# : len=10 : H4 00 H4
+ 1187 48 11 = O___O@__O# : len=10 : NO 00 NO
+ 1188 48 12 = O___O@O_O# : len=10 : D4 00 D4
+ 1189 48 13 = O___O@_OO# : len=10 : D4 00 D4
+ 1190 48 14 = O___O@OOO# : len=10 : L5 00 L5
+ 1191 48 15 = O___O@____# : len=11 : NO 00 NO
+ 1192 48 16 = O___O@O___# : len=11 : H3 22 H3
+ 1193 48 17 = O___O@_O__# : len=11 : D3 01 D3
+ 1194 48 18 = O___O@OO__# : len=11 : H4 00 H4
+ 1195 48 19 = O___O@__O_# : len=11 : NO 00 NO
+ 1196 48 20 = O___O@O_O_# : len=11 : D4 00 D4
+ 1197 48 21 = O___O@_OO_# : len=11 : D4 00 D4
+ 1198 48 22 = O___O@OOO_# : len=11 : L5 00 L5
+ 1199 48 23 = O___O@___O# : len=11 : NO 00 NO
+ 1200 48 24 = O___O@O__O# : len=11 : H3 22 H3
+ 1201 48 25 = O___O@_O_O# : len=11 : NO 00 D3
+ 1202 48 26 = O___O@OO_O# : len=11 : S4 00 H4
+ 1203 48 27 = O___O@__OO# : len=11 : NO 00 NO
+ 1204 48 28 = O___O@O_OO# : len=11 : NO 00 D4
+ 1205 48 29 = O___O@_OOO# : len=11 : NO 00 D4
+ 1206 48 30 = O___O@OOOO# : len=11 : L6 00 L6
+ 1207 48 31 = O___O@_____ : len=11 : NO 00 NO
+ 1208 48 32 = O___O@O____ : len=11 : H3 22 H3
+ 1209 48 33 = O___O@_O___ : len=11 : D3 01 D3
+ 1210 48 34 = O___O@OO___ : len=11 : H4 00 H4
+ 1211 48 35 = O___O@__O__ : len=11 : NO 00 NO
+ 1212 48 36 = O___O@O_O__ : len=11 : D4 00 D4
+ 1213 48 37 = O___O@_OO__ : len=11 : D4 00 D4
+ 1214 48 38 = O___O@OOO__ : len=11 : L5 00 L5
+ 1215 48 39 = O___O@___O_ : len=11 : NO 00 NO
+ 1216 48 40 = O___O@O__O_ : len=11 : H3 22 H3
+ 1217 48 41 = O___O@_O_O_ : len=11 : NO 00 D3
+ 1218 48 42 = O___O@OO_O_ : len=11 : S4 00 H4
+ 1219 48 43 = O___O@__OO_ : len=11 : NO 00 NO
+ 1220 48 44 = O___O@O_OO_ : len=11 : NO 00 D4
+ 1221 48 45 = O___O@_OOO_ : len=11 : NO 00 D4
+ 1222 48 46 = O___O@OOOO_ : len=11 : L6 00 L6
+ 1223 48 47 = O___O@____O : len=11 : NO 00 NO
+ 1224 48 48 = O___O@O___O : len=11 : H3 22 H3
+ 1225 49 0 = O__O_@# : len= 7 : NO 00 NO
+ 1226 49 1 = O__O_@_# : len= 8 : NO 00 NO
+ 1227 49 2 = O__O_@O# : len= 8 : NO 00 NO
+ 1228 49 3 = O__O_@__# : len= 9 : NO 00 NO
+ 1229 49 4 = O__O_@O_# : len= 9 : D3 10 D3
+ 1230 49 5 = O__O_@_O# : len= 9 : NO 00 NO
+ 1231 49 6 = O__O_@OO# : len= 9 : D4 00 D4
+ 1232 49 7 = O__O_@___# : len=10 : NO 00 NO
+ 1233 49 8 = O__O_@O__# : len=10 : D3 10 D3
+ 1234 49 9 = O__O_@_O_# : len=10 : NO 00 NO
+ 1235 49 10 = O__O_@OO_# : len=10 : D4 00 D4
+ 1236 49 11 = O__O_@__O# : len=10 : NO 00 NO
+ 1237 49 12 = O__O_@O_O# : len=10 : NO 00 D3
+ 1238 49 13 = O__O_@_OO# : len=10 : NO 00 NO
+ 1239 49 14 = O__O_@OOO# : len=10 : NO 00 D4
+ 1240 49 15 = O__O_@____# : len=11 : NO 00 NO
+ 1241 49 16 = O__O_@O___# : len=11 : D3 10 D3
+ 1242 49 17 = O__O_@_O__# : len=11 : NO 00 NO
+ 1243 49 18 = O__O_@OO__# : len=11 : D4 00 D4
+ 1244 49 19 = O__O_@__O_# : len=11 : NO 00 NO
+ 1245 49 20 = O__O_@O_O_# : len=11 : NO 00 D3
+ 1246 49 21 = O__O_@_OO_# : len=11 : NO 00 D3
+ 1247 49 22 = O__O_@OOO_# : len=11 : S4 00 H4
+ 1248 49 23 = O__O_@___O# : len=11 : NO 00 NO
+ 1249 49 24 = O__O_@O__O# : len=11 : D3 10 D3
+ 1250 49 25 = O__O_@_O_O# : len=11 : NO 00 NO
+ 1251 49 26 = O__O_@OO_O# : len=11 : X4 00 X4
+ 1252 49 27 = O__O_@__OO# : len=11 : NO 00 NO
+ 1253 49 28 = O__O_@O_OO# : len=11 : D4 00 D4
+ 1254 49 29 = O__O_@_OOO# : len=11 : D4 00 D4
+ 1255 49 30 = O__O_@OOOO# : len=11 : L5 00 L5
+ 1256 49 31 = O__O_@_____ : len=11 : NO 00 NO
+ 1257 49 32 = O__O_@O____ : len=11 : D3 10 D3
+ 1258 49 33 = O__O_@_O___ : len=11 : NO 00 NO
+ 1259 49 34 = O__O_@OO___ : len=11 : D4 00 D4
+ 1260 49 35 = O__O_@__O__ : len=11 : NO 00 NO
+ 1261 49 36 = O__O_@O_O__ : len=11 : NO 00 D3
+ 1262 49 37 = O__O_@_OO__ : len=11 : NO 00 D3
+ 1263 49 38 = O__O_@OOO__ : len=11 : S4 00 H4
+ 1264 49 39 = O__O_@___O_ : len=11 : NO 00 NO
+ 1265 49 40 = O__O_@O__O_ : len=11 : D3 10 D3
+ 1266 49 41 = O__O_@_O_O_ : len=11 : NO 00 NO
+ 1267 49 42 = O__O_@OO_O_ : len=11 : X4 00 X4
+ 1268 49 43 = O__O_@__OO_ : len=11 : NO 00 NO
+ 1269 49 44 = O__O_@O_OO_ : len=11 : D4 00 D4
+ 1270 49 45 = O__O_@_OOO_ : len=11 : D4 00 D4
+ 1271 49 46 = O__O_@OOOO_ : len=11 : L5 00 L5
+ 1272 49 47 = O__O_@____O : len=11 : NO 00 NO
+ 1273 49 48 = O__O_@O___O : len=11 : D3 10 D3
+ 1274 49 49 = O__O_@_O__O : len=11 : NO 00 NO
+ 1275 50 0 = O__OO@# : len= 7 : NO 00 NO
+ 1276 50 1 = O__OO@_# : len= 8 : NO 00 H3
+ 1277 50 2 = O__OO@O# : len= 8 : S4 00 S4
+ 1278 50 3 = O__OO@__# : len= 9 : H3 31 H3
+ 1279 50 4 = O__OO@O_# : len= 9 : H4 00 H4
+ 1280 50 5 = O__OO@_O# : len= 9 : D4 00 D4
+ 1281 50 6 = O__OO@OO# : len= 9 : L5 00 L5
+ 1282 50 7 = O__OO@___# : len=10 : H3 31 H3
+ 1283 50 8 = O__OO@O__# : len=10 : H4 00 H4
+ 1284 50 9 = O__OO@_O_# : len=10 : D4 00 D4
+ 1285 50 10 = O__OO@OO_# : len=10 : L5 00 L5
+ 1286 50 11 = O__OO@__O# : len=10 : NO 00 H3
+ 1287 50 12 = O__OO@O_O# : len=10 : S4 00 H4
+ 1288 50 13 = O__OO@_OO# : len=10 : NO 00 D4
+ 1289 50 14 = O__OO@OOO# : len=10 : L6 00 L6
+ 1290 50 15 = O__OO@____# : len=11 : H3 31 H3
+ 1291 50 16 = O__OO@O___# : len=11 : H4 00 H4
+ 1292 50 17 = O__OO@_O__# : len=11 : D4 00 D4
+ 1293 50 18 = O__OO@OO__# : len=11 : L5 00 L5
+ 1294 50 19 = O__OO@__O_# : len=11 : NO 00 H3
+ 1295 50 20 = O__OO@O_O_# : len=11 : S4 00 H4
+ 1296 50 21 = O__OO@_OO_# : len=11 : NO 00 D4
+ 1297 50 22 = O__OO@OOO_# : len=11 : L6 00 L6
+ 1298 50 23 = O__OO@___O# : len=11 : H3 31 H3
+ 1299 50 24 = O__OO@O__O# : len=11 : H4 00 H4
+ 1300 50 25 = O__OO@_O_O# : len=11 : D4 00 D4
+ 1301 50 26 = O__OO@OO_O# : len=11 : L5 00 L5
+ 1302 50 27 = O__OO@__OO# : len=11 : NO 00 H3
+ 1303 50 28 = O__OO@O_OO# : len=11 : S4 00 H4
+ 1304 50 29 = O__OO@_OOO# : len=11 : NO 00 D4
+ 1305 50 30 = O__OO@OOOO# : len=11 : L6 00 L6
+ 1306 50 31 = O__OO@_____ : len=11 : H3 31 H3
+ 1307 50 32 = O__OO@O____ : len=11 : H4 00 H4
+ 1308 50 33 = O__OO@_O___ : len=11 : D4 00 D4
+ 1309 50 34 = O__OO@OO___ : len=11 : L5 00 L5
+ 1310 50 35 = O__OO@__O__ : len=11 : NO 00 H3
+ 1311 50 36 = O__OO@O_O__ : len=11 : S4 00 H4
+ 1312 50 37 = O__OO@_OO__ : len=11 : NO 00 D4
+ 1313 50 38 = O__OO@OOO__ : len=11 : L6 00 L6
+ 1314 50 39 = O__OO@___O_ : len=11 : H3 31 H3
+ 1315 50 40 = O__OO@O__O_ : len=11 : H4 00 H4
+ 1316 50 41 = O__OO@_O_O_ : len=11 : D4 00 D4
+ 1317 50 42 = O__OO@OO_O_ : len=11 : L5 00 L5
+ 1318 50 43 = O__OO@__OO_ : len=11 : NO 00 H3
+ 1319 50 44 = O__OO@O_OO_ : len=11 : S4 00 H4
+ 1320 50 45 = O__OO@_OOO_ : len=11 : NO 00 D4
+ 1321 50 46 = O__OO@OOOO_ : len=11 : L6 00 L6
+ 1322 50 47 = O__OO@____O : len=11 : H3 31 H3
+ 1323 50 48 = O__OO@O___O : len=11 : H4 00 H4
+ 1324 50 49 = O__OO@_O__O : len=11 : D4 00 D4
+ 1325 50 50 = O__OO@OO__O : len=11 : L5 00 L5
+ 1326 51 0 = O_O__@# : len= 7 : NO 00 NO
+ 1327 51 1 = O_O__@_# : len= 8 : NO 00 NO
+ 1328 51 2 = O_O__@O# : len= 8 : NO 00 NO
+ 1329 51 3 = O_O__@__# : len= 9 : NO 00 NO
+ 1330 51 4 = O_O__@O_# : len= 9 : NO 00 NO
+ 1331 51 5 = O_O__@_O# : len= 9 : NO 00 NO
+ 1332 51 6 = O_O__@OO# : len= 9 : NO 00 NO
+ 1333 51 7 = O_O__@___# : len=10 : NO 00 NO
+ 1334 51 8 = O_O__@O__# : len=10 : NO 00 NO
+ 1335 51 9 = O_O__@_O_# : len=10 : NO 00 NO
+ 1336 51 10 = O_O__@OO_# : len=10 : NO 00 H3
+ 1337 51 11 = O_O__@__O# : len=10 : NO 00 NO
+ 1338 51 12 = O_O__@O_O# : len=10 : NO 00 NO
+ 1339 51 13 = O_O__@_OO# : len=10 : NO 00 NO
+ 1340 51 14 = O_O__@OOO# : len=10 : S4 00 S4
+ 1341 51 15 = O_O__@____# : len=11 : NO 00 NO
+ 1342 51 16 = O_O__@O___# : len=11 : NO 00 NO
+ 1343 51 17 = O_O__@_O__# : len=11 : NO 00 NO
+ 1344 51 18 = O_O__@OO__# : len=11 : H3 13 H3
+ 1345 51 19 = O_O__@__O_# : len=11 : NO 00 NO
+ 1346 51 20 = O_O__@O_O_# : len=11 : D3 02 D3
+ 1347 51 21 = O_O__@_OO_# : len=11 : D3 01 D3
+ 1348 51 22 = O_O__@OOO_# : len=11 : H4 00 H4
+ 1349 51 23 = O_O__@___O# : len=11 : NO 00 NO
+ 1350 51 24 = O_O__@O__O# : len=11 : NO 00 NO
+ 1351 51 25 = O_O__@_O_O# : len=11 : NO 00 NO
+ 1352 51 26 = O_O__@OO_O# : len=11 : D4 00 D4
+ 1353 51 27 = O_O__@__OO# : len=11 : NO 00 NO
+ 1354 51 28 = O_O__@O_OO# : len=11 : D4 00 D4
+ 1355 51 29 = O_O__@_OOO# : len=11 : D4 00 D4
+ 1356 51 30 = O_O__@OOOO# : len=11 : L5 00 L5
+ 1357 51 31 = O_O__@_____ : len=11 : NO 00 NO
+ 1358 51 32 = O_O__@O____ : len=11 : NO 00 NO
+ 1359 51 33 = O_O__@_O___ : len=11 : NO 00 NO
+ 1360 51 34 = O_O__@OO___ : len=11 : H3 13 H3
+ 1361 51 35 = O_O__@__O__ : len=11 : NO 00 NO
+ 1362 51 36 = O_O__@O_O__ : len=11 : D3 02 D3
+ 1363 51 37 = O_O__@_OO__ : len=11 : D3 01 D3
+ 1364 51 38 = O_O__@OOO__ : len=11 : H4 00 H4
+ 1365 51 39 = O_O__@___O_ : len=11 : NO 00 NO
+ 1366 51 40 = O_O__@O__O_ : len=11 : NO 00 NO
+ 1367 51 41 = O_O__@_O_O_ : len=11 : NO 00 NO
+ 1368 51 42 = O_O__@OO_O_ : len=11 : D4 00 D4
+ 1369 51 43 = O_O__@__OO_ : len=11 : NO 00 NO
+ 1370 51 44 = O_O__@O_OO_ : len=11 : D4 00 D4
+ 1371 51 45 = O_O__@_OOO_ : len=11 : D4 00 D4
+ 1372 51 46 = O_O__@OOOO_ : len=11 : L5 00 L5
+ 1373 51 47 = O_O__@____O : len=11 : NO 00 NO
+ 1374 51 48 = O_O__@O___O : len=11 : NO 00 NO
+ 1375 51 49 = O_O__@_O__O : len=11 : NO 00 NO
+ 1376 51 50 = O_O__@OO__O : len=11 : NO 00 H3
+ 1377 51 51 = O_O__@__O_O : len=11 : NO 00 NO
+ 1378 52 0 = O_O_O@# : len= 7 : NO 00 NO
+ 1379 52 1 = O_O_O@_# : len= 8 : NO 00 D3
+ 1380 52 2 = O_O_O@O# : len= 8 : D4 00 D4
+ 1381 52 3 = O_O_O@__# : len= 9 : NO 00 D3
+ 1382 52 4 = O_O_O@O_# : len= 9 : D4 00 D4
+ 1383 52 5 = O_O_O@_O# : len= 9 : NO 00 D3
+ 1384 52 6 = O_O_O@OO# : len= 9 : NO 00 D4
+ 1385 52 7 = O_O_O@___# : len=10 : NO 00 D3
+ 1386 52 8 = O_O_O@O__# : len=10 : D4 00 D4
+ 1387 52 9 = O_O_O@_O_# : len=10 : NO 00 D3
+ 1388 52 10 = O_O_O@OO_# : len=10 : S4 00 H4
+ 1389 52 11 = O_O_O@__O# : len=10 : NO 00 D3
+ 1390 52 12 = O_O_O@O_O# : len=10 : X4 00 X4
+ 1391 52 13 = O_O_O@_OO# : len=10 : D4 00 D4
+ 1392 52 14 = O_O_O@OOO# : len=10 : L5 00 L5
+ 1393 52 15 = O_O_O@____# : len=11 : NO 00 D3
+ 1394 52 16 = O_O_O@O___# : len=11 : D4 00 D4
+ 1395 52 17 = O_O_O@_O__# : len=11 : NO 00 D3
+ 1396 52 18 = O_O_O@OO__# : len=11 : S4 00 H4
+ 1397 52 19 = O_O_O@__O_# : len=11 : NO 00 D3
+ 1398 52 20 = O_O_O@O_O_# : len=11 : X4 00 X4
+ 1399 52 21 = O_O_O@_OO_# : len=11 : D4 00 D4
+ 1400 52 22 = O_O_O@OOO_# : len=11 : L5 00 L5
+ 1401 52 23 = O_O_O@___O# : len=11 : NO 00 D3
+ 1402 52 24 = O_O_O@O__O# : len=11 : D4 00 D4
+ 1403 52 25 = O_O_O@_O_O# : len=11 : NO 00 D3
+ 1404 52 26 = O_O_O@OO_O# : len=11 : NO 00 H4
+ 1405 52 27 = O_O_O@__OO# : len=11 : NO 00 D3
+ 1406 52 28 = O_O_O@O_OO# : len=11 : D4 00 X4
+ 1407 52 29 = O_O_O@_OOO# : len=11 : NO 00 D4
+ 1408 52 30 = O_O_O@OOOO# : len=11 : L6 00 L6
+ 1409 52 31 = O_O_O@_____ : len=11 : NO 00 D3
+ 1410 52 32 = O_O_O@O____ : len=11 : D4 00 D4
+ 1411 52 33 = O_O_O@_O___ : len=11 : NO 00 D3
+ 1412 52 34 = O_O_O@OO___ : len=11 : S4 00 H4
+ 1413 52 35 = O_O_O@__O__ : len=11 : NO 00 D3
+ 1414 52 36 = O_O_O@O_O__ : len=11 : X4 00 X4
+ 1415 52 37 = O_O_O@_OO__ : len=11 : D4 00 D4
+ 1416 52 38 = O_O_O@OOO__ : len=11 : L5 00 L5
+ 1417 52 39 = O_O_O@___O_ : len=11 : NO 00 D3
+ 1418 52 40 = O_O_O@O__O_ : len=11 : D4 00 D4
+ 1419 52 41 = O_O_O@_O_O_ : len=11 : NO 00 D3
+ 1420 52 42 = O_O_O@OO_O_ : len=11 : NO 00 H4
+ 1421 52 43 = O_O_O@__OO_ : len=11 : NO 00 D3
+ 1422 52 44 = O_O_O@O_OO_ : len=11 : D4 00 X4
+ 1423 52 45 = O_O_O@_OOO_ : len=11 : NO 00 D4
+ 1424 52 46 = O_O_O@OOOO_ : len=11 : L6 00 L6
+ 1425 52 47 = O_O_O@____O : len=11 : NO 00 D3
+ 1426 52 48 = O_O_O@O___O : len=11 : D4 00 D4
+ 1427 52 49 = O_O_O@_O__O : len=11 : NO 00 D3
+ 1428 52 50 = O_O_O@OO__O : len=11 : S4 00 H4
+ 1429 52 51 = O_O_O@__O_O : len=11 : NO 00 D3
+ 1430 52 52 = O_O_O@O_O_O : len=11 : X4 00 X4
+ 1431 53 0 = O_OO_@# : len= 7 : NO 00 NO
+ 1432 53 1 = O_OO_@_# : len= 8 : NO 00 D3
+ 1433 53 2 = O_OO_@O# : len= 8 : D4 00 D4
+ 1434 53 3 = O_OO_@__# : len= 9 : NO 00 D3
+ 1435 53 4 = O_OO_@O_# : len= 9 : D4 00 D4
+ 1436 53 5 = O_OO_@_O# : len= 9 : NO 00 D3
+ 1437 53 6 = O_OO_@OO# : len= 9 : NO 00 D4
+ 1438 53 7 = O_OO_@___# : len=10 : NO 00 D3
+ 1439 53 8 = O_OO_@O__# : len=10 : D4 00 D4
+ 1440 53 9 = O_OO_@_O_# : len=10 : NO 00 D3
+ 1441 53 10 = O_OO_@OO_# : len=10 : NO 00 D4
+ 1442 53 11 = O_OO_@__O# : len=10 : NO 00 D3
+ 1443 53 12 = O_OO_@O_O# : len=10 : D4 00 D4
+ 1444 53 13 = O_OO_@_OO# : len=10 : NO 00 D3
+ 1445 53 14 = O_OO_@OOO# : len=10 : NO 00 D4
+ 1446 53 15 = O_OO_@____# : len=11 : NO 00 D3
+ 1447 53 16 = O_OO_@O___# : len=11 : D4 00 D4
+ 1448 53 17 = O_OO_@_O__# : len=11 : NO 00 D3
+ 1449 53 18 = O_OO_@OO__# : len=11 : NO 00 D4
+ 1450 53 19 = O_OO_@__O_# : len=11 : NO 00 D3
+ 1451 53 20 = O_OO_@O_O_# : len=11 : D4 00 D4
+ 1452 53 21 = O_OO_@_OO_# : len=11 : NO 00 D3
+ 1453 53 22 = O_OO_@OOO_# : len=11 : S4 00 H4
+ 1454 53 23 = O_OO_@___O# : len=11 : NO 00 D3
+ 1455 53 24 = O_OO_@O__O# : len=11 : D4 00 D4
+ 1456 53 25 = O_OO_@_O_O# : len=11 : NO 00 D3
+ 1457 53 26 = O_OO_@OO_O# : len=11 : D4 00 X4
+ 1458 53 27 = O_OO_@__OO# : len=11 : NO 00 D3
+ 1459 53 28 = O_OO_@O_OO# : len=11 : X4 00 X4
+ 1460 53 29 = O_OO_@_OOO# : len=11 : D4 00 D4
+ 1461 53 30 = O_OO_@OOOO# : len=11 : L5 00 L5
+ 1462 53 31 = O_OO_@_____ : len=11 : NO 00 D3
+ 1463 53 32 = O_OO_@O____ : len=11 : D4 00 D4
+ 1464 53 33 = O_OO_@_O___ : len=11 : NO 00 D3
+ 1465 53 34 = O_OO_@OO___ : len=11 : NO 00 D4
+ 1466 53 35 = O_OO_@__O__ : len=11 : NO 00 D3
+ 1467 53 36 = O_OO_@O_O__ : len=11 : D4 00 D4
+ 1468 53 37 = O_OO_@_OO__ : len=11 : NO 00 D3
+ 1469 53 38 = O_OO_@OOO__ : len=11 : S4 00 H4
+ 1470 53 39 = O_OO_@___O_ : len=11 : NO 00 D3
+ 1471 53 40 = O_OO_@O__O_ : len=11 : D4 00 D4
+ 1472 53 41 = O_OO_@_O_O_ : len=11 : NO 00 D3
+ 1473 53 42 = O_OO_@OO_O_ : len=11 : D4 00 X4
+ 1474 53 43 = O_OO_@__OO_ : len=11 : NO 00 D3
+ 1475 53 44 = O_OO_@O_OO_ : len=11 : X4 00 X4
+ 1476 53 45 = O_OO_@_OOO_ : len=11 : D4 00 D4
+ 1477 53 46 = O_OO_@OOOO_ : len=11 : L5 00 L5
+ 1478 53 47 = O_OO_@____O : len=11 : NO 00 D3
+ 1479 53 48 = O_OO_@O___O : len=11 : D4 00 D4
+ 1480 53 49 = O_OO_@_O__O : len=11 : NO 00 D3
+ 1481 53 50 = O_OO_@OO__O : len=11 : NO 00 D4
+ 1482 53 51 = O_OO_@__O_O : len=11 : NO 00 D3
+ 1483 53 52 = O_OO_@O_O_O : len=11 : D4 00 D4
+ 1484 53 53 = O_OO_@_OO_O : len=11 : NO 00 D3
+ 1485 54 0 = O_OOO@# : len= 7 : NO 00 D4
+ 1486 54 1 = O_OOO@_# : len= 8 : S4 00 H4
+ 1487 54 2 = O_OOO@O# : len= 8 : L5 00 L5
+ 1488 54 3 = O_OOO@__# : len= 9 : S4 00 H4
+ 1489 54 4 = O_OOO@O_# : len= 9 : L5 00 L5
+ 1490 54 5 = O_OOO@_O# : len= 9 : NO 00 H4
+ 1491 54 6 = O_OOO@OO# : len= 9 : L6 00 L6
+ 1492 54 7 = O_OOO@___# : len=10 : S4 00 H4
+ 1493 54 8 = O_OOO@O__# : len=10 : L5 00 L5
+ 1494 54 9 = O_OOO@_O_# : len=10 : NO 00 H4
+ 1495 54 10 = O_OOO@OO_# : len=10 : L6 00 L6
+ 1496 54 11 = O_OOO@__O# : len=10 : S4 00 H4
+ 1497 54 12 = O_OOO@O_O# : len=10 : L5 00 L5
+ 1498 54 13 = O_OOO@_OO# : len=10 : NO 00 H4
+ 1499 54 14 = O_OOO@OOO# : len=10 : L6 00 L6
+ 1500 54 15 = O_OOO@____# : len=11 : S4 00 H4
+ 1501 54 16 = O_OOO@O___# : len=11 : L5 00 L5
+ 1502 54 17 = O_OOO@_O__# : len=11 : NO 00 H4
+ 1503 54 18 = O_OOO@OO__# : len=11 : L6 00 L6
+ 1504 54 19 = O_OOO@__O_# : len=11 : S4 00 H4
+ 1505 54 20 = O_OOO@O_O_# : len=11 : L5 00 L5
+ 1506 54 21 = O_OOO@_OO_# : len=11 : NO 00 H4
+ 1507 54 22 = O_OOO@OOO_# : len=11 : L6 00 L6
+ 1508 54 23 = O_OOO@___O# : len=11 : S4 00 H4
+ 1509 54 24 = O_OOO@O__O# : len=11 : L5 00 L5
+ 1510 54 25 = O_OOO@_O_O# : len=11 : NO 00 H4
+ 1511 54 26 = O_OOO@OO_O# : len=11 : L6 00 L6
+ 1512 54 27 = O_OOO@__OO# : len=11 : S4 00 H4
+ 1513 54 28 = O_OOO@O_OO# : len=11 : L5 00 L5
+ 1514 54 29 = O_OOO@_OOO# : len=11 : NO 00 H4
+ 1515 54 30 = O_OOO@OOOO# : len=11 : L6 00 L6
+ 1516 54 31 = O_OOO@_____ : len=11 : S4 00 H4
+ 1517 54 32 = O_OOO@O____ : len=11 : L5 00 L5
+ 1518 54 33 = O_OOO@_O___ : len=11 : NO 00 H4
+ 1519 54 34 = O_OOO@OO___ : len=11 : L6 00 L6
+ 1520 54 35 = O_OOO@__O__ : len=11 : S4 00 H4
+ 1521 54 36 = O_OOO@O_O__ : len=11 : L5 00 L5
+ 1522 54 37 = O_OOO@_OO__ : len=11 : NO 00 H4
+ 1523 54 38 = O_OOO@OOO__ : len=11 : L6 00 L6
+ 1524 54 39 = O_OOO@___O_ : len=11 : S4 00 H4
+ 1525 54 40 = O_OOO@O__O_ : len=11 : L5 00 L5
+ 1526 54 41 = O_OOO@_O_O_ : len=11 : NO 00 H4
+ 1527 54 42 = O_OOO@OO_O_ : len=11 : L6 00 L6
+ 1528 54 43 = O_OOO@__OO_ : len=11 : S4 00 H4
+ 1529 54 44 = O_OOO@O_OO_ : len=11 : L5 00 L5
+ 1530 54 45 = O_OOO@_OOO_ : len=11 : NO 00 H4
+ 1531 54 46 = O_OOO@OOOO_ : len=11 : L6 00 L6
+ 1532 54 47 = O_OOO@____O : len=11 : S4 00 H4
+ 1533 54 48 = O_OOO@O___O : len=11 : L5 00 L5
+ 1534 54 49 = O_OOO@_O__O : len=11 : NO 00 H4
+ 1535 54 50 = O_OOO@OO__O : len=11 : L6 00 L6
+ 1536 54 51 = O_OOO@__O_O : len=11 : S4 00 H4
+ 1537 54 52 = O_OOO@O_O_O : len=11 : L5 00 L5
+ 1538 54 53 = O_OOO@_OO_O : len=11 : NO 00 H4
+ 1539 54 54 = O_OOO@OOO_O : len=11 : L6 00 L6
+ 1540 55 0 = OO___@# : len= 7 : NO 00 NO
+ 1541 55 1 = OO___@_# : len= 8 : NO 00 NO
+ 1542 55 2 = OO___@O# : len= 8 : NO 00 NO
+ 1543 55 3 = OO___@__# : len= 9 : NO 00 NO
+ 1544 55 4 = OO___@O_# : len= 9 : NO 00 NO
+ 1545 55 5 = OO___@_O# : len= 9 : NO 00 NO
+ 1546 55 6 = OO___@OO# : len= 9 : NO 00 NO
+ 1547 55 7 = OO___@___# : len=10 : NO 00 NO
+ 1548 55 8 = OO___@O__# : len=10 : NO 00 NO
+ 1549 55 9 = OO___@_O_# : len=10 : NO 00 NO
+ 1550 55 10 = OO___@OO_# : len=10 : H3 13 H3
+ 1551 55 11 = OO___@__O# : len=10 : NO 00 NO
+ 1552 55 12 = OO___@O_O# : len=10 : NO 00 NO
+ 1553 55 13 = OO___@_OO# : len=10 : NO 00 NO
+ 1554 55 14 = OO___@OOO# : len=10 : S4 00 S4
+ 1555 55 15 = OO___@____# : len=11 : NO 00 NO
+ 1556 55 16 = OO___@O___# : len=11 : NO 00 NO
+ 1557 55 17 = OO___@_O__# : len=11 : NO 00 NO
+ 1558 55 18 = OO___@OO__# : len=11 : H3 13 H3
+ 1559 55 19 = OO___@__O_# : len=11 : NO 00 NO
+ 1560 55 20 = OO___@O_O_# : len=11 : D3 02 D3
+ 1561 55 21 = OO___@_OO_# : len=11 : D3 01 D3
+ 1562 55 22 = OO___@OOO_# : len=11 : H4 00 H4
+ 1563 55 23 = OO___@___O# : len=11 : NO 00 NO
+ 1564 55 24 = OO___@O__O# : len=11 : NO 00 NO
+ 1565 55 25 = OO___@_O_O# : len=11 : NO 00 NO
+ 1566 55 26 = OO___@OO_O# : len=11 : D4 00 D4
+ 1567 55 27 = OO___@__OO# : len=11 : NO 00 NO
+ 1568 55 28 = OO___@O_OO# : len=11 : D4 00 D4
+ 1569 55 29 = OO___@_OOO# : len=11 : D4 00 D4
+ 1570 55 30 = OO___@OOOO# : len=11 : L5 00 L5
+ 1571 55 31 = OO___@_____ : len=11 : NO 00 NO
+ 1572 55 32 = OO___@O____ : len=11 : NO 00 NO
+ 1573 55 33 = OO___@_O___ : len=11 : NO 00 NO
+ 1574 55 34 = OO___@OO___ : len=11 : H3 13 H3
+ 1575 55 35 = OO___@__O__ : len=11 : NO 00 NO
+ 1576 55 36 = OO___@O_O__ : len=11 : D3 02 D3
+ 1577 55 37 = OO___@_OO__ : len=11 : D3 01 D3
+ 1578 55 38 = OO___@OOO__ : len=11 : H4 00 H4
+ 1579 55 39 = OO___@___O_ : len=11 : NO 00 NO
+ 1580 55 40 = OO___@O__O_ : len=11 : NO 00 NO
+ 1581 55 41 = OO___@_O_O_ : len=11 : NO 00 NO
+ 1582 55 42 = OO___@OO_O_ : len=11 : D4 00 D4
+ 1583 55 43 = OO___@__OO_ : len=11 : NO 00 NO
+ 1584 55 44 = OO___@O_OO_ : len=11 : D4 00 D4
+ 1585 55 45 = OO___@_OOO_ : len=11 : D4 00 D4
+ 1586 55 46 = OO___@OOOO_ : len=11 : L5 00 L5
+ 1587 55 47 = OO___@____O : len=11 : NO 00 NO
+ 1588 55 48 = OO___@O___O : len=11 : NO 00 NO
+ 1589 55 49 = OO___@_O__O : len=11 : NO 00 NO
+ 1590 55 50 = OO___@OO__O : len=11 : H3 13 H3
+ 1591 55 51 = OO___@__O_O : len=11 : NO 00 NO
+ 1592 55 52 = OO___@O_O_O : len=11 : NO 00 D3
+ 1593 55 53 = OO___@_OO_O : len=11 : NO 00 D3
+ 1594 55 54 = OO___@OOO_O : len=11 : S4 00 H4
+ 1595 55 55 = OO___@___OO : len=11 : NO 00 NO
+ 1596 56 0 = OO__O@# : len= 7 : NO 00 NO
+ 1597 56 1 = OO__O@_# : len= 8 : NO 00 NO
+ 1598 56 2 = OO__O@O# : len= 8 : NO 00 NO
+ 1599 56 3 = OO__O@__# : len= 9 : NO 00 NO
+ 1600 56 4 = OO__O@O_# : len= 9 : NO 00 H3
+ 1601 56 5 = OO__O@_O# : len= 9 : NO 00 NO
+ 1602 56 6 = OO__O@OO# : len= 9 : S4 00 S4
+ 1603 56 7 = OO__O@___# : len=10 : NO 00 NO
+ 1604 56 8 = OO__O@O__# : len=10 : H3 22 H3
+ 1605 56 9 = OO__O@_O_# : len=10 : D3 01 D3
+ 1606 56 10 = OO__O@OO_# : len=10 : H4 00 H4
+ 1607 56 11 = OO__O@__O# : len=10 : NO 00 NO
+ 1608 56 12 = OO__O@O_O# : len=10 : D4 00 D4
+ 1609 56 13 = OO__O@_OO# : len=10 : D4 00 D4
+ 1610 56 14 = OO__O@OOO# : len=10 : L5 00 L5
+ 1611 56 15 = OO__O@____# : len=11 : NO 00 NO
+ 1612 56 16 = OO__O@O___# : len=11 : H3 22 H3
+ 1613 56 17 = OO__O@_O__# : len=11 : D3 01 D3
+ 1614 56 18 = OO__O@OO__# : len=11 : H4 00 H4
+ 1615 56 19 = OO__O@__O_# : len=11 : NO 00 NO
+ 1616 56 20 = OO__O@O_O_# : len=11 : D4 00 D4
+ 1617 56 21 = OO__O@_OO_# : len=11 : D4 00 D4
+ 1618 56 22 = OO__O@OOO_# : len=11 : L5 00 L5
+ 1619 56 23 = OO__O@___O# : len=11 : NO 00 NO
+ 1620 56 24 = OO__O@O__O# : len=11 : NO 00 H3
+ 1621 56 25 = OO__O@_O_O# : len=11 : NO 00 D3
+ 1622 56 26 = OO__O@OO_O# : len=11 : S4 00 H4
+ 1623 56 27 = OO__O@__OO# : len=11 : NO 00 NO
+ 1624 56 28 = OO__O@O_OO# : len=11 : NO 00 D4
+ 1625 56 29 = OO__O@_OOO# : len=11 : NO 00 D4
+ 1626 56 30 = OO__O@OOOO# : len=11 : L6 00 L6
+ 1627 56 31 = OO__O@_____ : len=11 : NO 00 NO
+ 1628 56 32 = OO__O@O____ : len=11 : H3 22 H3
+ 1629 56 33 = OO__O@_O___ : len=11 : D3 01 D3
+ 1630 56 34 = OO__O@OO___ : len=11 : H4 00 H4
+ 1631 56 35 = OO__O@__O__ : len=11 : NO 00 NO
+ 1632 56 36 = OO__O@O_O__ : len=11 : D4 00 D4
+ 1633 56 37 = OO__O@_OO__ : len=11 : D4 00 D4
+ 1634 56 38 = OO__O@OOO__ : len=11 : L5 00 L5
+ 1635 56 39 = OO__O@___O_ : len=11 : NO 00 NO
+ 1636 56 40 = OO__O@O__O_ : len=11 : NO 00 H3
+ 1637 56 41 = OO__O@_O_O_ : len=11 : NO 00 D3
+ 1638 56 42 = OO__O@OO_O_ : len=11 : S4 00 H4
+ 1639 56 43 = OO__O@__OO_ : len=11 : NO 00 NO
+ 1640 56 44 = OO__O@O_OO_ : len=11 : NO 00 D4
+ 1641 56 45 = OO__O@_OOO_ : len=11 : NO 00 D4
+ 1642 56 46 = OO__O@OOOO_ : len=11 : L6 00 L6
+ 1643 56 47 = OO__O@____O : len=11 : NO 00 NO
+ 1644 56 48 = OO__O@O___O : len=11 : H3 22 H3
+ 1645 56 49 = OO__O@_O__O : len=11 : D3 01 D3
+ 1646 56 50 = OO__O@OO__O : len=11 : H4 00 H4
+ 1647 56 51 = OO__O@__O_O : len=11 : NO 00 NO
+ 1648 56 52 = OO__O@O_O_O : len=11 : D4 00 D4
+ 1649 56 53 = OO__O@_OO_O : len=11 : D4 00 D4
+ 1650 56 54 = OO__O@OOO_O : len=11 : L5 00 L5
+ 1651 56 55 = OO__O@___OO : len=11 : NO 00 NO
+ 1652 56 56 = OO__O@O__OO : len=11 : NO 00 H3
+ 1653 57 0 = OO_O_@# : len= 7 : NO 00 NO
+ 1654 57 1 = OO_O_@_# : len= 8 : NO 00 NO
+ 1655 57 2 = OO_O_@O# : len= 8 : NO 00 NO
+ 1656 57 3 = OO_O_@__# : len= 9 : NO 00 NO
+ 1657 57 4 = OO_O_@O_# : len= 9 : NO 00 D3
+ 1658 57 5 = OO_O_@_O# : len= 9 : NO 00 NO
+ 1659 57 6 = OO_O_@OO# : len= 9 : D4 00 D4
+ 1660 57 7 = OO_O_@___# : len=10 : NO 00 NO
+ 1661 57 8 = OO_O_@O__# : len=10 : NO 00 D3
+ 1662 57 9 = OO_O_@_O_# : len=10 : NO 00 NO
+ 1663 57 10 = OO_O_@OO_# : len=10 : D4 00 D4
+ 1664 57 11 = OO_O_@__O# : len=10 : NO 00 NO
+ 1665 57 12 = OO_O_@O_O# : len=10 : NO 00 D3
+ 1666 57 13 = OO_O_@_OO# : len=10 : NO 00 NO
+ 1667 57 14 = OO_O_@OOO# : len=10 : NO 00 D4
+ 1668 57 15 = OO_O_@____# : len=11 : NO 00 NO
+ 1669 57 16 = OO_O_@O___# : len=11 : NO 00 D3
+ 1670 57 17 = OO_O_@_O__# : len=11 : NO 00 NO
+ 1671 57 18 = OO_O_@OO__# : len=11 : D4 00 D4
+ 1672 57 19 = OO_O_@__O_# : len=11 : NO 00 NO
+ 1673 57 20 = OO_O_@O_O_# : len=11 : NO 00 D3
+ 1674 57 21 = OO_O_@_OO_# : len=11 : NO 00 D3
+ 1675 57 22 = OO_O_@OOO_# : len=11 : S4 00 H4
+ 1676 57 23 = OO_O_@___O# : len=11 : NO 00 NO
+ 1677 57 24 = OO_O_@O__O# : len=11 : NO 00 D3
+ 1678 57 25 = OO_O_@_O_O# : len=11 : NO 00 NO
+ 1679 57 26 = OO_O_@OO_O# : len=11 : X4 00 X4
+ 1680 57 27 = OO_O_@__OO# : len=11 : NO 00 NO
+ 1681 57 28 = OO_O_@O_OO# : len=11 : D4 00 D4
+ 1682 57 29 = OO_O_@_OOO# : len=11 : D4 00 D4
+ 1683 57 30 = OO_O_@OOOO# : len=11 : L5 00 L5
+ 1684 57 31 = OO_O_@_____ : len=11 : NO 00 NO
+ 1685 57 32 = OO_O_@O____ : len=11 : NO 00 D3
+ 1686 57 33 = OO_O_@_O___ : len=11 : NO 00 NO
+ 1687 57 34 = OO_O_@OO___ : len=11 : D4 00 D4
+ 1688 57 35 = OO_O_@__O__ : len=11 : NO 00 NO
+ 1689 57 36 = OO_O_@O_O__ : len=11 : NO 00 D3
+ 1690 57 37 = OO_O_@_OO__ : len=11 : NO 00 D3
+ 1691 57 38 = OO_O_@OOO__ : len=11 : S4 00 H4
+ 1692 57 39 = OO_O_@___O_ : len=11 : NO 00 NO
+ 1693 57 40 = OO_O_@O__O_ : len=11 : NO 00 D3
+ 1694 57 41 = OO_O_@_O_O_ : len=11 : NO 00 NO
+ 1695 57 42 = OO_O_@OO_O_ : len=11 : X4 00 X4
+ 1696 57 43 = OO_O_@__OO_ : len=11 : NO 00 NO
+ 1697 57 44 = OO_O_@O_OO_ : len=11 : D4 00 D4
+ 1698 57 45 = OO_O_@_OOO_ : len=11 : D4 00 D4
+ 1699 57 46 = OO_O_@OOOO_ : len=11 : L5 00 L5
+ 1700 57 47 = OO_O_@____O : len=11 : NO 00 NO
+ 1701 57 48 = OO_O_@O___O : len=11 : NO 00 D3
+ 1702 57 49 = OO_O_@_O__O : len=11 : NO 00 NO
+ 1703 57 50 = OO_O_@OO__O : len=11 : D4 00 D4
+ 1704 57 51 = OO_O_@__O_O : len=11 : NO 00 NO
+ 1705 57 52 = OO_O_@O_O_O : len=11 : NO 00 D3
+ 1706 57 53 = OO_O_@_OO_O : len=11 : NO 00 D3
+ 1707 57 54 = OO_O_@OOO_O : len=11 : NO 00 H4
+ 1708 57 55 = OO_O_@___OO : len=11 : NO 00 NO
+ 1709 57 56 = OO_O_@O__OO : len=11 : NO 00 D3
+ 1710 57 57 = OO_O_@_O_OO : len=11 : NO 00 NO
+ 1711 58 0 = OO_OO@# : len= 7 : NO 00 D4
+ 1712 58 1 = OO_OO@_# : len= 8 : NO 00 D4
+ 1713 58 2 = OO_OO@O# : len= 8 : NO 00 D4
+ 1714 58 3 = OO_OO@__# : len= 9 : NO 00 D4
+ 1715 58 4 = OO_OO@O_# : len= 9 : S4 00 H4
+ 1716 58 5 = OO_OO@_O# : len= 9 : D4 00 X4
+ 1717 58 6 = OO_OO@OO# : len= 9 : L5 00 L5
+ 1718 58 7 = OO_OO@___# : len=10 : NO 00 D4
+ 1719 58 8 = OO_OO@O__# : len=10 : S4 00 H4
+ 1720 58 9 = OO_OO@_O_# : len=10 : D4 00 X4
+ 1721 58 10 = OO_OO@OO_# : len=10 : L5 00 L5
+ 1722 58 11 = OO_OO@__O# : len=10 : NO 00 D4
+ 1723 58 12 = OO_OO@O_O# : len=10 : NO 00 H4
+ 1724 58 13 = OO_OO@_OO# : len=10 : NO 00 X4
+ 1725 58 14 = OO_OO@OOO# : len=10 : L6 00 L6
+ 1726 58 15 = OO_OO@____# : len=11 : NO 00 D4
+ 1727 58 16 = OO_OO@O___# : len=11 : S4 00 H4
+ 1728 58 17 = OO_OO@_O__# : len=11 : D4 00 X4
+ 1729 58 18 = OO_OO@OO__# : len=11 : L5 00 L5
+ 1730 58 19 = OO_OO@__O_# : len=11 : NO 00 D4
+ 1731 58 20 = OO_OO@O_O_# : len=11 : NO 00 H4
+ 1732 58 21 = OO_OO@_OO_# : len=11 : NO 00 X4
+ 1733 58 22 = OO_OO@OOO_# : len=11 : L6 00 L6
+ 1734 58 23 = OO_OO@___O# : len=11 : NO 00 D4
+ 1735 58 24 = OO_OO@O__O# : len=11 : S4 00 H4
+ 1736 58 25 = OO_OO@_O_O# : len=11 : D4 00 X4
+ 1737 58 26 = OO_OO@OO_O# : len=11 : L5 00 L5
+ 1738 58 27 = OO_OO@__OO# : len=11 : NO 00 D4
+ 1739 58 28 = OO_OO@O_OO# : len=11 : NO 00 H4
+ 1740 58 29 = OO_OO@_OOO# : len=11 : NO 00 X4
+ 1741 58 30 = OO_OO@OOOO# : len=11 : L6 00 L6
+ 1742 58 31 = OO_OO@_____ : len=11 : NO 00 D4
+ 1743 58 32 = OO_OO@O____ : len=11 : S4 00 H4
+ 1744 58 33 = OO_OO@_O___ : len=11 : D4 00 X4
+ 1745 58 34 = OO_OO@OO___ : len=11 : L5 00 L5
+ 1746 58 35 = OO_OO@__O__ : len=11 : NO 00 D4
+ 1747 58 36 = OO_OO@O_O__ : len=11 : NO 00 H4
+ 1748 58 37 = OO_OO@_OO__ : len=11 : NO 00 X4
+ 1749 58 38 = OO_OO@OOO__ : len=11 : L6 00 L6
+ 1750 58 39 = OO_OO@___O_ : len=11 : NO 00 D4
+ 1751 58 40 = OO_OO@O__O_ : len=11 : S4 00 H4
+ 1752 58 41 = OO_OO@_O_O_ : len=11 : D4 00 X4
+ 1753 58 42 = OO_OO@OO_O_ : len=11 : L5 00 L5
+ 1754 58 43 = OO_OO@__OO_ : len=11 : NO 00 D4
+ 1755 58 44 = OO_OO@O_OO_ : len=11 : NO 00 H4
+ 1756 58 45 = OO_OO@_OOO_ : len=11 : NO 00 X4
+ 1757 58 46 = OO_OO@OOOO_ : len=11 : L6 00 L6
+ 1758 58 47 = OO_OO@____O : len=11 : NO 00 D4
+ 1759 58 48 = OO_OO@O___O : len=11 : S4 00 H4
+ 1760 58 49 = OO_OO@_O__O : len=11 : D4 00 X4
+ 1761 58 50 = OO_OO@OO__O : len=11 : L5 00 L5
+ 1762 58 51 = OO_OO@__O_O : len=11 : NO 00 D4
+ 1763 58 52 = OO_OO@O_O_O : len=11 : NO 00 H4
+ 1764 58 53 = OO_OO@_OO_O : len=11 : NO 00 X4
+ 1765 58 54 = OO_OO@OOO_O : len=11 : L6 00 L6
+ 1766 58 55 = OO_OO@___OO : len=11 : NO 00 D4
+ 1767 58 56 = OO_OO@O__OO : len=11 : S4 00 H4
+ 1768 58 57 = OO_OO@_O_OO : len=11 : D4 00 X4
+ 1769 58 58 = OO_OO@OO_OO : len=11 : L5 00 L5
+ 1770 59 0 = OOO__@# : len= 7 : NO 00 NO
+ 1771 59 1 = OOO__@_# : len= 8 : NO 00 NO
+ 1772 59 2 = OOO__@O# : len= 8 : NO 00 NO
+ 1773 59 3 = OOO__@__# : len= 9 : NO 00 NO
+ 1774 59 4 = OOO__@O_# : len= 9 : NO 00 NO
+ 1775 59 5 = OOO__@_O# : len= 9 : NO 00 NO
+ 1776 59 6 = OOO__@OO# : len= 9 : NO 00 NO
+ 1777 59 7 = OOO__@___# : len=10 : NO 00 NO
+ 1778 59 8 = OOO__@O__# : len=10 : NO 00 NO
+ 1779 59 9 = OOO__@_O_# : len=10 : NO 00 NO
+ 1780 59 10 = OOO__@OO_# : len=10 : NO 00 H3
+ 1781 59 11 = OOO__@__O# : len=10 : NO 00 NO
+ 1782 59 12 = OOO__@O_O# : len=10 : NO 00 NO
+ 1783 59 13 = OOO__@_OO# : len=10 : NO 00 NO
+ 1784 59 14 = OOO__@OOO# : len=10 : S4 00 S4
+ 1785 59 15 = OOO__@____# : len=11 : NO 00 NO
+ 1786 59 16 = OOO__@O___# : len=11 : NO 00 NO
+ 1787 59 17 = OOO__@_O__# : len=11 : NO 00 NO
+ 1788 59 18 = OOO__@OO__# : len=11 : H3 13 H3
+ 1789 59 19 = OOO__@__O_# : len=11 : NO 00 NO
+ 1790 59 20 = OOO__@O_O_# : len=11 : D3 02 D3
+ 1791 59 21 = OOO__@_OO_# : len=11 : D3 01 D3
+ 1792 59 22 = OOO__@OOO_# : len=11 : H4 00 H4
+ 1793 59 23 = OOO__@___O# : len=11 : NO 00 NO
+ 1794 59 24 = OOO__@O__O# : len=11 : NO 00 NO
+ 1795 59 25 = OOO__@_O_O# : len=11 : NO 00 NO
+ 1796 59 26 = OOO__@OO_O# : len=11 : D4 00 D4
+ 1797 59 27 = OOO__@__OO# : len=11 : NO 00 NO
+ 1798 59 28 = OOO__@O_OO# : len=11 : D4 00 D4
+ 1799 59 29 = OOO__@_OOO# : len=11 : D4 00 D4
+ 1800 59 30 = OOO__@OOOO# : len=11 : L5 00 L5
+ 1801 59 31 = OOO__@_____ : len=11 : NO 00 NO
+ 1802 59 32 = OOO__@O____ : len=11 : NO 00 NO
+ 1803 59 33 = OOO__@_O___ : len=11 : NO 00 NO
+ 1804 59 34 = OOO__@OO___ : len=11 : H3 13 H3
+ 1805 59 35 = OOO__@__O__ : len=11 : NO 00 NO
+ 1806 59 36 = OOO__@O_O__ : len=11 : D3 02 D3
+ 1807 59 37 = OOO__@_OO__ : len=11 : D3 01 D3
+ 1808 59 38 = OOO__@OOO__ : len=11 : H4 00 H4
+ 1809 59 39 = OOO__@___O_ : len=11 : NO 00 NO
+ 1810 59 40 = OOO__@O__O_ : len=11 : NO 00 NO
+ 1811 59 41 = OOO__@_O_O_ : len=11 : NO 00 NO
+ 1812 59 42 = OOO__@OO_O_ : len=11 : D4 00 D4
+ 1813 59 43 = OOO__@__OO_ : len=11 : NO 00 NO
+ 1814 59 44 = OOO__@O_OO_ : len=11 : D4 00 D4
+ 1815 59 45 = OOO__@_OOO_ : len=11 : D4 00 D4
+ 1816 59 46 = OOO__@OOOO_ : len=11 : L5 00 L5
+ 1817 59 47 = OOO__@____O : len=11 : NO 00 NO
+ 1818 59 48 = OOO__@O___O : len=11 : NO 00 NO
+ 1819 59 49 = OOO__@_O__O : len=11 : NO 00 NO
+ 1820 59 50 = OOO__@OO__O : len=11 : NO 00 H3
+ 1821 59 51 = OOO__@__O_O : len=11 : NO 00 NO
+ 1822 59 52 = OOO__@O_O_O : len=11 : NO 00 D3
+ 1823 59 53 = OOO__@_OO_O : len=11 : NO 00 D3
+ 1824 59 54 = OOO__@OOO_O : len=11 : S4 00 H4
+ 1825 59 55 = OOO__@___OO : len=11 : NO 00 NO
+ 1826 59 56 = OOO__@O__OO : len=11 : NO 00 NO
+ 1827 59 57 = OOO__@_O_OO : len=11 : NO 00 NO
+ 1828 59 58 = OOO__@OO_OO : len=11 : NO 00 D4
+ 1829 59 59 = OOO__@__OOO : len=11 : NO 00 NO
+ 1830 60 0 = OOO_O@# : len= 7 : NO 00 D4
+ 1831 60 1 = OOO_O@_# : len= 8 : NO 00 D4
+ 1832 60 2 = OOO_O@O# : len= 8 : NO 00 D4
+ 1833 60 3 = OOO_O@__# : len= 9 : NO 00 D4
+ 1834 60 4 = OOO_O@O_# : len= 9 : NO 00 D4
+ 1835 60 5 = OOO_O@_O# : len= 9 : NO 00 D4
+ 1836 60 6 = OOO_O@OO# : len= 9 : NO 00 D4
+ 1837 60 7 = OOO_O@___# : len=10 : NO 00 D4
+ 1838 60 8 = OOO_O@O__# : len=10 : NO 00 D4
+ 1839 60 9 = OOO_O@_O_# : len=10 : NO 00 D4
+ 1840 60 10 = OOO_O@OO_# : len=10 : S4 00 H4
+ 1841 60 11 = OOO_O@__O# : len=10 : NO 00 D4
+ 1842 60 12 = OOO_O@O_O# : len=10 : D4 00 X4
+ 1843 60 13 = OOO_O@_OO# : len=10 : D4 00 X4
+ 1844 60 14 = OOO_O@OOO# : len=10 : L5 00 L5
+ 1845 60 15 = OOO_O@____# : len=11 : NO 00 D4
+ 1846 60 16 = OOO_O@O___# : len=11 : NO 00 D4
+ 1847 60 17 = OOO_O@_O__# : len=11 : NO 00 D4
+ 1848 60 18 = OOO_O@OO__# : len=11 : S4 00 H4
+ 1849 60 19 = OOO_O@__O_# : len=11 : NO 00 D4
+ 1850 60 20 = OOO_O@O_O_# : len=11 : D4 00 X4
+ 1851 60 21 = OOO_O@_OO_# : len=11 : D4 00 X4
+ 1852 60 22 = OOO_O@OOO_# : len=11 : L5 00 L5
+ 1853 60 23 = OOO_O@___O# : len=11 : NO 00 D4
+ 1854 60 24 = OOO_O@O__O# : len=11 : NO 00 D4
+ 1855 60 25 = OOO_O@_O_O# : len=11 : NO 00 D4
+ 1856 60 26 = OOO_O@OO_O# : len=11 : NO 00 H4
+ 1857 60 27 = OOO_O@__OO# : len=11 : NO 00 D4
+ 1858 60 28 = OOO_O@O_OO# : len=11 : NO 00 X4
+ 1859 60 29 = OOO_O@_OOO# : len=11 : NO 00 X4
+ 1860 60 30 = OOO_O@OOOO# : len=11 : L6 00 L6
+ 1861 60 31 = OOO_O@_____ : len=11 : NO 00 D4
+ 1862 60 32 = OOO_O@O____ : len=11 : NO 00 D4
+ 1863 60 33 = OOO_O@_O___ : len=11 : NO 00 D4
+ 1864 60 34 = OOO_O@OO___ : len=11 : S4 00 H4
+ 1865 60 35 = OOO_O@__O__ : len=11 : NO 00 D4
+ 1866 60 36 = OOO_O@O_O__ : len=11 : D4 00 X4
+ 1867 60 37 = OOO_O@_OO__ : len=11 : D4 00 X4
+ 1868 60 38 = OOO_O@OOO__ : len=11 : L5 00 L5
+ 1869 60 39 = OOO_O@___O_ : len=11 : NO 00 D4
+ 1870 60 40 = OOO_O@O__O_ : len=11 : NO 00 D4
+ 1871 60 41 = OOO_O@_O_O_ : len=11 : NO 00 D4
+ 1872 60 42 = OOO_O@OO_O_ : len=11 : NO 00 H4
+ 1873 60 43 = OOO_O@__OO_ : len=11 : NO 00 D4
+ 1874 60 44 = OOO_O@O_OO_ : len=11 : NO 00 X4
+ 1875 60 45 = OOO_O@_OOO_ : len=11 : NO 00 X4
+ 1876 60 46 = OOO_O@OOOO_ : len=11 : L6 00 L6
+ 1877 60 47 = OOO_O@____O : len=11 : NO 00 D4
+ 1878 60 48 = OOO_O@O___O : len=11 : NO 00 D4
+ 1879 60 49 = OOO_O@_O__O : len=11 : NO 00 D4
+ 1880 60 50 = OOO_O@OO__O : len=11 : S4 00 H4
+ 1881 60 51 = OOO_O@__O_O : len=11 : NO 00 D4
+ 1882 60 52 = OOO_O@O_O_O : len=11 : D4 00 X4
+ 1883 60 53 = OOO_O@_OO_O : len=11 : D4 00 X4
+ 1884 60 54 = OOO_O@OOO_O : len=11 : L5 00 L5
+ 1885 60 55 = OOO_O@___OO : len=11 : NO 00 D4
+ 1886 60 56 = OOO_O@O__OO : len=11 : NO 00 D4
+ 1887 60 57 = OOO_O@_O_OO : len=11 : NO 00 D4
+ 1888 60 58 = OOO_O@OO_OO : len=11 : NO 00 H4
+ 1889 60 59 = OOO_O@__OOO : len=11 : NO 00 D4
+ 1890 60 60 = OOO_O@O_OOO : len=11 : NO 00 X4
+ 1891 61 0 = OOOO_@# : len= 7 : NO 00 D4
+ 1892 61 1 = OOOO_@_# : len= 8 : NO 00 D4
+ 1893 61 2 = OOOO_@O# : len= 8 : NO 00 D4
+ 1894 61 3 = OOOO_@__# : len= 9 : NO 00 D4
+ 1895 61 4 = OOOO_@O_# : len= 9 : NO 00 D4
+ 1896 61 5 = OOOO_@_O# : len= 9 : NO 00 D4
+ 1897 61 6 = OOOO_@OO# : len= 9 : NO 00 D4
+ 1898 61 7 = OOOO_@___# : len=10 : NO 00 D4
+ 1899 61 8 = OOOO_@O__# : len=10 : NO 00 D4
+ 1900 61 9 = OOOO_@_O_# : len=10 : NO 00 D4
+ 1901 61 10 = OOOO_@OO_# : len=10 : NO 00 D4
+ 1902 61 11 = OOOO_@__O# : len=10 : NO 00 D4
+ 1903 61 12 = OOOO_@O_O# : len=10 : NO 00 D4
+ 1904 61 13 = OOOO_@_OO# : len=10 : NO 00 D4
+ 1905 61 14 = OOOO_@OOO# : len=10 : NO 00 D4
+ 1906 61 15 = OOOO_@____# : len=11 : NO 00 D4
+ 1907 61 16 = OOOO_@O___# : len=11 : NO 00 D4
+ 1908 61 17 = OOOO_@_O__# : len=11 : NO 00 D4
+ 1909 61 18 = OOOO_@OO__# : len=11 : NO 00 D4
+ 1910 61 19 = OOOO_@__O_# : len=11 : NO 00 D4
+ 1911 61 20 = OOOO_@O_O_# : len=11 : NO 00 D4
+ 1912 61 21 = OOOO_@_OO_# : len=11 : NO 00 D4
+ 1913 61 22 = OOOO_@OOO_# : len=11 : S4 00 H4
+ 1914 61 23 = OOOO_@___O# : len=11 : NO 00 D4
+ 1915 61 24 = OOOO_@O__O# : len=11 : NO 00 D4
+ 1916 61 25 = OOOO_@_O_O# : len=11 : NO 00 D4
+ 1917 61 26 = OOOO_@OO_O# : len=11 : D4 00 X4
+ 1918 61 27 = OOOO_@__OO# : len=11 : NO 00 D4
+ 1919 61 28 = OOOO_@O_OO# : len=11 : D4 00 X4
+ 1920 61 29 = OOOO_@_OOO# : len=11 : D4 00 X4
+ 1921 61 30 = OOOO_@OOOO# : len=11 : L5 00 L5
+ 1922 61 31 = OOOO_@_____ : len=11 : NO 00 D4
+ 1923 61 32 = OOOO_@O____ : len=11 : NO 00 D4
+ 1924 61 33 = OOOO_@_O___ : len=11 : NO 00 D4
+ 1925 61 34 = OOOO_@OO___ : len=11 : NO 00 D4
+ 1926 61 35 = OOOO_@__O__ : len=11 : NO 00 D4
+ 1927 61 36 = OOOO_@O_O__ : len=11 : NO 00 D4
+ 1928 61 37 = OOOO_@_OO__ : len=11 : NO 00 D4
+ 1929 61 38 = OOOO_@OOO__ : len=11 : S4 00 H4
+ 1930 61 39 = OOOO_@___O_ : len=11 : NO 00 D4
+ 1931 61 40 = OOOO_@O__O_ : len=11 : NO 00 D4
+ 1932 61 41 = OOOO_@_O_O_ : len=11 : NO 00 D4
+ 1933 61 42 = OOOO_@OO_O_ : len=11 : D4 00 X4
+ 1934 61 43 = OOOO_@__OO_ : len=11 : NO 00 D4
+ 1935 61 44 = OOOO_@O_OO_ : len=11 : D4 00 X4
+ 1936 61 45 = OOOO_@_OOO_ : len=11 : D4 00 X4
+ 1937 61 46 = OOOO_@OOOO_ : len=11 : L5 00 L5
+ 1938 61 47 = OOOO_@____O : len=11 : NO 00 D4
+ 1939 61 48 = OOOO_@O___O : len=11 : NO 00 D4
+ 1940 61 49 = OOOO_@_O__O : len=11 : NO 00 D4
+ 1941 61 50 = OOOO_@OO__O : len=11 : NO 00 D4
+ 1942 61 51 = OOOO_@__O_O : len=11 : NO 00 D4
+ 1943 61 52 = OOOO_@O_O_O : len=11 : NO 00 D4
+ 1944 61 53 = OOOO_@_OO_O : len=11 : NO 00 D4
+ 1945 61 54 = OOOO_@OOO_O : len=11 : NO 00 H4
+ 1946 61 55 = OOOO_@___OO : len=11 : NO 00 D4
+ 1947 61 56 = OOOO_@O__OO : len=11 : NO 00 D4
+ 1948 61 57 = OOOO_@_O_OO : len=11 : NO 00 D4
+ 1949 61 58 = OOOO_@OO_OO : len=11 : NO 00 X4
+ 1950 61 59 = OOOO_@__OOO : len=11 : NO 00 D4
+ 1951 61 60 = OOOO_@O_OOO : len=11 : NO 00 X4
+ 1952 61 61 = OOOO_@_OOOO : len=11 : NO 00 X4
+*/
+
+#endif
diff --git a/pttbbs/include/modes.h b/pttbbs/include/modes.h
new file mode 100644
index 00000000..b5bc9833
--- /dev/null
+++ b/pttbbs/include/modes.h
@@ -0,0 +1,197 @@
+/* $Id$ */
+#ifndef INCLUDE_MODES_H
+#define INCLUDE_MODES_H
+
+#define DONOTHING 0 /* Read menu command return states */
+#define FULLUPDATE 1 /* Entire screen was destroyed in this oper */
+#define PARTUPDATE 2 /* Only the top three lines were destroyed */
+#define DOQUIT 3 /* Exit read menu was executed */
+#define NEWDIRECT 4 /* Directory has changed, re-read files */
+#define READ_NEXT 5 /* Direct read next file */
+#define READ_PREV 6 /* Direct read prev file */
+#define DIRCHANGED 8 /* Index file was changed */
+#define READ_REDRAW 9
+#define PART_REDRAW 10
+#define TITLE_REDRAW 11
+#define READ_SKIP 12
+#define HEADERS_RELOAD 13
+
+/* user 操作狀態與模式 */
+#define IDLE 0
+#define MMENU 1 /* menu mode */
+#define ADMIN 2
+#define MAIL 3
+#define TMENU 4
+#define UMENU 5
+#define XMENU 6
+#define CLASS 7
+#define PMENU 8
+#define NMENU 9
+#define PSALE 10
+#define POSTING 11 /* boards & class */
+#define READBRD 12
+#define READING 13
+#define READNEW 14
+#define SELECT 15
+#define RMAIL 16 /* mail menu */
+#define SMAIL 17
+#define CHATING 18 /* talk menu */
+#define XMODE 19
+#define FRIEND 20
+#define LAUSERS 21
+#define LUSERS 22
+#define MONITOR 23
+#define PAGE 24
+#define TQUERY 25
+#define TALK 26
+#define EDITPLAN 27 /* user menu */
+#define EDITSIG 28
+#define VOTING 29
+#define XINFO 30
+#define MSYSOP 31
+#define WWW 32
+#define BIG2 33
+#define REPLY 34
+#define HIT 35
+#define DBACK 36
+#define NOTE 37
+#define EDITING 38
+#define MAILALL 39
+#define MJ 40
+#define P_FRIEND 41
+#define LOGIN 42 /* main menu */
+#define DICT 43
+#define BRIDGE 44
+#define ARCHIE 45
+#define GOPHER 46
+#define NEWS 47
+#define LOVE 48
+#define EDITEXP 49
+#define IPREG 50
+#define NADM 51
+#define DRINK 52
+#define CAL 53
+#define PROVERB 54
+#define ANNOUNCE 55 /* announce */
+#define EDNOTE 56
+#define CDICT 57
+#define LOBJ 58
+#define OSONG 59
+#define CHICKEN 60
+#define TICKET 61
+#define GUESSNUM 62
+#define AMUSE 63
+#define OTHELLO 64
+#define DICE 65
+#define VICE 66
+#define BBCALL 67
+#define VIOLATELAW 68
+#define M_FIVE 69
+#define JACK_CARD 70
+#define TENHALF 71
+#define CARD_99 72
+#define RAIL_WAY 73
+#define SREG 74
+#define CHC 75 /* Chinese chess */
+#define DARK 76 /* 中國暗棋 */
+#define TMPJACK 77
+#define JCEE 78
+#define REEDIT 79
+#define BLOGGING 80
+#define CHESSWATCHING 81
+#define UMODE_GO 82
+#define DEBUGSLEEPING 83
+#define UMODE_CONN6 84
+#define REVERSI 85
+#define MODE_MAX 86 /* 所有其他選單動態須在此之前 */
+
+/* menu.c 中的模式 */
+#define QUIT 0x666 /* Return value to abort recursive functions */
+#define XEASY 0x333 /* Return value to un-redraw screen */
+
+/* for currmode */
+#define MODE_STARTED 0x0001 /* 是否已經進入系統 */
+#define MODE_POST 0x0002 /* 是否可以在 currboard 發表文章 */
+#define MODE_POSTCHECKED 0x0004 /* 是否已檢查在 currboard 發表文章的權限 */
+#define MODE_BOARD 0x0008 /* 是否可以在 currboard 刪除、mark文章 */
+#define MODE_GROUPOP 0x0010 /* 是否為小組長 (可以在 MENU 開板) */
+#define MODE_DIGEST 0x0020 /* 是否為 digest mode */
+#define MODE_SELECT 0x0080 /* 搜尋使用者標題 */
+#define MODE_DIRTY 0x0100 /* 是否更動過 userflag */
+
+/* for curredit */
+#define EDIT_MAIL 1 /* 目前是 mail/board ? */
+#define EDIT_LIST 2 /* 是否為 mail list ? */
+#define EDIT_BOTH 4 /* both reply to author/board ? */
+#define EDIT_ITEM 8 /* ITEM ? */
+
+/* read.c 中的模式 */
+#define TAG_NIN 0 /* 不屬於 TagList */
+#define TAG_TOGGLE 1 /* 切換 Taglist */
+#define TAG_INSERT 2 /* 加入 TagList */
+
+
+#define RS_FORWARD 0x01 /* backward */
+#define RS_TITLE 0x02 /* author/title */
+#define RS_KEYWORD 0x04
+#define RS_FIRST 0x08 /* find first article */
+#define RS_CURRENT 0x10 /* match current read article */
+#define RS_MARK 0x20 /* search the first article */
+#define RS_AUTHOR 0x40 /* search author's article */
+#define RS_NEWPOST 0x80 /* search new posts */
+#define RS_KEYWORD_EXCLUDE 0x100 /* exclude keyword */
+#define RS_RECOMMEND 0x200 /* search by recommends */
+#define RS_MONEY 0x400 /* search by recommends */
+
+#define CURSOR_FIRST (RS_TITLE | RS_FIRST)
+#define CURSOR_NEXT (RS_TITLE | RS_FORWARD)
+#define CURSOR_PREV (RS_TITLE)
+#define RELATE_FIRST (RS_TITLE | RS_FIRST | RS_CURRENT)
+#define RELATE_NEXT (RS_TITLE | RS_FORWARD | RS_CURRENT)
+#define RELATE_PREV (RS_TITLE | RS_CURRENT)
+#define NEWPOST_NEXT (RS_NEWPOST | RS_FORWARD)
+#define NEWPOST_PREV (RS_NEWPOST)
+#define AUTHOR_NEXT (RS_AUTHOR | RS_FORWARD)
+#define AUTHOR_PREV (RS_AUTHOR)
+
+/* DBCS aware modes */
+enum {
+ DBCS_ASCII,
+ DBCS_LEADING,
+ DBCS_TRAILING,
+} _DBCS_STATUS;
+
+enum {STRIP_ALL = 0, ONLY_COLOR, NO_RELOAD};
+
+#define SIG_PK 0
+#define SIG_TALK 1
+#define SIG_BROADCAST 2
+#define SIG_GOMO 3
+#define SIG_CHC 4
+#define SIG_DARK 5
+#define SIG_GO 6
+#define SIG_REVERSI 7
+
+/* talk.c 中的模式 */
+#define WATERBALL_GENERAL 0
+#define WATERBALL_PREEDIT 1
+#define WATERBALL_ALOHA 2
+#define WATERBALL_SYSOP 3
+#define WATERBALL_CONFIRM 4
+#ifdef PLAY_ANGEL
+#define WATERBALL_ANGEL 5
+#define WATERBALL_ANSWER 6
+#define WATERBALL_CONFIRM_ANGEL 7
+#define WATERBALL_CONFIRM_ANSWER 8
+#endif
+
+/* chat.c, talk.c: pager modes */
+#define PAGER_OFF (0)
+#define PAGER_ON (1)
+#define PAGER_DISABLE (2)
+#define PAGER_ANTIWB (3)
+#define PAGER_FRIENDONLY (4)
+
+#define PAGER_MODES (5)
+
+#endif
diff --git a/pttbbs/include/osdep.h b/pttbbs/include/osdep.h
new file mode 100644
index 00000000..117574c5
--- /dev/null
+++ b/pttbbs/include/osdep.h
@@ -0,0 +1,73 @@
+
+#ifndef __OSDEP_H__
+#define __OSDEP_H__
+
+/* os dependant include file, define */
+#ifdef __FreeBSD__
+ #if __FreeBSD__ >= 5
+ #include <sys/limits.h>
+ #else
+ #include <machine/limits.h>
+ #endif
+
+ #include <machine/param.h>
+
+ #define _XOPEN_SOURCE
+ #define _ISOC99_SOURCE
+
+ #define HAVE_SETPROCTITLE
+
+#elif defined(__linux__)
+
+ #define _GNU_SOURCE
+
+ #include <sys/ioctl.h>
+ #include <sys/file.h> /* for flock() */
+ #include <strings.h> /* for strcasecmp() */
+
+ #define NEED_STRCASESTR
+ #define NEED_STRLCPY
+ #define NEED_STRLCAT
+
+#elif defined(Solaris)
+
+ #include <alloca.h>
+ #include <crypt.h>
+ #include <sys/param.h>
+ #include <sys/ioctl.h>
+ #include <limits.h>
+ #include <strings.h> /* for strcasecmp() */
+
+ #define _ISOC99_SOURCE
+
+ #define NEED_FLOCK
+ #define NEED_UNSETENV
+ #define NEED_SCANDIR
+ #define NEED_STRCASESTR
+ #define NEED_TIMEGM
+
+ #if __OS_MAJOR_VERSION__ == 5 && __OS_MINOR_VERSION__ < 8
+ #define NEED_STRLCPY
+ #define NEED_STRLCAT
+ #define NEED_INET_PTON
+ #endif
+
+ #if __OS_MAJOR_VERSION__ == 5 && __OS_MAJOR_VERSION__ < 6
+ #define NEED_BSD_SIGNAL
+ #endif
+
+#else
+
+ #error "Unknown OSTYPE"
+
+#endif
+
+
+#ifdef Solaris
+ #define Signal (bsd_signal)
+#else
+ #define Signal (signal)
+#endif
+
+
+#endif
diff --git a/pttbbs/include/perm.h b/pttbbs/include/perm.h
new file mode 100644
index 00000000..d135ba70
--- /dev/null
+++ b/pttbbs/include/perm.h
@@ -0,0 +1,68 @@
+/* $Id$ */
+#ifndef INCLUDE_PERM_H
+#define INCLUDE_PERM_H
+
+#define PERM_BASIC 000000000001 /* 基本權力 */
+#define PERM_CHAT 000000000002 /* 進入聊天室 */
+#define PERM_PAGE 000000000004 /* 找人聊天 */
+#define PERM_POST 000000000010 /* 發表文章 */
+#define PERM_LOGINOK 000000000020 /* 註冊程序認證 */
+#define PERM_MAILLIMIT 000000000040 /* 信件無上限 */
+#define PERM_CLOAK 000000000100 /* 目前隱形中 */
+#define PERM_SEECLOAK 000000000200 /* 看見忍者 */
+#define PERM_XEMPT 000000000400 /* 永久保留帳號 */
+#define PERM_SYSOPHIDE 000000001000 /* 站長隱身術 */
+#define PERM_BM 000000002000 /* 板主 */
+#define PERM_ACCOUNTS 000000004000 /* 帳號總管 */
+#define PERM_CHATROOM 000000010000 /* 聊天室總管 */
+#define PERM_BOARD 000000020000 /* 看板總管 */
+#define PERM_SYSOP 000000040000 /* 站長 */
+#define PERM_BBSADM 000000100000 /* BBSADM */
+#define PERM_NOTOP 000000200000 /* 不列入排行榜 */
+#define PERM_VIOLATELAW 000000400000 /* 違法通緝中 */
+#ifdef PLAY_ANGEL
+#define PERM_ANGEL 000001000000 /* 有資格擔任小天使 */
+#endif
+#define OLD_PERM_NOOUTMAIL 000001000000 /* 不接受站外的信 */
+#define PERM_NOREGCODE 000002000000 /*不允許認證碼註冊*/
+#define PERM_VIEWSYSOP 000004000000 /* 視覺站長 */
+#define PERM_LOGUSER 000010000000 /* 觀察使用者行蹤 */
+#define PERM_NOCITIZEN 000020000000 /* 搋奪公權 */
+#define PERM_SYSSUPERSUBOP 000040000000 /* 群組長 */
+#define PERM_ACCTREG 000100000000 /* 帳號審核組 */
+#define PERM_PRG 000200000000 /* 程式組 */
+#define PERM_ACTION 000400000000 /* 活動組 */
+#define PERM_PAINT 001000000000 /* 美工組 */
+#define PERM_POLICE_MAN 002000000000 /* 警察總管 */
+#define PERM_SYSSUBOP 004000000000 /* 小組長 */
+#define PERM_OLDSYSOP 010000000000 /* 退休站長 */
+#define PERM_POLICE 020000000000 /* 警察 */
+
+#define NUMPERMS 32
+
+#define PERM_DEFAULT (PERM_BASIC | PERM_CHAT | PERM_PAGE )
+#define PERM_MANAGER (PERM_ACCTREG | PERM_ACTION | PERM_PAINT)
+#define PERM_ADMIN (PERM_ACCOUNTS | PERM_SYSOP | PERM_SYSSUBOP | PERM_SYSSUPERSUBOP | PERM_MANAGER | PERM_BM)
+#define PERM_ALLBOARD (PERM_SYSOP | PERM_BOARD)
+#define PERM_LOGINCLOAK (PERM_SYSOP | PERM_ACCOUNTS)
+#define PERM_SEEULEVELS (PERM_SYSOP)
+#define PERM_SEEBLEVELS (PERM_SYSOP | PERM_BM)
+#define PERM_NOTIMEOUT (PERM_SYSOP)
+#define PERM_READMAIL (PERM_BASIC)
+#define PERM_FORWARD (PERM_BASIC) /* to do the forwarding */
+#define PERM_INTERNET (PERM_LOGINOK) /* 身份認證過關的才能寄信到 Internet */
+
+#define HasUserPerm(x) (cuser.userlevel & (x))
+#define PERM_HIDE(u) (u && (u)->userlevel & PERM_SYSOPHIDE)
+
+#define IS_BOARD(bptr) ((bptr)->brdname[0] && \
+ !((bptr)->brdattr & BRD_GROUPBOARD))
+#define IS_GROUP(bptr) ((bptr)->brdname[0] && \
+ ((bptr)->brdattr & BRD_GROUPBOARD))
+
+#define IS_OPENBRD(bptr) \
+ (!(((bptr)->brdattr & (BRD_HIDE | BRD_TOP)) || \
+ ((bptr)->level && !((bptr)->brdattr & BRD_POSTMASK) && \
+ ((bptr)->level & \
+ ~(PERM_BASIC|PERM_CHAT|PERM_PAGE|PERM_POST|PERM_LOGINOK)))))
+#endif
diff --git a/pttbbs/include/proto.h b/pttbbs/include/proto.h
new file mode 100644
index 00000000..eef17976
--- /dev/null
+++ b/pttbbs/include/proto.h
@@ -0,0 +1,825 @@
+/* $Id$ */
+#ifndef INCLUDE_PROTO_H
+#define INCLUDE_PROTO_H
+
+#ifdef __GNUC__
+#define GCC_CHECK_FORMAT(a,b) __attribute__ ((format (printf, a, b)))
+#define GCC_NORETURN __attribute__ ((__noreturn__))
+#else
+#define GCC_CHECK_FORMAT(a,b)
+#define GCC_NORETURN
+#endif
+
+#ifdef __dietlibc__
+#define random glibc_random
+#define srandom glibc_srandom
+#define initstate glibc_initstate
+#define setstate glibc_setstate
+long int random(void);
+void srandom(unsigned int seed);
+char *initstate(unsigned int seed, char *state, size_t n);
+char *setstate(char *state);
+#endif
+
+/* admin */
+int m_loginmsg(void);
+int m_mod_board(char *bname);
+int m_newbrd(int whatclass, int recover);
+int scan_register_form(const char *regfile, int automode, int neednum);
+int m_user(void);
+int search_user_bypwd(void);
+int search_user_bybakpwd(void);
+int m_board(void);
+int m_register(void);
+int cat_register(void);
+unsigned int setperms(unsigned int pbits, const char * const pstring[]);
+void setup_man(const boardheader_t * board, const boardheader_t * oldboard);
+void delete_symbolic_link(boardheader_t *bh, int bid);
+int make_symbolic_link(const char *bname, int gid);
+int make_symbolic_link_interactively(int gid);
+void merge_dir(const char *dir1, const char *dir2, int isoutter);
+
+/* announce */
+int a_menu(const char *maintitle, const char *path, int lastlevel, char *trans_buffer);
+void a_copyitem(const char* fpath, const char* title, const char* owner, int mode);
+int Announce(void);
+void gem(char* maintitle, item_t* path, int update);
+#ifdef BLOG
+void BlogMain(int);
+#endif
+
+/* args */
+void initsetproctitle(int argc, char **argv, char **envp);
+void setproctitle(const char* format, ...) GCC_CHECK_FORMAT(1,2);
+
+/* assess */
+int inc_goodpost(const char *, int num);
+int inc_badpost(const char *, int num);
+int inc_goodsale(const char *, int num);
+int inc_badsale(const char *, int num);
+//void set_assess(int uid, unsigned char num, int type);
+
+/* bbs */
+void delete_allpost(char *userid);
+int invalid_brdname(const char *brd);
+void chomp(char *src);
+int del_range(int ent, const fileheader_t *fhdr, const char *direct);
+int cmpfowner(fileheader_t *fhdr);
+int b_note_edit_bname(int bid);
+int Read(void);
+int CheckPostPerm(void);
+void anticrosspost(void);
+int Select(void);
+void do_reply_title(int row, const char *title);
+void outgo_post(const fileheader_t *fh, const char *board, const char *userid, const char *username);
+int edit_title(int ent, fileheader_t *fhdr, const char *direct);
+int whereami(void);
+void set_board(void);
+int do_post(void);
+void ReadSelect(void);
+int save_violatelaw(void);
+int board_select(void);
+int board_digest(void);
+int do_limitedit(int ent, fileheader_t * fhdr, const char *direct);
+#ifdef USE_COOLDOWN
+int check_cooldown(boardheader_t *bp);
+#endif
+
+/* board */
+#define setutmpbid(bid) currutmp->brc_id=bid;
+int HasBoardPerm(boardheader_t *bptr);
+int New(void);
+int Favorite(void);
+int Class(void);
+void save_brdbuf(void);
+void init_brdbuf(void);
+#ifdef CRITICAL_MEMORY
+void sigfree(int);
+#endif
+
+/* brc */
+int brc_initialize(void);
+void brc_finalize(void);
+
+int brc_unread(int bid, const char *fname);
+int brc_unread_time(int bid, time4_t ftime);
+int brc_initial_board(const char *boardname);
+void brc_addlist(const char* fname);
+
+void brc_update(void);
+
+void brc_toggle_all_read(int bid, int is_all_read);
+
+/* cache */
+#define demoney(money) deumoney(usernum, money)
+#define search_ulist(uid) search_ulistn(uid, 1)
+#define getbcache(bid) (bcache + bid - 1)
+#define moneyof(uid) SHM->money[uid - 1]
+#define getbtotal(bid) SHM->total[bid - 1]
+#define getbottomtotal(bid) SHM->n_bottom[bid-1]
+void sort_bcache(void);
+int getuser(const char *userid, userec_t *xuser);
+void setuserid(int num, const char *userid);
+int dosearchuser(const char *userid, char *rightid);
+int searchuser(const char *userid, char *rightid);
+int getbnum(const char *bname);
+void touchbpostnum(int bid, int delta);
+void reset_board(int bid);
+void touch_boards(void);
+void addbrd_touchcache(void);
+void setapath(char *buf, const char *boardname);
+void setutmpmode(unsigned int mode);
+void setadir(char *buf, const char *path);
+int apply_boards(int (*func)(boardheader_t *));
+int haspostperm(const char *bname);
+void setbtotal(int bid);
+void setbottomtotal(int bid);
+unsigned int safe_sleep(unsigned int seconds);
+int apply_ulist(int (*fptr)(const userinfo_t *));
+userinfo_t *search_ulistn(int uid, int unum);
+void purge_utmp(userinfo_t *uentp);
+void getnewutmpent(const userinfo_t *up);
+void resolve_garbage(void);
+void resolve_boards(void);
+void resolve_fcache(void);
+void sem_init(int semkey,int *semid);
+void sem_lock(int op,int semid);
+char *u_namearray(char buf[][IDLEN + 1], int *pnum, char *tag);
+char *getuserid(int num);
+int searchnewuser(int mode);
+int count_logins(int uid, int show);
+void remove_from_uhash(int n);
+void add_to_uhash(int n, const char *id);
+int setumoney(int uid, int money);
+userinfo_t *search_ulist_pid(int pid);
+userinfo_t *search_ulist_userid(const char *userid);
+void hbflreload(int bid);
+int hbflcheck(int bid, int uid);
+void *attach_shm(int shmkey, int shmsize);
+void attach_SHM(void);
+int is_BM_cache(int);
+void buildBMcache(int);
+void reload_bcache(void);
+void reload_fcache(void);
+#ifdef USE_COOLDOWN
+#define cooldowntimeof(uid) (SHM->cooldowntime[uid - 1] & 0xFFFFFFF0)
+#define posttimesof(uid) (SHM->cooldowntime[uid - 1] & 0xF)
+void add_cooldowntime(int uid, int min);
+void add_posttimes(int uid, int times);
+#endif
+
+/* cal */
+int give_tax(int money);
+const char* money_level(int money);
+int vice(int money, const char* item);
+#define reload_money() cuser.money=moneyof(usernum)
+int deumoney(int uid, int money);
+int lockutmpmode(int unmode, int state);
+int unlockutmpmode(void);
+int x_file(void);
+int give_money(void);
+int p_sysinfo(void);
+int do_give_money(char *id, int uid, int money);
+int p_give(void);
+int p_cloak(void);
+int p_from(void);
+int ordersong(void);
+int p_exmail(void);
+void mail_redenvelop(const char* from, const char* to, int money, char mode);
+
+/* card */
+int g_card_jack(void);
+int g_ten_helf(void);
+int card_99(void);
+
+/* chat */
+int t_chat(void);
+
+/* chc */
+void chc(int s, ChessGameMode mode);
+int chc_main(void);
+int chc_personal(void);
+int chc_watch(void);
+ChessInfo* chc_replay(FILE* fp);
+
+/* chicken */
+void ch_buyitem(int money, const char *picture, int *item, int haveticket);
+int chicken_main(void);
+int chickenpk(int fd);
+void time_diff(chicken_t *thechicken);
+int isdeadth(const chicken_t *thechicken);
+void show_chicken_data(chicken_t *thechicken, chicken_t *pkchicken);
+int reload_chicken(void);
+
+/* dark */
+int main_dark(int fd,userinfo_t *uin);
+
+/* dice */
+int dice_main(void);
+
+/* edit */
+int vedit(char *fpath, int saveheader, int *islocal);
+void write_header(FILE *fp, char *mytitle);
+void addsignature(FILE *fp, int ifuseanony);
+void auto_backup(void);
+void restore_backup(void);
+char *ask_tmpbuf(int y);
+
+/* fav */
+void fav_set_old_folder(fav_t *fp);
+int get_data_number(fav_t *fp);
+int get_current_fav_level(void);
+fav_t *get_current_fav(void);
+int get_item_type(fav_type_t *ft);
+char *get_item_title(fav_type_t *ft);
+char *get_folder_title(int fid);
+void set_attr(fav_type_t *ft, int bit, char boolean);
+void fav_sort_by_name(void);
+void fav_sort_by_class(void);
+int fav_load(void);
+int fav_save(void);
+void fav_remove_item(int id, char type);
+fav_type_t *getadmtag(int bid);
+fav_type_t *getboard(int bid);
+fav_type_t *getfolder(int fid);
+char getbrdattr(int bid);
+time4_t getbrdtime(int bid);
+void setbrdtime(int bid, time4_t t);
+int fav_getid(fav_type_t *ft);
+void fav_tag(int id, char type, char boolean);
+void move_in_current_folder(int from, int to);
+void fav_move(int from, int to);
+fav_type_t *fav_add_line(void);
+fav_type_t *fav_add_folder(void);
+fav_type_t *fav_add_board(int bid);
+fav_type_t *fav_add_admtag(int bid);
+void fav_remove_all_tagged_item(void);
+void fav_add_all_tagged_item(void);
+void fav_remove_all_tag(void);
+void fav_set_folder_title(fav_type_t *ft, char *title);
+int fav_stack_full(void);
+void fav_folder_in(int fid);
+void fav_folder_out(void);
+void fav_free(void);
+int fav_v3_to_v4(void);
+int is_set_attr(fav_type_t *ft, char bit);
+void fav_cleanup(void);
+void fav_clean_invisible(void);
+fav_t *get_fav_folder(fav_type_t *ft);
+fav_t *get_fav_root(void);
+int updatenewfav(int mode);
+void subscribe_newfav(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);
+
+/* friend */
+void friend_edit(int type);
+void friend_load(int);
+int t_override(void);
+int t_reject(void);
+void friend_add(const char *uident, int type, const char *des);
+void friend_delete(const char *uident, int type);
+void friend_delete_all(const char *uident, int type);
+void friend_special(void);
+void setfriendfile(char *fpath, int type);
+
+/* gamble */
+int ticket_main(void);
+int openticket(int bid);
+int ticket(int bid);
+
+/* go */
+void gochess(int s, ChessGameMode mode);
+int gochess_main(void);
+int gochess_personal(void);
+int gochess_watch(void);
+ChessInfo* gochess_replay(FILE* fp);
+
+/* gomo */
+void gomoku(int s, ChessGameMode mode);
+int gomoku_main(void);
+int gomoku_personal(void);
+int gomoku_watch(void);
+ChessInfo* gomoku_replay(FILE* fp);
+
+/* guess */
+int guess_main(void);
+
+/* indict */
+int x_dict(void);
+int use_dict(char *dict,char *database);
+
+/* convert */
+void set_converting_type(int which);
+
+/* io */
+int getdata(int line, int col, const char *prompt, char *buf, int len, int echo);
+int igetch(void);
+int wait_input(float f, int flDoRefresh);
+int getdata_str(int line, int col, const char *prompt, char *buf, int len, int echo, const char *defaultstr);
+int getdata_buf(int line, int col, const char *prompt, char *buf, int len, int echo);
+void add_io(int fd, int timeout);
+void oflush(void);
+int strip_ansi(char *buf, const char *str, int mode);
+void strip_nonebig5(unsigned char *str, int maxlen);
+int oldgetdata(int line, int col, const char *prompt, char *buf, int len, int echo);
+void output(const char *s, int len);
+int num_in_buf(void);
+int ochar(int c);
+
+/* kaede */
+int Rename(const char* src, const char* dst);
+int Copy(const char *src, const char *dst);
+int CopyN(const char *src, const char *dst, int n);
+int AppendTail(const char *src, const char *dst, int off);
+int Link(const char* src, const char* dst);
+char *Ptt_prints(char *str, size_t size, int mode);
+char *my_ctime(const time4_t *t, char *ans, int len);
+
+/* lovepaper */
+int x_love(void);
+
+/* mail */
+int load_mailalert(const char *userid);
+int sendalert(const char *userid, int alert);
+int mail_muser(const userec_t muser, const char *title, const char *filename);
+int mail_id(const char* id, const char *title, const char *filename, const char *owner);
+int m_read(void);
+int doforward(const char *direct, const fileheader_t *fh, int mode);
+int mail_reply(int ent, fileheader_t *fhdr, const char *direct);
+int bsmtp(const char *fpath, const char *title, const char *rcpt);
+void hold_mail(const char *fpath, const char *receiver);
+void m_init(void);
+int chkmailbox(void);
+int mail_man(void);
+int m_new(void);
+int m_send(void);
+int mail_list(void);
+int setforward(void);
+int m_internet(void);
+int mail_mbox(void);
+int built_mail_index(void);
+int mail_all(void);
+int invalidaddr(const char *addr);
+int do_send(const char *userid, const char *title);
+void my_send(const char *uident);
+void setupmailusage(void);
+
+/* mbbsd */
+void show_call_in(int save, int which);
+void write_request (int sig);
+void mkuserdir(const char *userid);
+void log_usies(const char *mode, const char *mesg);
+void system_abort(void);
+void abort_bbs(int sig) GCC_NORETURN;
+void del_distinct(const char *fname, const char *line, int casesensitive);
+void add_distinct(const char *fname, const char *line);
+void u_exit(const char *mode);
+void talk_request(int sig);
+int reply_connection_request(const userinfo_t *uip);
+int establish_talk_connection(const userinfo_t *uip);
+void my_talk(userinfo_t * uin, int fri_stat, char defact);
+ssize_t tty_read(unsigned char *buf, size_t max);
+int query_file_money(const fileheader_t *pfh);
+
+/* menu */
+void showtitle(const char *title, const char *mid);
+void movie(int i);
+int main_menu(void);
+int admin(void);
+int Mail(void);
+int Talk(void);
+int User(void);
+int Xyz(void);
+int Play_Play(void);
+int Name_Menu(void);
+
+#ifdef MERGEBBS
+/* merge */
+int m_sob(void);
+void m_sob_brd(char *bname,char *fromdir);
+#endif
+
+/* old more */
+int more(char *fpath, int promptend);
+/* piaip's new pager */
+int pmore(char *fpath, int promptend);
+
+/* name */
+typedef int (*gnc_comp_func)(int, const char*, int);
+typedef int (*gnc_perm_func)(int);
+typedef char* (*gnc_getname_func)(int);
+
+extern void NameList_init(struct NameList *self);
+extern void NameList_delete(struct NameList *self);
+extern void NameList_clear(struct NameList *self);
+extern void NameList_add(struct NameList *self, const char *name);
+extern void namecomplete2(struct NameList *namelist, const char *prompt, char *data);
+
+void usercomplete(const char *prompt, char *data);
+void namecomplete(const char *prompt, char *data);
+void AddNameList(const char *name);
+void FreeNameList(void);
+void CreateNameList(void);
+int chkstr(char *otag, const char *tag, const char *name);
+int InNameList(const char *name);
+void ShowNameList(int row, int column, const char *prompt);
+int RemoveNameList(const char *name);
+void ToggleNameList(int *reciper, const char *listfile, const char *msg);
+int generalnamecomplete(const char *prompt, char *data, int len, size_t nmemb,
+ gnc_comp_func compar, gnc_perm_func permission,
+ gnc_getname_func getname);
+int completeboard_compar(int where, const char *str, int len);
+int completeboard_permission(int where);
+int complete_board_and_group_permission(int where);
+char *completeboard_getname(int where);
+int completeutmp_compar(int where, const char *str, int len);
+int completeutmp_permission(int where);
+char *completeutmp_getname(int where);
+
+#define CompleteBoard(MSG,BUF) \
+ generalnamecomplete(MSG, BUF, sizeof(BUF), SHM->Bnumber, \
+ &completeboard_compar, &completeboard_permission, \
+ &completeboard_getname)
+#define CompleteBoardAndGroup(MSG,BUF) \
+ generalnamecomplete(MSG, BUF, sizeof(BUF), SHM->Bnumber, \
+ &completeboard_compar, &complete_board_and_group_permission, \
+ &completeboard_getname)
+#define CompleteOnlineUser(MSG,BUF) \
+ generalnamecomplete(MSG, BUF, sizeof(BUF), SHM->UTMPnumber, \
+ &completeutmp_compar, &completeutmp_permission, \
+ &completeutmp_getname)
+
+/* osdep */
+int cpuload(char *str);
+double swapused(int *total, int *used);
+
+#ifdef NEED_FLOCK
+ #define LOCK_EX 1
+ #define LOCK_UN 2
+
+ int flock(int, int);
+#endif
+
+#ifdef NEED_UNSETENV
+ void unsetenv(char *name);
+#endif
+
+#ifdef NEED_STRCASESTR
+ char *strcasestr(const char *big, const char *little);
+#endif
+
+#ifdef NEED_STRLCPY
+ size_t strlcpy(char *dst, const char *src, size_t size);
+#endif
+
+#ifdef NEED_STRLCAT
+ size_t strlcat(char *dst, const char *src, size_t size);
+#endif
+
+#ifdef NEED_SCANDIR
+ int scandir(const char *dirname, struct dirent ***namelist, int (*select)(struct dirent *), int (*compar)(const void *, const void *));
+ int alphasort(const void *d1, const void *d2);
+#endif
+
+#ifdef NEED_INET_PTON
+ int inet_pton(int af, const char *src, void *dst);
+#endif
+
+/* othello */
+int othello_main(void);
+
+/* page */
+int main_railway(void);
+
+/* read */
+void i_read(int cmdmode, const char *direct, void (*dotitle)(), void (*doentry)(), const onekey_t *rcmdlist, int bidcache);
+void fixkeep(const char *s, int first);
+keeploc_t *getkeep(const char *s, int def_topline, int def_cursline);
+int Tagger(time4_t chrono, int recno, int mode);
+void EnumTagFhdr(fileheader_t *fhdr, char *direct, int locus);
+void UnTagger (int locus);
+/* record */
+int substitute_record(const char *fpath, const void *rptr, int size, int id);
+int lock_substitute_record(const char *fpath, void *rptr, int size, int id, int);
+int get_record(const char *fpath, void *rptr, int size, int id);
+int get_record_keep(const char *fpath, void *rptr, int size, int id, int *fd);
+int get_record_keep_seek(const char *fpath, void *rptr, int size, int id, int *fd, int toseek);
+int append_record(const char *fpath, const fileheader_t *record, int size);
+int stampfile_u(char *fpath, fileheader_t *fh);
+inline int stampfile(char *fpath, fileheader_t *fh);
+void stampdir(char *fpath, fileheader_t *fh);
+int get_num_records(const char *fpath, int size);
+int get_records(const char *fpath, void *rptr, int size, int id, int number);
+int get_records_fd(const char *fpath, void *rptr, int size, int id, int number, int *use_fd);
+void stamplink(char *fpath, fileheader_t *fh);
+int delete_record(const char fpath[], int size, int id);
+int delete_files(const char* dirname, int (*filecheck)(), int record);
+#ifdef SAFE_ARTICLE_DELETE
+#ifndef _BBS_UTIL_C_
+void safe_delete_range(const char *fpath, int id1, int id2);
+#endif
+int safe_article_delete(int ent, const fileheader_t *fhdr, const char *direct);
+int safe_article_delete_range(const char *direct, int from, int to);
+#endif
+int delete_file(const char *dirname, int size, int ent, int (*filecheck)());
+int delete_range(const char *fpath, int id1, int id2);
+int apply_record(const char *fpath, int (*fptr)(void*,void*), int size,void *arg);
+int search_rec(const char* dirname, int (*filecheck)());
+int append_record_forward(char *fpath, fileheader_t *record, int size, const char *origid);
+int get_sum_records(const char* fpath, int size);
+int substitute_ref_record(const char* direct, fileheader_t *fhdr, int ent);
+inline
+int getindex(const char *fpath, fileheader_t *fh, int start);
+
+/* register */
+int getnewuserid(void);
+int bad_user_id(const char *userid);
+void new_register(void);
+int checkpasswd(const char *passwd, char *test);
+void check_register(void);
+char *genpasswd(char *pw);
+int setupnewuser(const userec_t *user);
+
+/* reversi */
+void reversi(int s, ChessGameMode mode);
+int reversi_main(void);
+int reversi_personal(void);
+int reversi_watch(void);
+ChessInfo* reversi_replay(FILE* fp);
+
+/* screen */
+void mouts(int y, int x, const char *str);
+void move(int y, int x);
+void outs(const char *str);
+void outs_n(const char *str, int n);
+void outslr(const char *left, int leftlen, const char *right, int rightlen);
+void clrtoeol(void);
+void clear(void);
+void refresh(void);
+void clrtobot(void);
+void outmsg(const char *msg);
+void outmsglr(const char *msg, int llen, const char *rmsg, int rlen);
+void prints(const char *fmt, ...) GCC_CHECK_FORMAT(1,2);
+void region_scroll_up(int top, int bottom);
+void outc(unsigned char ch);
+void redoscr(void);
+void redoln(void);
+void clrtoline(int line);
+void standout(void);
+void standend(void);
+void edit_outs(const char *text);
+void edit_outs_n(const char *text, int n);
+void outch(unsigned char c);
+void rscroll(void);
+void scroll(void);
+void getyx(int *y, int *x);
+void initscr(void);
+void out_lines(const char *str, int line);
+void screen_backup(screen_backup_t *buf);
+void screen_restore(const screen_backup_t *buf);
+
+/* stuff */
+#define isprint2(ch) ((ch & 0x80) || isprint(ch))
+#define not_alpha(ch) (ch < 'A' || (ch > 'Z' && ch < 'a') || ch > 'z')
+#define not_alnum(ch) (ch < '0' || (ch > '9' && ch < 'A') || (ch > 'Z' && ch < 'a') || ch > 'z')
+#define pressanykey() vmsg(NULL)
+int log_user(const char *fmt, ...) GCC_CHECK_FORMAT(1,2);
+unsigned int ipstr2int(const char *ip);
+time4_t gettime(int line, time4_t dt, const char* head);
+void setcalfile(char *buf, char *userid);
+void stand_title(const char *title);
+char getans(const char *fmt,...) GCC_CHECK_FORMAT(1,2);
+int getkey(const char *fmt,...) GCC_CHECK_FORMAT(1,2);
+int vmsgf(const char *fmt,...) GCC_CHECK_FORMAT(1,2);
+int vmsg(const char *msg);
+void trim(char *buf);
+int show_file(const char *filename, int y, int lines, int mode);
+void bell(void);
+void setbpath(char *buf, const char *boardname);
+int dashf(const char *fname);
+void sethomepath(char *buf, const char *userid);
+void sethomedir(char *buf, const char *userid);
+char *Cdate(const time4_t *clock);
+void sethomefile(char *buf, const char *userid, const char *fname);
+int log_file(const char *fn, int flag, const char *fmt,...);
+void str_lower(char *t, const char *s);
+int cursor_key(int row, int column);
+int search_num(int ch, int max);
+void setuserfile(char *buf, const char *fname);
+int is_BM(const char *list);
+time4_t dasht(const char *fname);
+int dashd(const char *fname);
+int invalid_pname(const char *str);
+void setbdir(char *buf, const char *boardname);
+void setbfile(char *buf, const char *boardname, const char *fname);
+void setbnfile(char *buf, const char *boardname, const char *fname, int n);
+int dashl(const char *fname);
+char *subject(char *title);
+void setdirpath(char *buf, const char *direct, const char *fname);
+int str_checksum(const char *str);
+void show_help(const char * const helptext[]);
+void show_helpfile(const char * helpfile);
+int copy_file(const char *src, const char *dst);
+int belong(const char *filelist, const char *key);
+char *Cdatedate(const time4_t *clock);
+void sethomeman(char *buf, const char *userid);
+off_t dashs(const char *fname);
+void cursor_clear(int row, int column);
+void cursor_show(int row, int column);
+void printdash(const char *mesg, int msglen);
+char *Cdatelite(const time4_t *clock);
+int is_validuserid(const char *ident);
+int userid_is_BM(const char *userid, const char *list);
+int is_uBM(const char *list, const char *id);
+inline int *intbsearch(int key, const int *base0, int nmemb);
+inline unsigned int *uintbsearch(const unsigned int, const unsigned int *, const int);
+int qsort_intcompar(const void *a, const void *b);
+#ifndef CRITICAL_MEMORY
+ #define MALLOC(p) malloc(p)
+ #define FREE(p) free(p)
+#else
+ void *MALLOC(int size);
+ void FREE(void *ptr);
+#endif
+#ifdef OUTTACACHE
+int tobind(const char *iface_ip, int port);
+int toconnect(const char *host, int port);
+int toread(int fd, void *buf, int len);
+int towrite(int fd, const void *buf, int len);
+#endif
+#ifdef PLAY_ANGEL
+void pressanykey_or_callangel(void);
+#endif
+#ifdef TIMET64
+ struct tm *localtime4(const time4_t *);
+ time4_t time4(time4_t *);
+ char *ctime4(const time4_t *);
+#else
+ #define localtime4(a) localtime(a)
+ #define time4(a) time(a)
+ #define ctime4(a) ctime(a)
+#endif
+
+/* syspost */
+int post_msg(const char* bname, const char* title, const char *msg, const char* author);
+int post_file(const char *bname, const char *title, const char *filename, const char *author);
+void post_newboard(const char *bgroup, const char *bname, const char *bms);
+void post_violatelaw(const char *crime, const char *police, const char *reason, const char *result);
+void post_change_perm(int oldperm, int newperm, const char *sysopid, const char *userid);
+void post_policelog(const char *bname, const char *atitle, const char *action, const char *reason, const int toggle);
+
+/* talk */
+#define iswritable(uentp) \
+ (iswritable_stat(uentp, friend_stat(currutmp, uentp)))
+#define isvisible(me, uentp) \
+ (isvisible_stat(currutmp, uentp, friend_stat(me, uentp)))
+
+int iswritable_stat(const userinfo_t *uentp, int fri_stat);
+int isvisible_stat(const userinfo_t * me, const userinfo_t * uentp, int fri_stat);
+int cmpwatermtime(const void *a, const void *b);
+void getmessage(msgque_t msg);
+void my_write2(void);
+int t_idle(void);
+void check_water_init(void);
+const char *modestring(const userinfo_t * uentp, int simple);
+int t_users(void);
+int my_write(pid_t pid, const char *hint, const char *id, int flag, userinfo_t *);
+void t_display_new(void);
+void talkreply(void);
+int t_pager(void);
+int t_query(void);
+int t_qchicken(void);
+int t_talk(void);
+int t_display(void);
+int my_query(const char *uident);
+int logout_friend_online(userinfo_t*);
+void login_friend_online(void);
+int isvisible_uid(int tuid);
+int friend_stat(const userinfo_t *me, const userinfo_t * ui);
+int call_in(const userinfo_t *uentp, int fri_stat);
+int make_connection_to_somebody(userinfo_t *uin, int timeout);
+#ifdef PLAY_ANGEL
+int t_changeangel(void);
+int t_angelmsg(void);
+void CallAngel(void);
+void SwitchBeingAngel(void);
+void SwitchAngelSex(int);
+int t_switchangel(void);
+#endif
+
+/* tmpjack */
+int reg_barbq(void);
+int p_ticket_main(void);
+int j_ticket_main(void);
+
+/* term */
+void init_tty(void);
+int term_init(void);
+void term_resize(int w, int h);
+void save_cursor(void);
+void restore_cursor(void);
+void do_move(int destcol, int destline);
+void scroll_forward(void);
+void change_scroll_range(int top, int bottom);
+
+/* topsong */
+void sortsong(void);
+int topsong(void);
+
+/* user */
+int kill_user(int num, char *userid);
+int u_editcalendar(void);
+void user_display(const userec_t *u, int real);
+void uinfo_query(userec_t *u, int real, int unum);
+int showsignature(char *fname, int *j, SigInfo *psi);
+int u_cancelbadpost();
+void kick_all(char *user);
+void violate_law(userec_t * u, int unum);
+void mail_violatelaw(const char* crime, const char* police, const char* reason, const char* result);
+int u_info(void);
+void showplans(const char *uid);
+void showplans_userec(userec_t *u);
+int u_loginview(void);
+int u_ansi(void);
+int u_editplan(void);
+int u_editsig(void);
+int u_cloak(void);
+int u_register(void);
+int u_list(void);
+#ifdef DBCSAWARE
+int u_detectDBCSAwareEvilClient();
+int getDBCSstatus(unsigned char *s, int pos);
+#define ISDBCSAWARE() (cuser.uflag & DBCSAWARE_FLAG)
+#endif
+
+/* vote */
+void b_suckinfile(FILE *fp, char *fname);
+void b_suckinfile_invis(FILE * fp, char *fname, const char *boardname);
+int b_results(void);
+int b_vote(void);
+int b_vote_maintain(void);
+void auto_close_polls(void);
+
+/* vice */
+int vice_main(void);
+
+/* voteboard */
+int do_voteboard(int);
+void do_voteboardreply(const fileheader_t *fhdr);
+
+/* xyz */
+int m_sysop(void);
+int x_boardman(void);
+int x_note(void);
+int x_login(void);
+int x_week(void);
+int x_issue(void);
+int x_today(void);
+int x_yesterday(void);
+int x_user100(void);
+int x_birth(void);
+#if 0
+int x_90(void);
+int x_89(void);
+int x_88(void);
+int x_87(void);
+int x_86(void);
+#endif
+int x_history(void);
+int x_weather(void);
+int x_stock(void);
+int x_mrtmap(void);
+int note(void);
+int Goodbye(void);
+
+/* toolkit */
+unsigned StringHash(const char *s);
+
+/* passwd */
+int passwd_init(void);
+int passwd_update(int num, userec_t *buf);
+int passwd_query(int num, userec_t *buf);
+int passwd_apply(int (*fptr)(int, userec_t *));
+void passwd_lock(void);
+void passwd_unlock(void);
+int passwd_update_money(int num);
+int initcuser(const char *userid);
+int freecuser(void);
+
+
+/* calendar */
+int calendar(void);
+int ParseDate(const char *date, int *year, int *month, int *day);
+int getHoroscope(int m, int d);
+
+/* util */
+void touchbtotal(int bid);
+
+/* util_cache.c */
+void reload_pttcache(void);
+
+#endif
diff --git a/pttbbs/include/pttstruct.h b/pttbbs/include/pttstruct.h
new file mode 100644
index 00000000..64aac0ac
--- /dev/null
+++ b/pttbbs/include/pttstruct.h
@@ -0,0 +1,726 @@
+/* $Id$ */
+#ifndef INCLUDE_STRUCT_H
+#define INCLUDE_STRUCT_H
+
+
+#define IDLEN 12 /* Length of bid/uid */
+
+/* 競標資訊 */
+#define SALE_COMMENTED 0x1
+typedef struct bid_t {
+ int high; /* 目前最高價 */
+ int buyitnow; /* 直接購買價 */
+ int usermax; /* 自動競標最高價 */
+ int increment; /* 出價增額 */
+ char userid[IDLEN + 1]; /* 最高出價者 */
+ time4_t enddate; /* 結標日期 */
+ char payby; /* 付款方式 */
+ /* 1 cash 2 check or mail 4 wire 8 credit 16 postoffice */
+ char flag; /* 屬性 (是否已評價) */
+ char pad[2];
+ int shipping; /* 運費 */
+} bid_t;
+
+/* 小雞的資料 */
+typedef struct chicken_t {
+ char name[20];
+ char type; /* 物種 */
+ unsigned char tech[16]; /* 技能 */
+ time4_t birthday; /* 生日 */
+ time4_t lastvisit; /* 上次照顧時間 */
+ int oo; /* 補品 */
+ int food; /* 食物 */
+ int medicine; /* 藥品 */
+ int weight; /* 體重 */
+ int clean; /* 乾淨 */
+ int run; /* 敏捷度 */
+ int attack; /* 攻擊力 */
+ int book; /* 知識 */
+ int happy; /* 快樂 */
+ int satis; /* 滿意度 */
+ int temperament; /* 氣質 */
+ int tiredstrong; /* 疲勞度 */
+ int sick; /* 病氣指數 */
+ int hp; /* 血量 */
+ int hp_max; /* 滿血量 */
+ int mm; /* 法力 */
+ int mm_max; /* 滿法力 */
+ time4_t cbirth; /* 實際計算用的生日 */
+ int pad[2]; /* 留著以後用 */
+} chicken_t;
+
+#define PASSLEN 14 /* Length of encrypted passwd field */
+#define REGLEN 38 /* Length of registration data */
+
+#define PASSWD_VERSION 2275
+
+typedef struct userec_t {
+ unsigned int version; /* version number of this sturcture, we
+ * use revision number of project to denote.*/
+
+ char userid[IDLEN + 1]; /* ID */
+ char realname[20]; /* 真實姓名 */
+ char nickname[24]; /* 暱稱 */
+ char passwd[PASSLEN]; /* 密碼 */
+ char padx;
+ unsigned int uflag; /* 習慣1 */
+ unsigned int uflag2; /* 習慣2 */
+ unsigned int userlevel; /* 權限 */
+ unsigned int numlogins; /* 上站次數 */
+ unsigned int numposts; /* 文章篇數 */
+ time4_t firstlogin; /* 註冊時間 */
+ time4_t lastlogin; /* 最近上站時間 */
+ char lasthost[16]; /* 上次上站來源 */
+ int money; /* Ptt幣 */
+ char remoteuser[3]; /* 保留 目前沒用到的 */
+ char proverb; /* 座右銘 (unused) */
+ char email[50]; /* Email */
+ char address[50]; /* 住址 */
+ char justify[REGLEN + 1]; /* 審核資料 */
+ unsigned char month; /* 生日 月 */
+ unsigned char day; /* 生日 日 */
+ unsigned char year; /* 生日 年 */
+ unsigned char sex; /* 性別 */
+ unsigned char state; /* TODO unknown (unused ?) */
+ unsigned char pager; /* 呼叫器狀態 */
+ unsigned char invisible; /* 隱形狀態 */
+ char padxx[2];
+ unsigned int exmailbox; /* 購買信箱數 TODO short 就夠了 */
+ chicken_t mychicken; /* 寵物 */
+ time4_t lastsong; /* 上次點歌時間 */
+ unsigned int loginview; /* 進站畫面 */
+ unsigned char channel; /* TODO unused */
+ char padxxx;
+ unsigned short vl_count; /* 違法記錄 ViolateLaw counter */
+ unsigned short five_win; /* 五子棋戰績 勝 */
+ unsigned short five_lose; /* 五子棋戰績 敗 */
+ unsigned short five_tie; /* 五子棋戰績 和 */
+ unsigned short chc_win; /* 象棋戰績 勝 */
+ unsigned short chc_lose; /* 象棋戰績 敗 */
+ unsigned short chc_tie; /* 象棋戰績 和 */
+ int mobile; /* 手機號碼 */
+ char mind[4]; /* 心情 not a null-terminate string */
+ unsigned short go_win; /* 圍棋戰績 勝 */
+ unsigned short go_lose; /* 圍棋戰績 敗 */
+ unsigned short go_tie; /* 圍棋戰績 和 */
+ char pad0[5]; /* 從前放 ident 身份證字號,現在可以拿來做別的事了,
+ 不過最好記得要先清成 0 */
+ unsigned char signature; /* 慣用簽名檔 */
+
+ unsigned char goodpost; /* 評價為好文章數 */
+ unsigned char badpost; /* 評價為壞文章數 */
+ unsigned char goodsale; /* 競標 好的評價 */
+ unsigned char badsale; /* 競標 壞的評價 */
+ char myangel[IDLEN+1]; /* 我的小天使 */
+ char pad2;
+ unsigned short chess_elo_rating; /* 象棋等級分 */
+ unsigned int withme; /* 我想找人下棋,聊天.... */
+ time4_t timeremovebadpost; /* 上次刪除劣文時間 */
+ time4_t timeviolatelaw; /* 被開罰單時間 */
+ char pad[28];
+} userec_t;
+/* these are flags in userec_t.uflag */
+#define PAGER_FLAG 0x4 /* true if pager was OFF last session */
+#define CLOAK_FLAG 0x8 /* true if cloak was ON last session */
+#define FRIEND_FLAG 0x10 /* true if show friends only */
+#define BRDSORT_FLAG 0x20 /* true if the boards sorted alphabetical */
+#define MOVIE_FLAG 0x40 /* true if show movie */
+
+/* useless flag */
+//#define COLOR_FLAG 0x80 /* true if the color mode open */
+//#define MIND_FLAG 0x100 /* true if mind search mode open <-Heat*/
+
+#define DBCSAWARE_FLAG 0x200 /* true if DBCS-aware enabled. */
+/* please keep this even if you don't have DBCSAWARE features turned on */
+
+/* these are flags in userec_t.uflag2 */
+#define WATER_MASK 000003 /* water mask */
+#define WATER_ORIG 0x0
+#define WATER_NEW 0x1
+#define WATER_OFO 0x2
+#define WATERMODE(mode) ((cuser.uflag2 & WATER_MASK) == mode)
+#define FAVNOHILIGHT 0x10 /* false if hilight favorite */
+#define FAVNEW_FLAG 0x20 /* true if add new board into one's fav */
+#define FOREIGN 0x100 /* true if a foreign */
+#define LIVERIGHT 0x200 /* true if get "liveright" already */
+#define REJ_OUTTAMAIL 0x400 /* true if don't accept outside mails */
+#define REJECT_OUTTAMAIL (cuser.uflag2 & REJ_OUTTAMAIL)
+
+/* flags in userec_t.withme */
+#define WITHME_ALLFLAG 0x55555555
+#define WITHME_TALK 0x00000001
+#define WITHME_NOTALK 0x00000002
+#define WITHME_FIVE 0x00000004
+#define WITHME_NOFIVE 0x00000008
+#define WITHME_PAT 0x00000010
+#define WITHME_NOPAT 0x00000020
+#define WITHME_CHESS 0x00000040
+#define WITHME_NOCHESS 0x00000080
+#define WITHME_DARK 0x00000100
+#define WITHME_NODARK 0x00000200
+#define WITHME_GO 0x00000400
+#define WITHME_NOGO 0x00000800
+
+#ifdef PLAY_ANGEL
+#define REJ_QUESTION 0x800 /* true if don't want to be angel for a while */
+#define REJECT_QUESTION (cuser.uflag2 & REJ_QUESTION)
+#define ANGEL_MASK 0x3000
+#define ANGEL_R_MAEL 0x1000 /* true if reject male */
+#define ANGEL_R_FEMAEL 0x2000 /* true if reject female */
+#define ANGEL_STATUS() ((cuser.uflag2 & ANGEL_MASK) >> 12)
+#define ANGEL_SET(X) (cuser.uflag2 = (cuser.uflag2 & ~ANGEL_MASK) | \
+ (((X) & 3) << 12))
+#endif
+
+#define BTLEN 48 /* Length of board title */
+
+/* TODO 動態更新的欄位不應該跟要寫入檔案的混在一起,
+ * 至少用個 struct 包起來之類 */
+typedef struct boardheader_t {
+ char brdname[IDLEN + 1]; /* bid */
+ char title[BTLEN + 1];
+ char BM[IDLEN * 3 + 3]; /* BMs' userid, token '/' */
+ char pad1[3];
+ unsigned int brdattr; /* board的屬性 */
+ char chesscountry;
+ unsigned char vote_limit_posts; /* 連署 : 文章篇數下限 */
+ unsigned char vote_limit_logins; /* 連署 : 登入次數下限 */
+ unsigned char vote_limit_regtime; /* 連署 : 註冊時間限制 */
+ time4_t bupdate; /* note update time */
+ unsigned char post_limit_posts; /* 發表文章 : 文章篇數下限 */
+ unsigned char post_limit_logins; /* 發表文章 : 登入次數下限 */
+ unsigned char post_limit_regtime; /* 發表文章 : 註冊時間限制 */
+ unsigned char bvote; /* 正舉辦 Vote 數 */
+ time4_t vtime; /* Vote close time */
+ unsigned int level; /* 可以看此板的權限 */
+ time4_t perm_reload; /* 最後設定看板的時間 */
+ int gid; /* 看板所屬的類別 ID */
+ int next[2]; /* 在同一個gid下一個看板 動態產生*/
+ int firstchild[2]; /* 屬於這個看板的第一個子看板 */
+ int parent;
+ int childcount; /* 有多少個child */
+ int nuser; /* 多少人在這板 */
+ int postexpire; /* postexpire */
+ time4_t endgamble;
+ char posttype[33];
+ char posttype_f;
+ unsigned char fastrecommend_pause; /* 快速連推間隔 */
+ unsigned char vote_limit_badpost; /* 連署 : 劣文上限 */
+ unsigned char post_limit_badpost; /* 發表文章 : 劣文上限 */
+ char pad3[47];
+} boardheader_t;
+
+/* 下面是八進位喔 */
+#define BRD_NOZAP 0000000001 /* 不可zap */
+#define BRD_NOCOUNT 0000000002 /* 不列入統計 */
+#define BRD_NOTRAN 0000000004 /* 不轉信 */
+#define BRD_GROUPBOARD 0000000010 /* 群組板 */
+#define BRD_HIDE 0000000020 /* 隱藏板 (看板好友才可看) */
+#define BRD_POSTMASK 0000000040 /* 限制發表或閱讀 */
+#define BRD_ANONYMOUS 0000000100 /* 匿名板 */
+#define BRD_DEFAULTANONYMOUS 0000000200 /* 預設匿名板 */
+#define BRD_BAD 0000000400 /* 違法改進中看板 */
+#define BRD_VOTEBOARD 0000001000 /* 連署機看板 */
+#define BRD_WARNEL 0000002000 /* 連署機看板 */
+#define BRD_TOP 0000004000 /* 熱門看板群組 */
+#define BRD_NORECOMMEND 0000010000 /* 不可推薦 */
+#define BRD_BLOG 0000020000 /* BLOG */
+#define BRD_BMCOUNT 0000040000 /* 板主設定列入記錄 */
+#define BRD_SYMBOLIC 0000100000 /* symbolic link to board */
+#define BRD_NOBOO 0000200000 /* 不可噓 */
+#define BRD_LOCALSAVE 0000400000 /* 預設 Local Save */
+#define BRD_RESTRICTEDPOST 0001000000 /* 板友才能發文 */
+#define BRD_GUESTPOST 0002000000 /* guest能 post */
+#define BRD_COOLDOWN 0004000000 /* 冷靜 */
+#define BRD_CPLOG 0010000000 /* 自動留轉錄記錄 */
+#define BRD_NOFASTRECMD 0020000000 /* 禁止快速推文 */
+#define BRD_IPLOGRECMD 0040000000 /* 推文記錄 IP */
+#define BRD_OVER18 0100000000 /* 十八禁 */
+#define BRD_NOREPLY 0200000000 /* 不可回文 */
+
+#define BRD_LINK_TARGET(x) ((x)->postexpire)
+#define GROUPOP() (currmode & MODE_GROUPOP)
+
+#ifdef CHESSCOUNTRY
+#define CHESSCODE_NONE 0
+#define CHESSCODE_FIVE 1
+#define CHESSCODE_CCHESS 2
+#define CHESSCODE_GO 3
+#define CHESSCODE_MAX 3
+#endif /* defined(CHESSCOUNTRY) */
+
+
+#define TTLEN 64 /* Length of title */
+#define FNLEN 28 /* Length of filename */
+
+typedef struct fileheader_t {
+ char filename[FNLEN]; /* M.1120582370.A.1EA [19+1] */
+ int textlen; /* main text length in post */
+ char pad; /* padding, not used */
+ char recommend; /* important level */
+ char owner[IDLEN + 2]; /* uid[.] */
+ char date[6]; /* [02/02] or space(5) */
+ char title[TTLEN + 1];
+ /* TODO this multi is a mess now. */
+ char pad2;
+ union {
+ /* TODO: MOVE money to outside multi!!!!!! */
+ int money;
+ int anon_uid;
+ /* different order to match alignment */
+ struct {
+ unsigned char posts;
+ unsigned char logins;
+ unsigned char regtime;
+ unsigned char badpost;
+ } vote_limits;
+ struct {
+ /* is this ordering correct? */
+ unsigned int ref:31;
+ unsigned int flag:1;
+ } refer;
+ } multi; /* rocker: if bit32 on ==> reference */
+ /* XXX dirty, split into flag and money if money of each file is less than 16bit? */
+ unsigned char filemode; /* must be last field @ boards.c */
+ char pad3[3];
+} fileheader_t;
+
+#define FILE_LOCAL 0x1 /* local saved, non-mail */
+#define FILE_READ 0x1 /* already read, mail only */
+#define FILE_MARKED 0x2 /* non-mail + mail */
+#define FILE_DIGEST 0x4 /* digest, non-mail */
+#define FILE_REPLIED 0x4 /* replied, mail only */
+#define FILE_BOTTOM 0x8 /* push_bottom, non-mail */
+#define FILE_MULTI 0x8 /* multi send, mail only */
+#define FILE_SOLVED 0x10 /* problem solved, sysop/BM non-mail only */
+#define FILE_HIDE 0x20 /* hide, in announce */
+#define FILE_BID 0x20 /* bid, in non-announce */
+#define FILE_BM 0x40 /* BM only, in announce */
+#define FILE_VOTE 0x40 /* for vote, in non-announce */
+#define FILE_ANONYMOUS 0x80 /* anonymous file */
+
+#define STRLEN 80 /* Length of most string data */
+
+
+union xitem_t {
+ struct { /* bbs_item */
+ char fdate[9]; /* [mm/dd/yy] */
+ char editor[13]; /* user ID */
+ char fname[31];
+ } B;
+ struct { /* gopher_item */
+ char path[81];
+ char server[48];
+ int port;
+ } G;
+};
+
+typedef struct {
+ char title[63];
+ union xitem_t X;
+} item_t;
+
+typedef struct {
+ item_t *item[MAX_ITEMS];
+ char mtitle[STRLEN];
+ char *path;
+ int num, page, now, level;
+} gmenu_t;
+
+#define FAVMAX 1024 /* Max boards of Myfavorite */
+#define FAVGMAX 32 /* Max groups of Myfavorite */
+#define FAVGSLEN 8 /* Max Length of Description String */
+
+/* values of msgque_t::msgmode */
+#define MSGMODE_TALK 0
+#define MSGMODE_WRITE 1
+#ifdef PLAY_ANGEL
+#define MSGMODE_FROMANGEL 2
+#define MSGMODE_TOANGEL 3
+#endif
+
+typedef struct msgque_t {
+ pid_t pid;
+ char userid[IDLEN + 1];
+ char last_call_in[76];
+ int msgmode;
+} msgque_t;
+
+#define ALERT_NEW_MAIL 1
+#define ISNEWMAIL(utmp) utmp->alerts & ALERT_NEW_MAIL
+#define ALERT_PWD_PERM 2
+#define ALERT_PWD_BADPOST 4
+#define ALERT_PWD (ALERT_PWD_PERM|ALERT_PWD_BADPOST)
+/* user data in shm */
+/* use GAP to detect and avoid data overflow and overriding */
+typedef struct userinfo_t {
+ int uid; /* Used to find user name in passwd file */
+ pid_t pid; /* kill() to notify user of talk request */
+ int sockaddr; /* ... */
+
+ /* user data */
+ unsigned int userlevel;
+ char userid[IDLEN + 1];
+ char nickname[24];
+ char from[27]; /* machine name the user called in from */
+ int from_alias;
+ char sex;
+ char nonuse[4];
+ /*
+ unsigned char goodpost;
+ unsigned char badpost;
+ unsigned char goodsale;
+ unsigned char badsale;
+ */
+ unsigned char angel;
+
+ /* friends */
+ int friendtotal; /* 好友比較的cache 大小 */
+ short nFriends; /* 下面 friend[] 只用到前幾個,
+ 用來 bsearch */
+ int myfriend[MAX_FRIEND];
+ char gap_1[4];
+ int friend_online[MAX_FRIEND];/* point到線上好友 utmpshm的位置 */
+ /* 好友比較的cache 前兩個bit是狀態 */
+ char gap_2[4];
+ int reject[MAX_REJECT];
+ char gap_3[4];
+
+ /* messages */
+ char msgcount;
+ msgque_t msgs[MAX_MSGS];
+ char gap_4[sizeof(msgque_t)]; /* avoid msgs racing and overflow */
+
+ /* user status */
+ char birth; /* 是否是生日 Ptt*/
+ unsigned char active; /* When allocated this field is true */
+ unsigned char invisible; /* Used by cloaking function in Xyz menu */
+ unsigned char mode; /* UL/DL, Talk Mode, Chat Mode, ... */
+ unsigned char pager; /* pager toggle, YEA, or NA */
+ time4_t lastact; /* 上次使用者動的時間 */
+ char alerts; /* mail alert, passwd update... */
+ char mind[4];
+
+ /* chatroom/talk/games calling */
+ unsigned char sig; /* signal type */
+ int destuid; /* talk uses this to identify who called */
+ int destuip; /* dest index in utmpshm->uinfo[] */
+ unsigned char sockactive; /* Used to coordinate talk requests */
+
+ /* chat */
+ unsigned char in_chat; /* for in_chat commands */
+ char chatid[11]; /* chat id, if in chat mode */
+
+ /* games */
+ unsigned char lockmode; /* 不准 multi_login 玩的東西 */
+ char turn; /* 遊戲的先後 */
+ char mateid[IDLEN + 1]; /* 遊戲對手的 id */
+ char color; /* 暗棋 顏色 */
+
+ /* game record */
+ unsigned short int five_win;
+ unsigned short int five_lose;
+ unsigned short int five_tie;
+ unsigned short int chc_win;
+ unsigned short int chc_lose;
+ unsigned short int chc_tie;
+ unsigned short int chess_elo_rating;
+ unsigned short int go_win;
+ unsigned short int go_lose;
+ unsigned short int go_tie;
+
+ /* misc */
+ unsigned int withme;
+ unsigned int brc_id;
+
+
+#ifdef NOKILLWATERBALL
+ time4_t wbtime;
+#endif
+} userinfo_t;
+
+typedef struct water_t {
+ pid_t pid;
+ char userid[IDLEN + 1];
+ int top, count;
+ msgque_t msg[MAX_REVIEW];
+ userinfo_t *uin; // Ptt:這可以取代alive
+} water_t;
+
+typedef struct {
+ int row, col;
+ int y, x;
+ void *raw_memory;
+} screen_backup_t;
+
+typedef struct {
+ int header_size;
+ fileheader_t *header;
+ char mtitle[STRLEN];
+ const char *path;
+ int num, page, now, level;
+} menu_t;
+
+/* Used to pass commands to the readmenu.
+ * direct mapping, indexed by ascii code. */
+#define onekey_size ((int) 'z')
+/* keymap, 若 needitem = 0 表示不需要 item, func 的 type 應為 int (*)(void).
+ * 否則應為 int (*)(int ent, const fileheader_t *fhdr, const char *direct) */
+typedef struct {
+ char needitem;
+ int (*func)();
+} onekey_t;
+
+#define ANSILINELEN (511) /* Maximum Screen width in chars */
+
+/* anti_crosspost */
+typedef struct crosspost_t {
+ int checksum[4]; /* 0 -> 'X' cross post 1-3 -> 檢查文章行 */
+ short times; /* 第幾次 */
+ short last_bid; /* crossport to which board */
+} crosspost_t;
+
+#define SORT_BY_ID 0
+#define SORT_BY_CLASS 1
+#define SORT_BY_STAT 1
+#define SORT_BY_IDLE 2
+#define SORT_BY_FROM 3
+#define SORT_BY_FIVE 4
+#define SORT_BY_SEX 5
+
+typedef struct keeploc_t {
+ unsigned int hashkey;
+ int top_ln;
+ int crs_ln;
+} keeploc_t;
+
+#define VALID_USHM_ENTRY(X) ((X) >= 0 && (X) < USHM_SIZE)
+#define USHM_SIZE ((MAX_ACTIVE)*10/9)
+/* USHM_SIZE 比 MAX_ACTIVE 大是為了防止檢查人數上限時, 又同時衝進來
+ * 會造成找 shm 空位的無窮迴圈.
+ * 又, 因 USHM 中用 hash, 空間稍大時效率較好. */
+
+/* MAX_BMs is dirty hardcode 4 in mbbsd/cache.c:is_BM_cache() */
+#define MAX_BMs 4 /* for BMcache, 一個看板最多幾板主 */
+
+#define SHM_VERSION 3276
+typedef struct {
+ int version;
+ /* uhash */
+ /* uhash is a userid->uid hash table -- jochang */
+ char userid[MAX_USERS][IDLEN + 1];
+ char gap_1[IDLEN+1];
+ int next_in_hash[MAX_USERS];
+ char gap_2[sizeof(int)];
+ int money[MAX_USERS];
+ char gap_3[sizeof(int)];
+#ifdef USE_COOLDOWN
+ time4_t cooldowntime[MAX_USERS];
+#endif
+ char gap_4[sizeof(int)];
+ int hash_head[1 << HASH_BITS];
+ char gap_5[sizeof(int)];
+ int number; /* # of users total */
+ int loaded; /* .PASSWD has been loaded? */
+
+ /* utmpshm */
+ userinfo_t uinfo[USHM_SIZE];
+ char gap_6[sizeof(userinfo_t)];
+ int sorted[2][9][USHM_SIZE];
+ /* 第一維double buffer 由currsorted指向目前使用的
+ 第二維sort type */
+ char gap_7[sizeof(int)];
+ int currsorted;
+ time4_t UTMPuptime;
+ int UTMPnumber;
+ char UTMPneedsort;
+ char UTMPbusystate;
+
+ /* brdshm */
+ char gap_8[sizeof(int)];
+ int BMcache[MAX_BOARD][MAX_BMs];
+ char gap_9[sizeof(int)];
+ boardheader_t bcache[MAX_BOARD];
+ char gap_10[sizeof(int)];
+ int bsorted[2][MAX_BOARD]; /* 0: by name 1: by class */ /* 裡頭存的是 bid-1 */
+ char gap_11[sizeof(int)];
+#if HOTBOARDCACHE
+ unsigned char nHOTs;
+ int HBcache[HOTBOARDCACHE];
+#endif
+ char gap_12[sizeof(int)];
+ time4_t busystate_b[MAX_BOARD];
+ char gap_13[sizeof(int)];
+ int total[MAX_BOARD];
+ char gap_14[sizeof(int)];
+ unsigned char n_bottom[MAX_BOARD]; /* number of bottom */
+ char gap_15[sizeof(int)];
+ int hbfl[MAX_BOARD][MAX_FRIEND + 1]; /* hidden board friend list, 0: load time, 1-MAX_FRIEND: uid */
+ char gap_16[sizeof(int)];
+ time4_t lastposttime[MAX_BOARD];
+ char gap_17[sizeof(int)];
+ time4_t Buptime;
+ time4_t Btouchtime;
+ int Bnumber;
+ int Bbusystate;
+ time4_t close_vote_time;
+
+ /* pttcache */
+ char notes[MAX_MOVIE][256*11];
+ char gap_18[sizeof(int)];
+ char today_is[20];
+ // FIXME remove it
+ int __never_used__n_notes[MAX_MOVIE_SECTION]; /* 一節中有幾個 看板 */
+ char gap_19[sizeof(int)];
+ // FIXME remove it
+ int __never_used__next_refresh[MAX_MOVIE_SECTION]; /* 下一次要refresh的 看板 */
+ char gap_20[sizeof(int)];
+ msgque_t loginmsg; /* 進站水球 */
+ int last_film;
+ // FIXME remove it
+ int __never_used__max_history;
+ time4_t Puptime;
+ time4_t Ptouchtime;
+ int Pbusystate;
+
+ /* SHM 中的全域變數, 可用 shmctl 設定或顯示. 供動態調整或測試使用 */
+ union {
+ int v[512];
+ struct {
+ int dymaxactive; /* 動態設定最大人數上限 */
+ int toomanyusers; /* 超過人數上限不給進的個數 */
+ int noonlineuser; /* 站上使用者不高亮度顯示 */
+#ifdef OUTTA_TIMER
+ time4_t now;
+#endif
+ int nWelcomes;
+ int shutdown; /* shutdown flag */
+
+ /* 注意, 應保持 align sizeof(int) */
+ } e;
+ } GV2;
+ /* statistic */
+ int statistic[STAT_MAX];
+
+ /* 故鄉 fromcache */
+ unsigned int home_ip[MAX_FROM];
+ unsigned int home_mask[MAX_FROM];
+ char home_desc[MAX_FROM][32];
+ int home_num;
+
+ int max_user;
+ time4_t max_time;
+ time4_t Fuptime;
+ time4_t Ftouchtime;
+ int Fbusystate;
+
+#ifdef I18N
+ /* i18n(internationlization) */
+ char *i18nstrptr[MAX_LANG][MAX_STRING];
+ char i18nstrbody[22 * MAX_LANG * MAX_STRING];
+ /* Based on the statistis, we found the lengh of one string
+ is 22 bytes approximately.
+ */
+#endif
+} SHM_t;
+
+#ifdef SHMALIGNEDSIZE
+# define SHMSIZE (sizeof(SHM_t)/(SHMALIGNEDSIZE)+1)*SHMALIGNEDSIZE
+#else
+# define SHMSIZE (sizeof(SHM_t))
+#endif
+
+typedef struct {
+ unsigned short oldlen; /* previous line length */
+ unsigned short len; /* current length of line */
+ unsigned short smod; /* start of modified data */
+ unsigned short emod; /* end of modified data */
+ unsigned short sso; /* start stand out */
+ unsigned short eso; /* end stand out */
+ unsigned char mode; /* status of line, as far as update */
+ /* data 必需是最後一個欄位, see screen_backup() */
+ unsigned char data[ANSILINELEN + 1];
+} screenline_t;
+
+typedef struct {
+ int r, c;
+} rc_t;
+
+/* name.c 中運用的資料結構 */
+typedef struct word_t {
+ char *word;
+ struct word_t *next;
+} word_t;
+
+struct NameList {
+ int size;
+ int capacity;
+ char (*base)[IDLEN+1];
+};
+
+typedef struct commands_t {
+ int (*cmdfunc)();
+ int level;
+ char *desc; /* next/key/description */
+} commands_t;
+
+typedef struct MailQueue {
+ char filepath[FNLEN];
+ char subject[STRLEN];
+ time4_t mailtime;
+ char sender[IDLEN + 1];
+ char username[24];
+ char rcpt[50];
+ int method;
+ char *niamod;
+} MailQueue;
+
+enum {MQ_TEXT, MQ_UUENCODE, MQ_JUSTIFY};
+
+typedef struct
+{
+ time4_t chrono;
+ int recno;
+} TagItem;
+
+/*
+ * signature information
+ */
+typedef struct
+{
+ int total; /* total sig files found */
+ int max; /* max number of available sig */
+ int show_start; /* by which page to start display */
+ int show_max; /* max covered range in last display */
+} SigInfo;
+
+/* type in gomo.c, structure passing through socket */
+typedef struct {
+ char x;
+ char y;
+} Horder_t;
+
+#ifdef OUTTACACHE
+typedef struct {
+ int index; // 在 SHM->uinfo[index]
+ int uid; // 避免在 cache server 上不同步, 再確認用.
+ int friendstat;
+ int rfriendstat;
+} ocfs_t;
+#endif
+
+// kcwu: for bug tracking
+/* not used right now */
+enum {
+ F_VER,
+ F_EDIT,
+ F_MORE,
+ F_WRITE_REQUEST,
+ F_TALK_REQUEST,
+ F_WATER,
+ F_USERLIST,
+ F_GEM,
+};
+
+
+#endif
diff --git a/pttbbs/include/statistic.h b/pttbbs/include/statistic.h
new file mode 100644
index 00000000..0d22f6f0
--- /dev/null
+++ b/pttbbs/include/statistic.h
@@ -0,0 +1,52 @@
+#ifndef _STATISTIC_H_
+#define _STATISTIC_H_
+#define STAT(X, OP) do { \
+ if(SHM && SHM->version==SHM_VERSION && 0<=(X) && (X)<STAT_MAX) \
+ SHM->statistic[X] OP; \
+} while(0)
+#define STATINC(X) STAT(X, ++)
+
+enum { // XXX description in shmctl.c
+ STAT_LOGIN,
+ STAT_SHELLLOGIN,
+ STAT_VEDIT,
+ STAT_TALKREQUEST,
+ STAT_WRITEREQUEST,
+ STAT_MORE,
+ STAT_SYSWRITESOCKET,
+ STAT_SYSSELECT,
+ STAT_SYSREADSOCKET,
+ STAT_DOSEND,
+ STAT_SEARCHUSER,
+ STAT_THREAD,
+ STAT_SELECTREAD,
+ STAT_QUERY,
+ STAT_DOTALK,
+ STAT_FRIENDDESC,
+ STAT_FRIENDDESC_FILE,
+ STAT_PICKMYFRIEND,
+ STAT_PICKBFRIEND,
+ STAT_GAMBLE,
+ STAT_DOPOST,
+ STAT_READPOST,
+ STAT_RECOMMEND,
+ STAT_TODAYLOGIN_MIN,
+ STAT_TODAYLOGIN_MAX,
+ STAT_SIGINT,
+ STAT_SIGQUIT,
+ STAT_SIGILL,
+ STAT_SIGABRT,
+ STAT_SIGFPE,
+ STAT_SIGBUS,
+ STAT_SIGSEGV,
+ STAT_READPOST_12HR,
+ STAT_READPOST_1DAY,
+ STAT_READPOST_3DAY,
+ STAT_READPOST_7DAY,
+ STAT_READPOST_OLD,
+ STAT_SIGXCPU,
+ /* insert here. don't forget update shmctl.c */
+ STAT_NUM,
+ STAT_MAX=512
+};
+#endif
diff --git a/pttbbs/include/util.h b/pttbbs/include/util.h
new file mode 100644
index 00000000..44f8ca58
--- /dev/null
+++ b/pttbbs/include/util.h
@@ -0,0 +1,6 @@
+#ifndef INCLUDE_UTIL_H
+#define INCLUDE_UTIL_H
+int passwd_mmap(void);
+int passwd_apply(int (*fptr)(int, userec_t *));
+
+#endif // INCLUDE_UTIL_H
diff --git a/pttbbs/innbbsd/COPYRIGHT.nocem b/pttbbs/innbbsd/COPYRIGHT.nocem
new file mode 100644
index 00000000..fdd43b20
--- /dev/null
+++ b/pttbbs/innbbsd/COPYRIGHT.nocem
@@ -0,0 +1,11 @@
+# Author: Yen-Ming Lee <leeym@cae.ce.ntu.edu.tw>
+# Start Date: Thu Feb 25 1999 +0800
+# Project: INNBBSD - NoCeM
+# File: nocem.c nocem.h
+#
+# Copyright: Copyright (c) 2000 by Yen-Ming Lee
+#
+# Permission to use, copy, modify, and distribute this
+# software for any purpose with or without fee is hereby
+# granted, provided that the above copyright notice and this
+# permission notice appear in all copies.
diff --git a/pttbbs/innbbsd/Makefile b/pttbbs/innbbsd/Makefile
new file mode 100644
index 00000000..0e6c75cc
--- /dev/null
+++ b/pttbbs/innbbsd/Makefile
@@ -0,0 +1,71 @@
+# $Id$
+.include "../pttbbs.mk"
+
+VERSION= 0.50-pttpatch
+ADMINUSER?= root@your.domain.name
+
+# FreeBSD為了 innbbsd額外需加的參數
+inn_CFLAGS_FreeBSD= -DBSD44 -DMMAP -DGETRUSAGE
+inn_LDFLAGS_FreeBSD= -L/usr/local/lib -lcrypt -liconv
+
+# Linux為了 innbbsd額外需加的參數
+inn_CFLAGS_Linux= -DLINUX -DGETRUSAGE
+inn_LDFLAGS_Linux=
+
+# Solaris為了innbbsd額外需加的參數
+inn_CFLAGS_Solaris= -DMMAP -DSolaris -DSYSV -I/usr/local/include/
+inn_LDFLAGS_Solaris= -L/usr/local/lib -liconv -lsocket -lnsl -lkstat
+
+CFLAGS+= -DVERSION=\"${VERSION}\" \
+ -DADMINUSER=\"${ADMINUSER}\" \
+ -DMapleBBS -DDBZDEBUG -I. \
+ ${inn_CFLAGS_${OSTYPE}} -DHMM_USE_ANTI_SPAM
+
+LDFLAGS+= ${inn_LDFLAGS_${OSTYPE}}
+
+PROGS= bbslink bbsnnrp ctlinnbbsd \
+ innbbsd mkhistory
+
+all: ${PROGS}
+
+# bbs util
+UTIL_DIR= ../util
+UTIL_OBJS= \
+ util_cache.o util_record.o util_passwd.o util_var.o \
+ util_stuff.o util_osdep.o util_args.o util_file.o
+
+.for fn in ${UTIL_OBJS}
+LINK_UTIL_OBJS+= ${UTIL_DIR}/${fn}
+
+${UTIL_DIR}/${fn}: # FIXME: dependency
+ cd ${UTIL_DIR}; make ${fn}
+.endfor
+
+
+echobbslib.o: echobbslib.c
+ ${CC} ${CFLAGS} -DWITH_ECHOMAIL -c echobbslib.c
+
+innbbsd: inndchannel.o innbbsd.o connectsock.o rfc931.o daemon.o \
+ file.o pmain.o his.o dbz.o closeonexec.o dbztool.o \
+ inntobbs.o receive_article.o echobbslib.o str_decode.o nocem.o
+ ${CCACHE} ${CC} -o $@ ${LDFLAGS} $? ${LINK_UTIL_OBJS}
+
+bbslink: bbslink.o pmain.o inntobbs.o echobbslib.o connectsock.o \
+ file.o port.o str_decode.o
+ ${CCACHE} ${CC} -o $@ ${LDFLAGS} $? ${LINK_UTIL_OBJS}
+
+bbsnnrp: bbsnnrp.o pmain.o bbslib.o connectsock.o file.o
+ ${CCACHE} ${CC} -o $@ ${LDFLAGS} $? ${LINK_UTIL_OBJS}
+
+ctlinnbbsd: ctlinnbbsd.o pmain.o bbslib.o connectsock.o file.o
+ ${CCACHE} ${CC} -o $@ ${LDFLAGS} $? ${LINK_UTIL_OBJS}
+
+mkhistory: mkhistory.o bbslib.o file.o his.o dbz.o port.o closeonexec.o
+ ${CCACHE} ${CC} -o $@ ${LDFLAGS} $? ${LINK_UTIL_OBJS}
+
+install: ${PROGS}
+ install -d ${BBSHOME}/innd/
+ install -c -m 755 ${PROGS} ${BBSHOME}/innd/
+
+clean:
+ rm -f *.o ${PROGS} core *.core
diff --git a/pttbbs/innbbsd/antisplam.h b/pttbbs/innbbsd/antisplam.h
new file mode 100644
index 00000000..0832533f
--- /dev/null
+++ b/pttbbs/innbbsd/antisplam.h
@@ -0,0 +1,25 @@
+#include "bbs.h"
+#define char_lower(c) ((c >= 'A' && c <= 'Z') ? c|32 : c)
+
+#if 0 /* string.h , libc */
+int
+strcasestr(str, tag)
+ char *str, *tag; /* tag : lower-case string */
+{
+ char buf[256];
+
+ str_lower(buf, str);
+ return (int)strstr(buf, tag);
+}
+#endif
+
+int
+bad_subject(char *subject)
+{
+ char *badkey[] = {"無碼", "avcd", "mp3", NULL};
+ int i;
+ for (i = 0; badkey[i]; i++)
+ if (strcasestr(subject, badkey[i]))
+ return 1;
+ return 0;
+}
diff --git a/pttbbs/innbbsd/bbslib.c b/pttbbs/innbbsd/bbslib.c
new file mode 100644
index 00000000..326c7d87
--- /dev/null
+++ b/pttbbs/innbbsd/bbslib.c
@@ -0,0 +1,773 @@
+#include <stdlib.h>
+#if defined( LINUX )
+#include "innbbsconf.h"
+#include "bbslib.h"
+#include <stdarg.h>
+#else
+#include <stdarg.h>
+#include "innbbsconf.h"
+#include "bbslib.h"
+#endif
+#include "config.h"
+#include "externs.h"
+
+char INNBBSCONF[MAXPATHLEN];
+char INNDHOME[MAXPATHLEN];
+char HISTORY[MAXPATHLEN];
+char LOGFILE[MAXPATHLEN];
+char MYBBSID[MAXPATHLEN];
+char ECHOMAIL[MAXPATHLEN];
+char BBSFEEDS[MAXPATHLEN];
+char LOCALDAEMON[MAXPATHLEN];
+
+int His_Maint_Min = HIS_MAINT_MIN;
+int His_Maint_Hour = HIS_MAINT_HOUR;
+int Expiredays = EXPIREDAYS;
+
+nodelist_t *NODELIST = NULL, **NODELIST_BYNODE = NULL;
+newsfeeds_t *NEWSFEEDS = NULL, **NEWSFEEDS_BYBOARD = NULL;
+static char *NODELIST_BUF, *NEWSFEEDS_BUF;
+int NFCOUNT, NLCOUNT;
+int LOCALNODELIST = 0, NONENEWSFEEDS = 0;
+
+#ifndef _PATH_BBSHOME
+#define _PATH_BBSHOME "/u/staff/bbsroot/csie_util/bntpd/home"
+#endif
+
+static FILE *bbslogfp;
+
+static int
+ verboseFlag = 0;
+
+static char *
+ verboseFilename = NULL;
+static char verbosename[MAXPATHLEN];
+
+void
+verboseon(filename)
+ char *filename;
+{
+ verboseFlag = 1;
+ if (filename != NULL) {
+ if (strchr(filename, '/') == NULL) {
+ sprintf(verbosename, "%s/innd/%s", BBSHOME, filename);
+ filename = verbosename;
+ }
+ }
+ verboseFilename = filename;
+}
+void
+verboseoff()
+{
+ verboseFlag = 0;
+}
+
+void
+setverboseon(void)
+{
+ verboseFlag = 1;
+}
+
+int
+isverboselog(void)
+{
+ return verboseFlag;
+}
+
+void
+setverboseoff(void)
+{
+ verboseoff();
+ if (bbslogfp != NULL) {
+ fclose(bbslogfp);
+ bbslogfp = NULL;
+ }
+}
+
+void
+verboselog(char *fmt,...)
+{
+ va_list ap;
+ char datebuf[40];
+ time_t now;
+
+ if (verboseFlag == 0)
+ return;
+
+ va_start(ap, fmt);
+
+ time(&now);
+ strftime(datebuf, sizeof(datebuf), "%b %d %X ", localtime(&now));
+
+ if (bbslogfp == NULL) {
+ if (verboseFilename != NULL)
+ bbslogfp = fopen(verboseFilename, "a");
+ else
+ bbslogfp = fdopen(1, "a");
+ }
+ if (bbslogfp == NULL) {
+ va_end(ap);
+ return;
+ }
+ fprintf(bbslogfp, "%s[%d] ", datebuf, getpid());
+ vfprintf(bbslogfp, fmt, ap);
+ fflush(bbslogfp);
+ va_end(ap);
+}
+
+void
+#ifdef PalmBBS
+xbbslog(char *fmt,...)
+#else
+bbslog(char *fmt,...)
+#endif
+{
+ va_list ap;
+ char datebuf[40];
+ time_t now;
+
+ va_start(ap, fmt);
+
+ time(&now);
+ strftime(datebuf, sizeof(datebuf), "%b %d %X ", localtime(&now));
+
+ if (bbslogfp == NULL) {
+ bbslogfp = fopen(LOGFILE, "a");
+ }
+ if (bbslogfp == NULL) {
+ va_end(ap);
+ return;
+ }
+ fprintf(bbslogfp, "%s[%d] ", datebuf, getpid());
+ vfprintf(bbslogfp, fmt, ap);
+ fflush(bbslogfp);
+ va_end(ap);
+}
+
+int
+initial_bbs(outgoing)
+ char *outgoing;
+{
+ /* reopen bbslog */
+ if (bbslogfp != NULL) {
+ fclose(bbslogfp);
+ bbslogfp = NULL;
+ }
+#ifdef WITH_ECHOMAIL
+ init_echomailfp();
+ init_bbsfeedsfp();
+#endif
+
+ LOCALNODELIST = 0, NONENEWSFEEDS = 0;
+ sprintf(INNDHOME, "%s/innd", BBSHOME);
+ sprintf(HISTORY, "%s/history", INNDHOME);
+ sprintf(LOGFILE, "%s/bbslog", INNDHOME);
+ sprintf(ECHOMAIL, "%s/echomail.log", BBSHOME);
+ sprintf(LOCALDAEMON, "%s/.innbbsd", INNDHOME);
+ sprintf(INNBBSCONF, "%s/innbbs.conf", INNDHOME);
+ sprintf(BBSFEEDS, "%s/bbsfeeds.log", INNDHOME);
+
+ if (isfile(INNBBSCONF)) {
+ FILE *conf;
+ char buffer[MAXPATHLEN];
+ conf = fopen(INNBBSCONF, "r");
+ if (conf != NULL) {
+ while (fgets(buffer, sizeof buffer, conf) != NULL) {
+ char *ptr, *front = NULL, *value = NULL, *value2 = NULL,
+ *value3 = NULL;
+ if (buffer[0] == '#' || buffer[0] == '\n')
+ continue;
+ for (front = buffer; *front && isspace(*front); front++);
+ for (ptr = front; *ptr && !isspace(*ptr); ptr++);
+ if (*ptr == '\0')
+ continue;
+ *ptr++ = '\0';
+ for (; *ptr && isspace(*ptr); ptr++);
+ if (*ptr == '\0')
+ continue;
+ value = ptr++;
+ for (; *ptr && !isspace(*ptr); ptr++);
+ if (*ptr) {
+ *ptr++ = '\0';
+ for (; *ptr && isspace(*ptr); ptr++);
+ value2 = ptr++;
+ for (; *ptr && !isspace(*ptr); ptr++);
+ if (*ptr) {
+ *ptr++ = '\0';
+ for (; *ptr && isspace(*ptr); ptr++);
+ value3 = ptr++;
+ for (; *ptr && !isspace(*ptr); ptr++);
+ if (*ptr) {
+ *ptr++ = '\0';
+ }
+ }
+ }
+ if (strcasecmp(front, "expiredays") == 0) {
+ Expiredays = atoi(value);
+ if (Expiredays < 0) {
+ Expiredays = EXPIREDAYS;
+ }
+ } else if (strcasecmp(front, "expiretime") == 0) {
+ ptr = strchr(value, ':');
+ if (ptr == NULL) {
+ fprintf(stderr, "Syntax error in innbbs.conf\n");
+ } else {
+ *ptr++ = '\0';
+ His_Maint_Hour = atoi(value);
+ His_Maint_Min = atoi(ptr);
+ if (His_Maint_Hour < 0)
+ His_Maint_Hour = HIS_MAINT_HOUR;
+ if (His_Maint_Min < 0)
+ His_Maint_Min = HIS_MAINT_MIN;
+ }
+ } else if (strcasecmp(front, "newsfeeds") == 0) {
+ if (strcmp(value, "none") == 0)
+ NONENEWSFEEDS = 1;
+ } else if (strcasecmp(front, "nodelist") == 0) {
+ if (strcmp(value, "local") == 0)
+ LOCALNODELIST = 1;
+ } /* else if ( strcasecmp(front,"newsfeeds") ==
+ * 0) { printf("newsfeeds %s\n", value); }
+ * else if ( strcasecmp(front,"nodelist") ==
+ * 0) { printf("nodelist %s\n", value); }
+ * else if ( strcasecmp(front,"bbsname") ==
+ * 0) { printf("bbsname %s\n", value); } */
+ }
+ fclose(conf);
+ }
+ }
+#ifdef WITH_ECHOMAIL
+ bbsnameptr = (char *)fileglue("%s/bbsname.bbs", INNDHOME);
+ if ((FN = fopen(bbsnameptr, "r")) == NULL) {
+ fprintf(stderr, "can't open file %s\n", bbsnameptr);
+ return 0;
+ }
+ while (fscanf(FN, "%s", MYBBSID) != EOF);
+ fclose(FN);
+ if (!isdir(fileglue("%s/out.going", BBSHOME))) {
+ mkdir((char *)fileglue("%s/out.going", BBSHOME), 0750);
+ }
+ if (NONENEWSFEEDS == 0)
+ readnffile(INNDHOME);
+ readNCMfile(INNDHOME);
+ if (LOCALNODELIST == 0) {
+ if (readnlfile(INNDHOME, outgoing) != 0)
+ return 0;
+ }
+#endif
+ return 1;
+}
+
+static int
+nf_byboardcmp(a, b)
+ newsfeeds_t **a, **b;
+{
+ /*
+ * if (!a || !*a || !(*a)->board) return -1; if (!b || !*b ||
+ * !(*b)->board) return 1;
+ */
+ return strcasecmp((*a)->board, (*b)->board);
+}
+
+static int
+nfcmp(a, b)
+ newsfeeds_t *a, *b;
+{
+ /*
+ * if (!a || !a->newsgroups) return -1; if (!b || !b->newsgroups) return
+ * 1;
+ */
+ return strcasecmp(a->newsgroups, b->newsgroups);
+}
+
+static int
+nlcmp(a, b)
+ nodelist_t *a, *b;
+{
+ /*
+ * if (!a || !a->host) return -1; if (!b || !b->host) return 1;
+ */
+ return strcasecmp(a->host, b->host);
+}
+
+static int
+nl_bynodecmp(a, b)
+ nodelist_t **a, **b;
+{
+ /*
+ * if (!a || !*a || !(*a)->node) return -1; if (!b || !*b || !(*b)->node)
+ * return 1;
+ */
+ return strcasecmp((*a)->node, (*b)->node);
+}
+
+/* read in newsfeeds.bbs and nodelist.bbs */
+int
+readnlfile(inndhome, outgoing)
+ char *inndhome;
+ char *outgoing;
+{
+ FILE *fp;
+ char buff[1024];
+ struct stat st;
+ int i, count;
+ char *ptr, *nodelistptr;
+ static int lastcount = 0;
+
+ sprintf(buff, "%s/nodelist.bbs", inndhome);
+ fp = fopen(buff, "r");
+ if (fp == NULL) {
+ fprintf(stderr, "open fail %s", buff);
+ return -1;
+ }
+ if (fstat(fileno(fp), &st) != 0) {
+ fprintf(stderr, "stat fail %s", buff);
+ return -1;
+ }
+ if (NODELIST_BUF == NULL) {
+ NODELIST_BUF = (char *)mymalloc(st.st_size + 1);
+ } else {
+ NODELIST_BUF = (char *)myrealloc(NODELIST_BUF, st.st_size + 1);
+ }
+ i = 0, count = 0;
+ while (fgets(buff, sizeof buff, fp) != NULL) {
+ if (buff[0] == '#')
+ continue;
+ if (buff[0] == '\n')
+ continue;
+ strcpy(NODELIST_BUF + i, buff);
+ i += strlen(buff);
+ count++;
+ }
+ fclose(fp);
+ if (NODELIST == NULL) {
+ NODELIST = (nodelist_t *) mymalloc(sizeof(nodelist_t) * (count + 1));
+ NODELIST_BYNODE = (nodelist_t **) mymalloc(sizeof(nodelist_t *) * (count + 1));
+ } else {
+ NODELIST = (nodelist_t *) myrealloc(NODELIST, sizeof(nodelist_t) * (count + 1));
+ NODELIST_BYNODE = (nodelist_t **) myrealloc(NODELIST_BYNODE, sizeof(nodelist_t *) * (count + 1));
+ }
+ for (i = lastcount; i < count; i++) {
+ NODELIST[i].feedfp = NULL;
+ }
+ lastcount = count;
+ NLCOUNT = 0;
+ for (ptr = NODELIST_BUF; (nodelistptr = (char *)strchr(ptr, '\n')) != NULL; ptr = nodelistptr + 1, NLCOUNT++) {
+ char *nptr, *tptr;
+ *nodelistptr = '\0';
+ NODELIST[NLCOUNT].host = "";
+ NODELIST[NLCOUNT].exclusion = "";
+ NODELIST[NLCOUNT].node = "";
+ NODELIST[NLCOUNT].protocol = "IHAVE(119)";
+ NODELIST[NLCOUNT].comments = "";
+ NODELIST_BYNODE[NLCOUNT] = NODELIST + NLCOUNT;
+ for (nptr = ptr; *nptr && isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0') {
+ bbslog("nodelist.bbs %d entry read error\n", NLCOUNT);
+ return -1;
+ }
+ /* NODELIST[NLCOUNT].id = nptr; */
+ NODELIST[NLCOUNT].node = nptr;
+ for (nptr++; *nptr && !isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0') {
+ bbslog("nodelist.bbs node %d entry read error\n", NLCOUNT);
+ return -1;
+ }
+ *nptr = '\0';
+ if ((tptr = strchr(NODELIST[NLCOUNT].node, '/'))) {
+ *tptr = '\0';
+ NODELIST[NLCOUNT].exclusion = tptr + 1;
+ } else {
+ NODELIST[NLCOUNT].exclusion = "";
+ }
+ for (nptr++; *nptr && isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ if (*nptr == '+' || *nptr == '-') {
+ NODELIST[NLCOUNT].feedtype = *nptr;
+ if (NODELIST[NLCOUNT].feedfp != NULL) {
+ fclose(NODELIST[NLCOUNT].feedfp);
+ }
+ if (NODELIST[NLCOUNT].feedtype == '+')
+ if (outgoing != NULL) {
+ NODELIST[NLCOUNT].feedfp = fopen((char *)fileglue("%s/out.going/%s.%s", BBSHOME, NODELIST[NLCOUNT].node, outgoing), "a");
+ }
+ nptr++;
+ } else {
+ NODELIST[NLCOUNT].feedtype = ' ';
+ }
+ NODELIST[NLCOUNT].host = nptr;
+ for (nptr++; *nptr && !isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0') {
+ continue;
+ }
+ *nptr = '\0';
+ for (nptr++; *nptr && isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ NODELIST[NLCOUNT].protocol = nptr;
+ for (nptr++; *nptr && !isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ *nptr = '\0';
+ for (nptr++; *nptr && strchr(" \t\r\n", *nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ NODELIST[NLCOUNT].comments = nptr;
+ }
+ qsort(NODELIST, NLCOUNT, sizeof(nodelist_t), nlcmp);
+ qsort(NODELIST_BYNODE, NLCOUNT, sizeof(nodelist_t *), nl_bynodecmp);
+ return 0;
+}
+
+int
+readnffile(inndhome)
+ char *inndhome;
+{
+ FILE *fp;
+ char buff[1024];
+ struct stat st;
+ int i, count;
+ char *ptr, *newsfeedsptr;
+
+ sprintf(buff, "%s/newsfeeds.bbs", inndhome);
+ fp = fopen(buff, "r");
+ if (fp == NULL) {
+ fprintf(stderr, "open fail %s", buff);
+ return -1;
+ }
+ if (fstat(fileno(fp), &st) != 0) {
+ fprintf(stderr, "stat fail %s", buff);
+ return -1;
+ }
+ if (NEWSFEEDS_BUF == NULL) {
+ NEWSFEEDS_BUF = (char *)mymalloc(st.st_size + 1);
+ } else {
+ NEWSFEEDS_BUF = (char *)myrealloc(NEWSFEEDS_BUF, st.st_size + 1);
+ }
+ i = 0, count = 0;
+ while (fgets(buff, sizeof buff, fp) != NULL) {
+ if (buff[0] == '#')
+ continue;
+ if (buff[0] == '\n')
+ continue;
+ strcpy(NEWSFEEDS_BUF + i, buff);
+ i += strlen(buff);
+ count++;
+ }
+ fclose(fp);
+ if (NEWSFEEDS == NULL) {
+ NEWSFEEDS = (newsfeeds_t *) mymalloc(sizeof(newsfeeds_t) * (count + 1));
+ NEWSFEEDS_BYBOARD = (newsfeeds_t **) mymalloc(sizeof(newsfeeds_t *) * (count + 1));
+ } else {
+ NEWSFEEDS = (newsfeeds_t *) myrealloc(NEWSFEEDS, sizeof(newsfeeds_t) * (count + 1));
+ NEWSFEEDS_BYBOARD = (newsfeeds_t **) myrealloc(NEWSFEEDS_BYBOARD, sizeof(newsfeeds_t *) * (count + 1));
+ }
+ NFCOUNT = 0;
+ for (ptr = NEWSFEEDS_BUF; (newsfeedsptr = (char *)strchr(ptr, '\n')) != NULL; ptr = newsfeedsptr + 1, NFCOUNT++) {
+ char *nptr;
+ *newsfeedsptr = '\0';
+ NEWSFEEDS[NFCOUNT].newsgroups = "";
+ NEWSFEEDS[NFCOUNT].board = "";
+ NEWSFEEDS[NFCOUNT].path = NULL;
+ NEWSFEEDS_BYBOARD[NFCOUNT] = NEWSFEEDS + NFCOUNT;
+ for (nptr = ptr; *nptr && isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ NEWSFEEDS[NFCOUNT].newsgroups = nptr;
+ for (nptr++; *nptr && !isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ *nptr = '\0';
+ for (nptr++; *nptr && isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ NEWSFEEDS[NFCOUNT].board = nptr;
+ for (nptr++; *nptr && !isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ *nptr = '\0';
+ for (nptr++; *nptr && isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ NEWSFEEDS[NFCOUNT].path = nptr;
+ for (nptr++; *nptr && !strchr("\r\n", *nptr);)
+ nptr++;
+ *nptr = '\0';
+ }
+ qsort(NEWSFEEDS, NFCOUNT, sizeof(newsfeeds_t), nfcmp);
+ qsort(NEWSFEEDS_BYBOARD, NFCOUNT, sizeof(newsfeeds_t *), nf_byboardcmp);
+ return 0;
+}
+
+newsfeeds_t *
+search_board(board)
+ char *board;
+{
+ newsfeeds_t nft, *nftptr, **find;
+ if (NONENEWSFEEDS)
+ return NULL;
+ nft.board = board;
+ nftptr = &nft;
+ find = (newsfeeds_t **) bsearch((char *)&nftptr, NEWSFEEDS_BYBOARD, NFCOUNT, sizeof(newsfeeds_t *), nf_byboardcmp);
+ if (find != NULL)
+ return *find;
+ return NULL;
+}
+
+nodelist_t *
+search_nodelist_bynode(node)
+ char *node;
+{
+ nodelist_t nlt, *nltptr, **find;
+ if (LOCALNODELIST)
+ return NULL;
+ nlt.node = node;
+ nltptr = &nlt;
+ find = (nodelist_t **) bsearch((char *)&nltptr, NODELIST_BYNODE, NLCOUNT, sizeof(nodelist_t *), nl_bynodecmp);
+ if (find != NULL)
+ return *find;
+ return NULL;
+}
+
+
+nodelist_t *
+search_nodelist(site, identuser)
+ char *site;
+ char *identuser;
+{
+ nodelist_t nlt, *find;
+ char buffer[1024];
+ if (LOCALNODELIST)
+ return NULL;
+ nlt.host = site;
+ find = (nodelist_t *) bsearch((char *)&nlt, NODELIST, NLCOUNT, sizeof(nodelist_t), nlcmp);
+ if (find == NULL && identuser != NULL) {
+ sprintf(buffer, "%s@%s", identuser, site);
+ nlt.host = buffer;
+ find = (nodelist_t *) bsearch((char *)&nlt, NODELIST, NLCOUNT, sizeof(nodelist_t), nlcmp);
+ }
+ return find;
+}
+
+newsfeeds_t *
+search_group(newsgroup)
+ char *newsgroup;
+{
+ newsfeeds_t nft, *find;
+ if (NONENEWSFEEDS)
+ return NULL;
+ nft.newsgroups = newsgroup;
+ find = (newsfeeds_t *) bsearch((char *)&nft, NEWSFEEDS, NFCOUNT, sizeof(newsfeeds_t), nfcmp);
+ return find;
+}
+
+char *
+ascii_date(now)
+ time_t now;
+{
+ static char datebuf[40];
+ /*
+ * time_t now; time(&now);
+ */
+ strftime(datebuf, sizeof(datebuf), "%d %b %Y %X " INNTIMEZONE, gmtime(&now));
+ return datebuf;
+}
+
+char *
+restrdup(ptr, string)
+ char *ptr;
+ char *string;
+{
+ int len;
+ if (string == NULL) {
+ if (ptr != NULL)
+ *ptr = '\0';
+ return ptr;
+ }
+ len = strlen(string) + 1;
+ if (ptr != NULL) {
+ ptr = (char *)myrealloc(ptr, len);
+ } else
+ ptr = (char *)mymalloc(len);
+ strcpy(ptr, string);
+ return ptr;
+}
+
+
+
+void *
+mymalloc(size)
+ int size;
+{
+ char *ptr = (char *)malloc(size);
+ if (ptr == NULL) {
+ fprintf(stderr, "cant allocate memory\n");
+ syslog(LOG_ERR, "cant allocate memory %m");
+ exit(1);
+ }
+ return ptr;
+}
+
+void *
+myrealloc(optr, size)
+ void *optr;
+ int size;
+{
+ char *ptr = (char *)realloc(optr, size);
+ if (ptr == NULL) {
+ fprintf(stderr, "cant allocate memory\n");
+ syslog(LOG_ERR, "cant allocate memory %m");
+ exit(1);
+ }
+ return ptr;
+}
+
+void
+testandmkdir(dir)
+ char *dir;
+{
+ if (!isdir(dir)) {
+ char path[MAXPATHLEN + 12];
+ sprintf(path, "mkdir -p %s", dir);
+ system(path);
+ }
+}
+
+static char splitbuf[2048];
+static char joinbuf[1024];
+#define MAXTOK 50
+static char *Splitptr[MAXTOK];
+char **
+split(line, pat)
+ char *line, *pat;
+{
+ char *p;
+ int i;
+
+ for (i = 0; i < MAXTOK; ++i)
+ Splitptr[i] = NULL;
+ strncpy(splitbuf, line, sizeof splitbuf - 1);
+ /* printf("%d %d\n",strlen(line),strlen(splitbuf)); */
+ splitbuf[sizeof splitbuf - 1] = '\0';
+ for (i = 0, p = splitbuf; *p && i < MAXTOK - 1;) {
+ for (Splitptr[i++] = p; *p && !strchr(pat, *p); p++);
+ if (*p == '\0')
+ break;
+ for (*p++ = '\0'; *p && strchr(pat, *p); p++);
+ }
+ return Splitptr;
+}
+
+char **
+BNGsplit(line)
+ char *line;
+{
+ char **ptr = split(line, ",");
+ newsfeeds_t *nf1, *nf2;
+ char *n11, *n12, *n21, *n22;
+ int i, j;
+ for (i = 0; ptr[i] != NULL; i++) {
+ nf1 = (newsfeeds_t *) search_group(ptr[i]);
+ for (j = i + 1; ptr[j] != NULL; j++) {
+ if (strcmp(ptr[i], ptr[j]) == 0) {
+ *ptr[j] = '\0';
+ continue;
+ }
+ nf2 = (newsfeeds_t *) search_group(ptr[j]);
+ if (nf1 && nf2) {
+ if (strcmp(nf1->board, nf2->board) == 0) {
+ *ptr[j] = '\0';
+ continue;
+ }
+ for (n11 = nf1->board, n12 = (char *)strchr(n11, ',');
+ n11 && *n11; n12 = (char *)strchr(n11, ',')) {
+ if (n12)
+ *n12 = '\0';
+ for (n21 = nf2->board, n22 = (char *)strchr(n21, ',');
+ n21 && *n21; n22 = (char *)strchr(n21, ',')) {
+ if (n22)
+ *n22 = '\0';
+ if (strcmp(n11, n21) == 0) {
+ *n21 = '\t';
+ }
+ if (n22) {
+ *n22 = ',';
+ n21 = n22 + 1;
+ } else
+ break;
+ }
+ if (n12) {
+ *n12 = ',';
+ n11 = n12 + 1;
+ } else
+ break;
+ }
+ }
+ }
+ }
+ return ptr;
+}
+
+char **
+ssplit(line, pat)
+ char *line, *pat;
+{
+ char *p;
+ int i;
+ for (i = 0; i < MAXTOK; ++i)
+ Splitptr[i] = NULL;
+ strncpy(splitbuf, line, 1024);
+ for (i = 0, p = splitbuf; *p && i < MAXTOK;) {
+ for (Splitptr[i++] = p; *p && !strchr(pat, *p); p++);
+ if (*p == '\0')
+ break;
+ *p = 0;
+ p++;
+ /* for (*p='\0'; strchr(pat,*p);p++); */
+ }
+ return Splitptr;
+}
+
+char *
+join(lineptr, pat, num)
+ char **lineptr, *pat;
+ int num;
+{
+ int i;
+ joinbuf[0] = '\0';
+ if (lineptr[0] != NULL)
+ strncpy(joinbuf, lineptr[0], 1024);
+ else {
+ joinbuf[0] = '\0';
+ return joinbuf;
+ }
+ for (i = 1; i < num; i++) {
+ strcat(joinbuf, pat);
+ if (lineptr[i] != NULL)
+ strcat(joinbuf, lineptr[i]);
+ else
+ break;
+ }
+ return joinbuf;
+}
+
+#ifdef BBSLIB
+main()
+{
+ initial_bbs("feed");
+ printf("%s\n", ascii_date());
+}
+#endif
diff --git a/pttbbs/innbbsd/bbslib.h b/pttbbs/innbbsd/bbslib.h
new file mode 100644
index 00000000..2aebcb96
--- /dev/null
+++ b/pttbbs/innbbsd/bbslib.h
@@ -0,0 +1,62 @@
+#ifndef BBSLIB_H
+#define BBSLIB_H
+#include <stdio.h> /* for FILE */
+typedef struct nodelist_t {
+ char *node;
+ char *exclusion;
+ char *host;
+ char *protocol;
+ char *comments;
+ int feedtype;
+ FILE *feedfp;
+} nodelist_t;
+
+typedef struct newsfeeds_t {
+ char *newsgroups;
+ char *board;
+ char *path;
+} newsfeeds_t;
+
+typedef struct overview_t {
+ char *board, *filename, *group;
+ time_t mtime;
+ char *from, *subject;
+} overview_t;
+
+extern char MYBBSID[];
+extern char ECHOMAIL[];
+extern char BBSFEEDS[];
+extern char LOCALDAEMON[];
+extern char INNDHOME[];
+extern char HISTORY[];
+extern char LOGFILE[];
+extern char INNBBSCONF[];
+extern nodelist_t *NODELIST;
+extern nodelist_t **NODELIST_BYNODE;
+extern newsfeeds_t *NEWSFEEDS, **NEWSFEEDS_BYBOARD;
+extern int NFCOUNT, NLCOUNT;
+extern int Expiredays, His_Maint_Min, His_Maint_Hour;
+extern int LOCALNODELIST, NONENEWSFEEDS;
+extern int Maxclient;
+
+#ifndef ARG
+#ifdef __STDC__
+#define ARG(x) x
+#else
+#define ARG(x) ()
+#endif
+#endif
+
+int initial_bbs ARG((char *));
+char *restrdup ARG((char *, char *));
+nodelist_t *search_nodelist ARG((char *, char *));
+newsfeeds_t *search_group ARG((char *));
+void bbslog(char *fmt,...);
+void *mymalloc ARG((int));
+void *myrealloc ARG((void *, int));
+
+#ifdef PalmBBS
+#define bbslog xbbslog
+#endif
+
+#endif
diff --git a/pttbbs/innbbsd/bbslink.c b/pttbbs/innbbsd/bbslink.c
new file mode 100644
index 00000000..b23c0bdc
--- /dev/null
+++ b/pttbbs/innbbsd/bbslink.c
@@ -0,0 +1,1806 @@
+#include "antisplam.h"
+#if defined( LINUX )
+#include "innbbsconf.h"
+#include "bbslib.h"
+#include <stdarg.h>
+#else
+#include <stdarg.h>
+#include "innbbsconf.h"
+#include "bbslib.h"
+#endif
+
+#include <sys/mman.h>
+
+#ifndef AIX
+#include <sys/fcntl.h>
+#endif
+
+#if defined(PalmBBS)
+#include <utime.h>
+#endif
+
+
+#include "daemon.h"
+#include "nntp.h"
+#include "externs.h"
+
+/*
+ * TODO 1. read newsfeeds.bbs, read nodelist.bbs, read bbsname.bbs 2. scan
+ * new posts and append to .link 3. rename .link to .send (must lock) 4.
+ * start to send .send out and append not sent to .link
+ *
+ * 5. node.LOCK (with pid) 6. log articles sent
+ */
+
+
+#ifndef MAXBUFLEN
+#define MAXBUFLEN 256
+#endif
+
+#define MAX_OUTGO_POST 100 /* bbslink 一次處理的轉出最大文章數量 */
+
+typedef struct my_out_bntp {
+ char *board, *filename, *userid, *nickname, *subject;
+} my_out_bntp;
+struct my_out_bntp out_bntp[MAX_OUTGO_POST];
+
+int innbbsd_outgo_post = 0;
+
+typedef struct Over_t {
+ time_t mtime;
+ char date[MAXBUFLEN];
+ char nickname[MAXBUFLEN];
+ char subject[MAXBUFLEN];
+ char from[MAXBUFLEN];
+ char msgid[MAXBUFLEN];
+ char site[MAXBUFLEN];
+ char board[MAXBUFLEN];
+} linkoverview_t;
+
+typedef struct SendOver_t {
+ char *board, *filename, *group, *from, *subject;
+ char *outgoingtype, *msgid, *path;
+ char *date, *control;
+ time_t mtime;
+} soverview_t;
+
+typedef struct Stat_t {
+ int localsendout;
+ int localfailed;
+ int remotesendout;
+ int remotefailed;
+} stat_t;
+
+static stat_t *BBSLINK_STAT;
+
+static int NoAction = 0;
+static int Verbose = 0;
+static int VisitOnly = 0;
+static int NoVisit = 0;
+static char *DefaultFeedSite = "";
+static int KillFormerBBSLINK = 0;
+
+extern char *SITE;
+extern char *GROUPS;
+
+char NICKNAME[MAXBUFLEN];
+
+char DATE_BUF[MAXBUFLEN];
+extern char *DATE;
+
+char FROM_BUF[MAXBUFLEN];
+extern char *FROM;
+
+#ifndef MapleBBS
+char POSTER_BUF[MAXBUFLEN];
+char *POSTER;
+#endif
+
+char MYADDR[MAXBUFLEN];
+char MYSITE[MAXBUFLEN];
+
+char SUBJECT_BUF[MAXBUFLEN];
+extern char *SUBJECT;
+
+char MSGID_BUF[MAXBUFLEN];
+extern char *MSGID;
+
+char LINKPROTOCOL[MAXBUFLEN];
+int LINKPORT;
+char ORGANIZATION[MAXBUFLEN];
+char NEWSCONTROL[MAXBUFLEN];
+char NEWSAPPROVED[MAXBUFLEN];
+char NNTPHOST_BUF[MAXBUFLEN];
+extern char *NNTPHOST;
+char PATH_BUF[MAXBUFLEN];
+extern char *PATH;
+
+char CONTROL_BUF[MAXBUFLEN];
+extern char *CONTROL;
+
+char *BODY, *HEAD;
+
+int USEIHAVE = 1;
+int USEPOST = 0;
+int USEDATA = 0;
+int FEEDTYPE = ' ';
+
+int NNTP = -1;
+FILE *NNTPrfp = NULL;
+FILE *NNTPwfp = NULL;
+char NNTPbuffer[1024];
+static char *NEWSFEED;
+static char *REMOTE = "REMOTE";
+static char *LOCAL = "LOCAL";
+
+static int FD, FD_SIZE;
+static char *FD_BUF;
+static char *FD_END;
+
+static char *COMMENT = "\n";
+/* "[Ptt 送出]\n"; */
+
+int
+is_outgo_post(board, filename, userid, nickname, subject)
+ char *board, *filename, *userid, *nickname, *subject;
+{
+ int mypost;
+
+ for (mypost = 0; mypost < innbbsd_outgo_post; mypost++) {
+ if (!strcmp(out_bntp[mypost].filename, filename))
+ if (!strcmp(out_bntp[mypost].userid, userid))
+ if (!strcmp(out_bntp[mypost].board, board))
+ if (!strcmp(out_bntp[mypost].nickname, nickname))
+ if (!strcmp(out_bntp[mypost].subject, subject)) {
+ if (Verbose)
+ printf("bad_cancel: %s, %s(%s), %s, %s\n",
+ board, userid, nickname, subject, filename);
+ bbslog("bad_cancel: %s, %s(%s), %s, %s\n",
+ board, userid, nickname, subject, filename);
+ return 1;
+ }
+ }
+ return 0;
+}
+/*
+ * woju Cross-fs rename()
+ */
+int
+Rename(const char *src, const char *dst)
+{
+ char cmd[200];
+
+ if (rename(src, dst) == 0)
+ return 0;
+
+ sprintf(cmd, "/bin/mv %s %s", src, dst);
+ return system(cmd);
+
+}
+
+void
+bbslink_un_lock(file)
+ char *file;
+{
+ char *lockfile = fileglue("%s.LOCK", file);
+
+ if (isfile(lockfile))
+ unlink(lockfile);
+}
+
+int
+bbslink_get_lock(file)
+ char *file;
+{
+ int lockfd;
+ char LockFile[MAXPATHLEN];
+
+ strncpy(LockFile, (char *)fileglue("%s.LOCK", file), sizeof LockFile);
+ if ((lockfd = open(LockFile, O_RDONLY)) >= 0) {
+ char buf[10];
+ int pid;
+
+ if (read(lockfd, buf, sizeof buf) > 0 &&
+ (pid = atoi(buf)) > 0 && kill(pid, 0) == 0) {
+ if (KillFormerBBSLINK) {
+ kill(pid, SIGTERM);
+ unlink(LockFile);
+ } else {
+ fprintf(stderr, "another process [%d] running\n", pid);
+ return 0;
+ }
+ } else {
+ fprintf(stderr, "no process [%d] running, but lock file existed, unlinked\n", pid);
+ unlink(LockFile);
+ }
+ close(lockfd);
+ }
+ if ((lockfd = open(LockFile, O_RDWR | O_CREAT | O_EXCL, 0644)) < 0) {
+ fprintf(stderr, "lock %s error: another bbslink process running\n", LockFile);
+ return 0;
+ } else {
+ char buf[10];
+
+ sprintf(buf, "%-.8d\n", getpid());
+ write(lockfd, buf, strlen(buf));
+ close(lockfd);
+ return 1;
+ }
+}
+
+
+int
+tcpcommand(char *fmt,...)
+{
+ va_list ap;
+ char *ptr;
+
+ va_start(ap, fmt);
+ vfprintf(NNTPwfp, fmt, ap);
+ fprintf(NNTPwfp, "\r\n");
+ fflush(NNTPwfp);
+
+ fgets(NNTPbuffer, sizeof NNTPbuffer, NNTPrfp);
+ ptr = strchr(NNTPbuffer, '\r');
+ if (ptr)
+ *ptr = '\0';
+ ptr = strchr(NNTPbuffer, '\n');
+ if (ptr)
+ *ptr = '\0';
+ va_end(ap);
+ return atoi(NNTPbuffer);
+}
+
+char *
+tcpmessage()
+{
+ char *ptr;
+
+ ptr = strchr(NNTPbuffer, ' ');
+ if (ptr)
+ return ptr;
+ return NNTPbuffer;
+}
+
+int
+read_article(lover, filename, userid)
+ linkoverview_t *lover;
+ char *filename, *userid;
+{
+ int fd;
+ struct stat st;
+ char *buffer;
+ char *artend, *artback;
+
+ if (stat(filename, &st) != 0)
+ return 0;
+ lover->mtime = st.st_mtime;
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ bbslog("<bbslink> Err: can't open %s\n", filename);
+ return 0;
+ }
+ if (FD_BUF == NULL) {
+ FD_BUF = mymalloc(st.st_size + 1 + strlen(COMMENT));
+ } else {
+ FD_BUF = myrealloc(FD_BUF, st.st_size + 1 + strlen(COMMENT));
+ }
+ FD_BUF[st.st_size] = '\0';
+ read(fd, FD_BUF, st.st_size);
+ sprintf(FD_BUF + st.st_size, "%s", COMMENT);
+ st.st_size += strlen(COMMENT);
+ FD_SIZE = st.st_size;
+ for (buffer = FD_BUF, artend = FD_BUF + st.st_size,
+ artback = strchr(buffer, '\n');
+ buffer && buffer < artend && *buffer;
+ artback = strchr(buffer, '\n')
+ ) {
+ /* while( fgets(buffer, sizeof buffer, fp) != NULL) { */
+
+ if (artback != NULL)
+ *artback = '\0';
+ if (*buffer == '\0')
+ break;
+
+#ifndef MapleBBS
+ if (strstr(buffer, userid) != NULL) {
+ m = strchr(buffer, '(');
+ n = strrchr(buffer, ')');
+ if (m != NULL && n != NULL) {
+ strncpy(lover->nickname, m + 1, n - m - 1);
+ lover->nickname[n - m - 1] = '\0';
+ } else {
+ *lover->nickname = '\0';
+ }
+ } else if (strncmp(buffer, "Date: ", 11) == 0) {
+ strcpy(lover->date, buffer + 11);
+ } else if (strncmp(buffer, "發信站: ", 8) == 0) {
+ m = strchr(buffer, '(');
+ n = strrchr(buffer, ')');
+ strncpy(lover->date, m + 1, n - m - 1);
+ lover->date[n - m - 1] = '\0';
+ }
+#endif
+
+ if (artback != NULL) {
+ *artback = '\n';
+ buffer = artback + 1;
+ } else {
+ break;
+ }
+ }
+ if (artback != NULL)
+ BODY = artback + 1;
+ else
+ BODY = "";
+ close(fd);
+ return 1;
+}
+
+void
+save_outgoing(sover, filename, userid, poster, mtime)
+ soverview_t *sover, *filename, *userid, *poster;
+ time_t mtime;
+{
+ newsfeeds_t *nf;
+ char *group, *server, *serveraddr;
+ char *board;
+ char *ptr1, *ptr2;
+
+ board = sover->board;
+
+ PATH = MYBBSID;
+ nf = (newsfeeds_t *) search_board(board);
+ if (nf == NULL) {
+ bbslog("<bbslink> save_outgoing: No such board %s\n", board);
+ return;
+ } else {
+ group = nf->newsgroups;
+ server = nf->path;
+ }
+ if (!server || !*server) {
+ sprintf(PATH_BUF, "%.*s (local)", sizeof PATH_BUF - 9, MYBBSID);
+ PATH = PATH_BUF;
+ serveraddr = "";
+ sover->path = PATH;
+ }
+ for (ptr1 = server; ptr1 && *ptr1;) {
+ nodelist_t *nl;
+ char savech;
+
+ for (; *ptr1 && isspace(*ptr1); ptr1++);
+ if (!*ptr1)
+ break;
+ for (ptr2 = ptr1; *ptr2 && !isspace(*ptr2); ptr2++);
+ savech = *ptr2;
+ *ptr2 = '\0';
+ nl = (nodelist_t *) search_nodelist_bynode(ptr1);
+ *ptr2 = savech;
+ ptr1 = ptr2++;
+ if (nl == NULL)
+ continue;
+ /* if (nl->feedfp == NULL) continue; */
+
+ if (nl->host && *nl->host) {
+ if (nl->feedfp == NULL) {
+ nl->feedfp = fopen(fileglue("%s/%s.link", INNDHOME, nl->node), "a");
+ if (nl->feedfp == NULL) {
+ bbslog("<save outgoing> append failed for %s/%s.link", INNDHOME, nl->node);
+ }
+ }
+ if (nl->feedfp != NULL) {
+ flock(fileno(nl->feedfp), LOCK_EX);
+ fprintf(nl->feedfp, "%s\t%s\t%s\t%ld\t%s\t%s\n", sover->board, filename, group, mtime, FROM, sover->subject);
+ fflush(nl->feedfp);
+ flock(fileno(nl->feedfp), LOCK_UN);
+ }
+ }
+ if (savech == '\0')
+ break;
+ }
+}
+
+
+#ifndef MapleBBS
+save_article(board, filename, sover)
+ char *board, *filename;
+ soverview_t *sover;
+{
+ FILE *FN;
+
+ if (Verbose)
+ printf("<save_article> %s %s\n", board, filename);
+ FN = fopen(fileglue("%s/boards/%c/%s/%s", BBSHOME, board[0], board, filename), "w");
+ if (FN == NULL) {
+ bbslog("<save_article> err: %s %s\n", board, filename);
+ if (Verbose)
+ printf("<save_article> err: %s %s\n", board, filename);
+ return 0;
+ }
+ flock(fileno(FN), LOCK_EX);
+ fprintf(FN, "發信人: %s, 信區: %s\n", POSTER, sover->board);
+ fprintf(FN, "標 題: %s\n", sover->subject);
+ fprintf(FN, "發信站: %s (%s)\n", MYSITE, sover->date);
+ fprintf(FN, "轉信站: %s\n", sover->path);
+ fprintf(FN, "\n");
+ fputs(BODY, FN);
+ flock(fileno(FN), LOCK_UN);
+ fclose(FN);
+
+#if defined(PalmBBS)
+ {
+ struct utimbuf times;
+
+ times.actime = sover->mtime;
+ times.modtime = sover->mtime;
+ utime(fileglue("%s/boards/%c/%s/%s", BBSHOME, board[0], board, filename), &times);
+ utime(fileglue("%s/.bcache/%s", BBSHOME, board), NULL);
+ }
+#endif
+}
+#endif
+
+/* process_article() read_article() save_outgoing() save_article() */
+
+void
+process_article(board, filename, userid, nickname, subject)
+ char *board, *filename, *userid, *nickname, *subject;
+{
+ char *filepath;
+ char poster[MAXBUFLEN];
+ soverview_t sover;
+
+ if (!*userid) {
+ return;
+ } else if (!subject || !*subject) {
+ subject = "無題";
+ }
+ filepath = fileglue("%s/boards/%c/%s/%s", BBSHOME, board[0], board, filename);
+ if (isfile(filepath)) {
+ linkoverview_t lover;
+
+ if (read_article(&lover, filepath, userid)) {
+
+#ifndef MapleBBS
+ strncpy(POSTER_BUF, fileglue("%s@%s (%s)", userid, MYBBSID, nickname), sizeof POSTER_BUF);
+ POSTER = POSTER_BUF;
+#endif
+
+ strncpy(FROM_BUF, fileglue("%s.bbs@%s (%s)", userid, MYADDR, nickname), sizeof FROM_BUF);
+ FROM = FROM_BUF;
+ sover.from = FROM;
+ sover.board = board;
+ sover.subject = subject;
+ PATH = MYBBSID;
+ sover.path = MYBBSID;
+ sover.date = lover.date;
+ sover.mtime = lover.mtime;
+ if (!VisitOnly) {
+ save_outgoing(&sover, filename, userid, poster, lover.mtime);
+
+#ifndef MapleBBS
+ save_article(board, filename, &sover);
+#endif
+ }
+ }
+ }
+}
+
+
+char *
+baseN(val, base, len)
+ int val, base, len;
+{
+ int n;
+ static char str[MAXBUFLEN];
+ int index;
+
+ for (index = len - 1; index >= 0; index--) {
+ n = val % base;
+ val /= base;
+ if (n < 10) {
+ n += '0';
+ } else if (n < 36) {
+ n += 'A' - 10;
+ } else if (n < 62) {
+ n += 'a' - 36;
+ } else {
+ n = '_';
+ }
+ str[index] = n;
+ }
+ str[len] = '\0';
+ return str;
+}
+
+char *
+hash_value(str)
+ char *str;
+{
+ int val, n;
+ char *ptr;
+
+ if (*str)
+ ptr = str + strlen(str) - 1;
+ else
+ ptr = str;
+ val = 0;
+ while (ptr >= str) {
+ n = *ptr;
+ val = (val + n * 0x100) ^ n;
+ ptr--;
+ }
+ return baseN(val, 64, 3);
+}
+
+/* process_cancel() save_outgoing() hash_value(); baseN(); ascii_date(); */
+
+
+int
+read_outgoing(sover)
+ soverview_t *sover;
+{
+ char *board, *filename, *group, *from, *subject, *outgoingtype,
+ *msgid, *path;
+ char *buffer, *bufferp;
+ char *hash;
+ char times[MAXBUFLEN];
+ time_t mtime;
+
+ board = sover->board;
+ filename = sover->filename;
+ group = sover->group;
+ mtime = sover->mtime;
+ from = sover->from;
+ subject = sover->subject;
+ outgoingtype = sover->outgoingtype;
+ msgid = sover->msgid;
+ path = sover->path;
+ if (Verbose) {
+ printf("<read_outgoing> %s:%s:%s\n", board, filename, group);
+ printf(" => %ld:%s\n", mtime, from);
+ printf(" => %s\n", subject);
+ printf(" => %s:%s\n", outgoingtype, msgid);
+ printf(" => %s\n", path);
+ }
+ if (NEWSFEED == LOCAL) {
+ char *end = strrchr(filename, '.');
+
+ if (end)
+ *end = '\0';
+ strncpy(times, baseN(atol(filename + 2), 48, 6), sizeof times);
+ if (end)
+ *end = '.';
+ hash = hash_value(fileglue("%s.%s", filename, board));
+ sprintf(MSGID_BUF, "%s$%s@%s", times, hash, MYADDR);
+ } else {
+ strncpy(MSGID_BUF, msgid, sizeof MSGID_BUF);
+ }
+ sover->msgid = MSGID;
+ if ((mtime == -1) || (mtime == 4294967295)) {
+ static char BODY_BUF[MAXBUFLEN];
+
+ strncpy(BODY_BUF, fileglue("%s\r\n", subject), sizeof BODY_BUF);
+ BODY = BODY_BUF;
+ sprintf(SUBJECT_BUF, "cmsg cancel <%s>", MSGID);
+ SUBJECT = SUBJECT_BUF;
+ sprintf(CONTROL_BUF, "cancel <%s>", MSGID);
+ CONTROL = CONTROL_BUF;
+ strncpy(MSGID_BUF, fileglue("%d.%s", getpid(), MSGID_BUF), sizeof MSGID_BUF);
+ sprintf(DATE_BUF, "%s", ascii_date(time(NULL)));
+ DATE = DATE_BUF;
+ sover->subject = SUBJECT;
+ sover->control = CONTROL;
+ sover->msgid = MSGID;
+ sover->date = DATE;
+ } else {
+ sover->control = CONTROL;
+ sover->date = DATE_BUF;
+ DATE = DATE_BUF;
+ *CONTROL = '\0';
+ sprintf(DATE, "%s", ascii_date((mtime)));
+ if (NEWSFEED == LOCAL && !NoAction) {
+ SITE = MYSITE;
+ PATH = MYBBSID;
+ GROUPS = group;
+
+#ifndef MapleBBS
+ echomaillog();
+#endif
+ }
+ BODY = "";
+ FD = open(fileglue("%s/boards/%c/%s/%s", BBSHOME, board[0], board, filename), O_RDONLY);
+ if (FD < 0) {
+ if (Verbose)
+ printf(" !! can't open %s/boards/%c/%s/%s\n", BBSHOME, board[0], board, filename);
+ else
+ fprintf(stderr, "can't open %s/boards/%c/%s/%s\n", BBSHOME, board[0], board, filename);
+ return -1;
+ }
+ FD_SIZE = filesize(fileglue("%s/boards/%c/%s/%s", BBSHOME, board[0], board, filename));
+ if (FD_BUF == NULL) {
+ FD_BUF = (char *)mymalloc(FD_SIZE + 1 + strlen(COMMENT));
+ } else {
+ FD_BUF = (char *)myrealloc(FD_BUF, FD_SIZE + 1 + strlen(COMMENT));
+ }
+ FD_END = FD_BUF + FD_SIZE;
+ *FD_END = '\0';
+ read(FD, FD_BUF, FD_SIZE);
+ sprintf(FD_END, "%s", COMMENT);
+ FD_SIZE += strlen(COMMENT);
+ FD_END += strlen(COMMENT);
+ if (Verbose) {
+ printf("<read in> %s/boards/%c/%s/%s\n", BBSHOME, board[0], board, filename);
+ }
+ *ORGANIZATION = '\0';
+ *NEWSCONTROL = '\0';
+ *NEWSAPPROVED = '\0';
+ *NNTPHOST_BUF = '\0';
+ NNTPHOST = NULL;
+
+ for (buffer = FD_BUF, bufferp = strchr(buffer, '\n');
+ buffer && *buffer; bufferp = strchr(buffer, '\n')) {
+ if (bufferp)
+ *bufferp = '\0';
+ if (*buffer == '\0') {
+ break;
+ }
+ /* printf("get buffer %s\n", buffer); */
+ if (NEWSFEED == REMOTE) {
+ if (strncmp(buffer, "Date: ", 11) == 0) {
+ strcpy(DATE_BUF, buffer + 11);
+ DATE = DATE_BUF;
+ } else if (strncmp(buffer, "發信站: ", 8) == 0) {
+ char *m, *n;
+
+ m = strchr(buffer, '(');
+ n = strrchr(buffer, ')');
+ if (m && n) {
+ strncpy(DATE_BUF, m + 1, n - m - 1);
+ DATE_BUF[n - m - 1] = '\0';
+ DATE = DATE_BUF;
+ strncpy(ORGANIZATION, buffer + 8, m - 8 - buffer - 1);
+ ORGANIZATION[m - 8 - buffer - 1] = '\0';
+ }
+ } else if (strncmp(buffer, "Control: ", 9) == 0) {
+ strcpy(NEWSCONTROL, buffer + 9);
+ } else if (strncmp(buffer, "Approved: ", 10) == 0) {
+ strcpy(NEWSAPPROVED, buffer + 10);
+ } else if (strncmp(buffer, "Origin: ", 8) == 0) {
+ strcpy(NNTPHOST_BUF, buffer + 8);
+ NNTPHOST = NNTPHOST_BUF;
+ }
+ }
+ if (bufferp) {
+ *bufferp = '\n';
+ buffer = bufferp + 1;
+ } else {
+ break;
+ }
+ }
+ if (bufferp) {
+ BODY = bufferp + 1;
+ } else
+ BODY = "";
+ if (bufferp)
+ for (buffer = bufferp + 1, bufferp = strchr(buffer, '\n');
+ buffer && *buffer; bufferp = strchr(buffer, '\n')) {
+ if (bufferp)
+ *bufferp = '\0';
+ /* printf("get line (%s)\n", buffer); */
+ /*
+ * if( strcmp(buffer,".")==0 ) { buffer[1]='.';
+ * buffer[2]='\0'; }
+ */
+ if (NEWSFEED == REMOTE &&
+ strncmp(NEWSCONTROL, "cancel", 5) == 0 &&
+ strncmp(buffer, "------------------", 18) == 0) {
+ break;
+ } else if (strncmp(buffer, "◆ From: ", 9) == 0) {
+ strcpy(NNTPHOST_BUF, buffer + 9);
+ NNTPHOST = NNTPHOST_BUF;
+ }
+ /* $BODY[ @BODY ] = "$_\r\n"; */
+ if (bufferp) {
+ *bufferp = '\n';
+ buffer = bufferp + 1;
+ } else {
+ break;
+ }
+ }
+ /* # fprintf("BODY @BODY\n"; */
+ close(FD);
+ }
+ return 0;
+}
+
+#ifdef TEST
+#endif
+
+void
+openfeed(node)
+ nodelist_t *node;
+{
+ if (node->feedfp == NULL) {
+ node->feedfp = fopen(fileglue("%s/%s.link", INNDHOME, node->node), "a");
+ }
+}
+
+void
+queuefeed(node, textline)
+ nodelist_t *node;
+ char *textline;
+{
+ openfeed(node);
+ if (node->feedfp != NULL) {
+ flock(fileno(node->feedfp), LOCK_EX);
+ fprintf(node->feedfp, "%s", textline);
+ fflush(node->feedfp);
+ flock(fileno(node->feedfp), LOCK_UN);
+ }
+}
+
+int
+post_article(node, site, sover, textline)
+ nodelist_t *node;
+ char *site;
+ soverview_t *sover;
+ char *textline;
+{
+ int status;
+ char *filename = sover->filename;
+ char *msgid = sover->msgid;
+ char *board = sover->board;
+ char *bodyp, *body;
+
+ if (Verbose)
+ {
+ fprintf(stdout, "<post_article> %s %s %s\n", site, filename, msgid);
+ if(NNTPHOST && *NNTPHOST)
+ printf(" ==> NNTPHOST: %s\n", NNTPHOST);
+ }
+ if (NoAction && Verbose) {
+ printf(" ==>%s\n", sover->path);
+ printf(" ==>%s:%s\n", sover->from, sover->group);
+ printf(" ==>%s:%s\n", sover->subject, sover->date);
+ body = BODY;
+ bodyp = strchr(body, '\n');
+ if (bodyp)
+ *bodyp = '\0';
+ printf(" ==>%s\n", body);
+ if (bodyp)
+ *bodyp = '\n';
+ if (bodyp) {
+ body = bodyp + 1;
+ bodyp = strchr(body, '\n');
+ if (bodyp)
+ *bodyp = '\0';
+ printf(" ==>%s\n", body);
+ if (bodyp)
+ *bodyp = '\n';
+ }
+ }
+ if (NoAction)
+ return 1;
+ if (NEWSFEED == REMOTE) {
+ fprintf(NNTPwfp, "Path: %s\r\n", sover->path);
+ fprintf(NNTPwfp, "From: %s\r\n", sover->from);
+ fprintf(NNTPwfp, "Newsgroups: %s\r\n", sover->group);
+ fprintf(NNTPwfp, "Subject: %s\r\n", sover->subject);
+ /* # fprintf( NNTPwfp,"Post with subject ($subject)\n"); */
+ fprintf(NNTPwfp, "Date: %s\r\n", sover->date);
+ if (*ORGANIZATION)
+ fprintf(NNTPwfp, "Organization: %s\r\n", ORGANIZATION);
+ fprintf(NNTPwfp, "Message-ID: <%s>\r\n", sover->msgid);
+ if (*NEWSCONTROL)
+ fprintf(NNTPwfp, "Control: %s\r\n", NEWSCONTROL);
+ if (*NEWSAPPROVED)
+ fprintf(NNTPwfp, "Approved: %s\r\n", NEWSAPPROVED);
+ } else {
+ fprintf(NNTPwfp, "Path: %s\r\n", MYBBSID);
+ fprintf(NNTPwfp, "From: %s\r\n", sover->from);
+ fprintf(NNTPwfp, "Newsgroups: %s\r\n", sover->group);
+ fprintf(NNTPwfp, "Subject: %s\r\n", sover->subject);
+ fprintf(NNTPwfp, "Date: %s\r\n", sover->date);
+ fprintf(NNTPwfp, "Organization: %s\r\n", MYSITE);
+ fprintf(NNTPwfp, "Message-ID: <%s>\r\n", sover->msgid);
+ fprintf(NNTPwfp, "Mime-Version: 1.0\r\n");
+ fprintf(NNTPwfp, "Content-Type: text/plain; charset=big5\r\n");
+ fprintf(NNTPwfp, "Content-Transfer-Encoding: 8bit\r\n");
+ fprintf(NNTPwfp, "X-Filename: %s/%s\r\n", sover->board, sover->filename);
+ }
+ if (NNTPHOST && *NNTPHOST && USEIHAVE)
+ fprintf(NNTPwfp, "NNTP-Posting-Host: %s\r\n", NNTPHOST);
+ else if (NNTPHOST && *NNTPHOST)
+ fprintf(NNTPwfp, "X-Auth-From: %s\r\n", NNTPHOST);
+ if (*CONTROL) {
+ fprintf(NNTPwfp, "Control: %s\r\n", CONTROL);
+ }
+ fputs("\r\n", NNTPwfp);
+ for (body = BODY, bodyp = strchr(body, '\n');
+ body && *body; bodyp = strchr(body, '\n')) {
+ if (bodyp)
+ *bodyp = '\0';
+
+ fputs(body, NNTPwfp);
+ if (body[0] == '.' && body[1] == '\0')
+ fputs(".", NNTPwfp);
+ fputs("\r\n", NNTPwfp);
+ if (bodyp) {
+ *bodyp = '\n';
+ body = bodyp + 1;
+ } else {
+ break;
+ }
+ }
+ /* print "send out @BODY\n"; */
+ status = tcpcommand(".");
+ /* 435 duplicated article 437 invalid header */
+
+ if (USEIHAVE) {
+ if (status == 235) {
+ if (NEWSFEED == LOCAL) {
+ bbslog("Sendout <%s> from %s/%s\n", msgid, board, filename);
+ }
+ } else if (status == 437 || status == 435) {
+ bbslog("<bbslink> :Warn: %d %s <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (Verbose)
+ printf(":Warn: %d %s <%s>\n", status, (char *)tcpmessage(), msgid);
+ return 0;
+ } else {
+ bbslog("<bbslink> :Err: %d %s of <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (Verbose)
+ printf(":Err: %d %s of <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (!strstr(tcpmessage(), "Article not posted")&&
+ !strstr(tcpmessage(), "Duplicate"))
+ queuefeed(node, textline);
+ return 0;
+ }
+ } else if (USEPOST) {
+ if (status == 240) {
+ bbslog("Sendout <%s> from %s/%s\n", msgid, board, filename);
+ } else if (status == 437 || status == 435) {
+ bbslog("<bbslink> :Warn: %d %s <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (Verbose)
+ printf(":Warn: %d %s <%s>\n", status, (char *)tcpmessage(), msgid);
+ return 0;
+ } else {
+ bbslog("<bbslink> :Err: %d %s of <%s>\n", status, (char *)tcpmessage(), msgid);
+ if(Verbose)
+ printf(":Warn: %d %s <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (!strstr(tcpmessage(), "Article not posted")&&
+ !strstr(tcpmessage(), "435 Duplicate") &&
+ !strstr(tcpmessage(), "No valid newsgroups") &&
+ (strncmp(tcpmessage(), " 437 ", 5) != 0))
+ queuefeed(node, textline);
+ return 0;
+ }
+ } else {
+ if (status == 250) {
+ bbslog("<bbslink> DATA Sendout <%s> from %s/%s\n", msgid, board, filename);
+ if (Verbose)
+ printf("<DATA Sendout> <%s> from %s/%s\n", msgid, board, filename);
+ } else {
+ bbslog("<bbslink> :Err: %d %s of <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (Verbose)
+ printf(":Err: %d %s of <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (!strstr(tcpmessage(), "Article not posted")&&
+ !strstr(tcpmessage(), "Duplicate"))
+ queuefeed(node, textline);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+void
+process_cancel(board, filename, userid, nickname, subject)
+ char *board, *filename, *userid, *nickname, *subject;
+{
+ time_t mtime;
+ soverview_t sover;
+
+ if (!userid || !*userid) {
+ return;
+ }
+ mtime = -1;
+ strncpy(FROM_BUF, fileglue("%s.bbs@%s (%s)", userid, MYADDR, nickname), sizeof FROM_BUF);
+ FROM = FROM_BUF;
+ sover.from = FROM;
+ sover.board = board;
+ sover.subject = subject;
+ PATH = MYBBSID;
+ sover.path = MYBBSID;
+ /* save_outgoing(&sover, filename, userid, poster, -1); */
+ save_outgoing(&sover, filename, userid, userid, -1);
+}
+
+int
+open_link(hostname, hostprot, hostport)
+ char *hostname, *hostprot, *hostport;
+{
+ USEIHAVE = 1;
+ USEPOST = 0;
+ USEDATA = 0;
+ FEEDTYPE = ' ';
+ if (Verbose)
+ printf("<OPEN_link> %s %s %s\n", hostname, hostprot, hostport);
+ if (strncasecmp(hostprot, "IHAVE", 5) != 0) {
+ USEIHAVE = 0;
+ USEPOST = 1;
+ if (strncasecmp(hostprot, "POST", 4) == 0) {
+ USEPOST = 1;
+ } else if (strncasecmp(hostprot, "DATA", 4) == 0) {
+ USEPOST = 0;
+ USEDATA = 1;
+ }
+ }
+ FEEDTYPE = hostname[0];
+ if (!USEDATA) {
+ char *atsign;
+
+ if (FEEDTYPE == '-' || FEEDTYPE == '+') {
+ hostname = hostname + 1;
+ }
+ atsign = strchr(hostname, '@');
+ if (atsign != NULL) {
+ hostname = atsign + 1;
+ }
+ if (!NoAction) {
+ if (Verbose)
+ printf("<inetclient> %s %s\n", hostname, hostport);
+ if ((NNTP = inetclient(hostname, hostport, "tcp")) < 0) {
+ bbslog("<bbslink> :Err: server %s %s error: cant connect\n", hostname, hostport);
+ if (Verbose)
+ printf(":Err: server %s %s error: cant connect\n", hostname, hostport);
+ return 0;
+ /* exit( 0 ); */
+ /* return; */
+ }
+ NNTPrfp = fdopen(NNTP, "r");
+ NNTPwfp = fdopen(NNTP, "w");
+ fgets(NNTPbuffer, sizeof NNTPbuffer, NNTPrfp);
+ if (atoi(NNTPbuffer) != 200) {
+ bbslog("<bbslink> :Err: server error: %s", NNTPbuffer);
+ if (Verbose)
+ printf(":Err: server error: %s", NNTPbuffer);
+ return 0;
+ /* exit( 0 ); */
+ }
+ } else {
+ if (Verbose)
+ printf("<inetclient> %s %s\n", hostname, hostport);
+ }
+ } else {
+ if (!NoAction) {
+ if (Verbose)
+ printf("<inetclient> localhost %s\n", hostport);
+ if ((NNTP = inetclient("localhost", hostport, "tcp")) < 0) {
+ bbslog("<bbslink> :Err: server %s port %s error: cant connect\n", hostname, hostport);
+ if (Verbose)
+ printf(":Err: server error: cant connect");
+ return 0;
+ /* exit( 0 ); */
+ /* return; */
+ }
+ NNTPrfp = fdopen(NNTP, "r");
+ NNTPwfp = fdopen(NNTP, "w");
+ fgets(NNTPbuffer, sizeof NNTPbuffer, NNTPrfp);
+ if (strncmp(NNTPbuffer, "220", 3) != 0) {
+ bbslog("<bbslink> :Err: server error: %s", NNTPbuffer);
+ if (Verbose)
+ printf(":Err: server error: %s", NNTPbuffer);
+ return 0;
+ /* exit( 0 ); */
+ }
+ if (strncmp(NNTPbuffer, "220-", 4) == 0) {
+ fgets(NNTPbuffer, sizeof NNTPbuffer, NNTPrfp);
+ }
+ } else {
+ if (Verbose)
+ printf("<inetclient> %s %s\n", hostname, hostport);
+ }
+ }
+ return 1;
+}
+
+int
+send_outgoing(node, site, hostname, sover, textline)
+ nodelist_t *node;
+ soverview_t *sover;
+ char *hostname, *site;
+ char *textline;
+{
+ int status;
+ char *board, *filepath, *msgid;
+ int returnstatus = 0;
+
+ board = sover->board;
+ filepath = sover->filename;
+ msgid = sover->msgid;
+
+ if (Verbose)
+ printf("<send_outgoing> %s:%s:%s:%s\n", site, board, filepath, msgid);
+ if (BODY != NULL && !NoAction) {
+ if (USEIHAVE) {
+ /* status = tcpcommand("IHAVE <%s>", msgid); */
+ char buf[80];
+ sprintf(buf, "IHAVE <%s>", msgid);
+ status = tcpcommand(buf);
+ if (status == 335) {
+ returnstatus = post_article(node, site, sover, textline);
+ } else if (status == 435) {
+ bbslog("<bbslink> :Warn: %d %s, IHAVE <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (Verbose)
+ printf(":Warn: %d %s, IHAVE <%s>\n", status, (char *)tcpmessage(), msgid);
+ returnstatus = 0;
+ } else {
+ bbslog("<bbslink> :Err: %d %s, IHAVE <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (Verbose)
+ printf(":Err: %d %s, IHAVE <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (!strstr(tcpmessage(), "Article not posted"))
+ queuefeed(node, textline);
+ returnstatus = 0;
+ }
+ } else if (USEPOST) {
+ tcpcommand("MODE READER");
+ status = tcpcommand("POST");
+ if (status == 340) {
+ returnstatus = post_article(node, site, sover, textline);
+ } else if (status == 441) {
+ bbslog("<bbslink> :Warn: %d %s, POST <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (Verbose)
+ printf(":Warn: %d %s, POST <%s>\n", status, (char *)tcpmessage(), msgid);
+ returnstatus = 0;
+ } else {
+ bbslog("<bbslink> :Err: %d %s, POST <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (Verbose)
+ printf(":Err: %d %s, POST <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (!strstr(tcpmessage(), "Article not posted"))
+ queuefeed(node, textline);
+ returnstatus = 0;
+ }
+ } else {
+ tcpcommand("HELO");
+ tcpcommand("MAIL FROM: bbs");
+ tcpcommand("RCPT TO: %s", hostname);
+ status = tcpcommand("DATA");
+ if (status == 354) {
+ returnstatus = post_article(node, site, sover, textline);
+ } else {
+ bbslog("<bbslink> :Err: %d %s, DATA <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (Verbose)
+ printf(":Err: %d %s, DATA <%s>\n", status, (char *)tcpmessage(), msgid);
+ if (!strstr(tcpmessage(), "Article not posted"))
+ queuefeed(node, textline);
+ returnstatus = 0;
+ }
+ }
+ } else if (NoAction) {
+ returnstatus = post_article(node, site, sover, textline);
+ }
+ return returnstatus;
+}
+
+int
+save_nntplink(node, overview)
+ nodelist_t *node;
+ char *overview;
+{
+ FILE *POSTS;
+ char buffer[1024];
+
+ openfeed(node);
+ POSTS = fopen(overview, "r");
+ if (POSTS == NULL)
+ return 0;
+ openfeed(node);
+ /* if (node->feedfp == NULL) return 0; */
+ flock(fileno(node->feedfp), LOCK_EX);
+ while (fgets(buffer, sizeof buffer, POSTS) != NULL) {
+ fputs(buffer, node->feedfp);
+ fflush(node->feedfp);
+ }
+ flock(fileno(node->feedfp), LOCK_UN);
+ fclose(POSTS);
+ if (Verbose)
+ printf("<Unlinking> %s\n", overview);
+ if (!NoAction)
+ unlink(overview);
+ return 1;
+}
+
+
+char *
+get_tmpfile(tmpfile)
+ char *tmpfile;
+{
+ FILE *FN;
+ static char result[256];
+
+ FN = fopen(tmpfile, "r");
+ fgets(result, sizeof result, FN);
+ fclose(FN);
+ unlink(tmpfile);
+ return (result);
+}
+
+/* cancel moderating posts */
+
+int
+cancel_outgoing(board, filename, from, subject)
+ char *board, *filename, *from, *subject;
+{
+ char filepath[MAXPATHLEN];
+ FILE *FN;
+ char *result;
+ char TMPFILE[MAXPATHLEN];
+
+ if (Verbose) {
+ printf("<cancel_outgoing> %s %s %s %s\n", board, filename, from, subject);
+ }
+ sprintf(TMPFILE, "/tmp/cancel_outgoing.%d.%d", getuid(), getpid());
+
+ bbslog("<cancel_outgoing> Try to move moderated post from %s to deleted\n", board);
+ if (Verbose)
+ printf("Try to move moderated post from %s to deleted\n", board);
+ FN = popen(fileglue("%s/bbspost post %s/boards/d/deleted > %s",
+ INNDHOME, BBSHOME, TMPFILE), "w");
+ if (FN == NULL) {
+ bbslog("<cancel_outgoing> can't run %s/bbspost\n", INNDHOME);
+ if (Verbose)
+ printf("<cancel_outgoing> can't run %s/bbspost\n", INNDHOME);
+ return 0;
+ }
+ fprintf(FN, "%s\n", from);
+ fprintf(FN, "%s\n", subject);
+ fprintf(FN, "發信人: %s, 信區: %s\n", from, board);
+ fprintf(FN, "標 題: %s\n", subject);
+ fprintf(FN, "發信站: %s (%s)\n", MYSITE, DATE);
+ fprintf(FN, "轉信站: %s\n", MYBBSID);
+ fputs("\n", FN);
+ fputs(BODY, FN);
+ pclose(FN);
+ result = (char *)get_tmpfile(TMPFILE);
+ if (strncmp(result, "post to ", 8) == 0) {
+ /* try to remove it */
+ strncpy(filepath, fileglue("%s/boards/%c/%s/%s", BBSHOME, board[0], board, filename), sizeof filepath);
+ if (isfile(filepath)) {
+ Rename(filepath, fileglue("%s.cancel", filepath));
+ }
+ FN = fopen(filepath, "w");
+
+ fprintf(FN, "發信人: %s, 信區: %s\n", from, board);
+ fprintf(FN, "標 題: <article cancelled and mailed to the moderator\n");
+ fprintf(FN, "發信站: %s (%s)\n", MYSITE, DATE);
+ fprintf(FN, "轉信站: %s\n", MYBBSID);
+ fprintf(FN, "\n");
+ fputs("\n", FN);
+ fprintf(FN, "你的文章 \"%s\" 已經送往審核中. 請等待回覆.\n", subject);
+ fputs("\n", FN);
+ fputs("Your post has been sent to the moderator and move\n", FN);
+ fputs("into the deleted board. If the post accepted by the moderator,\n", FN);
+ fputs("it will be posted in this board again. Please wait.\n", FN);
+
+ } else {
+ bbslog("%s", result);
+ }
+
+ bbslog("%s/bbspost cancel %s %s %s moderate\n", INNDHOME, BBSHOME, board, filename);
+ if (Verbose)
+ printf("%s/bbspost cancel %s %s %s moderate\n", INNDHOME, BBSHOME, board, filename);
+ system(fileglue("%s/bbspost cancel %s %s %s moderate",
+ INNDHOME, BBSHOME, board, filename));
+ return 1;
+}
+
+void
+close_link()
+{
+ int status;
+
+ if (Verbose)
+ printf("<close_link>\n");
+ if (NoAction)
+ return;
+ status = tcpcommand("QUIT");
+ if (status != 205 && status != 221) {
+ bbslog("<bbslink> :Err: Cannot quit message '%d %s'\n", status, (char *)tcpmessage());
+ if (Verbose)
+ printf(":Err: Cannot quit message '%d %s'\n", status, (char *)tcpmessage());
+ }
+ fclose(NNTPwfp);
+ fclose(NNTPrfp);
+ close(NNTP);
+}
+
+/*
+ * send_nntplink open_link read_outgoing send_outgoing post_article
+ * cancel_outgoing
+ */
+int
+send_nntplink(node, site, hostname, hostprot, hostport, overview, nlcount)
+ nodelist_t *node;
+ char *site, *hostname, *hostprot, *hostport, *overview;
+ int nlcount;
+{
+ FILE *POSTS;
+ char textline[1024];
+ char baktextline[1024];
+
+ if (Verbose) {
+ printf("<send nntplink> %s %s %s %s\n", site, hostname, hostprot, hostport);
+ printf(" ==> %s\n", overview);
+ }
+ if (!open_link(hostname, hostprot, hostport)) {
+ save_nntplink(node, overview);
+ return 0;
+ }
+ POSTS = fopen(overview, "r");
+ if (POSTS == NULL) {
+ if (Verbose)
+ printf("open %s failed\n", overview);
+ return 0;
+ }
+ while (fgets(textline, sizeof textline, POSTS) != NULL) {
+ char *linebreak = strchr(textline, '\n');
+ char *ptr;
+ char *board, *filename, *subject, *group, *mtime, *from;
+ char *outgoingtype;
+ char *msgid, *path;
+ soverview_t soverview;
+
+ strcpy(baktextline, textline);
+ if (linebreak)
+ *linebreak = '\0';
+
+ board = "", filename = "", mtime = "", group = "", from = "", subject = "";
+ outgoingtype = "", msgid = "", path = "";
+ /* get board field */
+ board = textline;
+ ptr = strchr(textline, '\t');
+ if (ptr == NULL)
+ continue;
+ *ptr++ = '\0';
+
+ /* filename field */
+ filename = ptr;
+
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ continue;
+
+ *ptr++ = '\0';
+
+ /* group field */
+ group = ptr;
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ continue;
+ *ptr++ = '\0';
+
+ /* mtime field */
+ mtime = ptr;
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ continue;
+ *ptr++ = '\0';
+
+ /* from field */
+ from = ptr;
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ continue;
+ *ptr++ = '\0';
+
+ /* subject */
+ subject = ptr;
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ goto try_read_outgoing;
+ *ptr++ = '\0';
+
+ /* outgoing type field */
+ outgoingtype = ptr;
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ goto try_read_outgoing;
+ *ptr++ = '\0';
+
+ /* msgid */
+ msgid = ptr;
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ goto try_read_outgoing;
+ *ptr++ = '\0';
+
+ /* path */
+ path = ptr;
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ goto try_read_outgoing;
+
+try_read_outgoing:
+
+ NEWSFEED = LOCAL;
+ if (outgoingtype && msgid && path && *outgoingtype && *msgid && *path) {
+ char *left, *right;
+
+ NEWSFEED = REMOTE;
+ left = strchr(msgid, '<');
+ right = strrchr(msgid, '>');
+ if (left)
+ msgid = left + 1;
+ if (right)
+ *right = '\0';
+ }
+ soverview.board = board;
+ soverview.filename = filename;
+ soverview.group = group;
+ soverview.mtime = atol(mtime);
+ soverview.from = from;
+ str_decode_M3(subject);
+ soverview.subject = subject;
+ soverview.outgoingtype = outgoingtype;
+ soverview.msgid = msgid;
+ soverview.path = path;
+ if (read_outgoing(&soverview) == 0) {
+ int sendresult = send_outgoing(node, site, hostname, &soverview, baktextline);
+ int sendfailed = 1 - sendresult;
+
+ if (NEWSFEED == REMOTE) {
+ BBSLINK_STAT[nlcount].remotesendout += sendresult;
+ BBSLINK_STAT[nlcount].remotefailed += sendfailed;
+ } else {
+ BBSLINK_STAT[nlcount].localsendout += sendresult;
+ BBSLINK_STAT[nlcount].localfailed += sendfailed;
+ }
+ if (node->feedtype == '-') {
+ if (!NoAction && sendresult)
+ cancel_outgoing(board, filename, from, subject);
+ }
+ }
+ }
+ fclose(POSTS);
+ close_link();
+ if (Verbose)
+ printf("<Unlinking> %s\n", overview);
+ if (!NoAction)
+ unlink(overview);
+ return 0;
+}
+
+
+/*
+ * send_article() send_nntplink() read_outgoing()
+ *
+ */
+
+void
+send_article()
+{
+ char *site, *op;
+ char *nntphost;
+ int nlcount;
+
+ chdir(INNDHOME);
+
+ for (nlcount = 0; nlcount < NLCOUNT; nlcount++) {
+ nodelist_t *node;
+ char linkfile[MAXPATHLEN];
+ char sendfile[MAXPATHLEN];
+ char feedfile[MAXPATHLEN];
+ char feedingfile[MAXPATHLEN];
+ char protocol[MAXBUFLEN], port[MAXBUFLEN];
+
+ node = NODELIST + nlcount;
+ site = node->node;
+ nntphost = node->host;
+ op = node->protocol;
+
+ if (DefaultFeedSite && *DefaultFeedSite) {
+ if (strcmp(node->node, DefaultFeedSite) != 0)
+ continue;
+ }
+ if (op && (strncasecmp(op, "ihave", 5) == 0 ||
+ strncasecmp(op, "post", 4) == 0 ||
+ strncasecmp(op, "data", 4) == 0)) {
+ char *left, *right;
+
+ left = strchr(op, '('), right = strrchr(op, ')');
+ if (left && right) {
+ *left = '\0';
+ *right = '\0';
+ strncpy(protocol, op, sizeof protocol);
+ strncpy(port, left + 1, sizeof port);
+ *left = '(';
+ *right = ')';
+ } else {
+ strncpy(protocol, op, sizeof protocol);
+ strncpy(port, "nntp", sizeof port);
+ }
+ } else {
+ strcpy(protocol, "IHAVE");
+ strcpy(port, "nntp");
+ }
+ sprintf(linkfile, "%s.link", site);
+ sprintf(sendfile, "%s.sending", site);
+ sprintf(feedfile, "%s.feed", site);
+ sprintf(feedingfile, "%s.feeding", site);
+ if (isfile(sendfile) && !iszerofile(sendfile)) {
+ if (bbslink_get_lock(sendfile)) {
+ send_nntplink(node, site, nntphost, protocol, port, sendfile, nlcount);
+ bbslink_un_lock(sendfile);
+ }
+ }
+ if (isfile(linkfile) && !iszerofile(linkfile)) {
+ if (!NoAction) {
+ if (bbslink_get_lock(sendfile) && bbslink_get_lock(linkfile) &&
+ bbslink_get_lock(feedingfile)) {
+ if (isfile(sendfile) && !iszerofile(sendfile)) {
+ save_nntplink(node, sendfile);
+ }
+ if (node->feedfp) {
+ fclose(node->feedfp);
+ node->feedfp = NULL;
+ }
+ Rename(linkfile, sendfile);
+ send_nntplink(node, site, nntphost, protocol, port, sendfile, nlcount);
+ bbslink_un_lock(linkfile);
+ bbslink_un_lock(sendfile);
+ bbslink_un_lock(feedingfile);
+ }
+ } else {
+ send_nntplink(node, site, nntphost, protocol, port, linkfile, nlcount);
+ }
+ }
+ if (isfile(feedingfile) && !iszerofile(feedingfile)) {
+ if (bbslink_get_lock(feedingfile)) {
+ send_nntplink(node, site, nntphost, protocol, port, feedingfile, nlcount);
+ bbslink_un_lock(feedingfile);
+ }
+ }
+ if (isfile(feedfile) && !iszerofile(feedfile)) {
+ if (!NoAction) {
+ if (bbslink_get_lock(feedfile) && bbslink_get_lock(feedingfile)) {
+ if (isfile(feedingfile) && !iszerofile(feedingfile)) {
+ save_nntplink(node, feedingfile);
+ if (node->feedfp) {
+ fclose(node->feedfp);
+ node->feedfp = NULL;
+ }
+ }
+ Rename(feedfile, feedingfile);
+ system(fileglue("%s/ctlinnbbsd reload > /dev/null", INNDHOME));
+ send_nntplink(node, site, nntphost, protocol, port, feedingfile, nlcount);
+ bbslink_un_lock(feedfile);
+ bbslink_un_lock(feedingfile);
+ }
+ } else {
+ send_nntplink(node, site, nntphost, protocol, port, feedfile, nlcount);
+ }
+ }
+ }
+}
+
+/* bntplink() bbspost() process_article() process_cancel() send_article() */
+
+
+void
+show_usage(argv)
+ char *argv;
+{
+ fprintf(stderr, "%s initialization failed or improper options !!\n", argv);
+ fprintf(stderr, "Usage: %s [options] bbs_home\n", argv);
+ fprintf(stderr, " -v (show transmission status)\n");
+ fprintf(stderr, " -n (dont send out articles and leave queue untouched)\n");
+ fprintf(stderr, " -s site (only process articles sent to site)\n");
+ fprintf(stderr, " -V (visit only: bbspost visit)\n");
+ fprintf(stderr, " -N (no visit, and only process batch queue)\n");
+ fprintf(stderr, " -k (kill the former bbslink process before started)\n\n");
+ fprintf(stderr, "本程式要正常執行必須將以下檔案置於 %s/innd 下:\n", BBSHOME);
+ fprintf(stderr, "bbsname.bbs 設定貴站的 BBS ID (請儘量簡短)\n");
+ fprintf(stderr, "nodelist.bbs 設定網路各 BBS 站的 ID, Address 和 fullname\n");
+ fprintf(stderr, "newsfeeds.bbs 設定網路信件的 newsgroup board nodelist ...\n");
+}
+
+
+int
+bntplink(argc, argv)
+ int argc;
+ char **argv;
+{
+ static char *OUTING = ".outing";
+ nodelist_t *nl;
+ char result[4096];
+ char cancelfile[MAXPATHLEN], cancelpost[MAXPATHLEN];
+ char bbslink_lockfile[MAXPATHLEN];
+ FILE *NEWPOST;
+ char *left, *right;
+ int nlcount;
+
+ //strcpy(BBSHOME, argv[0]);
+ if (initial_bbs("link") == 0) {
+ return -1;
+ }
+ BBSLINK_STAT = (stat_t *) malloc(sizeof(stat_t) * (NLCOUNT + 1));
+ for (nlcount = 0; nlcount < NLCOUNT; nlcount++) {
+ BBSLINK_STAT[nlcount].localsendout = 0;
+ BBSLINK_STAT[nlcount].remotesendout = 0;
+ BBSLINK_STAT[nlcount].localfailed = 0;
+ BBSLINK_STAT[nlcount].remotefailed = 0;
+ }
+
+ nl = (nodelist_t *) search_nodelist_bynode(MYBBSID);
+ if (nl == NULL) {
+ *MYADDR = '\0';
+ *MYSITE = '\0';
+ *LINKPROTOCOL = '\0';
+ } else {
+ strncpy(MYADDR, nl->host, sizeof MYADDR);
+ strncpy(LINKPROTOCOL, nl->protocol, sizeof LINKPROTOCOL);
+ strncpy(MYSITE, nl->comments, sizeof MYSITE);
+ }
+ if (Verbose) {
+ printf("MYADDR: %s\n", MYADDR);
+ printf("MYSITE: %s\n", MYSITE);
+ }
+ left = nl ? strchr(nl->protocol, '(') : NULL;
+ right = nl ? strrchr(nl->protocol, ')') : NULL;
+ if (left && right) {
+ *right = '\0';
+ strncpy(LINKPROTOCOL, nl->protocol, sizeof LINKPROTOCOL);
+ LINKPORT = atoi(left + 1);
+ *right = ')';
+ }
+ if (!NoVisit) {
+ sprintf(bbslink_lockfile, "%s/.bbslink.visit", INNDHOME);
+ if (!Rename(fileglue("%s/out.bntp", INNDHOME), OUTING) && bbslink_get_lock(bbslink_lockfile)) {
+ /* When try to visit new post, try to lock it */
+ NEWPOST = fopen(OUTING, "r");
+ if (NEWPOST == NULL) {
+ bbslog("<bbslink> Err: can't open %s\n", OUTING);
+ bbslink_un_lock(bbslink_lockfile);
+ return -1;
+ }
+ while (fgets(result, sizeof result, NEWPOST)) {
+ /* chop( $_ ); */
+ char *board, *filename, *userid, *nickname, *subject;
+ char *ptr;
+
+ ptr = strchr(result, '\n');
+ if (ptr)
+ *ptr = '\0';
+
+ board = filename = userid = nickname = subject = NULL;
+ /* board field */
+ board = result;
+ ptr = strchr(result, '\t');
+ if (ptr == NULL)
+ continue;
+ *ptr++ = '\0';
+
+ /* filename field */
+ filename = ptr;
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ continue;
+ *ptr++ = '\0';
+
+ /* userid field */
+ userid = ptr;
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ continue;
+ *ptr++ = '\0';
+
+ /* nickname field */
+ nickname = ptr;
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ continue;
+ *ptr++ = '\0';
+
+ /* subject field */
+ subject = ptr;
+ /*
+ * ptr = strchr(ptr, '\t'); if (ptr == NULL) continue; ptr++
+ * = '\0';
+ */
+
+ if (bad_subject(subject))
+ continue;
+
+ if (innbbsd_outgo_post < MAX_OUTGO_POST) {
+ out_bntp[innbbsd_outgo_post].board = board;
+ out_bntp[innbbsd_outgo_post].filename = filename;
+ out_bntp[innbbsd_outgo_post].userid = userid;
+ out_bntp[innbbsd_outgo_post].nickname = nickname;
+ out_bntp[innbbsd_outgo_post].subject = subject;
+ innbbsd_outgo_post++;
+ }
+ process_article(board, filename, userid, nickname, subject);
+ }
+ fclose(NEWPOST);
+ unlink(OUTING);
+ bbslink_un_lock(bbslink_lockfile);
+ } /* getlock */
+ } /* if NoVisit is false */
+ sprintf(cancelpost, "%s/cancel.bntp", INNDHOME);
+ if (isfile(cancelpost)) {
+ FILE *CANCELFILE;
+
+ if (bbslink_get_lock(cancelpost) && bbslink_get_lock(cancelfile)) {
+ sprintf(cancelfile, "%s.%d", cancelpost, getpid());
+ Rename(cancelpost, cancelfile);
+ CANCELFILE = fopen(cancelfile, "r");
+ while (fgets(result, sizeof result, CANCELFILE) != NULL) {
+ /* chop( $_ ); */
+ char *board, *filename, *userid, *nickname, *subject;
+ char *ptr;
+
+ ptr = strchr(result, '\n');
+ if (ptr)
+ *ptr = '\0';
+ board = filename = userid = nickname = subject = NULL;
+
+ /* board field */
+ board = result;
+ ptr = strchr(result, '\t');
+ if (ptr == NULL)
+ continue;
+ *ptr++ = '\0';
+
+ /* filename field */
+ filename = ptr;
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ continue;
+ *ptr++ = '\0';
+
+ /* userid field */
+ userid = ptr;
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ continue;
+ *ptr++ = '\0';
+
+ /* nickname field */
+ nickname = ptr;
+ ptr = strchr(ptr, '\t');
+ if (ptr == NULL)
+ continue;
+ *ptr++ = '\0';
+
+ /* subject field */
+ subject = ptr;
+ /*
+ * ptr = strchr(ptr, '\t'); if (ptr == NULL) continue; ptr++
+ * = '\0';
+ */
+ if (!is_outgo_post(board, filename, userid, nickname, subject))
+ process_cancel(board, filename, userid, nickname, subject);
+ }
+ fclose(CANCELFILE);
+ if (Verbose)
+ printf("Unlinking %s\n", cancelfile);
+ if (!NoAction)
+ unlink(cancelfile);
+ bbslink_un_lock(cancelfile);
+ bbslink_un_lock(cancelpost);
+ }
+ }
+ for (nlcount = 0; nlcount < NLCOUNT; nlcount++) {
+ if (NODELIST[nlcount].feedfp != NULL) {
+ fclose(NODELIST[nlcount].feedfp);
+ NODELIST[nlcount].feedfp = NULL;
+ }
+ }
+
+ send_article();
+ for (nlcount = 0; nlcount < NLCOUNT; nlcount++) {
+ int localsendout, remotesendout, localfailed, remotefailed;
+
+ localsendout = BBSLINK_STAT[nlcount].localsendout;
+ remotesendout = BBSLINK_STAT[nlcount].remotesendout;
+ localfailed = BBSLINK_STAT[nlcount].localfailed;
+ remotefailed = BBSLINK_STAT[nlcount].remotefailed;
+ if (localsendout || remotesendout || localfailed || remotefailed)
+ bbslog("<bbslink> [%s]%s lsend:%d rsend:%d lfail:%d rfail:%d\n",
+ NODELIST[nlcount].node, NoAction ? "NoAction" : "", localsendout, remotesendout,
+ localfailed, remotefailed);
+ if (NODELIST[nlcount].feedfp != NULL) {
+ fclose(NODELIST[nlcount].feedfp);
+ NODELIST[nlcount].feedfp = NULL;
+ }
+ }
+ if (BBSLINK_STAT);
+ free(BBSLINK_STAT);
+ return 0;
+}
+/*
+ * termbbslink(sig) int sig; { bbslog("kill signal received %d,
+ * terminated\n", sig); if (Verbose) printf("kill signal received %d,
+ * terminated\n", sig); exit(0); }
+ */
+void
+termbbslink()
+{
+ bbslog("kill signal received ??, terminated\n");
+ if (Verbose)
+ printf("kill signal received ??, terminated\n");
+ exit(0);
+}
+
+char *REMOTEUSERNAME = "";
+char *REMOTEHOSTNAME = "";
+
+extern char *optarg;
+extern int opterr, optind;
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ int c, errflag = 0;
+
+ CONTROL = CONTROL_BUF;
+ MSGID = MSGID_BUF;
+
+ /* For debug Only */
+#define DEBUGBBSLINK
+
+#ifdef DEBUGBBSLINK
+ NoAction = 0;
+ Verbose = 0;
+ VisitOnly = 0;
+ NoVisit = 0;
+ DefaultFeedSite = "";
+#endif
+
+ while ((c = getopt(argc, argv, "s:hnvVNk")) != -1)
+ switch (c) {
+ case 's':
+ DefaultFeedSite = optarg;
+ break;
+ case 'n':
+ NoAction = 1;
+ break;
+ case 'v':
+ Verbose = 1;
+ break;
+ case 'V':
+ VisitOnly = 1;
+ break;
+ case 'N':
+ NoVisit = 1;
+ break;
+ case 'k':
+ KillFormerBBSLINK = 1;
+ break;
+ case 'h':
+ default:
+ errflag++;
+ break;
+ }
+ if (errflag > 0) {
+ show_usage(argv[0]);
+ return (1);
+ }
+ if (argc - optind < 1) {
+ show_usage(argv[0]);
+ exit(1);
+ }
+ signal(SIGTERM, termbbslink);
+ if (bntplink(argc - optind, argv + optind) != 0) {
+ show_usage(argv[0]);
+ exit(1);
+ }
+ return 0;
+}
+
+void
+readNCMfile()
+{
+}
diff --git a/pttbbs/innbbsd/bbsnnrp.c b/pttbbs/innbbsd/bbsnnrp.c
new file mode 100644
index 00000000..bee96899
--- /dev/null
+++ b/pttbbs/innbbsd/bbsnnrp.c
@@ -0,0 +1,1247 @@
+/*
+ * Usage: bbsnnrp [options] nntpserver activefile -h|? (help) -v (verbose
+ * protocol transactions) -c (reset active files only; don't receive
+ * articles) -r remotehost(send articles to remotehost, default=local) -p
+ * port|(send articles to remotehost at port, default=7777) path(send
+ * articles to local at path, default=~bbs/innd/.innbbsd) -n (don't ask
+ * innbbsd server and stat articles) -w seconds (wait for seconds and run
+ * infinitely, default=once) -a max_art (maximum number of articles received
+ * for a group each time) -s max_stat(maximum number of articles stated for a
+ * group each time) -t stdin|nntp (default=nntp)
+ */
+
+#include <stdlib.h>
+#include "innbbsconf.h"
+#include "osdep.h"
+#include <sys/mman.h>
+#ifndef AIX
+#include <sys/fcntl.h>
+#endif
+#include "bbslib.h"
+#include "daemon.h"
+#include "nntp.h"
+#include "externs.h"
+
+#ifndef MAX_ARTS
+#define MAX_ARTS 100
+#endif
+#ifndef MAX_STATS
+#define MAX_STATS 1000
+#endif
+
+#if defined(__linux)
+#define NO_USE_MMAP
+#else
+#define USE_MMAP
+#endif
+
+int Max_Arts = MAX_ARTS;
+int Max_Stats = MAX_STATS;
+
+typedef struct NEWSRC_T {
+ char *nameptr, *lowptr, *highptr, *modeptr;
+ int namelen, lowlen, highlen;
+ ULONG low, high;
+ int mode, subscribe;
+} newsrc_t;
+
+typedef struct NNRP_T {
+ int nnrpfd;
+ int innbbsfd;
+ FILE *nnrpin, *nnrpout;
+ FILE *innbbsin, *innbbsout;
+ char activefile[MAXPATHLEN];
+ char rcfile[MAXPATHLEN];
+ newsrc_t *newsrc;
+ char *actpointer, *actend;
+ int actsize, actfd, actdirty;
+} nnrp_t;
+
+typedef struct XHDR_T {
+ char *header;
+ ULONG artno;
+} xhdr_t;
+
+xhdr_t XHDR[MAX_ARTS];
+char LockFile[1024];
+
+#define NNRPGroupOK NNTP_GROUPOK_VAL
+#define NNRPXhdrOK NNTP_HEAD_FOLLOWS_VAL
+#define NNRParticleOK NNTP_ARTICLE_FOLLOWS_VAL
+#define INNBBSstatOK NNTP_NOTHING_FOLLOWS_VAL
+#define INNBBSihaveOK NNTP_SENDIT_VAL
+#define NNRPconnectOK NNTP_POSTOK_VAL
+#define NNRPstatOK NNTP_NOTHING_FOLLOWS_VAL
+#define INNBBSconnectOK NNTP_POSTOK_VAL
+
+nnrp_t BBSNNRP;
+int writerc(nnrp_t *);
+int INNBBSihave(nnrp_t *, ULONG, char *);
+
+void
+doterm(s)
+ int s;
+{
+ printf("bbsnnrp terminated. Signal %d\n", s);
+ writerc(&BBSNNRP);
+ if (isfile(LockFile))
+ unlink(LockFile);
+ exit(1);
+}
+
+extern char *optarg;
+extern int opterr, optind;
+
+#ifndef MIN_WAIT
+#define MIN_WAIT 60
+#endif
+
+int ResetActive = 0;
+int StatHistory = 1;
+int AskLocal = 1;
+int RunOnce = 1;
+
+int DefaultWait = MIN_WAIT;
+
+char *DefaultPort = DefaultINNBBSPort;
+char *DefaultPath = LOCALDAEMON;
+char *DefaultRemoteHost;
+
+#ifndef MAXBUFLEN
+#define MAXBUFLEN 256
+#endif
+char DefaultNewsgroups[MAXBUFLEN];
+char DefaultOrganization[MAXBUFLEN];
+char DefaultModerator[MAXBUFLEN];
+char DefaultTrustfrom[MAXBUFLEN];
+char DefaultTrustFrom[MAXBUFLEN];
+
+void
+usage(arg)
+ char *arg;
+{
+ fprintf(stderr, "Usage: %s [options] nntpserver activefile\n", arg);
+ fprintf(stderr, " -h|? (help) \n");
+ fprintf(stderr, " -v (verbose protocol transactions)\n");
+ fprintf(stderr, " -c (reset active files only; don't receive articles)\n");
+ fprintf(stderr, " -r [proto:]remotehost\n");
+ fprintf(stderr, " (send articles to remotehost, default=ihave:local)\n");
+ fprintf(stderr, " -p port|(send articles to remotehost at port, default=%s)\n", DefaultINNBBSPort);
+ fprintf(stderr, " path(send articles to local at path, default=~bbs/innd/.innbbsd)\n");
+ fprintf(stderr, " -w seconds ( > 1 wait for seconds and run infinitely, default=once)\n");
+ fprintf(stderr, " -n (don't ask innbbsd server and stat articles)\n");
+ fprintf(stderr, " -a max_art(maximum number of articles received for a group each time)\n");
+ fprintf(stderr, " default=%d\n", MAX_ARTS);
+ fprintf(stderr, " -s max_stat(maximum number of articles stated for a group each time)\n");
+ fprintf(stderr, " default=%d\n", MAX_STATS);
+ fprintf(stderr, " -t stdin|nntp (default=nntp)\n");
+ fprintf(stderr, " -g newsgroups\n");
+ fprintf(stderr, " -m moderator\n");
+ fprintf(stderr, " -o organization\n");
+ fprintf(stderr, " -f trust_user (From: trust_user)\n");
+ fprintf(stderr, " -F trust_user (From trust_user)\n");
+ fprintf(stderr, " Please E-mail bug to skhuang@csie.nctu.edu.tw or\n");
+ fprintf(stderr, " post to tw.bbs.admin.installbbs\n");
+}
+
+static char *StdinInputType = "stdin";
+static char *NntpInputType = "nntp";
+static char *NntpIhaveProtocol = "ihave";
+static char *NntpPostProtocol = "post";
+static char *DefaultNntpProtocol;
+
+int
+headbegin(buffer)
+ char *buffer;
+{
+ if (strncmp(buffer, "Path: ", 6) == 0) {
+ if (strchr(buffer + 6, '!') != NULL)
+ return 1;
+ }
+ if (strncmp(buffer, "From ", 5) == 0) {
+ if (strchr(buffer + 5, ':') != NULL)
+ return 1;
+ }
+ return 0;
+}
+
+int
+stdinreadnews(bbsnnrp)
+ nnrp_t *bbsnnrp;
+{
+ char buffer[4096];
+ char tmpfilename[MAXPATHLEN];
+ FILE *tmpfp = NULL;
+ char mid[1024];
+ int pathagain;
+ int ngmet, submet, midmet, pathmet, orgmet, approvedmet;
+ int discard;
+ char sending_path[MAXPATHLEN];
+ int sending_path_len = 0;
+
+ strncpy(tmpfilename, (char *)fileglue("/tmp/bbsnnrp-stdin-%d-%d", getuid(), getpid()), sizeof tmpfilename);
+ fgets(buffer, sizeof buffer, bbsnnrp->innbbsin);
+ verboselog("innbbsGet: %s", buffer);
+ if (atoi(buffer) != INNBBSconnectOK) {
+ fprintf(stderr, "INNBBS server not OK\n");
+ return;
+ }
+ if (DefaultNntpProtocol == NntpPostProtocol) {
+ fputs("MODE READER\r\n", bbsnnrp->innbbsout);
+ fflush(bbsnnrp->innbbsout);
+ verboselog("innbbsPut: MODE READER\n");
+ fgets(buffer, sizeof buffer, bbsnnrp->innbbsin);
+ verboselog("innbbsGet: %s", buffer);
+ }
+ if (StatHistory == 0) {
+ fputs("MIDCHECK OFF\r\n", bbsnnrp->innbbsout);
+ fflush(bbsnnrp->innbbsout);
+ verboselog("innbbsPut: MIDCHECK OFF\n");
+ fgets(buffer, sizeof buffer, bbsnnrp->innbbsin);
+ verboselog("innbbsGet: %s", buffer);
+ }
+ tmpfp = fopen(tmpfilename, "w");
+ if (tmpfp == NULL)
+ return;
+ *mid = '\0';
+ for (;;) {
+ fprintf(stderr, "Try to read from stdin ...\n");
+ ngmet = 0, submet = 0, midmet = 0, pathmet = 0, orgmet = 0, approvedmet = 0;
+ discard = 0;
+ while (fgets(buffer, sizeof buffer, stdin) != NULL) {
+ char *tmpptr;
+ tmpptr = strchr(buffer, '\n');
+ if (tmpptr != NULL)
+ *tmpptr = '\0';
+ if (strncasecmp(buffer, "Message-ID: ", 12) == 0) {
+ strncpy(mid, buffer + 12, sizeof mid);
+ midmet = 1;
+ } else if (strncmp(buffer, "Subject: ", 9) == 0) {
+ submet = 1;
+ } else if (strncmp(buffer, "Path: ", 6) == 0) {
+ pathmet = 1;
+ } else if (strncmp(buffer, "Organization: ", 14) == 0) {
+ orgmet = 1;
+ } else if (strncmp(buffer, "Approved: ", 10) == 0) {
+ approvedmet = 1;
+ } else if (strncmp(buffer, "From: ", 6) == 0 && *DefaultTrustfrom) {
+ if (strstr(buffer + 6, DefaultTrustfrom) == NULL) {
+ discard = 1;
+ verboselog("Discard: %s for %s", buffer, DefaultTrustfrom);
+ }
+ } else if (strncmp(buffer, "From ", 5) == 0 && *DefaultTrustFrom) {
+ if (strstr(buffer + 5, DefaultTrustFrom) == NULL) {
+ discard = 1;
+ verboselog("Discard: %s for %s", buffer, DefaultTrustFrom);
+ }
+ } else if (strncmp(buffer, "Received: ", 10) == 0) {
+ char *rptr = buffer + 10, *rrptr;
+ int savech, len;
+ if (strncmp(buffer + 10, "from ", 5) == 0) {
+ rptr += 5;
+ rrptr = strchr(rptr, '(');
+ if (rrptr != NULL)
+ rptr = rrptr + 1;
+ rrptr = strchr(rptr, ' ');
+ savech = *rrptr;
+ if (rrptr != NULL)
+ *rrptr = '\0';
+ } else if (strncmp(buffer + 10, "(from ", 6) == 0) {
+ rptr += 6;
+ rrptr = strchr(rptr, ')');
+ savech = *rrptr;
+ if (rrptr != NULL)
+ *rrptr = '\0';
+ }
+ len = strlen(rptr) + 1;
+ if (*rptr && sending_path_len + len < sizeof(sending_path)) {
+ if (*sending_path)
+ strcat(sending_path, "!");
+ strcat(sending_path, rptr);
+ sending_path_len += len;
+ }
+ if (rrptr != NULL)
+ *rrptr = savech;
+ }
+ if (strncmp(buffer, "Newsgroups: ", 12) == 0) {
+ if (*DefaultNewsgroups) {
+ fprintf(tmpfp, "Newsgroups: %s\r\n", DefaultNewsgroups);
+ } else {
+ fprintf(tmpfp, "%s\r\n", buffer);
+ }
+ ngmet = 1;
+ } else {
+ if (buffer[0] == '\0') {
+ if (!ngmet && *DefaultNewsgroups) {
+ fprintf(tmpfp, "Newsgroups: %s\r\n", DefaultNewsgroups);
+ }
+ if (!submet) {
+ fprintf(tmpfp, "Subject: (no subject)\r\n");
+ }
+ if (!pathmet) {
+ fprintf(tmpfp, "Path: from-mail\r\n");
+ }
+ if (!midmet) {
+ static int seed;
+ time_t now;
+ time(&now);
+ fprintf(tmpfp, "Message-ID: <%d@%d.%d.%d>\r\n", now, getpid(), getuid(), seed);
+ sprintf(mid, "<%d@%d.%d.%d>", now, getpid(), getuid(), seed);
+ seed++;
+ }
+ if (!orgmet && *DefaultOrganization) {
+ fprintf(tmpfp, "Organization: %s\r\n", DefaultOrganization);
+ }
+ if (!approvedmet && *DefaultModerator) {
+ fprintf(tmpfp, "Approved: %s\r\n", DefaultModerator);
+ }
+ }
+ if (strncmp(buffer, "From ", 5) != 0 && strncmp(buffer, "To: ", 4) != 0) {
+ if (buffer[0] == '\0') {
+ if (*sending_path) {
+ fprintf(tmpfp, "X-Sending-Path: %s\r\n", sending_path);
+ }
+ }
+ fprintf(tmpfp, "%s\r\n", buffer);
+ }
+ }
+ if (buffer[0] == '\0')
+ break;
+ }
+ fprintf(stderr, "Article Body begin ...\n");
+ pathagain = 0;
+ while (fgets(buffer, sizeof buffer, stdin) != NULL) {
+ char *tmpptr;
+ tmpptr = strchr(buffer, '\n');
+ if (tmpptr != NULL)
+ *tmpptr = '\0';
+ if (headbegin(buffer)) {
+ FILE *oldfp = bbsnnrp->nnrpin;
+ pathagain = 1;
+ fputs(".\r\n", tmpfp);
+ fclose(tmpfp);
+ fprintf(stderr, "Try to post ...\n");
+ tmpfp = fopen(tmpfilename, "r");
+ bbsnnrp->nnrpin = tmpfp;
+ if (!discard)
+ if (INNBBSihave(bbsnnrp, -1, mid) == -1) {
+ fprintf(stderr, "post failed\n");
+ }
+ bbsnnrp->nnrpin = oldfp;
+ fclose(tmpfp);
+ *mid = '\0';
+ tmpfp = fopen(tmpfilename, "w");
+ fprintf(tmpfp, "%s\r\n", buffer);
+ break;
+ } else {
+ fprintf(tmpfp, "%s\r\n", buffer);
+ }
+ }
+ if (!pathagain)
+ break;
+ }
+ if (!pathagain && tmpfp) {
+ FILE *oldfp = bbsnnrp->nnrpin;
+ fputs(".\r\n", tmpfp);
+ fclose(tmpfp);
+ fprintf(stderr, "Try to post ...\n");
+ tmpfp = fopen(tmpfilename, "r");
+ bbsnnrp->nnrpin = tmpfp;
+ if (!discard)
+ if (INNBBSihave(bbsnnrp, -1, mid) == -1) {
+ fprintf(stderr, "post failed\n");
+ }
+ bbsnnrp->nnrpin = oldfp;
+ fclose(tmpfp);
+ }
+ if (isfile(tmpfilename)) {
+ unlink(tmpfilename);
+ }
+ return 0;
+}
+
+static char *ACT_BUF, *RC_BUF;
+int ACT_COUNT;
+
+int
+initrcfiles(bbsnnrp)
+ nnrp_t *bbsnnrp;
+{
+ int actfd, i, count;
+ struct stat st;
+ char *actlistptr, *ptr;
+
+ actfd = open(bbsnnrp->activefile, O_RDWR);
+ if (actfd < 0) {
+ fprintf(stderr, "can't read/write %s\n", bbsnnrp->activefile);
+ exit(1);
+ }
+ if (fstat(actfd, &st) != 0) {
+ fprintf(stderr, "can't stat %s\n", bbsnnrp->activefile);
+ exit(1);
+ }
+ bbsnnrp->actfd = actfd;
+ bbsnnrp->actsize = st.st_size;
+#ifdef USE_MMAP
+ bbsnnrp->actpointer = mmap(0, st.st_size, PROT_WRITE | PROT_READ,
+ MAP_SHARED, actfd, 0);
+ if (bbsnnrp->actpointer == (char *)-1) {
+ fprintf(stderr, "mmap error \n");
+ exit(1);
+ }
+#else
+ if (bbsnnrp->actpointer == NULL) {
+ bbsnnrp->actpointer = (char *)mymalloc(st.st_size);
+ } else {
+ bbsnnrp->actpointer = (char *)myrealloc(bbsnnrp->actpointer, st.st_size);
+ }
+ if (bbsnnrp->actpointer == NULL || read(actfd, bbsnnrp->actpointer, st.st_size) <= 0) {
+ fprintf(stderr, "read act error \n");
+ exit(1);
+ }
+#endif
+ bbsnnrp->actend = bbsnnrp->actpointer + st.st_size;
+ i = 0, count = 0;
+ for (ptr = bbsnnrp->actpointer; ptr < bbsnnrp->actend && (actlistptr = (char *)strchr(ptr, '\n')) != NULL; ptr = actlistptr + 1, ACT_COUNT++) {
+ if (*ptr == '\n')
+ continue;
+ if (*ptr == '#')
+ continue;
+ count++;
+ }
+ bbsnnrp->newsrc = (newsrc_t *) mymalloc(sizeof(newsrc_t) * count);
+ ACT_COUNT = 0;
+ for (ptr = bbsnnrp->actpointer; ptr < bbsnnrp->actend && (actlistptr = (char *)strchr(ptr, '\n')) != NULL; ptr = actlistptr + 1) {
+ register newsrc_t *rcptr;
+ char *nptr;
+ /**actlistptr = '\0';*/
+ if (*ptr == '\n')
+ continue;
+ if (*ptr == '#')
+ continue;
+ rcptr = &bbsnnrp->newsrc[ACT_COUNT];
+ rcptr->nameptr = NULL;
+ rcptr->namelen = 0;
+ rcptr->lowptr = NULL;
+ rcptr->lowlen = 0;
+ rcptr->highptr = NULL;
+ rcptr->highlen = 0;
+ rcptr->modeptr = NULL;
+ rcptr->low = 0;
+ rcptr->high = 0;
+ rcptr->mode = 'y';
+ for (nptr = ptr; *nptr && isspace(*nptr);)
+ nptr++;
+ if (nptr == actlistptr)
+ continue;
+ rcptr->nameptr = nptr;
+ for (nptr++; *nptr && !isspace(*nptr);)
+ nptr++;
+ rcptr->namelen = (int)(nptr - rcptr->nameptr);
+ if (nptr == actlistptr)
+ continue;
+ for (nptr++; *nptr && isspace(*nptr);)
+ nptr++;
+ if (nptr == actlistptr)
+ continue;
+ rcptr->highptr = nptr;
+ rcptr->high = atol(nptr);
+ for (nptr++; *nptr && !isspace(*nptr);)
+ nptr++;
+ rcptr->highlen = (int)(nptr - rcptr->highptr);
+ if (nptr == actlistptr)
+ continue;
+ for (nptr++; *nptr && isspace(*nptr);)
+ nptr++;
+ if (nptr == actlistptr)
+ continue;
+ rcptr->lowptr = nptr;
+ rcptr->low = atol(nptr);
+ for (nptr++; *nptr && !isspace(*nptr);)
+ nptr++;
+ rcptr->lowlen = (int)(nptr - rcptr->lowptr);
+ if (nptr == actlistptr)
+ continue;
+ for (nptr++; *nptr && isspace(*nptr);)
+ nptr++;
+ if (nptr == actlistptr)
+ continue;
+ rcptr->mode = *nptr;
+ rcptr->modeptr = nptr;
+ ACT_COUNT++;
+ }
+ return 0;
+}
+
+int
+initsockets(server, bbsnnrp, type)
+ char *server;
+ nnrp_t *bbsnnrp;
+ char *type;
+{
+ int nnrpfd;
+ int innbbsfd;
+ if (AskLocal) {
+ innbbsfd = unixclient(DefaultPath, "tcp");
+ if (innbbsfd < 0) {
+ fprintf(stderr, "Connect to %s error. You may not run innbbsd\n", LOCALDAEMON);
+ /*
+ * unix connect fail, may run by inetd, try to connect to local
+ * once
+ */
+ innbbsfd = inetclient("localhost", DefaultPort, "tcp");
+ if (innbbsfd < 0) {
+ exit(2);
+ }
+ close(innbbsfd);
+ /* try again */
+ innbbsfd = unixclient(DefaultPath, "tcp");
+ if (innbbsfd < 0) {
+ exit(3);
+ }
+ }
+ verboselog("INNBBS connect to %s\n", DefaultPath);
+ } else {
+ innbbsfd = inetclient(DefaultRemoteHost, DefaultPort, "tcp");
+ if (innbbsfd < 0) {
+ fprintf(stderr, "Connect to %s at %s error. Remote Server not Ready\n", DefaultRemoteHost, DefaultPort);
+ exit(2);
+ }
+ verboselog("INNBBS connect to %s\n", DefaultRemoteHost);
+ }
+ if (type == StdinInputType) {
+ bbsnnrp->nnrpfd = 0;
+ bbsnnrp->innbbsfd = innbbsfd;
+ if ((bbsnnrp->nnrpin = fdopen(0, "r")) == NULL ||
+ (bbsnnrp->nnrpout = fdopen(1, "w")) == NULL ||
+ (bbsnnrp->innbbsin = fdopen(innbbsfd, "r")) == NULL ||
+ (bbsnnrp->innbbsout = fdopen(innbbsfd, "w")) == NULL) {
+ fprintf(stderr, "fdopen error\n");
+ exit(3);
+ }
+ return;
+ }
+ nnrpfd = inetclient(server, "nntp", "tcp");
+ if (nnrpfd < 0) {
+ fprintf(stderr, " connect to %s error \n", server);
+ exit(2);
+ }
+ verboselog("NNRP connect to %s\n", server);
+ bbsnnrp->nnrpfd = nnrpfd;
+ bbsnnrp->innbbsfd = innbbsfd;
+ if ((bbsnnrp->nnrpin = fdopen(nnrpfd, "r")) == NULL ||
+ (bbsnnrp->nnrpout = fdopen(nnrpfd, "w")) == NULL ||
+ (bbsnnrp->innbbsin = fdopen(innbbsfd, "r")) == NULL ||
+ (bbsnnrp->innbbsout = fdopen(innbbsfd, "w")) == NULL) {
+ fprintf(stderr, "fdopen error\n");
+ exit(3);
+ }
+ return 0;
+}
+
+int
+closesockets()
+{
+ fclose(BBSNNRP.nnrpin);
+ fclose(BBSNNRP.nnrpout);
+ fclose(BBSNNRP.innbbsin);
+ fclose(BBSNNRP.innbbsout);
+ close(BBSNNRP.nnrpfd);
+ close(BBSNNRP.innbbsfd);
+ return 0;
+}
+
+void
+updaterc(actptr, len, value)
+ char *actptr;
+ int len;
+ ULONG value;
+{
+ for (actptr += len - 1; len-- > 0;) {
+ *actptr-- = value % 10 + '0';
+ value /= 10;
+ }
+}
+
+/*
+ * if old file is empty, don't need to update prevent from disk full
+ */
+int
+myrename(old, new)
+ char *old, *new;
+{
+ struct stat st;
+ if (stat(old, &st) != 0)
+ return -1;
+ if (st.st_size <= 0)
+ return -1;
+ return rename(old, new);
+}
+
+void
+flushrc(bbsnnrp)
+ nnrp_t *bbsnnrp;
+{
+ int backfd;
+ char *bak1;
+ if (bbsnnrp->actdirty == 0)
+ return;
+ bak1 = (char *)strdup((char *)fileglue("%s.BAK", bbsnnrp->activefile));
+ if (isfile(bak1)) {
+ myrename(bak1, (char *)fileglue("%s.BAK.OLD", bbsnnrp->activefile));
+ }
+#ifdef USE_MMAP
+ if ((backfd = open((char *)fileglue("%s.BAK", bbsnnrp->activefile), O_WRONLY | O_TRUNC | O_CREAT, 0664)) < 0 || write(backfd, bbsnnrp->actpointer, bbsnnrp->actsize) < bbsnnrp->actsize)
+#else
+ myrename(bbsnnrp->activefile, bak1);
+ if ((backfd = open(bbsnnrp->activefile, O_WRONLY | O_TRUNC | O_CREAT, 0664)) < 0 || write(backfd, bbsnnrp->actpointer, bbsnnrp->actsize) < bbsnnrp->actsize)
+#endif
+ {
+ char emergent[128];
+ sprintf(emergent, "/tmp/bbsnnrp.%d.active", getpid());
+ fprintf(stderr, "write to backup active fail. Maybe disk full\n");
+ fprintf(stderr, "try to write in %s\n", emergent);
+ if ((backfd = open(emergent, O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0 || write(backfd, bbsnnrp->actpointer, bbsnnrp->actsize) < bbsnnrp->actsize) {
+ fprintf(stderr, "write to %sfail.\n", emergent);
+ } else {
+ close(backfd);
+ }
+ /* if write fail, should leave */
+ /* exit(1); */
+ } else {
+ close(backfd);
+ }
+ free(bak1);
+ bbsnnrp->actdirty = 0;
+}
+
+int
+writerc(bbsnnrp)
+ nnrp_t *bbsnnrp;
+{
+ if (bbsnnrp->actpointer) {
+ flushrc(bbsnnrp);
+#ifdef USE_MMAP
+ if (munmap(bbsnnrp->actpointer, bbsnnrp->actsize) < 0)
+ fprintf(stderr, "can't unmap\n");
+ /* free(bbsnnrp->actpointer); */
+ bbsnnrp->actpointer = NULL;
+#endif
+ if (close(bbsnnrp->actfd) < 0)
+ fprintf(stderr, "can't close actfd\n");
+ }
+ return 0;
+}
+
+static FILE *Xhdrfp;
+static char NNRPbuffer[4096];
+static char INNBBSbuffer[4096];
+
+char *
+NNRPgets(string, len, fp)
+ char *string;
+ int len;
+ FILE *fp;
+{
+ char *re = fgets(string, len, fp);
+ char *ptr;
+ if (re != NULL) {
+ if ((ptr = (char *)strchr(string, '\r')) != NULL)
+ *ptr = '\0';
+ if ((ptr = (char *)strchr(string, '\n')) != NULL)
+ *ptr = '\0';
+ }
+ return re;
+}
+
+int
+NNRPstat(bbsnnrp, artno, mid)
+ nnrp_t *bbsnnrp;
+ ULONG artno;
+ char **mid;
+{
+ char *ptr;
+ int code;
+
+ *mid = NULL;
+ fprintf(bbsnnrp->nnrpout, "STAT %d\r\n", artno);
+ fflush(bbsnnrp->nnrpout);
+ verboselog("nnrpPut: STAT %d\n", artno);
+ NNRPgets(NNRPbuffer, sizeof NNRPbuffer, bbsnnrp->nnrpin);
+ verboselog("nnrpGet: %s\n", NNRPbuffer);
+
+ ptr = (char *)strchr(NNRPbuffer, ' ');
+ if (ptr != NULL)
+ *ptr++ = '\0';
+ code = atoi(NNRPbuffer);
+ ptr = (char *)strchr(ptr, ' ');
+ if (ptr != NULL)
+ *ptr++ = '\0';
+ *mid = ptr;
+ ptr = (char *)strchr(ptr, ' ');
+ if (ptr != NULL)
+ *ptr++ = '\0';
+ return code;
+}
+
+int
+NNRPxhdr(pattern, bbsnnrp, i, low, high)
+ char *pattern;
+ nnrp_t *bbsnnrp;
+ int i;
+ ULONG low, high;
+{
+ int code;
+
+ Xhdrfp = bbsnnrp->nnrpin;
+ fprintf(bbsnnrp->nnrpout, "XHDR %s %d-%d\r\n", pattern, low, high);
+#ifdef BBSNNRPDEBUG
+ printf("XHDR %s %d-%d\r\n", pattern, low, high);
+#endif
+ fflush(bbsnnrp->nnrpout);
+ verboselog("nnrpPut: XHDR %s %d-%d\n", pattern, low, high);
+ NNRPgets(NNRPbuffer, sizeof NNRPbuffer, bbsnnrp->nnrpin);
+ verboselog("nnrpGet: %s\n", NNRPbuffer);
+ code = atoi(NNRPbuffer);
+ return code;
+}
+
+int
+NNRPxhdrget(artno, mid, iscontrol)
+ int *artno;
+ char **mid;
+ int iscontrol;
+{
+ *mid = NULL;
+ *artno = 0;
+ if (NNRPgets(NNRPbuffer, sizeof NNRPbuffer, Xhdrfp) == NULL)
+ return 0;
+ else {
+ char *ptr, *s;
+ if (strcmp(NNRPbuffer, ".") == 0)
+ return 0;
+ ptr = (char *)strchr(NNRPbuffer, ' ');
+ if (!ptr)
+ return 1;
+ *ptr++ = '\0';
+ *artno = atol(NNRPbuffer);
+ if (iscontrol) {
+ ptr = (char *)strchr(s = ptr, ' ');
+ if (!ptr)
+ return 1;
+ *ptr++ = '\0';
+ if (strcmp(s, "cancel") != 0)
+ return 1;
+ }
+ *mid = ptr;
+ return 1;
+ }
+}
+
+int
+INNBBSstat(bbsnnrp, i, mid)
+ nnrp_t *bbsnnrp;
+ int i;
+ char *mid;
+{
+
+ fprintf(bbsnnrp->innbbsout, "STAT %s\r\n", mid);
+ fflush(bbsnnrp->innbbsout);
+ verboselog("innbbsPut: STAT %s\n", mid);
+ NNRPgets(INNBBSbuffer, sizeof INNBBSbuffer, bbsnnrp->innbbsin);
+ verboselog("innbbsGet: %s\n", INNBBSbuffer);
+ return atol(INNBBSbuffer);
+}
+
+int
+INNBBSihave(bbsnnrp, artno, mid)
+ nnrp_t *bbsnnrp;
+ ULONG artno;
+ char *mid;
+{
+ int code;
+ int header = 1;
+
+ if (DefaultNntpProtocol == NntpPostProtocol) {
+ fprintf(bbsnnrp->innbbsout, "POST\r\n");
+ fflush(bbsnnrp->innbbsout);
+ verboselog("innbbsPut: POST %s\n", mid);
+ } else {
+ fprintf(bbsnnrp->innbbsout, "IHAVE %s\r\n", mid);
+ fflush(bbsnnrp->innbbsout);
+ verboselog("innbbsPut: IHAVE %s\n", mid);
+ }
+ if (NNRPgets(INNBBSbuffer, sizeof INNBBSbuffer, bbsnnrp->innbbsin) == NULL) {
+ return -1;
+ }
+ verboselog("innbbsGet: %s\n", INNBBSbuffer);
+#ifdef BBSNNRPDEBUG
+ printf("ihave got %s\n", INNBBSbuffer);
+#endif
+
+ if (DefaultNntpProtocol == NntpPostProtocol) {
+ if ((code = atol(INNBBSbuffer)) != NNTP_START_POST_VAL) {
+ if (code == NNTP_POSTFAIL_VAL)
+ return 0;
+ else
+ return -1;
+ }
+ } else {
+ if ((code = atol(INNBBSbuffer)) != INNBBSihaveOK) {
+ if (code == 435 || code == 437)
+ return 0;
+ else
+ return -1;
+ }
+ }
+ if (artno != -1) {
+ fprintf(bbsnnrp->nnrpout, "ARTICLE %d\r\n", artno);
+ verboselog("nnrpPut: ARTICLE %d\n", artno);
+#ifdef BBSNNRPDEBUG
+ printf("ARTICLE %d\r\n", artno);
+#endif
+ fflush(bbsnnrp->nnrpout);
+ if (NNRPgets(NNRPbuffer, sizeof NNRPbuffer, bbsnnrp->nnrpin) == NULL) {
+ return -1;
+ }
+ verboselog("nnrpGet: %s\n", NNRPbuffer);
+#ifdef BBSNNRPDEBUG
+ printf("article got %s\n", NNRPbuffer);
+#endif
+ if (atol(NNRPbuffer) != NNRParticleOK) {
+ fputs(".\r\n", bbsnnrp->innbbsout);
+ fflush(bbsnnrp->innbbsout);
+ NNRPgets(INNBBSbuffer, sizeof INNBBSbuffer, bbsnnrp->innbbsin);
+ verboselog("innbbsGet: %s\n", INNBBSbuffer);
+ return 0;
+ }
+ }
+ header = 1;
+ while (fgets(NNRPbuffer, sizeof NNRPbuffer, bbsnnrp->nnrpin) != NULL) {
+ if (strcmp(NNRPbuffer, "\r\n") == 0)
+ header = 0;
+ if (strcmp(NNRPbuffer, ".\r\n") == 0) {
+ verboselog("nnrpGet: .\n");
+ fputs(NNRPbuffer, bbsnnrp->innbbsout);
+ fflush(bbsnnrp->innbbsout);
+ verboselog("innbbsPut: .\n");
+ if (NNRPgets(INNBBSbuffer, sizeof INNBBSbuffer, bbsnnrp->innbbsin) == NULL)
+ return -1;
+ verboselog("innbbsGet: %s\n", INNBBSbuffer);
+#ifdef BBSNNRPDEBUG
+ printf("end ihave got %s\n", INNBBSbuffer);
+#endif
+ code = atol(INNBBSbuffer);
+ if (DefaultNntpProtocol == NntpPostProtocol) {
+ if (code == NNTP_POSTEDOK_VAL)
+ return 1;
+ if (code == NNTP_POSTFAIL_VAL)
+ return 0;
+ } else {
+ if (code == 235)
+ return 1;
+ if (code == 437 || code == 435)
+ return 0;
+ }
+ break;
+ }
+ if (DefaultNntpProtocol == NntpPostProtocol &&
+ header && strncasecmp(NNRPbuffer, "NNTP-Posting-Host: ", 19) == 0) {
+ fprintf(bbsnnrp->innbbsout, "X-%s", NNRPbuffer);
+ } else {
+ fputs(NNRPbuffer, bbsnnrp->innbbsout);
+ }
+ }
+ fflush(bbsnnrp->innbbsout);
+ return -1;
+}
+
+int
+NNRPgroup(bbsnnrp, i, low, high)
+ nnrp_t *bbsnnrp;
+ int i;
+ ULONG *low, *high;
+{
+ newsrc_t *rcptr = &bbsnnrp->newsrc[i];
+ int size, code;
+ ULONG tmp;
+
+ fprintf(bbsnnrp->nnrpout, "GROUP %-.*s\r\n",
+ rcptr->namelen, rcptr->nameptr);
+ printf("GROUP %-.*s\r\n", rcptr->namelen, rcptr->nameptr);
+ verboselog("nnrpPut: GROUP %-.*s\n", rcptr->namelen, rcptr->nameptr);
+ fflush(bbsnnrp->nnrpout);
+ NNRPgets(NNRPbuffer, sizeof NNRPbuffer, bbsnnrp->nnrpin);
+ verboselog("nnrpGet: %s\n", NNRPbuffer);
+ printf("%s\n", NNRPbuffer);
+ sscanf(NNRPbuffer, "%d %d %ld %ld", &code, &size, low, high);
+ if (*low > *high) {
+ tmp = *low;
+ *low = *high;
+ *high = tmp;
+ }
+ return code;
+}
+
+
+void
+readnews(server, bbsnnrp)
+ char *server;
+ nnrp_t *bbsnnrp;
+{
+ int i;
+ char buffer[4096];
+ ULONG low, high;
+
+ fgets(buffer, sizeof buffer, bbsnnrp->innbbsin);
+ verboselog("innbbsGet: %s", buffer);
+ if (atoi(buffer) != INNBBSconnectOK) {
+ fprintf(stderr, "INNBBS server not OK\n");
+ return;
+ }
+#ifdef BBSNNRPDEBUG
+ printf("%s", buffer);
+#endif
+ fgets(buffer, sizeof buffer, bbsnnrp->nnrpin);
+ verboselog("nnrpGet: %s", buffer);
+ if (buffer[0] != '2') {
+ /*
+ * if (atoi(buffer) != NNRPconnectOK && atoi(buffer) !=
+ * NNTP_NOPOSTOK_VAL) {
+ */
+ fprintf(stderr, "NNRP server not OK\n");
+ return;
+ }
+#ifdef BBSNNRPDEBUG
+ printf("%s", buffer);
+#endif
+ fputs("MODE READER\r\n", bbsnnrp->nnrpout);
+ fflush(bbsnnrp->nnrpout);
+ verboselog("nnrpPut: MODE READER\n");
+ fgets(buffer, sizeof buffer, bbsnnrp->nnrpin);
+ verboselog("nnrpGet: %s", buffer);
+
+ if (DefaultNntpProtocol == NntpPostProtocol) {
+ fputs("MODE READER\r\n", bbsnnrp->innbbsout);
+ fflush(bbsnnrp->innbbsout);
+ verboselog("innbbsPut: MODE READER\n");
+ fgets(buffer, sizeof buffer, bbsnnrp->innbbsin);
+ verboselog("innbbsGet: %s", buffer);
+ }
+#ifdef BBSNNRPDEBUG
+ printf("%s", buffer);
+#endif
+
+ if (StatHistory == 0) {
+ fputs("MIDCHECK OFF\r\n", bbsnnrp->innbbsout);
+ fflush(bbsnnrp->innbbsout);
+ verboselog("innbbsPut: MIDCHECK OFF\n");
+ fgets(buffer, sizeof buffer, bbsnnrp->innbbsin);
+ verboselog("innbbsGet: %s", buffer);
+ }
+ bbsnnrp->actdirty = 0;
+ for (i = 0; i < ACT_COUNT; i++) {
+ int code = NNRPgroup(bbsnnrp, i, &low, &high);
+ newsrc_t *rcptr = &bbsnnrp->newsrc[i];
+ ULONG artno;
+ char *mid;
+ int artcount;
+
+#ifdef BBSNNRPDEBUG
+ printf("got reply %d %ld %ld\n", code, low, high);
+#endif
+ artcount = 0;
+ if (code == 411) {
+ FILE *ff = fopen(BBSHOME "/innd/log/badgroup.log", "a");
+ fprintf(ff, "%s\t%-.*s\r\n", server, rcptr->namelen, rcptr->nameptr);
+ fclose(ff);
+ } else if (code == NNRPGroupOK) {
+ int xcount;
+ ULONG maxartno = rcptr->high;
+ int isCancelControl = (strncmp(rcptr->nameptr, "control", rcptr->namelen) == 0)
+ ||
+ (strncmp(rcptr->nameptr, "control.cancel", rcptr->namelen) == 0);
+
+ /* less than or equal to high, for server renumber */
+ if (rcptr->low != low) {
+ bbsnnrp->actdirty = 1;
+ rcptr->low = low;
+ updaterc(rcptr->lowptr, rcptr->lowlen, rcptr->low);
+ }
+ if (ResetActive) {
+ if (rcptr->high != high) {
+ bbsnnrp->actdirty = 1;
+ rcptr->high = high;
+ updaterc(rcptr->highptr, rcptr->highlen, rcptr->high);
+ }
+ } else if (rcptr->high < high) {
+ int xhdrcode;
+ ULONG maxget = high;
+ int exception = 0;
+ if (rcptr->high < low) {
+ bbsnnrp->actdirty = 1;
+ rcptr->high = low;
+ updaterc(rcptr->highptr, rcptr->highlen, low);
+ }
+ if (high > rcptr->high + Max_Stats) {
+ maxget = rcptr->high + Max_Stats;
+ }
+ if (isCancelControl)
+ xhdrcode = NNRPxhdr("Control", bbsnnrp, i, rcptr->high + 1, maxget);
+ else
+ xhdrcode = NNRPxhdr("Message-ID", bbsnnrp, i, rcptr->high + 1, maxget);
+
+ maxartno = maxget;
+ if (xhdrcode == NNRPXhdrOK) {
+ while (NNRPxhdrget(&artno, &mid, isCancelControl)) {
+ /* #define DEBUG */
+#ifdef DEBUG
+ printf("no %d id %s\n", artno, mid);
+#endif
+ if (artcount < Max_Arts) {
+ if (mid != NULL && !isCancelControl) {
+ if (!StatHistory || INNBBSstat(bbsnnrp, i, mid) != INNBBSstatOK) {
+ printf("** %d ** %d need it %s\n", artcount, artno, mid);
+ XHDR[artcount].artno = artno;
+ XHDR[artcount].header = restrdup(XHDR[artcount].header, mid);
+ /* INNBBSihave(bbsnnrp,i,artno,mid); */
+ /* to get it */
+ artcount++;
+ }
+ } else if (mid != NULL) {
+ if (INNBBSstat(bbsnnrp, i, mid) == INNBBSstatOK) {
+ printf("** %d ** %d need cancel %s\n", artcount, artno, mid);
+ XHDR[artcount].artno = artno;
+ XHDR[artcount].header = restrdup(XHDR[artcount].header, mid);
+ artcount++;
+ }
+ }
+ maxartno = artno;
+ }
+ }
+ } /* while xhdr OK */
+ exception = 0;
+ for (xcount = 0; xcount < artcount; xcount++) {
+ ULONG artno;
+ char *mid;
+ artno = XHDR[xcount].artno;
+ mid = XHDR[xcount].header;
+ if (isCancelControl) {
+ if (NNRPstat(bbsnnrp, artno, &mid) == NNRPstatOK) {
+ }
+ }
+ printf("** %d ** %d i have it %s\n", xcount, artno, mid);
+ if (!ResetActive && mid != NULL)
+ exception = INNBBSihave(bbsnnrp, artno, mid);
+ if (exception == -1)
+ break;
+ if (rcptr->high != artno) {
+ rcptr->high = artno;
+ updaterc(rcptr->highptr, rcptr->highlen, rcptr->high);
+ }
+ }
+ if (rcptr->high != maxartno && exception != -1) {
+ bbsnnrp->actdirty = 1;
+ rcptr->high = maxartno;
+ updaterc(rcptr->highptr, rcptr->highlen, maxartno);
+ }
+ }
+ }
+ /*
+ * flushrc(bbsnnrp);
+ */
+ }
+ fprintf(bbsnnrp->innbbsout, "quit\r\n");
+ fprintf(bbsnnrp->nnrpout, "quit\r\n");
+ fflush(bbsnnrp->innbbsout);
+ fflush(bbsnnrp->nnrpout);
+ fgets(NNRPbuffer, sizeof NNRPbuffer, bbsnnrp->nnrpin);
+ fgets(INNBBSbuffer, sizeof INNBBSbuffer, bbsnnrp->innbbsin);
+
+ /*
+ * bbsnnrp->newsrc[0].high = 1900; updaterc(bbsnnrp->newsrc[0].highptr,
+ * bbsnnrp->newsrc[0].highlen, bbsnnrp->newsrc[0].high);
+ */
+}
+
+void
+INNBBSDhalt()
+{
+}
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ char *ptr, *server, *active;
+ int c, errflag = 0;
+ int lockfd;
+ char *inputtype;
+
+ DefaultNntpProtocol = NntpIhaveProtocol;
+ *DefaultNewsgroups = '\0';
+ *DefaultModerator = '\0';
+ *DefaultOrganization = '\0';
+ *DefaultTrustFrom = '\0';
+ *DefaultTrustfrom = '\0';
+ inputtype = NntpInputType;
+ while ((c = getopt(argc, argv, "f:F:m:o:g:w:r:p:a:s:t:h?ncv")) != -1)
+ switch (c) {
+ case 'v':
+ verboseon("bbsnnrp.log");
+ break;
+ case 'c':
+ ResetActive = 1;
+ break;
+ case 'g':
+ strncpy(DefaultNewsgroups, optarg, sizeof DefaultNewsgroups);
+ break;
+ case 'm':
+ strncpy(DefaultModerator, optarg, sizeof DefaultModerator);
+ break;
+ case 'o':
+ strncpy(DefaultOrganization, optarg, sizeof DefaultOrganization);
+ break;
+ case 'f':
+ strncpy(DefaultTrustfrom, optarg, sizeof DefaultTrustfrom);
+ break;
+ case 'F':
+ strncpy(DefaultTrustFrom, optarg, sizeof DefaultTrustFrom);
+ break;
+ case 'r':{
+ char *hostptr;
+ AskLocal = 0;
+ DefaultRemoteHost = optarg;
+ if ((hostptr = strchr(optarg, ':')) != NULL) {
+ *hostptr = '\0';
+ DefaultRemoteHost = hostptr + 1;
+ if (strcasecmp(optarg, "post") == 0)
+ DefaultNntpProtocol = NntpPostProtocol;
+ *hostptr = ':';
+ }
+ break;
+ }
+ case 'w':
+ RunOnce = 0;
+ DefaultWait = atoi(optarg);
+ if (DefaultWait < MIN_WAIT)
+ DefaultWait = MIN_WAIT;
+ break;
+ case 'p':
+ if (AskLocal == 0) {
+ DefaultPort = optarg;
+ } else {
+ DefaultPath = optarg;
+ }
+ break;
+ case 'n':
+ StatHistory = 0;
+ break;
+ case 'a':
+ Max_Arts = atol(optarg);
+ if (Max_Arts < 0)
+ Max_Arts = 0;
+ break;
+ case 's':
+ Max_Stats = atol(optarg);
+ if (Max_Stats < 0)
+ Max_Stats = 0;
+ break;
+ case 't':
+ if (strcasecmp(optarg, StdinInputType) == 0) {
+ inputtype = StdinInputType;
+ }
+ break;
+ case 'h':
+ case '?':
+ default:
+ errflag++;
+ }
+ if (errflag > 0) {
+ usage(argv[0]);
+ return (1);
+ }
+ if (inputtype == NntpInputType && argc - optind < 2) {
+ usage(argv[0]);
+ exit(1);
+ }
+ if (inputtype == NntpInputType) {
+ server = argv[optind];
+ active = argv[optind + 1];
+ if (isfile(active)) {
+ strncpy(BBSNNRP.activefile, active, sizeof BBSNNRP.activefile);
+ } else if (strchr(active, '/') == NULL) {
+ sprintf(BBSNNRP.activefile, "%s/innd/%.*s", BBSHOME, sizeof BBSNNRP.activefile - 7 - strlen(BBSHOME), active);
+ } else {
+ strncpy(BBSNNRP.activefile, active, sizeof BBSNNRP.activefile);
+ }
+
+ strncpy(LockFile, (char *)fileglue("%s.lock", active), sizeof LockFile);
+ if ((lockfd = open(LockFile, O_RDONLY)) >= 0) {
+ char buf[10];
+ int pid;
+
+ if (read(lockfd, buf, sizeof buf) > 0 && (pid = atoi(buf)) > 0 && kill(pid, 0) == 0) {
+ fprintf(stderr, "another process [%d] running\n", pid);
+ exit(1);
+ } else {
+ fprintf(stderr, "no process [%d] running, but lock file existed, unlinked\n", pid);
+ unlink(LockFile);
+ }
+ close(lockfd);
+ }
+ if ((lockfd = open(LockFile, O_RDWR | O_CREAT | O_EXCL, 0644)) < 0) {
+ fprintf(stderr, "maybe another %s process running\n", argv[0]);
+ exit(1);
+ } else {
+ char buf[10];
+ sprintf(buf, "%-.8d\n", getpid());
+ write(lockfd, buf, strlen(buf));
+ close(lockfd);
+ }
+ for (;;) {
+ if (!initial_bbs(NULL)) {
+ fprintf(stderr, "Initial BBS failed\n");
+ exit(1);
+ }
+ initsockets(server, &BBSNNRP, inputtype);
+ ptr = (char *)strrchr(active, '/');
+ if (ptr != NULL)
+ ptr++;
+ else
+ ptr = active;
+ sprintf(BBSNNRP.rcfile, "%s/.newsrc.%s.%s", INNDHOME, server, ptr);
+ initrcfiles(&BBSNNRP);
+
+ Signal(SIGTERM, doterm);
+ Signal(SIGKILL, doterm);
+ Signal(SIGHUP, doterm);
+ Signal(SIGPIPE, doterm);
+
+ readnews(server, &BBSNNRP);
+ writerc(&BBSNNRP);
+ closesockets();
+
+ if (RunOnce)
+ break;
+ sleep(DefaultWait);
+ }
+ unlink(LockFile);
+ }
+ /* NntpInputType */
+ else {
+ if (!initial_bbs(NULL)) {
+ fprintf(stderr, "Initial BBS failed\n");
+ exit(1);
+ }
+ initsockets(server, &BBSNNRP, inputtype);
+ Signal(SIGTERM, doterm);
+ Signal(SIGKILL, doterm);
+ Signal(SIGHUP, doterm);
+ Signal(SIGPIPE, doterm);
+
+ stdinreadnews(&BBSNNRP);
+ closesockets();
+ } /* stdin input type */
+ return 0;
+}
diff --git a/pttbbs/innbbsd/clibrary.h b/pttbbs/innbbsd/clibrary.h
new file mode 100644
index 00000000..1248e650
--- /dev/null
+++ b/pttbbs/innbbsd/clibrary.h
@@ -0,0 +1,142 @@
+/*
+ * $Revision: 1.1 $ *
+ *
+ * Here be declarations of routines and variables in the C library. * You
+ * must #include <sys/types.h> and <stdio.h> before this file.
+ */
+
+#if defined(DO_HAVE_UNISTD)
+#include <unistd.h>
+#endif /* defined(DO_HAVE_UNISTD) */
+
+#if defined(DO_HAVE_VFORK)
+#include <vfork.h>
+#endif /* defined(DO_HAVE_VFORK) */
+
+/* Generic pointer, used by memcpy, malloc, etc. */
+/* =()<typedef @<POINTER>@ *POINTER;>()= */
+typedef char *POINTER;
+/* What is a file offset? Will not work unless long! */
+/* =()<typedef @<OFFSET_T>@ OFFSET_T;>()= */
+typedef long OFFSET_T;
+/* What is the type of an object size? */
+/* =()<typedef @<SIZE_T>@ SIZE_T;>()= */
+typedef int SIZE_T;
+/* What is the type of a passwd uid and gid, for use in chown(2)? */
+/* =()<typedef @<UID_T>@ UID_T;>()= */
+typedef int UID_T;
+/* =()<typedef @<GID_T>@ GID_T;>()= */
+typedef int GID_T;
+/* =()<typedef @<PID_T>@ PID_T;>()= */
+typedef int PID_T;
+/* What should a signal handler return? */
+/* =()<#define SIGHANDLER @<SIGHANDLER>@>()= */
+#define SIGHANDLER void
+
+#if defined(SIG_DFL)
+/* What types of variables can be modified in a signal handler? */
+/* =()<typedef @<SIGVAR>@ SIGVAR;>()= */
+typedef int SIGVAR;
+#endif /* defined(SIG_DFL) */
+
+/* =()<#include @<STR_HEADER>@>()= */
+#include <string.h>
+/* =()<#include @<MEM_HEADER>@>()= */
+#include <memory.h>
+
+
+/*
+ * * It's a pity we have to go through these contortions, for broken *
+ * systems that have fd_set but not the FD_SET.
+ */
+#if defined(FD_SETSIZE)
+#define FDSET fd_set
+#else
+#include <sys/param.h>
+#if !defined(NOFILE)
+error--
+#define NOFILE to the number of files allowed on your machine!
+#endif /* !defined(NOFILE) */
+#if !defined(howmany)
+#define howmany(x, y) (((x) + ((y) - 1)) / (y))
+#endif /* !defined(howmany) */
+#define FD_SETSIZE NOFILE
+#define NFDBITS (sizeof (long) * 8)
+typedef struct _FDSET {
+ long fds_bits[howmany(FD_SETSIZE, NFDBITS)];
+} FDSET;
+#define FD_SET(n, p) (p)->fds_bits[(n) / NFDBITS] |= (1 << ((n) % NFDBITS))
+#define FD_CLR(n, p) (p)->fds_bits[(n) / NFDBITS] &= ~(1 << ((n) % NFDBITS))
+#define FD_ISSET(n, p) ((p)->fds_bits[(n) / NFDBITS] & (1 << ((n) % NFDBITS)))
+#define FD_ZERO(p) (void)memset((POINTER)(p), 0, sizeof *(p))
+#endif /* defined(FD_SETSIZE) */
+
+
+#if !defined(SEEK_SET)
+#define SEEK_SET 0
+#endif /* !defined(SEEK_SET) */
+#if !defined(SEEK_END)
+#define SEEK_END 2
+#endif /* !defined(SEEK_END) */
+
+/*
+ * * We must use #define to set FREEVAL, since "typedef void FREEVAL;"
+ * doesn't * work on some broken compilers, sigh.
+ */
+/* =()<#define FREEVAL @<FREEVAL>@>()= */
+#define FREEVAL int
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#if 0 /* old style, use stdio, stdlib, unistd,
+ * string now */
+extern int optind;
+extern char *optarg;
+#if !defined(__STDC__)
+extern int errno;
+#endif /* !defined(__STDC__) */
+
+extern char *getenv();
+extern char *inet_ntoa();
+extern char *mktemp();
+#if !defined(strerror)
+extern char *strerror();
+#endif /* !defined(strerror) */
+extern long atol();
+extern time_t time();
+extern unsigned long inet_addr();
+extern FREEVAL free();
+extern POINTER malloc();
+extern POINTER realloc();
+#if defined(ACT_MMAP)
+extern char *mmap();
+#endif /* defined(ACT_MMAP) */
+
+/* Some backward systems need this. */
+extern FILE *popen();
+
+/*
+ * This is in <mystring.h>, but not in some system string headers, so we put
+ * it here just in case.
+ */
+extern int strncasecmp();
+
+/* =()<extern @<ABORTVAL>@ abort();>()= */
+extern int abort();
+/* =()<extern @<ALARMVAL>@ alarm();>()= */
+extern int alarm();
+/* =()<extern @<EXITVAL>@ exit();>()= */
+extern void exit();
+/* =()<extern @<GETPIDVAL>@ getpid();>()= */
+extern int getpid();
+/* =()<extern @<LSEEKVAL>@ lseek();>()= */
+extern off_t lseek();
+/* =()<extern @<QSORTVAL>@ qsort();>()= */
+extern int qsort();
+/* =()<extern @<SLEEPVAL>@ sleep();>()= */
+extern int sleep();
+/* =()<extern @<_EXITVAL>@ _exit();>()= */
+extern int _exit();
+#endif
diff --git a/pttbbs/innbbsd/closeonexec.c b/pttbbs/innbbsd/closeonexec.c
new file mode 100644
index 00000000..1fd1a24e
--- /dev/null
+++ b/pttbbs/innbbsd/closeonexec.c
@@ -0,0 +1,69 @@
+/*
+ * $Revision: 1.1 $ *
+ *
+ */
+/* #include "configdata.h" */
+#include <stdio.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include "clibrary.h"
+
+#ifndef CLX_IOCTL
+#define CLX_IOCTL
+#endif
+#ifndef CLX_FCNTL
+#define CLX_FCNTL
+#endif
+
+
+
+
+#if defined(CLX_IOCTL) && !defined(IRIX)
+#ifdef __linux
+#include <termios.h>
+#else
+#include <sgtty.h>
+#endif
+
+
+/*
+ * * Mark a file close-on-exec so that it doesn't get shared with our *
+ * children. Ignore any error codes.
+ */
+void
+closeOnExec(fd, flag)
+ int fd;
+ int flag;
+{
+ int oerrno;
+
+ oerrno = errno;
+ (void)ioctl(fd, flag ? FIOCLEX : FIONCLEX, (char *)NULL);
+ errno = oerrno;
+}
+#endif /* defined(CLX_IOCTL) */
+
+
+
+
+#if defined(CLX_FCNTL)
+#include <fcntl.h>
+
+
+/*
+ * * Mark a file close-on-exec so that it doesn't get shared with our *
+ * children. Ignore any error codes.
+ */
+void
+CloseOnExec(fd, flag)
+ int fd;
+ int flag;
+{
+ int oerrno;
+
+ oerrno = errno;
+ (void)fcntl(fd, F_SETFD, flag ? 1 : 0);
+ errno = oerrno;
+}
+#endif /* defined(CLX_FCNTL) */
diff --git a/pttbbs/innbbsd/connectsock.c b/pttbbs/innbbsd/connectsock.c
new file mode 100644
index 00000000..5e526715
--- /dev/null
+++ b/pttbbs/innbbsd/connectsock.c
@@ -0,0 +1,464 @@
+#include <stdlib.h>
+#include "osdep.h"
+#include "innbbsconf.h"
+#include "daemon.h"
+#include <signal.h>
+#include <setjmp.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include "externs.h"
+static jmp_buf timebuf;
+
+static void
+timeout(sig)
+ int sig;
+{
+ longjmp(timebuf, sig);
+}
+
+extern int errno;
+static void
+reapchild(s)
+ int s;
+{
+ int state;
+ while (waitpid(-1, &state, WNOHANG | WUNTRACED) > 0) {
+ /* printf("reaping child\n"); */
+ }
+}
+
+void
+dokill(s)
+ int s;
+{
+ kill(0, SIGKILL);
+}
+
+static int INETDstart = 0;
+void
+startfrominetd(int flag)
+{
+ INETDstart = flag;
+}
+
+
+void
+standalonesetup(fd)
+ int fd;
+{
+ int on = 1;
+ struct linger foobar;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)
+ syslog(LOG_ERR, "setsockopt (SO_REUSEADDR): %m");
+ foobar.l_onoff = 0;
+ if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *)&foobar, sizeof(foobar)) < 0)
+ syslog(LOG_ERR, "setsockopt (SO_LINGER): %m");
+}
+
+static char *UNIX_SERVER_PATH;
+static int (*halt) (int);
+
+void
+sethaltfunction(haltfunc)
+ int (*haltfunc) (int);
+{
+ halt = haltfunc;
+}
+
+void
+docompletehalt(s)
+ int s;
+{
+ /*
+ * printf("try to remove %s\n", UNIX_SERVER_PATH);
+ * unlink(UNIX_SERVER_PATH);
+ */
+ exit(0);
+ /* dokill(); */
+}
+
+void
+doremove(s)
+ int s;
+{
+ if (halt != NULL)
+ (*halt) (s);
+ else
+ docompletehalt(s);
+}
+
+
+int
+initunixserver(path, protocol)
+ char *path;
+ char *protocol;
+{
+ struct sockaddr_un s_un;
+ /* unix endpoint address */
+ struct protoent *pe; /* protocol information entry */
+ int s;
+
+ bzero((char *)&s_un, sizeof(s_un));
+ s_un.sun_family = AF_UNIX;
+ strcpy(s_un.sun_path, path);
+ if (protocol == NULL)
+ protocol = "tcp";
+ /* map protocol name to protocol number */
+ pe = getprotobyname(protocol);
+ if (pe == NULL) {
+ fprintf(stderr, "%s: Unknown protocol.\n", protocol);
+ return (-1);
+ }
+ /* Allocate a socket */
+ s = socket(PF_UNIX, strcmp(protocol, "tcp") ? SOCK_DGRAM : SOCK_STREAM, 0);
+ if (s < 0) {
+ printf("protocol %d\n", pe->p_proto);
+ perror("socket");
+ return -1;
+ }
+ /* standalonesetup(s); */
+ Signal(SIGHUP, SIG_IGN);
+ Signal(SIGUSR1, SIG_IGN);
+ Signal(SIGCHLD, reapchild);
+ UNIX_SERVER_PATH = path;
+ Signal(SIGINT, doremove);
+ Signal(SIGTERM, doremove);
+
+ chdir("/");
+ if (bind(s, (struct sockaddr *) & s_un, sizeof(struct sockaddr_un)) < 0) {
+ perror("bind");
+ perror(path);
+ return -1;
+ }
+ listen(s, 10);
+ return s;
+}
+
+int
+initinetserver(service, protocol)
+ char *service;
+ char *protocol;
+{
+ struct servent *se; /* service information entry */
+ struct protoent *pe; /* protocol information entry */
+ struct sockaddr_in sin; /* Internet endpoint address */
+ int port, s;
+ int randomport = 0;
+
+ bzero((char *)&sin, sizeof(sin));
+ sin.sin_family = AF_INET;
+ if (!strcmp("0", service)) {
+ randomport = 1;
+ sin.sin_addr.s_addr = INADDR_ANY;
+ }
+ if (service == NULL)
+ service = DEFAULTPORT;
+ if (protocol == NULL)
+ protocol = "tcp";
+ /* map service name to port number */
+ /* service ---> port */
+ se = getservbyname(service, protocol);
+ if (se == NULL) {
+ port = htons((u_short) atoi(service));
+ if (port == 0 && !randomport) {
+ fprintf(stderr, "%s/%s: Unknown service.\n", service, protocol);
+ return (-1);
+ }
+ } else
+ port = se->s_port;
+ sin.sin_port = port;
+
+ /* map protocol name to protocol number */
+ pe = getprotobyname(protocol);
+ if (pe == NULL) {
+ fprintf(stderr, "%s: Unknown protocol.\n", protocol);
+ return (-1);
+ }
+ /* Allocate a socket */
+ s = socket(PF_INET, strcmp(protocol, "tcp") ? SOCK_DGRAM : SOCK_STREAM, pe->p_proto);
+ if (s < 0) {
+ perror("socket");
+ return -1;
+ }
+ standalonesetup(s);
+ Signal(SIGHUP, SIG_IGN);
+ Signal(SIGUSR1, SIG_IGN);
+ Signal(SIGCHLD, reapchild);
+ Signal(SIGINT, dokill);
+ Signal(SIGTERM, dokill);
+
+ chdir("/");
+ if (bind(s, (struct sockaddr *) & sin, sizeof(struct sockaddr_in)) < 0) {
+ perror("bind");
+ return -1;
+ }
+ listen(s, 10);
+#ifdef DEBUG
+ {
+ int length = sizeof(sin);
+ getsockname(s, &sin, &length);
+ printf("portnum alocalted %d\n", sin.sin_port);
+ }
+#endif
+ return s;
+}
+
+int
+open_unix_listen(path, protocol, initfunc)
+ char *path;
+ char *protocol;
+ int (*initfunc) ARG((int));
+{
+ int s;
+ s = initunixserver(path, protocol);
+ if (s < 0) {
+ return -1;
+ }
+ if (initfunc != NULL) {
+ printf("in inetsingleserver before initfunc s %d\n", s);
+ if ((*initfunc) (s) < 0) {
+ perror("initfunc error");
+ return -1;
+ }
+ printf("end inetsingleserver before initfunc \n");
+ }
+ return s;
+}
+
+int
+open_listen(service, protocol, initfunc)
+ char *service;
+ char *protocol;
+ int (*initfunc) ARG((int));
+{
+ int s;
+ if (!INETDstart)
+ s = initinetserver(service, protocol);
+ else
+ s = 0;
+ if (s < 0) {
+ return -1;
+ }
+ if (initfunc != NULL) {
+ printf("in inetsingleserver before initfunc s %d\n", s);
+ if ((*initfunc) (s) < 0) {
+ perror("initfunc error");
+ return -1;
+ }
+ printf("end inetsingleserver before initfunc \n");
+ }
+ return s;
+}
+
+int
+inetsingleserver(service, protocol, serverfunc, initfunc)
+ char *service;
+ char *protocol;
+ int (*initfunc) ARG((int));
+ int (*serverfunc) ARG((int));
+{
+ int s;
+ if (!INETDstart)
+ s = initinetserver(service, protocol);
+ else
+ s = 0;
+ if (s < 0) {
+ return -1;
+ }
+ if (initfunc != NULL) {
+ printf("in inetsingleserver before initfunc s %d\n", s);
+ if ((*initfunc) (s) < 0) {
+ perror("initfunc error");
+ return -1;
+ }
+ printf("end inetsingleserver before initfunc \n");
+ } {
+ int ns = tryaccept(s);
+ int result = 0;
+ if (ns < 0 && errno != EINTR) {
+#ifdef DEBUGSERVER
+ perror("accept");
+#endif
+ }
+ close(s);
+ if (serverfunc != NULL)
+ result = (*serverfunc) (ns);
+ close(ns);
+ return (result);
+ }
+}
+
+
+int
+tryaccept(s)
+ int s;
+{
+ int ns, fromlen;
+ struct sockaddr sockaddr; /* Internet endpoint address */
+ fromlen = sizeof(struct sockaddr_in);
+
+#ifdef DEBUGSERVER
+ fputs("Listening again\n", stdout);
+#endif
+ do {
+ ns = accept(s, &sockaddr, &fromlen);
+ errno = 0;
+ } while (ns < 0 && errno == EINTR);
+ return ns;
+}
+
+int
+inetserver(service, protocol, serverfunc)
+ char *service;
+ char *protocol;
+ int (*serverfunc) ARG((int));
+{
+ int s;
+
+ if (!INETDstart)
+ s = initinetserver(service, protocol);
+ else
+ s = 0;
+ if (s < 0) {
+ return -1;
+ }
+ for (;;) {
+ int ns = tryaccept(s);
+ int result = 0;
+ int pid;
+ if (ns < 0 && errno != EINTR) {
+#ifdef DEBUGSERVER
+ perror("accept");
+#endif
+ continue;
+ }
+#ifdef DEBUGSERVER
+ fputs("Accept OK\n", stdout);
+#endif
+ pid = fork();
+ if (pid == 0) {
+ close(s);
+ if (serverfunc != NULL)
+ result = (*serverfunc) (ns);
+ close(ns);
+ exit(result);
+ } else if (pid < 0) {
+ perror("fork");
+ return -1;
+ }
+ close(ns);
+ }
+ return 0;
+}
+
+int
+inetclient(server, service, protocol)
+ char *server;
+ char *protocol;
+ char *service;
+{
+ struct servent *se; /* service information entry */
+ struct hostent *he; /* host information entry */
+ struct protoent *pe; /* protocol information entry */
+ struct sockaddr_in sin; /* Internet endpoint address */
+ int port, s;
+
+ bzero((char *)&sin, sizeof(sin));
+ sin.sin_family = AF_INET;
+
+ if (service == NULL)
+ service = DEFAULTPORT;
+ if (protocol == NULL)
+ protocol = "tcp";
+ if (server == NULL)
+ server = DEFAULTSERVER;
+ /* map service name to port number */
+ /* service ---> port */
+ se = getservbyname(service, protocol);
+ if (se == NULL) {
+ port = htons((u_short) atoi(service));
+ if (port == 0) {
+ fprintf(stderr, "%s/%s: Unknown service.\n", service, protocol);
+ return (-1);
+ }
+ } else
+ port = se->s_port;
+ sin.sin_port = port;
+
+ /* map server hostname to IP address, allowing for dotted decimal */
+ he = gethostbyname(server);
+ if (he == NULL) {
+ sin.sin_addr.s_addr = inet_addr(server);
+ if (sin.sin_addr.s_addr == INADDR_NONE) {
+ fprintf(stderr, "%s: Unknown host.\n", server);
+ return (-1);
+ }
+ } else
+ bcopy(he->h_addr, (char *)&sin.sin_addr, he->h_length);
+
+ /* map protocol name to protocol number */
+ pe = getprotobyname(protocol);
+ if (pe == NULL) {
+ fprintf(stderr, "%s: Unknown protocol.\n", protocol);
+ return (-1);
+ }
+ /* Allocate a socket */
+ s = socket(PF_INET, strcmp(protocol, "tcp") ? SOCK_DGRAM : SOCK_STREAM, pe->p_proto);
+ if (s < 0) {
+ perror("socket");
+ return -1;
+ }
+ if (setjmp(timebuf) == 0) {
+ Signal(SIGALRM, timeout);
+ alarm(5);
+ if (connect(s, (struct sockaddr *) & sin, sizeof(sin)) < 0) {
+ alarm(0);
+ return -1;
+ }
+ } else {
+ alarm(0);
+ return -1;
+ }
+ alarm(0);
+
+ return s;
+}
+
+int
+unixclient(path, protocol)
+ char *path;
+ char *protocol;
+{
+ struct protoent *pe; /* protocol information entry */
+ struct sockaddr_un s_un; /* unix endpoint address */
+ int s;
+
+ bzero((char *)&s_un, sizeof(s_un));
+ s_un.sun_family = AF_UNIX;
+
+ if (path == NULL)
+ path = DEFAULTPATH;
+ if (protocol == NULL)
+ protocol = "tcp";
+ strcpy(s_un.sun_path, path);
+
+ /* map protocol name to protocol number */
+ pe = getprotobyname(protocol);
+ if (pe == NULL) {
+ fprintf(stderr, "%s: Unknown protocol.\n", protocol);
+ return (-1);
+ }
+ /* Allocate a socket */
+ s = socket(PF_UNIX, strcmp(protocol, "tcp") ? SOCK_DGRAM : SOCK_STREAM, 0);
+ if (s < 0) {
+ perror("socket");
+ return -1;
+ }
+ /* Connect the socket to the server */
+ if (connect(s, (struct sockaddr *) & s_un, sizeof(s_un)) < 0) {
+ /* perror("connect"); */
+ return -1;
+ }
+ return s;
+}
diff --git a/pttbbs/innbbsd/ctlinnbbsd.c b/pttbbs/innbbsd/ctlinnbbsd.c
new file mode 100644
index 00000000..95e7d315
--- /dev/null
+++ b/pttbbs/innbbsd/ctlinnbbsd.c
@@ -0,0 +1,167 @@
+#include <stdlib.h>
+#include "innbbsconf.h"
+#include "bbslib.h"
+#include "externs.h"
+
+extern char *optarg;
+extern int opterr, optind;
+
+void
+usage(name)
+ char *name;
+{
+ fprintf(stderr, "Usage: %s [-p path] commands\n", name);
+ fprintf(stderr, " where available commands:\n");
+ fprintf(stderr, " ctlinnbbsd reload : reload datafiles for innbbsd\n");
+ fprintf(stderr, " ctlinnbbsd shutdown : shutdown innbbsd gracefully\n");
+ fprintf(stderr, " ctlinnbbsd mode : examine mode of innbbsd\n");
+ fprintf(stderr, " ctlinnbbsd addhist <mid> path: add history\n");
+ fprintf(stderr, " ctlinnbbsd grephist <mid>: query history\n");
+ fprintf(stderr, " ctlinnbbsd verboselog on|off : verboselog on/off\n");
+ fprintf(stderr, " ctlinnbbsd hismaint : maintain history\n");
+ fprintf(stderr, " ctlinnbbsd listnodelist : list nodelist.bbs\n");
+ fprintf(stderr, " ctlinnbbsd listnewsfeeds : list newsfeeds.bbs\n");
+#ifdef GETRUSAGE
+ fprintf(stderr, " ctlinnbbsd getrusage: get resource usage\n");
+#endif
+#ifdef MALLOCMAP
+ fprintf(stderr, " ctlinnbbsd mallocmap: get malloc map\n");
+#endif
+}
+
+
+char *DefaultPath = LOCALDAEMON;
+char INNBBSbuffer[4096];
+
+FILE *innbbsin, *innbbsout;
+int innbbsfd;
+
+void
+ctlinnbbsd(argc, argv)
+ int argc;
+ char **argv;
+{
+ fgets(INNBBSbuffer, sizeof INNBBSbuffer, innbbsin);
+ printf("%s", INNBBSbuffer);
+ if (strcasecmp(argv[0], "shutdown") == 0 ||
+ strcasecmp(argv[0], "reload") == 0 ||
+ strcasecmp(argv[0], "hismaint") == 0 ||
+#ifdef GETRUSAGE
+ strcasecmp(argv[0], "getrusage") == 0 ||
+#endif
+#ifdef MALLOCMAP
+ strcasecmp(argv[0], "mallocmap") == 0 ||
+#endif
+ strcasecmp(argv[0], "mode") == 0 ||
+ strcasecmp(argv[0], "listnodelist") == 0 ||
+ strcasecmp(argv[0], "listnewsfeeds") == 0
+ ) {
+ fprintf(innbbsout, "%s\r\n", argv[0]);
+ fflush(innbbsout);
+ fgets(INNBBSbuffer, sizeof INNBBSbuffer, innbbsin);
+ printf("%s", INNBBSbuffer);
+ if (strcasecmp(argv[0], "mode") == 0
+#ifdef GETRUSAGE
+ ||
+ strcasecmp(argv[0], "getrusage") == 0
+ ||
+ strcasecmp(argv[0], "mallocmap") == 0
+#endif
+ ||
+ strcasecmp(argv[0], "listnodelist") == 0
+ ||
+ strcasecmp(argv[0], "listnewsfeeds") == 0
+ ) {
+ while (fgets(INNBBSbuffer, sizeof INNBBSbuffer, innbbsin) != NULL) {
+ if (strcmp(INNBBSbuffer, ".\r\n") == 0) {
+ break;
+ }
+ printf("%s", INNBBSbuffer);
+ }
+ }
+ } else if (strcasecmp(argv[0], "grephist") == 0 ||
+ strcasecmp(argv[0], "verboselog") == 0) {
+ if (argc < 2) {
+ usage("ctlinnbbsd");
+ } else {
+ fprintf(innbbsout, "%s %s\r\n", argv[0], argv[1]);
+ fflush(innbbsout);
+ fgets(INNBBSbuffer, sizeof INNBBSbuffer, innbbsin);
+ printf("%s\n", INNBBSbuffer);
+ }
+ } else if (strcasecmp(argv[0], "addhist") == 0) {
+ if (argc < 3) {
+ usage("ctlinnbbsd");
+ } else {
+ fprintf(innbbsout, "%s %s %s\r\n", argv[0], argv[1], argv[2]);
+ fflush(innbbsout);
+ fgets(INNBBSbuffer, sizeof INNBBSbuffer, innbbsin);
+ printf("%s", INNBBSbuffer);
+ }
+ } else {
+ fprintf(stderr, "invalid command %s\n", argv[0]);
+ }
+ if (strcasecmp(argv[0], "shutdown") != 0) {
+ fprintf(innbbsout, "QUIT\r\n");
+ fflush(innbbsout);
+ fgets(INNBBSbuffer, sizeof INNBBSbuffer, innbbsin);
+ }
+}
+
+void
+initsocket()
+{
+ innbbsfd = unixclient(DefaultPath, "tcp");
+ if (innbbsfd < 0) {
+ fprintf(stderr, "Connect to %s error. You may not run innbbsd\n", DefaultPath);
+ exit(2);
+ }
+ if ((innbbsin = fdopen(innbbsfd, "r")) == NULL ||
+ (innbbsout = fdopen(innbbsfd, "w")) == NULL) {
+ fprintf(stderr, "fdopen error\n");
+ exit(3);
+ }
+}
+
+void
+closesocket()
+{
+ if (innbbsin != NULL)
+ fclose(innbbsin);
+ if (innbbsout != NULL)
+ fclose(innbbsout);
+ if (innbbsfd >= 0)
+ close(innbbsfd);
+}
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ int c, errflag = 0;
+
+ while ((c = getopt(argc, argv, "p:h?")) != -1)
+ switch (c) {
+ case 'p':
+ DefaultPath = optarg;
+ break;
+ case 'h':
+ case '?':
+ default:
+ errflag++;
+ break;
+ }
+ if (errflag > 0) {
+ usage(argv[0]);
+ return (1);
+ }
+ if (argc - optind < 1) {
+ usage(argv[0]);
+ exit(1);
+ }
+ initial_bbs(NULL);
+ initsocket();
+ ctlinnbbsd(argc - optind, argv + optind);
+ closesocket();
+}
diff --git a/pttbbs/innbbsd/daemon.c b/pttbbs/innbbsd/daemon.c
new file mode 100644
index 00000000..b764fda1
--- /dev/null
+++ b/pttbbs/innbbsd/daemon.c
@@ -0,0 +1,177 @@
+#include <string.h>
+#include "daemon.h"
+/*
+ * typedef struct daemoncmd { char *cmdname; char *usage; int argc; int
+ * (*main) ARG((FILE*,FILE*,int,char**,char*)); } daemoncmd_t;
+ *
+ */
+
+void deargify ARG((char ***));
+static daemoncmd_t *dcmdp = NULL;
+static char *startupmessage = NULL;
+static int startupcode = 100;
+static FILE *DIN, *DOUT, *DIO;
+typedef int (*F) ();
+
+void
+installdaemon(cmds, code, startupmsg)
+ daemoncmd_t *cmds;
+ int code;
+ char *startupmsg;
+{
+ dcmdp = cmds;
+ startupcode = code;
+ startupmessage = startupmsg;
+}
+
+daemoncmd_t *
+searchcmd(cmd)
+ char *cmd;
+{
+ daemoncmd_t *p;
+ for (p = dcmdp; p->name != NULL; p++) {
+#ifdef DEBUGCMD
+ printf("searching name %s for cmd %s\n", p->name, cmd);
+#endif
+ if (!strncasecmp(p->name, cmd, 1024))
+ return p;
+ }
+ return NULL;
+}
+
+#if 0
+int
+daemon(dfd)
+ int dfd;
+{
+ static char BUF[1024];
+ /* hash_init(); */
+ if (dfd > 0) {
+ DIO = fdopen(dfd, "rw");
+ DIN = fdopen(dfd, "r");
+ DOUT = fdopen(dfd, "w");
+ if (DIO == NULL || DIN == NULL || DOUT == NULL) {
+ perror("fdopen");
+ return -1;
+ }
+ }
+ if (startupmessage) {
+ fprintf(DOUT, "%d %s\n", startupcode, startupmessage);
+ fflush(DOUT);
+ }
+ while (fgets(BUF, 1024, DIN) != NULL) {
+ int i;
+ int (*Main) ();
+ daemoncmd_t *dp;
+ argv_t Argv;
+
+ char *p = (char *)strchr(BUF, '\r');
+ if (p == NULL)
+ p = (char *)strchr(BUF, '\n');
+ if (p == NULL)
+ continue;
+ *p = '\0';
+ if (p == BUF)
+ continue;
+
+ Argv.argc = 0, Argv.argv = NULL, Argv.inputline = BUF;
+ Argv.in = DIN, Argv.out = DOUT;
+ printf("command entered: %s\n", BUF);
+#ifdef DEBUGSERVER
+ fprintf(DOUT, "BUF in client %s\n", BUF);
+ fprintf(stdout, "BUF in server %s\n", BUF);
+ fflush(DOUT);
+#endif
+ Argv.argc = argify(BUF, &Argv.argv);
+#ifdef DEBUGSERVER
+ fprintf(stdout, "argc %d argv ", Argv.argc);
+ for (i = 0; i < Argv.argc; ++i)
+ fprintf(stdout, "%s ", Argv.argv[i]);
+ fprintf(stdout, "\n");
+#endif
+ dp = searchcmd(Argv.argv[0]);
+ Argv.dc = dp;
+ if (dp) {
+#ifdef DEBUGSERVER
+ printf("find cmd %s by %s\n", dp->name, dp->usage);
+#endif
+ if (Argv.argc < dp->argc) {
+ fprintf(DOUT, "%d Usage: %s\n", dp->errorcode, dp->usage);
+ fflush(DOUT);
+ goto cont;
+ }
+ if (dp->argno != 0 && Argv.argc > dp->argno) {
+ fprintf(DOUT, "%d Usage: %s\n", dp->errorcode, dp->usage);
+ fflush(DOUT);
+ goto cont;
+ }
+ Main = dp->main;
+ if (Main) {
+ fflush(stdout);
+ (*Main) (&Argv);
+ }
+ } else {
+ fprintf(DOUT, "99 command %s not available\n", Argv.argv[0]);
+ fflush(DOUT);
+ }
+cont:
+ deargify(&Argv.argv);
+ }
+ /* hash_reclaim(); */
+}
+#endif
+
+#define MAX_ARG 32
+#define MAX_ARG_SIZE 16384
+
+int
+argify(line, argvp)
+ char *line, ***argvp;
+{
+ static char *argvbuffer[MAX_ARG + 2];
+ char **argv = argvbuffer;
+ int i;
+ static char argifybuffer[MAX_ARG_SIZE];
+ char *p;
+ while (strchr("\t\n\r ", *line))
+ line++;
+ i = strlen(line);
+ /* p=(char*) mymalloc(i+1); */
+ p = argifybuffer;
+ strncpy(p, line, sizeof argifybuffer);
+ for (*argvp = argv, i = 0; *p && i < MAX_ARG;) {
+ for (*argv++ = p; *p && !strchr("\t\r\n ", *p); p++);
+ if (*p == '\0')
+ break;
+ for (*p++ = '\0'; strchr("\t\r\n ", *p) && *p; p++);
+ }
+ *argv = NULL;
+ return argv - *argvp;
+}
+
+void
+deargify(argv)
+ char ***argv;
+{
+ return;
+ /*
+ * if (*argv != NULL) { if (*argv[0] != NULL){ free(*argv[0]); argv[0] =
+ * NULL; } free(*argv); argv = NULL; }
+ */
+}
+
+int
+daemonprintf(format)
+ char *format;
+{
+ fprintf(DOUT, format);
+ fflush(DOUT);
+}
+
+int
+daemonputs(output)
+ char *output;
+{
+ fputs(output, DOUT);
+ fflush(DOUT);
+}
diff --git a/pttbbs/innbbsd/daemon.h b/pttbbs/innbbsd/daemon.h
new file mode 100644
index 00000000..36384a20
--- /dev/null
+++ b/pttbbs/innbbsd/daemon.h
@@ -0,0 +1,54 @@
+#ifndef DAEMON_H
+#define DAEMON_H
+
+#include <stdio.h>
+#include <time.h>
+
+#ifndef ARG
+#ifdef __STDC__
+#define ARG(x) x
+#else
+#define ARG(x) ()
+#endif
+#endif
+
+
+struct Argv_t {
+ FILE *in, *out;
+ int argc;
+ char **argv;
+ char *inputline;
+ struct Daemoncmd *dc;
+};
+
+typedef struct Argv_t argv_t;
+
+typedef struct Buffer_t {
+ char *data;
+ int used, left, lastread;
+} buffer_t;
+
+typedef struct ClientType {
+ char hostname[1024];
+ char username[32];
+ char buffer[4096];
+ int mode;
+ argv_t Argv;
+ int fd, access, lastread, midcheck;
+ buffer_t in, out;
+ int ihavecount, ihavesize, ihaveduplicate, ihavefail;
+ int statcount, statfail;
+ time_t begin;
+} ClientType;
+
+typedef struct Daemoncmd {
+ char *name;
+ char *usage;
+ int argc, argno, errorcode, normalcode;
+ int (*main) ARG((ClientType *));
+} daemoncmd_t;
+
+extern void installdaemon ARG((daemoncmd_t *, int, char *));
+extern ClientType *Channel;
+
+#endif
diff --git a/pttbbs/innbbsd/dbz.c b/pttbbs/innbbsd/dbz.c
new file mode 100644
index 00000000..9b031678
--- /dev/null
+++ b/pttbbs/innbbsd/dbz.c
@@ -0,0 +1,1885 @@
+/*
+ * dbz.c V3.2
+ *
+ * Copyright 1988 Jon Zeeff (zeeff@b-tech.ann-arbor.mi.us) You can use this code
+ * in any manner, as long as you leave my name on it and don't hold me
+ * responsible for any problems with it.
+ *
+ * Hacked on by gdb@ninja.UUCP (David Butler); Sun Jun 5 00:27:08 CDT 1988
+ *
+ * Various improvments + INCORE by moraes@ai.toronto.edu (Mark Moraes)
+ *
+ * Major reworking by Henry Spencer as part of the C News project.
+ *
+ * Minor lint and CodeCenter (Saber) fluff removal by Rich $alz (March, 1991).
+ * Non-portable CloseOnExec() calls added by Rich $alz (September, 1991).
+ * Added "writethrough" and tagmask calculation code from
+ * <rob@violet.berkeley.edu> and <leres@ee.lbl.gov> by Rich $alz (December,
+ * 1992). Merged in MMAP code by David Robinson, formerly
+ * <david@elroy.jpl.nasa.gov> now <david.robinson@sun.com> (January, 1993).
+ *
+ * These routines replace dbm as used by the usenet news software (it's not a
+ * full dbm replacement by any means). It's fast and simple. It contains no
+ * AT&T code.
+ *
+ * In general, dbz's files are 1/20 the size of dbm's. Lookup performance is
+ * somewhat better, while file creation is spectacularly faster, especially
+ * if the incore facility is used.
+ *
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <errno.h>
+#ifndef __STDC__
+extern int errno;
+#endif
+#include <dbz.h>
+#include "clibrary.h"
+
+/*
+ * #ifdef index. "LIA" = "leave it alone unless you know what you're doing".
+ *
+ * FUNNYSEEKS SEEK_SET is not 0, get it from <unistd.h> INDEX_SIZE
+ * backward compatibility with old dbz; avoid using this NMEMORY
+ * number of days of memory for use in sizing new table (LIA) INCORE
+ * backward compatibility with old dbz; use dbzincore() instead DBZDEBUG
+ * enable debugging DEFSIZE default table size (not as critical as in old
+ * dbz) OLDBNEWS default case mapping as in old B News; set NOBUFFER
+ * BNEWS default case mapping as in current B News; set NOBUFFER
+ * DEFCASE default case-map algorithm selector NOTAGS fseek offsets
+ * are strange, do not do tagging (see below) NPAGBUF size of .pag buffer,
+ * in longs (LIA) SHISTBUF size of ASCII-file buffer, in bytes (LIA)
+ * MAXRUN length of run which shifts to next table (see below) (LIA)
+ * OVERFLOW long-int arithmetic overflow must be avoided, will trap
+ * NOBUFFER do not buffer hash-table i/o, B News locking is defective
+ * MMAP Use SunOS style mmap() for efficient incore
+ */
+/* SUPPRESS 530 *//* Empty body for statement */
+/* SUPPRESS 701 on free *//* Conflicting declaration */
+
+#ifdef FUNNYSEEKS
+#include <unistd.h>
+#else
+#define SEEK_SET 0
+#endif
+#ifdef OVERFLOW
+#include <limits.h>
+#endif
+
+static int dbzversion = 3; /* for validating .dir file format */
+
+/*
+ * The dbz database exploits the fact that when news stores a <key,value>
+ * tuple, the `value' part is a seek offset into a text file, pointing to a
+ * copy of the `key' part. This avoids the need to store a copy of the key
+ * in the dbz files. However, the text file *must* exist and be consistent
+ * with the dbz files, or things will fail.
+ *
+ * The basic format of the database is a simple hash table containing the
+ * values. A value is stored by indexing into the table using a hash value
+ * computed from the key; collisions are resolved by linear probing (just
+ * search forward for an empty slot, wrapping around to the beginning of the
+ * table if necessary). Linear probing is a performance disaster when the
+ * table starts to get full, so a complication is introduced. The database
+ * is actually one *or more* tables, stored sequentially in the .pag file,
+ * and the length of linear-probe sequences is limited. The search (for an
+ * existing item or an empty slot) always starts in the first table, and
+ * whenever MAXRUN probes have been done in table N, probing continues in
+ * table N+1. This behaves reasonably well even in cases of massive
+ * overflow. There are some other small complications added, see comments
+ * below.
+ *
+ * The table size is fixed for any particular database, but is determined
+ * dynamically when a database is rebuilt. The strategy is to try to pick
+ * the size so the first table will be no more than 2/3 full, that being
+ * slightly before the point where performance starts to degrade. (It is
+ * desirable to be a bit conservative because the overflow strategy tends to
+ * produce files with holes in them, which is a nuisance.)
+ */
+
+/*
+ * The following is for backward compatibility.
+ */
+#ifdef INDEX_SIZE
+#define DEFSIZE INDEX_SIZE
+#endif
+
+/*
+ * ANSI C says an offset into a file is a long, not an off_t, for some
+ * reason. This actually does simplify life a bit, but it's still nice to
+ * have a distinctive name for it. Beware, this is just for readability,
+ * don't try to change this.
+ */
+#define of_t long
+#define SOF (sizeof(of_t))
+
+/*
+ * We assume that unused areas of a binary file are zeros, and that the bit
+ * pattern of `(of_t)0' is all zeros. The alternative is rather painful file
+ * initialization. Note that okayvalue(), if OVERFLOW is defined, knows what
+ * value of an offset would cause overflow.
+ */
+#define VACANT ((of_t)0)
+#define BIAS(o) ((o)+1) /* make any valid of_t non-VACANT */
+#define UNBIAS(o) ((o)-1) /* reverse BIAS() effect */
+
+/*
+ * In a Unix implementation, or indeed any in which an of_t is a byte count,
+ * there are a bunch of high bits free in an of_t. There is a use for them.
+ * Checking a possible hit by looking it up in the base file is relatively
+ * expensive, and the cost can be dramatically reduced by using some of those
+ * high bits to tag the value with a few more bits of the key's hash. This
+ * detects most false hits without the overhead of seek+read+strcmp. We use
+ * the top bit to indicate whether the value is tagged or not, and don't tag
+ * a value which is using the tag bits itself. We're in trouble if the of_t
+ * representation wants to use the top bit. The actual bitmasks and offset
+ * come from the configuration stuff, which permits fiddling with them as
+ * necessary, and also suppressing them completely (by defining the masks to
+ * 0). We build pre-shifted versions of the masks for efficiency.
+ */
+static of_t tagbits; /* pre-shifted tag mask */
+static of_t taghere; /* pre-shifted tag-enable bit */
+static of_t tagboth; /* tagbits|taghere */
+#define HASTAG(o) ((o)&taghere)
+#define TAG(o) ((o)&tagbits)
+#define NOTAG(o) ((o)&~tagboth)
+#define CANTAG(o) (((o)&tagboth) == 0)
+#define MKTAG(v) (((v)<<conf.tagshift)&tagbits)
+
+/*
+ * A new, from-scratch database, not built as a rebuild of an old one, needs
+ * to know table size, casemap algorithm, and tagging. Normally the user
+ * supplies this info, but there have to be defaults.
+ */
+#ifndef DEFSIZE
+#define DEFSIZE 120011 /* 300007 might be better */
+#endif
+#ifdef OLDBNEWS
+#define DEFCASE '0' /* B2.10 -- no mapping */
+#define NOBUFFER /* B News locking is defective */
+#endif
+#ifdef BNEWS
+#define DEFCASE '=' /* B2.11 -- all mapped */
+#define NOBUFFER /* B News locking is defective */
+#endif
+#ifndef DEFCASE /* C News compatibility is the default */
+#define DEFCASE 'C' /* C News -- RFC822 mapping */
+#endif
+#ifndef NOTAGS
+#define TAGENB 0x80 /* tag enable is top bit, tag is next 7 */
+#define TAGMASK 0x7f
+#define TAGSHIFT 24
+#else
+#define TAGENB 0 /* no tags */
+#define TAGMASK 0
+#define TAGSHIFT 0
+#endif
+
+/*
+ * We read configuration info from the .dir file into this structure, so we
+ * can avoid wired-in assumptions for an existing database.
+ *
+ * Among the info is a record of recent peak usages, so that a new table size
+ * can be chosen intelligently when rebuilding. 10 is a good number of
+ * usages to keep, since news displays marked fluctuations in volume on a
+ * 7-day cycle.
+ */
+struct dbzconfig {
+ int olddbz; /* .dir file empty but .pag not? */
+ of_t tsize; /* table size */
+#ifndef NMEMORY
+#define NMEMORY 10 /* # days of use info to remember */
+#endif
+#define NUSEDS (1+NMEMORY)
+ of_t used[NUSEDS]; /* entries used today, yesterday, ... */
+ int valuesize; /* size of table values, == SOF */
+ int bytemap[SOF]; /* byte-order map */
+ char casemap; /* case-mapping algorithm (see cipoint()) */
+ char fieldsep; /* field separator in base file, if any */
+ of_t tagenb; /* unshifted tag-enable bit */
+ of_t tagmask; /* unshifted tag mask */
+ int tagshift; /* shift count for tagmask and tagenb */
+};
+static struct dbzconfig conf;
+static int getconf();
+static long getno();
+static int putconf();
+static void mybytemap();
+static of_t bytemap();
+
+/*
+ * Using mmap() is a more efficent way of keeping the .pag file incore. On
+ * average, it cuts the number of system calls and buffer copies in half. It
+ * also allows one copy to be shared among many processes without consuming
+ * any extra resources.
+ */
+#ifdef MMAP
+#include <sys/mman.h>
+#ifdef MAP_FILE
+#define MAP__ARG (MAP_FILE | MAP_SHARED)
+#else
+#define MAP__ARG (MAP_SHARED)
+#endif
+#ifndef INCORE
+#define INCORE
+#endif
+#endif
+
+/*
+ * For a program that makes many, many references to the database, it is a
+ * large performance win to keep the table in core, if it will fit. Note that
+ * this does hurt robustness in the event of crashes, and dbmclose() *must*
+ * be called to flush the in-core database to disk. The code is prepared to
+ * deal with the possibility that there isn't enough memory. There *is* an
+ * assumption that a size_t is big enough to hold the size (in bytes) of one
+ * table, so dbminit() tries to figure out whether this is possible first.
+ *
+ * The preferred way to ask for an in-core table is to do dbzincore(1) before
+ * dbminit(). The default is not to do it, although -DINCORE overrides this
+ * for backward compatibility with old dbz.
+ *
+ * We keep only the first table in core. This greatly simplifies the code, and
+ * bounds memory demand. Furthermore, doing this is a large performance win
+ * even in the event of massive overflow.
+ */
+#ifdef INCORE
+static int incore = 1;
+#else
+static int incore = 0;
+#endif
+
+/*
+ * Write to filesystem even if incore? This replaces a single multi-
+ * megabyte write when doing a dbzsync with a multi-byte write each time an
+ * article is added. On most systems, this will give an overall performance
+ * boost.
+ */
+static int writethrough = 0;
+
+/*
+ * Stdio buffer for .pag reads. Buffering more than about 16 does not help
+ * significantly at the densities we try to maintain, and the much larger
+ * buffers that most stdios default to are much more expensive to fill. With
+ * small buffers, stdio is performance-competitive with raw read(), and it's
+ * much more portable.
+ */
+#ifndef NPAGBUF
+#define NPAGBUF 16
+#endif
+#ifndef NOBUFFER
+#ifdef _IOFBF
+static of_t pagbuf[NPAGBUF];/* only needed if !NOBUFFER && _IOFBF */
+#endif
+#endif
+
+/*
+ * Stdio buffer for base-file reads. Message-IDs (all news ever needs to
+ * read) are essentially never longer than 64 bytes, and the typical stdio
+ * buffer is so much larger that it is much more expensive to fill.
+ */
+#ifndef SHISTBUF
+#define SHISTBUF 64
+#endif
+#ifdef _IOFBF
+static char basebuf[SHISTBUF]; /* only needed if _IOFBF exists */
+#endif
+
+/*
+ * Data structure for recording info about searches.
+ */
+struct searcher {
+ of_t place; /* current location in file */
+ int tabno; /* which table we're in */
+ int run; /* how long we'll stay in this table */
+#ifndef MAXRUN
+#define MAXRUN 100
+#endif
+ long hash; /* the key's hash code (for optimization) */
+ of_t tag; /* tag we are looking for */
+ int seen; /* have we examined current location? */
+ int aborted; /* has i/o error aborted search? */
+};
+static void start();
+#define FRESH ((struct searcher *)NULL)
+static of_t search();
+#define NOTFOUND ((of_t)-1)
+static int okayvalue();
+static int set();
+
+/*
+ * Arguably the searcher struct for a given routine ought to be local to it,
+ * but a fetch() is very often immediately followed by a store(), and in some
+ * circumstances it is a useful performance win to remember where the fetch()
+ * completed. So we use a global struct and remember whether it is current.
+ */
+static struct searcher srch;
+static struct searcher *prevp; /* &srch or FRESH */
+
+/* byte-ordering stuff */
+static int mybmap[SOF]; /* my byte order (see mybytemap()) */
+static int bytesame; /* is database order same as mine? */
+#define MAPIN(o) ((bytesame) ? (o) : bytemap((o), conf.bytemap, mybmap))
+#define MAPOUT(o) ((bytesame) ? (o) : bytemap((o), mybmap, conf.bytemap))
+
+/*
+ * The double parentheses needed to make this work are ugly, but the
+ * alternative (under most compilers) is to pack around 2K of unused strings
+ * -- there's just no way to get rid of them.
+ */
+#ifdef DBZDEBUG
+static int debug; /* controlled by dbzdebug() */
+#define DEBUG(args) if (debug) { (void) printf args ; } else
+#else
+#define DEBUG(args) ;
+#endif
+
+/* externals used */
+#if 0
+extern char *memcpy();
+extern char *memchr();
+extern char *malloc();
+extern char *calloc();
+extern void free(); /* ANSI C; some old implementations say int */
+#endif /* 0 */
+extern int atoi();
+extern long atol();
+extern void CloseOnExec();
+
+/* misc. forwards */
+static long hash();
+static void crcinit();
+static char *cipoint();
+static char *mapcase();
+static int isprime();
+static FILE *latebase();
+
+/* file-naming stuff */
+static char dir[] = ".dir";
+static char pag[] = ".pag";
+static char *enstring();
+
+/* central data structures */
+static FILE *basef; /* descriptor for base file */
+static char *basefname; /* name for not-yet-opened base file */
+static FILE *dirf; /* descriptor for .dir file */
+static int dirronly; /* dirf open read-only? */
+static FILE *pagf = NULL; /* descriptor for .pag file */
+static of_t pagpos; /* posn in pagf; only search may set != -1 */
+static int pagronly; /* pagf open read-only? */
+static of_t *corepag; /* incore version of .pag file, if any */
+static FILE *bufpagf; /* well-buffered pagf, for incore rewrite */
+static of_t *getcore();
+#ifndef MMAP
+static int putcore();
+#endif
+static int written; /* has a store() been done? */
+
+/*
+ * - dbzfresh - set up a new database, no historical info
+ */
+int /* 0 success, -1 failure */
+dbzfresh(name, size, fs, cmap, tagmask)
+ char *name; /* base name; .dir and .pag must exist */
+ long size; /* table size (0 means default) */
+ int fs; /* field-separator character in base file */
+ int cmap; /* case-map algorithm (0 means default) */
+ of_t tagmask; /* 0 default, 1 no tags */
+{
+ register char *fn;
+ struct dbzconfig c;
+ register of_t m;
+ register FILE *f;
+
+ if (pagf != NULL) {
+ DEBUG(("dbzfresh: database already open\n"));
+ return (-1);
+ }
+ if (size != 0 && size < 2) {
+ DEBUG(("dbzfresh: preposterous size (%ld)\n", size));
+ return (-1);
+ }
+ /* get default configuration */
+ if (getconf((FILE *) NULL, (FILE *) NULL, &c) < 0)
+ return (-1); /* "can't happen" */
+
+ /* and mess with it as specified */
+ if (size != 0)
+ c.tsize = size;
+ c.fieldsep = fs;
+ switch (cmap) {
+ case 0:
+ case '0':
+ case 'B': /* 2.10 compat */
+ c.casemap = '0'; /* '\0' nicer, but '0' printable! */
+ break;
+ case '=':
+ case 'b': /* 2.11 compat */
+ c.casemap = '=';
+ break;
+ case 'C':
+ c.casemap = 'C';
+ break;
+ case '?':
+ c.casemap = DEFCASE;
+ break;
+ default:
+ DEBUG(("dbzfresh case map `%c' unknown\n", cmap));
+ return (-1);
+ }
+ switch ((int)tagmask) {
+ case 0: /* default */
+ break;
+ case 1: /* no tags */
+ c.tagshift = 0;
+ c.tagmask = 0;
+ c.tagenb = 0;
+ break;
+ default:
+ m = tagmask;
+ c.tagshift = 0;
+ while (!(m & 01)) {
+ m >>= 1;
+ c.tagshift++;
+ }
+ c.tagmask = m;
+ c.tagenb = (m << 1) & ~m;
+ break;
+ }
+
+ /* write it out */
+ fn = enstring(name, dir);
+ if (fn == NULL)
+ return (-1);
+ f = fopen(fn, "w");
+ free((POINTER) fn);
+ if (f == NULL) {
+ DEBUG(("dbzfresh: unable to write config\n"));
+ return (-1);
+ }
+ if (putconf(f, &c) < 0) {
+ (void)fclose(f);
+ return (-1);
+ }
+ if (fclose(f) == EOF) {
+ DEBUG(("dbzfresh: fclose failure\n"));
+ return (-1);
+ }
+ /* create/truncate .pag */
+ fn = enstring(name, pag);
+ if (fn == NULL)
+ return (-1);
+ f = fopen(fn, "w");
+ free((POINTER) fn);
+ if (f == NULL) {
+ DEBUG(("dbzfresh: unable to create/truncate .pag file\n"));
+ return (-1);
+ } else
+ (void)fclose(f);
+
+ /* and punt to dbminit for the hard work */
+ return (dbminit(name));
+}
+
+/*
+ * - dbzsize - what's a good table size to hold this many entries?
+ */
+long
+dbzsize(contents)
+ long contents; /* 0 means what's the default */
+{
+ register long n;
+
+ if (contents <= 0) { /* foulup or default inquiry */
+ DEBUG(("dbzsize: preposterous input (%ld)\n", contents));
+ return (DEFSIZE);
+ }
+ n = (contents / 2) * 3; /* try to keep table at most 2/3 full */
+ if (!(n & 01)) /* make it odd */
+ n++;
+ DEBUG(("dbzsize: tentative size %ld\n", n));
+ while (!isprime(n)) /* and look for a prime */
+ n += 2;
+ DEBUG(("dbzsize: final size %ld\n", n));
+
+ return (n);
+}
+
+/*
+ * - isprime - is a number prime?
+ *
+ * This is not a terribly efficient approach.
+ */
+static int /* predicate */
+isprime(x)
+ register long x;
+{
+ static int quick[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 0};
+ register int *ip;
+ register long div;
+ register long stop;
+
+ /* hit the first few primes quickly to eliminate easy ones */
+ /* this incidentally prevents ridiculously small tables */
+ for (ip = quick; (div = *ip) != 0; ip++)
+ if (x % div == 0) {
+ DEBUG(("isprime: quick result on %ld\n", (long)x));
+ return (0);
+ }
+ /* approximate square root of x */
+ for (stop = x; x / stop < stop; stop >>= 1)
+ continue;
+ stop <<= 1;
+
+ /* try odd numbers up to stop */
+ for (div = *--ip; div < stop; div += 2)
+ if (x % div == 0)
+ return (0);
+
+ return (1);
+}
+
+/*
+ * - dbzagain - set up a new database to be a rebuild of an old one
+ */
+int /* 0 success, -1 failure */
+dbzagain(name, oldname)
+ char *name; /* base name; .dir and .pag must exist */
+ char *oldname; /* base name; all must exist */
+{
+ register char *fn;
+ struct dbzconfig c;
+ register int i;
+ register long top;
+ register FILE *f;
+ register int newtable;
+ register of_t newsize;
+ struct stat sb;
+ register of_t m;
+
+ if (pagf != NULL) {
+ DEBUG(("dbzagain: database already open\n"));
+ return (-1);
+ }
+ /* pick up the old configuration */
+ fn = enstring(oldname, dir);
+ if (fn == NULL)
+ return (-1);
+ f = fopen(fn, "r");
+ free((POINTER) fn);
+ if (f == NULL) {
+ DEBUG(("dbzagain: cannot open old .dir file\n"));
+ return (-1);
+ }
+ i = getconf(f, (FILE *) NULL, &c);
+ (void)fclose(f);
+ if (i < 0) {
+ DEBUG(("dbzagain: getconf failed\n"));
+ return (-1);
+ }
+ /* calculate tagging from old file */
+ if (stat(oldname, &sb) != -1) {
+ for (m = 1, i = 0; m < sb.st_size; i++, m <<= 1)
+ continue;
+
+ /* if we had more tags than the default, use the new data */
+ if ((c.tagmask | c.tagenb) && m > (1 << TAGSHIFT)) {
+ c.tagshift = i;
+ c.tagmask = (~(unsigned long)0) >> (i + 1);
+ c.tagenb = (c.tagmask << 1) & ~c.tagmask;
+ }
+ }
+ /* tinker with it */
+ top = 0;
+ newtable = 0;
+ for (i = 0; i < NUSEDS; i++) {
+ if (top < c.used[i])
+ top = c.used[i];
+ if (c.used[i] == 0)
+ newtable = 1; /* hasn't got full usage history yet */
+ }
+ if (top == 0) {
+ DEBUG(("dbzagain: old table has no contents!\n"));
+ newtable = 1;
+ }
+ for (i = NUSEDS - 1; i > 0; i--)
+ c.used[i] = c.used[i - 1];
+ c.used[0] = 0;
+ newsize = dbzsize(top);
+ if (!newtable || newsize > c.tsize) /* don't shrink new table */
+ c.tsize = newsize;
+
+ /* write it out */
+ fn = enstring(name, dir);
+ if (fn == NULL)
+ return (-1);
+ f = fopen(fn, "w");
+ free((POINTER) fn);
+ if (f == NULL) {
+ DEBUG(("dbzagain: unable to write new .dir\n"));
+ return (-1);
+ }
+ i = putconf(f, &c);
+ (void)fclose(f);
+ if (i < 0) {
+ DEBUG(("dbzagain: putconf failed\n"));
+ return (-1);
+ }
+ /* create/truncate .pag */
+ fn = enstring(name, pag);
+ if (fn == NULL)
+ return (-1);
+ f = fopen(fn, "w");
+ free((POINTER) fn);
+ if (f == NULL) {
+ DEBUG(("dbzagain: unable to create/truncate .pag file\n"));
+ return (-1);
+ } else
+ (void)fclose(f);
+
+ /* and let dbminit do the work */
+ return (dbminit(name));
+}
+
+/*
+ * - dbminit - open a database, creating it (using defaults) if necessary
+ *
+ * We try to leave errno set plausibly, to the extent that underlying functions
+ * permit this, since many people consult it if dbminit() fails.
+ */
+int /* 0 success, -1 failure */
+dbminit(name)
+ char *name;
+{
+ register int i;
+ register size_t s;
+ register char *dirfname;
+ register char *pagfname;
+
+ if (pagf != NULL) {
+ DEBUG(("dbminit: dbminit already called once\n"));
+ errno = 0;
+ return (-1);
+ }
+ /* open the .dir file */
+ dirfname = enstring(name, dir);
+ if (dirfname == NULL)
+ return (-1);
+ dirf = fopen(dirfname, "r+");
+ if (dirf == NULL) {
+ dirf = fopen(dirfname, "r");
+ dirronly = 1;
+ } else
+ dirronly = 0;
+ free((POINTER) dirfname);
+ if (dirf == NULL) {
+ DEBUG(("dbminit: can't open .dir file\n"));
+ return (-1);
+ }
+ CloseOnExec((int)fileno(dirf), 1);
+
+ /* open the .pag file */
+ pagfname = enstring(name, pag);
+ if (pagfname == NULL) {
+ (void)fclose(dirf);
+ return (-1);
+ }
+ pagf = fopen(pagfname, "r+b");
+ if (pagf == NULL) {
+ pagf = fopen(pagfname, "rb");
+ if (pagf == NULL) {
+ DEBUG(("dbminit: .pag open failed\n"));
+ (void)fclose(dirf);
+ free((POINTER) pagfname);
+ return (-1);
+ }
+ pagronly = 1;
+ } else if (dirronly)
+ pagronly = 1;
+ else
+ pagronly = 0;
+ if (pagf != NULL)
+ CloseOnExec((int)fileno(pagf), 1);
+#ifdef NOBUFFER
+ /*
+ * B News does not do adequate locking on its database accesses. Why it
+ * doesn't get into trouble using dbm is a mystery. In any case, doing
+ * unbuffered i/o does not cure the problem, but does enormously reduce
+ * its incidence.
+ */
+ (void)setbuf(pagf, (char *)NULL);
+#else
+#ifdef _IOFBF
+ (void)setvbuf(pagf, (char *)pagbuf, _IOFBF, sizeof(pagbuf));
+#endif
+#endif
+ pagpos = -1;
+ /* don't free pagfname, need it below */
+
+ /* open the base file */
+ basef = fopen(name, "r");
+ if (basef == NULL) {
+ DEBUG(("dbminit: basefile open failed\n"));
+ basefname = enstring(name, "");
+ if (basefname == NULL) {
+ (void)fclose(pagf);
+ (void)fclose(dirf);
+ free((POINTER) pagfname);
+ pagf = NULL;
+ return (-1);
+ }
+ } else
+ basefname = NULL;
+ if (basef != NULL)
+ CloseOnExec((int)fileno(basef), 1);
+#ifdef _IOFBF
+ if (basef != NULL)
+ (void)setvbuf(basef, basebuf, _IOFBF, sizeof(basebuf));
+#endif
+
+ /* pick up configuration */
+ if (getconf(dirf, pagf, &conf) < 0) {
+ DEBUG(("dbminit: getconf failure\n"));
+ (void)fclose(basef);
+ (void)fclose(pagf);
+ (void)fclose(dirf);
+ free((POINTER) pagfname);
+ pagf = NULL;
+ errno = EDOM; /* kind of a kludge, but very portable */
+ return (-1);
+ }
+ tagbits = conf.tagmask << conf.tagshift;
+ taghere = conf.tagenb << conf.tagshift;
+ tagboth = tagbits | taghere;
+ mybytemap(mybmap);
+ bytesame = 1;
+ for (i = 0; i < SOF; i++)
+ if (mybmap[i] != conf.bytemap[i])
+ bytesame = 0;
+
+ /* get first table into core, if it looks desirable and feasible */
+ s = (size_t) conf.tsize * SOF;
+ if (incore && (of_t) (s / SOF) == conf.tsize) {
+ bufpagf = fopen(pagfname, (pagronly) ? "rb" : "r+b");
+ if (bufpagf != NULL) {
+ corepag = getcore(bufpagf);
+ CloseOnExec((int)fileno(bufpagf), 1);
+ }
+ } else {
+ bufpagf = NULL;
+ corepag = NULL;
+ }
+ free((POINTER) pagfname);
+
+ /* misc. setup */
+ crcinit();
+ written = 0;
+ prevp = FRESH;
+ DEBUG(("dbminit: succeeded\n"));
+ return (0);
+}
+
+/*
+ * - enstring - concatenate two strings into a malloced area
+ */
+static char * /* NULL if malloc fails */
+enstring(s1, s2)
+ char *s1;
+ char *s2;
+{
+ register char *p;
+
+ p = malloc((size_t) strlen(s1) + (size_t) strlen(s2) + 1);
+ if (p != NULL) {
+ (void)strcpy(p, s1);
+ (void)strcat(p, s2);
+ } else {
+ DEBUG(("enstring(%s, %s) out of memory\n", s1, s2));
+ }
+ return (p);
+}
+
+/*
+ * - dbmclose - close a database
+ */
+int
+dbmclose()
+{
+ register int ret = 0;
+
+ if (pagf == NULL) {
+ DEBUG(("dbmclose: not opened!\n"));
+ return (-1);
+ }
+ if (fclose(pagf) == EOF) {
+ DEBUG(("dbmclose: fclose(pagf) failed\n"));
+ ret = -1;
+ }
+ pagf = basef; /* ensure valid pointer; dbzsync checks it */
+ if (dbzsync() < 0)
+ ret = -1;
+ if (bufpagf != NULL && fclose(bufpagf) == EOF) {
+ DEBUG(("dbmclose: fclose(bufpagf) failed\n"));
+ ret = -1;
+ }
+ if (corepag != NULL)
+#ifdef MMAP
+ if (munmap((caddr_t) corepag, (int)conf.tsize * SOF) == -1) {
+ DEBUG(("dbmclose: munmap failed\n"));
+ ret = -1;
+ }
+#else
+ free((POINTER) corepag);
+#endif
+ corepag = NULL;
+ if (basef) {
+ if (fclose(basef) == EOF) {
+ DEBUG(("dbmclose: fclose(basef) failed\n"));
+ ret = -1;
+ }
+ }
+ if (basefname != NULL)
+ free((POINTER) basefname);
+ basef = NULL;
+ pagf = NULL;
+ if (fclose(dirf) == EOF) {
+ DEBUG(("dbmclose: fclose(dirf) failed\n"));
+ ret = -1;
+ }
+ DEBUG(("dbmclose: %s\n", (ret == 0) ? "succeeded" : "failed"));
+ return (ret);
+}
+
+/*
+ * - dbzsync - push all in-core data out to disk
+ */
+int
+dbzsync()
+{
+ register int ret = 0;
+
+ if (pagf == NULL) {
+ DEBUG(("dbzsync: not opened!\n"));
+ return (-1);
+ }
+ if (!written)
+ return (0);
+
+#ifndef MMAP
+ if (corepag != NULL && !writethrough) {
+ if (putcore(corepag, bufpagf) < 0) {
+ DEBUG(("dbzsync: putcore failed\n"));
+ ret = -1;
+ }
+ }
+#endif
+ if (!conf.olddbz)
+ if (putconf(dirf, &conf) < 0)
+ ret = -1;
+
+ DEBUG(("dbzsync: %s\n", (ret == 0) ? "succeeded" : "failed"));
+ return (ret);
+}
+
+/*
+ * - dbzcancel - cancel writing of in-core data Mostly for use from child
+ * processes. Note that we don't need to futz around with stdio buffers,
+ * because we always fflush them immediately anyway and so they never have
+ * stale data.
+ */
+int
+dbzcancel()
+{
+ if (pagf == NULL) {
+ DEBUG(("dbzcancel: not opened!\n"));
+ return (-1);
+ }
+ written = 0;
+ return (0);
+}
+
+/*
+ * - dbzfetch - fetch() with case mapping built in
+ */
+datum
+dbzfetch(key)
+ datum key;
+{
+ char buffer[DBZMAXKEY + 1];
+ datum mappedkey;
+ register size_t keysize;
+
+ DEBUG(("dbzfetch: (%s)\n", key.dptr));
+
+ /* Key is supposed to be less than DBZMAXKEY */
+ keysize = key.dsize;
+ if (keysize >= DBZMAXKEY) {
+ keysize = DBZMAXKEY;
+ DEBUG(("keysize is %d - truncated to %d\n", key.dsize, DBZMAXKEY));
+ }
+ mappedkey.dptr = mapcase(buffer, key.dptr, keysize);
+ buffer[keysize] = '\0'; /* just a debug aid */
+ mappedkey.dsize = keysize;
+
+ return (fetch(mappedkey));
+}
+
+/*
+ * - fetch - get an entry from the database
+ *
+ * Disgusting fine point, in the name of backward compatibility: if the last
+ * character of "key" is a NUL, that character is (effectively) not part of
+ * the comparison against the stored keys.
+ */
+datum /* dptr NULL, dsize 0 means failure */
+fetch(key)
+ datum key;
+{
+ char buffer[DBZMAXKEY + 1];
+ static of_t key_ptr; /* return value points here */
+ datum output;
+ register size_t keysize;
+ register size_t cmplen;
+ register char *sepp;
+
+ DEBUG(("fetch: (%s)\n", key.dptr));
+ output.dptr = NULL;
+ output.dsize = 0;
+ prevp = FRESH;
+
+ /* Key is supposed to be less than DBZMAXKEY */
+ keysize = key.dsize;
+ if (keysize >= DBZMAXKEY) {
+ keysize = DBZMAXKEY;
+ DEBUG(("keysize is %d - truncated to %d\n", key.dsize, DBZMAXKEY));
+ }
+ if (pagf == NULL) {
+ DEBUG(("fetch: database not open!\n"));
+ return (output);
+ } else if (basef == NULL) { /* basef didn't exist yet */
+ basef = latebase();
+ if (basef == NULL)
+ return (output);
+ }
+ cmplen = keysize;
+ sepp = &conf.fieldsep;
+ if (key.dptr[keysize - 1] == '\0') {
+ cmplen--;
+ sepp = &buffer[keysize - 1];
+ }
+ start(&srch, &key, FRESH);
+ while ((key_ptr = search(&srch)) != NOTFOUND) {
+ DEBUG(("got 0x%lx\n", key_ptr));
+
+ /* fetch the key */
+ if (fseek(basef, key_ptr, SEEK_SET) != 0) {
+ DEBUG(("fetch: seek failed\n"));
+ return (output);
+ }
+ if (fread((POINTER) buffer, 1, keysize, basef) != keysize) {
+ DEBUG(("fetch: read failed\n"));
+ return (output);
+ }
+ /* try it */
+ buffer[keysize] = '\0'; /* terminated for DEBUG */
+ (void)mapcase(buffer, buffer, keysize);
+ DEBUG(("fetch: buffer (%s) looking for (%s) size = %d\n",
+ buffer, key.dptr, keysize));
+ if (memcmp((POINTER) key.dptr, (POINTER) buffer, cmplen) == 0 &&
+ (*sepp == conf.fieldsep || *sepp == '\0')) {
+ /* we found it */
+ output.dptr = (char *)&key_ptr;
+ output.dsize = SOF;
+ DEBUG(("fetch: successful\n"));
+ return (output);
+ }
+ }
+
+ /* we didn't find it */
+ DEBUG(("fetch: failed\n"));
+ prevp = &srch; /* remember where we stopped */
+ return (output);
+}
+
+/*
+ * - latebase - try to open a base file that wasn't there at the start
+ */
+static FILE *
+latebase()
+{
+ register FILE *it;
+
+ if (basefname == NULL) {
+ DEBUG(("latebase: name foulup\n"));
+ return (NULL);
+ }
+ it = fopen(basefname, "r");
+ if (it == NULL) {
+ DEBUG(("latebase: still can't open base\n"));
+ } else {
+ DEBUG(("latebase: late open succeeded\n"));
+ free((POINTER) basefname);
+ basefname = NULL;
+#ifdef _IOFBF
+ (void)setvbuf(it, basebuf, _IOFBF, sizeof(basebuf));
+#endif
+ }
+ if (it != NULL)
+ CloseOnExec((int)fileno(it), 1);
+ return (it);
+}
+
+/*
+ * - dbzstore - store() with case mapping built in
+ */
+int
+dbzstore(key, data)
+ datum key;
+ datum data;
+{
+ char buffer[DBZMAXKEY + 1];
+ datum mappedkey;
+ register size_t keysize;
+
+ DEBUG(("dbzstore: (%s)\n", key.dptr));
+
+ /* Key is supposed to be less than DBZMAXKEY */
+ keysize = key.dsize;
+ if (keysize >= DBZMAXKEY) {
+ DEBUG(("dbzstore: key size too big (%d)\n", key.dsize));
+ return (-1);
+ }
+ mappedkey.dptr = mapcase(buffer, key.dptr, keysize);
+ buffer[keysize] = '\0'; /* just a debug aid */
+ mappedkey.dsize = keysize;
+
+ return (store(mappedkey, data));
+}
+
+/*
+ * - store - add an entry to the database
+ */
+int /* 0 success, -1 failure */
+store(key, data)
+ datum key;
+ datum data;
+{
+ of_t value;
+
+ if (pagf == NULL) {
+ DEBUG(("store: database not open!\n"));
+ return (-1);
+ } else if (basef == NULL) { /* basef didn't exist yet */
+ basef = latebase();
+ if (basef == NULL)
+ return (-1);
+ }
+ if (pagronly) {
+ DEBUG(("store: database open read-only\n"));
+ return (-1);
+ }
+ if (data.dsize != SOF) {
+ DEBUG(("store: value size wrong (%d)\n", data.dsize));
+ return (-1);
+ }
+ if (key.dsize >= DBZMAXKEY) {
+ DEBUG(("store: key size too big (%d)\n", key.dsize));
+ return (-1);
+ }
+ /* copy the value in to ensure alignment */
+ (void)memcpy((POINTER) & value, (POINTER) data.dptr, SOF);
+ DEBUG(("store: (%s, %ld)\n", key.dptr, (long)value));
+ if (!okayvalue(value)) {
+ DEBUG(("store: reserved bit or overflow in 0x%lx\n", value));
+ return (-1);
+ }
+ /* find the place, exploiting previous search if possible */
+ start(&srch, &key, prevp);
+ while (search(&srch) != NOTFOUND)
+ continue;
+
+ prevp = FRESH;
+ conf.used[0]++;
+ DEBUG(("store: used count %ld\n", conf.used[0]));
+ written = 1;
+ return (set(&srch, value));
+}
+
+/*
+ * - dbzincore - control attempts to keep .pag file in core
+ */
+int /* old setting */
+dbzincore(value)
+ int value;
+{
+ register int old = incore;
+
+#ifndef MMAP
+ incore = value;
+#endif
+ return (old);
+}
+
+/*
+ * - dbzwritethrough - write through the pag file in core
+ */
+int /* old setting */
+dbzwritethrough(value)
+ int value;
+{
+ register int old = writethrough;
+
+ writethrough = value;
+ return (old);
+}
+
+/*
+ * - dbztagmask - calculate the correct tagmask for the given base file size
+ */
+long
+dbztagmask(size)
+ register long size;
+{
+ register long m;
+ register long tagmask;
+ register int i;
+
+ if (size <= 0)
+ return (0L); /* silly size */
+
+ for (m = 1, i = 0; m < size; i++, m <<= 1)
+ continue;
+
+ if (m < (1 << TAGSHIFT))
+ return (0L); /* not worth tagging */
+
+ tagmask = (~(unsigned long)0) >> (i + 1);
+ tagmask = tagmask << i;
+ return (tagmask);
+}
+
+/*
+ * - getconf - get configuration from .dir file
+ */
+static int /* 0 success, -1 failure */
+getconf(df, pf, cp)
+ register FILE *df; /* NULL means just give me the default */
+ register FILE *pf; /* NULL means don't care about .pag */
+ register struct dbzconfig *cp;
+{
+ register int c;
+ register int i;
+ int err = 0;
+
+ c = (df != NULL) ? getc(df) : EOF;
+ if (c == EOF) { /* empty file, no configuration known */
+ cp->olddbz = 0;
+ if (df != NULL && pf != NULL && getc(pf) != EOF)
+ cp->olddbz = 1;
+ cp->tsize = DEFSIZE;
+ cp->fieldsep = '\t';
+ for (i = 0; i < NUSEDS; i++)
+ cp->used[i] = 0;
+ cp->valuesize = SOF;
+ mybytemap(cp->bytemap);
+ cp->casemap = DEFCASE;
+ cp->tagenb = TAGENB;
+ cp->tagmask = TAGMASK;
+ cp->tagshift = TAGSHIFT;
+ DEBUG(("getconf: defaults (%ld, %c, (0x%lx/0x%lx<<%d))\n",
+ cp->tsize, cp->casemap, cp->tagenb,
+ cp->tagmask, cp->tagshift));
+ return (0);
+ }
+ (void)ungetc(c, df);
+
+ /* first line, the vital stuff */
+ if (getc(df) != 'd' || getc(df) != 'b' || getc(df) != 'z')
+ err = -1;
+ if (getno(df, &err) != dbzversion)
+ err = -1;
+ cp->tsize = getno(df, &err);
+ cp->fieldsep = (int)getno(df, &err);
+ while ((c = getc(df)) == ' ')
+ continue;
+ cp->casemap = c;
+ cp->tagenb = getno(df, &err);
+ cp->tagmask = getno(df, &err);
+ cp->tagshift = getno(df, &err);
+ cp->valuesize = getno(df, &err);
+ if (cp->valuesize != SOF) {
+ DEBUG(("getconf: wrong of_t size (%d)\n", cp->valuesize));
+ err = -1;
+ cp->valuesize = SOF; /* to protect the loops below */
+ }
+ for (i = 0; i < cp->valuesize; i++)
+ cp->bytemap[i] = getno(df, &err);
+ if (getc(df) != '\n')
+ err = -1;
+#ifdef DBZDEBUG
+ DEBUG(("size %ld, sep %d, cmap %c, tags 0x%lx/0x%lx<<%d, ", cp->tsize,
+ cp->fieldsep, cp->casemap, cp->tagenb, cp->tagmask,
+ cp->tagshift));
+ DEBUG(("bytemap (%d)", cp->valuesize));
+ for (i = 0; i < cp->valuesize; i++) {
+ DEBUG((" %d", cp->bytemap[i]));
+ }
+ DEBUG(("\n"));
+#endif
+
+ /* second line, the usages */
+ for (i = 0; i < NUSEDS; i++)
+ cp->used[i] = getno(df, &err);
+ if (getc(df) != '\n')
+ err = -1;
+ DEBUG(("used %ld %ld %ld...\n", cp->used[0], cp->used[1], cp->used[2]));
+
+ if (err < 0) {
+ DEBUG(("getconf error\n"));
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * - getno - get a long
+ */
+static long
+getno(f, ep)
+ FILE *f;
+ int *ep;
+{
+ register char *p;
+#define MAXN 50
+ char getbuf[MAXN];
+ register int c;
+
+ while ((c = getc(f)) == ' ')
+ continue;
+ if (c == EOF || c == '\n') {
+ DEBUG(("getno: missing number\n"));
+ *ep = -1;
+ return (0);
+ }
+ p = getbuf;
+ *p++ = c;
+ while ((c = getc(f)) != EOF && c != '\n' && c != ' ')
+ if (p < &getbuf[MAXN - 1])
+ *p++ = c;
+ if (c == EOF) {
+ DEBUG(("getno: EOF\n"));
+ *ep = -1;
+ } else
+ (void)ungetc(c, f);
+ *p = '\0';
+
+ if (strspn(getbuf, "-1234567890") != strlen(getbuf)) {
+ DEBUG(("getno: `%s' non-numeric\n", getbuf));
+ *ep = -1;
+ }
+ return (atol(getbuf));
+}
+
+/*
+ * - putconf - write configuration to .dir file
+ */
+static int /* 0 success, -1 failure */
+putconf(f, cp)
+ register FILE *f;
+ register struct dbzconfig *cp;
+{
+ register int i;
+ register int ret = 0;
+
+ if (fseek(f, (of_t) 0, SEEK_SET) != 0) {
+ DEBUG(("fseek failure in putconf\n"));
+ ret = -1;
+ }
+ (void)fprintf(f, "dbz %d %ld %d %c %ld %ld %d %d", dbzversion, cp->tsize,
+ cp->fieldsep, cp->casemap, cp->tagenb,
+ cp->tagmask, cp->tagshift, cp->valuesize);
+ for (i = 0; i < cp->valuesize; i++)
+ (void)fprintf(f, " %d", cp->bytemap[i]);
+ (void)fprintf(f, "\n");
+ for (i = 0; i < NUSEDS; i++)
+ (void)fprintf(f, "%ld%c", cp->used[i], (i < NUSEDS - 1) ? ' ' : '\n');
+
+ (void)fflush(f);
+ if (ferror(f))
+ ret = -1;
+
+ DEBUG(("putconf status %d\n", ret));
+ return (ret);
+}
+
+/*
+ * - getcore - try to set up an in-core copy of .pag file
+ */
+static of_t * /* pointer to copy, or NULL */
+getcore(f)
+ FILE *f;
+{
+ register char *it;
+#ifdef MMAP
+ struct stat st;
+
+ if (fstat(fileno(f), &st) == -1) {
+ DEBUG(("getcore: fstat failed\n"));
+ return (NULL);
+ }
+ if (((size_t) conf.tsize * SOF) > st.st_size) {
+ /* file too small; extend it */
+ if (ftruncate((int)fileno(f), conf.tsize * SOF) == -1) {
+ DEBUG(("getcore: ftruncate failed\n"));
+ return (NULL);
+ }
+ }
+ it = mmap((caddr_t) 0, (size_t) conf.tsize * SOF,
+ pagronly ? PROT_READ : PROT_WRITE | PROT_READ, MAP__ARG,
+ (int)fileno(f), (off_t) 0);
+ if (it == (char *)-1) {
+ DEBUG(("getcore: mmap failed\n"));
+ return (NULL);
+ }
+#ifdef MC_ADVISE
+ /* not present in all versions of mmap() */
+ madvise(it, (size_t) conf.tsize * SOF, MADV_RANDOM);
+#endif
+#else
+ register of_t *p;
+ register size_t i;
+ register size_t nread;
+ it = malloc((size_t) conf.tsize * SOF);
+ if (it == NULL) {
+ DEBUG(("getcore: malloc failed\n"));
+ return (NULL);
+ }
+ nread = fread((POINTER) it, SOF, (size_t) conf.tsize, f);
+ if (ferror(f)) {
+ DEBUG(("getcore: read failed\n"));
+ free((POINTER) it);
+ return (NULL);
+ }
+ /* NOSTRICT *//* Possible pointer alignment problem */
+ p = (of_t *) it + nread;
+ i = (size_t) conf.tsize - nread;
+ while (i-- > 0)
+ *p++ = VACANT;
+#endif
+ /* NOSTRICT *//* Possible pointer alignment problem */
+ return ((of_t *) it);
+}
+
+#ifndef MMAP
+/*
+ * - putcore - try to rewrite an in-core table
+ */
+static int /* 0 okay, -1 fail */
+putcore(tab, f)
+ of_t *tab;
+ FILE *f;
+{
+ if (fseek(f, (of_t) 0, SEEK_SET) != 0) {
+ DEBUG(("fseek failure in putcore\n"));
+ return (-1);
+ }
+ (void)fwrite((POINTER) tab, SOF, (size_t) conf.tsize, f);
+ (void)fflush(f);
+ return ((ferror(f)) ? -1 : 0);
+}
+#endif
+
+/*
+ * - start - set up to start or restart a search
+ */
+static void
+start(sp, kp, osp)
+ register struct searcher *sp;
+ register datum *kp;
+ register struct searcher *osp; /* may be FRESH, i.e. NULL */
+{
+ register long h;
+
+ h = hash(kp->dptr, kp->dsize);
+ if (osp != FRESH && osp->hash == h) {
+ if (sp != osp)
+ *sp = *osp;
+ DEBUG(("search restarted\n"));
+ } else {
+ sp->hash = h;
+ sp->tag = MKTAG(h / conf.tsize);
+ DEBUG(("tag 0x%lx\n", sp->tag));
+ sp->place = h % conf.tsize;
+ sp->tabno = 0;
+ sp->run = (conf.olddbz) ? conf.tsize : MAXRUN;
+ sp->aborted = 0;
+ }
+ sp->seen = 0;
+}
+
+/*
+ * - search - conduct part of a search
+ */
+static of_t /* NOTFOUND if we hit VACANT or error */
+search(sp)
+ register struct searcher *sp;
+{
+ register of_t dest;
+ register of_t value;
+ of_t val; /* buffer for value (can't fread register) */
+ register of_t place;
+
+ if (sp->aborted)
+ return (NOTFOUND);
+
+ for (;;) {
+ /* determine location to be examined */
+ place = sp->place;
+ if (sp->seen) {
+ /* go to next location */
+ if (--sp->run <= 0) {
+ sp->tabno++;
+ sp->run = MAXRUN;
+ }
+ place = (place + 1) % conf.tsize + sp->tabno * conf.tsize;
+ sp->place = place;
+ } else
+ sp->seen = 1; /* now looking at current location */
+ DEBUG(("search @ %ld\n", place));
+
+ /* get the tagged value */
+ if (corepag != NULL && place < conf.tsize) {
+ DEBUG(("search: in core\n"));
+ value = MAPIN(corepag[place]);
+ } else {
+ /* seek, if necessary */
+ dest = place * SOF;
+ if (pagpos != dest) {
+ if (fseek(pagf, dest, SEEK_SET) != 0) {
+ DEBUG(("search: seek failed\n"));
+ pagpos = -1;
+ sp->aborted = 1;
+ return (NOTFOUND);
+ }
+ pagpos = dest;
+ }
+ /* read it */
+ if (fread((POINTER) & val, sizeof(val), 1, pagf) == 1)
+ value = MAPIN(val);
+ else if (ferror(pagf)) {
+ DEBUG(("search: read failed\n"));
+ pagpos = -1;
+ sp->aborted = 1;
+ return (NOTFOUND);
+ } else
+ value = VACANT;
+
+ /* and finish up */
+ pagpos += sizeof(val);
+ }
+
+ /* vacant slot is always cause to return */
+ if (value == VACANT) {
+ DEBUG(("search: empty slot\n"));
+ return (NOTFOUND);
+ };
+
+ /* check the tag */
+ value = UNBIAS(value);
+ DEBUG(("got 0x%lx\n", value));
+ if (!HASTAG(value)) {
+ DEBUG(("tagless\n"));
+ return (value);
+ } else if (TAG(value) == sp->tag) {
+ DEBUG(("match\n"));
+ return (NOTAG(value));
+ } else {
+ DEBUG(("mismatch 0x%lx\n", TAG(value)));
+ }
+ }
+ /* NOTREACHED */
+}
+
+/*
+ * - okayvalue - check that a value can be stored
+ */
+static int /* predicate */
+okayvalue(value)
+ of_t value;
+{
+ if (HASTAG(value))
+ return (0);
+#ifdef OVERFLOW
+ if (value == LONG_MAX) /* BIAS() and UNBIAS() will overflow */
+ return (0);
+#endif
+ return (1);
+}
+
+/*
+ * - set - store a value into a location previously found by search
+ */
+static int /* 0 success, -1 failure */
+set(sp, value)
+ register struct searcher *sp;
+ of_t value;
+{
+ register of_t place = sp->place;
+ register of_t v = value;
+
+ if (sp->aborted)
+ return (-1);
+
+ if (CANTAG(v) && !conf.olddbz) {
+ v |= sp->tag | taghere;
+ if (v != UNBIAS(VACANT))/* BIAS(v) won't look VACANT */
+#ifdef OVERFLOW
+ if (v != LONG_MAX) /* and it won't overflow */
+#endif
+ value = v;
+ }
+ DEBUG(("tagged value is 0x%lx\n", value));
+ value = BIAS(value);
+ value = MAPOUT(value);
+
+ /* If we have the index file in memory, use it */
+ if (corepag != NULL && place < conf.tsize) {
+ corepag[place] = value;
+ DEBUG(("set: incore\n"));
+#ifdef MMAP
+ return (0);
+#else
+ if (!writethrough)
+ return (0);
+#endif
+ }
+ /* seek to spot */
+ pagpos = -1; /* invalidate position memory */
+ if (fseek(pagf, (of_t) (place * SOF), SEEK_SET) != 0) {
+ DEBUG(("set: seek failed\n"));
+ sp->aborted = 1;
+ return (-1);
+ }
+ /* write in data */
+ if (fwrite((POINTER) & value, SOF, 1, pagf) != 1) {
+ DEBUG(("set: write failed\n"));
+ sp->aborted = 1;
+ return (-1);
+ }
+ /* fflush improves robustness, and buffer re-use is rare anyway */
+ if (fflush(pagf) == EOF) {
+ DEBUG(("set: fflush failed\n"));
+ sp->aborted = 1;
+ return (-1);
+ }
+ DEBUG(("set: succeeded\n"));
+ return (0);
+}
+
+/*
+ * - mybytemap - determine this machine's byte map
+ *
+ * A byte map is an array of ints, sizeof(of_t) of them. The 0th int is the
+ * byte number of the high-order byte in my of_t, and so forth.
+ */
+static void
+mybytemap(map)
+ int map[]; /* -> int[SOF] */
+{
+ union {
+ of_t o;
+ char c[SOF];
+ } u;
+ register int *mp = &map[SOF];
+ register int ntodo;
+ register int i;
+
+ u.o = 1;
+ for (ntodo = (int)SOF; ntodo > 0; ntodo--) {
+ for (i = 0; i < SOF; i++)
+ /* SUPPRESS 112 *//* Retrieving char where long is stored */
+ if (u.c[i] != 0)
+ break;
+ if (i == SOF) {
+ /* trouble -- set it to *something* consistent */
+ DEBUG(("mybytemap: nonexistent byte %d!!!\n", ntodo));
+ for (i = 0; i < SOF; i++)
+ map[i] = i;
+ return;
+ }
+ DEBUG(("mybytemap: byte %d\n", i));
+ *--mp = i;
+ /* SUPPRESS 112 *//* Retrieving char where long is stored */
+ while (u.c[i] != 0)
+ u.o <<= 1;
+ }
+}
+
+/*
+ * - bytemap - transform an of_t from byte ordering map1 to map2
+ */
+static of_t /* transformed result */
+bytemap(ino, map1, map2)
+ of_t ino;
+ int *map1;
+ int *map2;
+{
+ union oc {
+ of_t o;
+ char c[SOF];
+ };
+ union oc in;
+ union oc out;
+ register int i;
+
+ in.o = ino;
+ for (i = 0; i < SOF; i++)
+ out.c[map2[i]] = in.c[map1[i]];
+ return (out.o);
+}
+
+/*
+ * This is a simplified version of the pathalias hashing function. Thanks to
+ * Steve Belovin and Peter Honeyman
+ *
+ * hash a string into a long int. 31 bit crc (from andrew appel). the crc table
+ * is computed at run time by crcinit() -- we could precompute, but it takes
+ * 1 clock tick on a 750.
+ *
+ * This fast table calculation works only if POLY is a prime polynomial in the
+ * field of integers modulo 2. Since the coefficients of a 32-bit polynomial
+ * won't fit in a 32-bit word, the high-order bit is implicit. IT MUST ALSO
+ * BE THE CASE that the coefficients of orders 31 down to 25 are zero.
+ * Happily, we have candidates, from E. J. Watson, "Primitive Polynomials
+ * (Mod 2)", Math. Comp. 16 (1962): x^32 + x^7 + x^5 + x^3 + x^2 + x^1 + x^0
+ * x^31 + x^3 + x^0
+ *
+ * We reverse the bits to get: 111101010000000000000000000000001 but drop the
+ * last 1 f 5 0 0 0 0 0 0 010010000000000000000000000000001
+ * ditto, for 31-bit crc 4 8 0 0 0 0 0 0
+ */
+
+#define POLY 0x48000000L /* 31-bit polynomial (avoids sign problems) */
+
+static long CrcTable[128];
+
+/*
+ * - crcinit - initialize tables for hash function
+ */
+static void
+crcinit()
+{
+ register int i, j;
+ register long sum;
+
+ for (i = 0; i < 128; ++i) {
+ sum = 0L;
+ for (j = 7 - 1; j >= 0; --j)
+ if (i & (1 << j))
+ sum ^= POLY >> j;
+ CrcTable[i] = sum;
+ }
+ DEBUG(("crcinit: done\n"));
+}
+
+/*
+ * - hash - Honeyman's nice hashing function
+ */
+static long
+hash(name, size)
+ register char *name;
+ register int size;
+{
+ register long sum = 0L;
+
+ while (size--) {
+ sum = (sum >> 7) ^ CrcTable[(sum ^ (*name++)) & 0x7f];
+ }
+ DEBUG(("hash: returns (%ld)\n", sum));
+ return (sum);
+}
+
+/*
+ * case-mapping stuff
+ *
+ * Borrowed from C News, by permission of the authors. Somewhat modified.
+ *
+ * We exploit the fact that we are dealing only with headers here, and headers
+ * are limited to the ASCII characters by RFC822. It is barely possible that
+ * we might be dealing with a translation into another character set, but in
+ * particular it's very unlikely for a header character to be outside
+ * -128..255.
+ *
+ * Life would be a whole lot simpler if tolower() could safely and portably be
+ * applied to any char.
+ */
+
+#define OFFSET 128 /* avoid trouble with negative chars */
+
+/* must call casencmp before invoking TOLOW... */
+#define TOLOW(c) (cmap[(c)+OFFSET])
+
+/* ...but the use of it in CISTREQN is safe without the preliminary call (!) */
+/* CISTREQN is an optimised case-insensitive strncmp(a,b,n)==0; n > 0 */
+#define CISTREQN(a, b, n) \
+ (TOLOW((a)[0]) == TOLOW((b)[0]) && casencmp(a, b, n) == 0)
+
+#define MAPSIZE (256+OFFSET)
+static char cmap[MAPSIZE]; /* relies on init to '\0' */
+static int mprimed = 0; /* has cmap been set up? */
+
+/*
+ * - mapprime - set up case-mapping stuff
+ */
+static void
+mapprime()
+{
+ register char *lp;
+ register char *up;
+ register int c;
+ register int i;
+ static char lower[] = "abcdefghijklmnopqrstuvwxyz";
+ static char upper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ for (lp = lower, up = upper; *lp != '\0'; lp++, up++) {
+ c = *lp;
+ cmap[c + OFFSET] = c;
+ cmap[*up + OFFSET] = c;
+ }
+ for (i = 0; i < MAPSIZE; i++)
+ if (cmap[i] == '\0')
+ cmap[i] = (char)(i - OFFSET);
+ mprimed = 1;
+}
+
+/*
+ * - casencmp - case-independent strncmp
+ */
+static int /* < == > 0 */
+casencmp(s1, s2, len)
+ char *s1;
+ char *s2;
+ int len;
+{
+ register char *p1;
+ register char *p2;
+ register int n;
+
+ if (!mprimed)
+ mapprime();
+
+ p1 = s1;
+ p2 = s2;
+ n = len;
+ while (--n >= 0 && *p1 != '\0' && TOLOW(*p1) == TOLOW(*p2)) {
+ p1++;
+ p2++;
+ }
+ if (n < 0)
+ return (0);
+
+ /*
+ * The following case analysis is necessary so that characters which look
+ * negative collate low against normal characters but high against the
+ * end-of-string NUL.
+ */
+ if (*p1 == '\0' && *p2 == '\0')
+ return (0);
+ else if (*p1 == '\0')
+ return (-1);
+ else if (*p2 == '\0')
+ return (1);
+ else
+ return (TOLOW(*p1) - TOLOW(*p2));
+}
+
+/*
+ * - mapcase - do case-mapped copy
+ */
+static char * /* returns src or dst */
+mapcase(dst, src, siz)
+ char *dst; /* destination, used only if mapping needed */
+ char *src; /* source; src == dst is legal */
+ size_t siz;
+{
+ register char *s;
+ register char *d;
+ register char *c; /* case break */
+ register char *e; /* end of source */
+
+
+ c = cipoint(src, siz);
+ if (c == NULL)
+ return (src);
+
+ if (!mprimed)
+ mapprime();
+ s = src;
+ e = s + siz;
+ d = dst;
+
+ while (s < c)
+ *d++ = *s++;
+ while (s < e)
+ *d++ = TOLOW(*s++);
+
+ return (dst);
+}
+
+/*
+ * - cipoint - where in this message-ID does it become case-insensitive?
+ *
+ * The RFC822 code is not quite complete. Absolute, total, full RFC822
+ * compliance requires a horrible parsing job, because of the arcane quoting
+ * conventions -- abc"def"ghi is not equivalent to abc"DEF"ghi, for example.
+ * There are three or four things that might occur in the domain part of a
+ * message-id that are case-sensitive. They don't seem to ever occur in real
+ * news, thank Cthulhu. (What? You were expecting a merciful and forgiving
+ * deity to be invoked in connection with RFC822? Forget it; none of them
+ * would come near it.)
+ */
+static char * /* pointer into s, or NULL for "nowhere" */
+cipoint(s, siz)
+ char *s;
+ size_t siz;
+{
+ register char *p;
+ static char post[] = "postmaster";
+ static int plen = sizeof(post) - 1;
+
+ switch (conf.casemap) {
+ case '0': /* unmapped, sensible */
+ return (NULL);
+ case 'C': /* C News, RFC 822 conformant (approx.) */
+ p = memchr((POINTER) s, '@', siz);
+ if (p == NULL) /* no local/domain split */
+ return (NULL); /* assume all local */
+ if (p - (s + 1) == plen && CISTREQN(s + 1, post, plen)) {
+ /* crazy -- "postmaster" is case-insensitive */
+ return (s);
+ }
+ return (p);
+ case '=': /* 2.11, neither sensible nor conformant */
+ return (s); /* all case-insensitive */
+ }
+
+ DEBUG(("cipoint: unknown case mapping `%c'\n", conf.casemap));
+ return (NULL); /* just leave it alone */
+}
+
+/*
+ * - dbzdebug - control dbz debugging at run time
+ */
+#ifdef DBZDEBUG
+int /* old value */
+dbzdebug(value)
+ int value;
+{
+ register int old = debug;
+
+ debug = value;
+ return (old);
+}
+#endif
diff --git a/pttbbs/innbbsd/dbz.h b/pttbbs/innbbsd/dbz.h
new file mode 100644
index 00000000..4883c1e6
--- /dev/null
+++ b/pttbbs/innbbsd/dbz.h
@@ -0,0 +1,36 @@
+/* for dbm and dbz */
+#ifndef _DBZ_H
+#define _DBZ_H
+typedef struct {
+ char *dptr;
+ int dsize;
+} datum;
+
+/* standard dbm functions */
+extern int dbminit();
+extern datum fetch();
+extern int store();
+extern int delete(); /* not in dbz */
+extern datum firstkey(); /* not in dbz */
+extern datum nextkey(); /* not in dbz */
+extern int dbmclose(); /* in dbz, but not in old dbm */
+
+/* new stuff for dbz */
+extern int dbzfresh();
+extern int dbzagain();
+extern datum dbzfetch();
+extern int dbzstore();
+extern int dbzsync();
+extern long dbzsize();
+extern int dbzincore();
+extern int dbzcancel();
+extern int dbzdebug();
+
+/*
+ * In principle we could handle unlimited-length keys by operating a chunk at
+ * a time, but it's not worth it in practice. Setting a nice large bound on
+ * them simplifies the code and doesn't hurt anything.
+ */
+#define DBZMAXKEY 255
+
+#endif /* _DBZ_H */
diff --git a/pttbbs/innbbsd/dbztool.c b/pttbbs/innbbsd/dbztool.c
new file mode 100644
index 00000000..c2d77476
--- /dev/null
+++ b/pttbbs/innbbsd/dbztool.c
@@ -0,0 +1,97 @@
+#include <string.h>
+#include <unistd.h>
+#include <sys/file.h>
+#include "his.h"
+#include "externs.h"
+#include <time.h>
+
+#define DEBUG 1
+#undef DEBUG
+
+static datum content, inputkey;
+static char dboutput[1025];
+static char dbinput[1025];
+
+#if 0
+enum {
+ SUBJECT, FROM, NAME
+};
+#endif
+
+char *
+DBfetch(key)
+ char *key;
+{
+ char *ptr;
+ if (key == NULL)
+ return NULL;
+ sprintf(dbinput, "%.510s", key);
+ inputkey.dptr = dbinput;
+ inputkey.dsize = strlen(dbinput);
+ content.dptr = dboutput;
+ ptr = (char *)HISfilesfor(&inputkey, &content);
+ if (ptr == NULL) {
+ return NULL;
+ }
+ return ptr;
+}
+
+int
+DBstore(key, paths)
+ char *key;
+ char *paths;
+{
+ time_t now;
+ time(&now);
+ if (key == NULL)
+ return -1;
+ sprintf(dbinput, "%.510s", key);
+ inputkey.dptr = dbinput;
+ inputkey.dsize = strlen(dbinput);
+ if (HISwrite(&inputkey, now, paths) == FALSE) {
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+int
+storeDB(mid, paths)
+ char *mid;
+ char *paths;
+{
+ char *ptr;
+ ptr = DBfetch(mid);
+ if (ptr != NULL) {
+ return 0;
+ } else {
+ return DBstore(mid, paths);
+ }
+}
+
+int
+my_mkdir(idir, mode)
+ char *idir;
+ int mode;
+{
+ char buffer[LEN];
+ char *ptr, *dir = buffer;
+ struct stat st;
+ strncpy(dir, idir, LEN - 1);
+ for (; dir != NULL && *dir;) {
+ ptr = (char *)strchr(dir, '/');
+ if (ptr != NULL) {
+ *ptr = '\0';
+ }
+ if (stat(dir, &st) != 0) {
+ if (mkdir(dir, mode) != 0)
+ return -1;
+ }
+ chdir(dir);
+ if (ptr != NULL)
+ dir = ptr + 1;
+ else
+ dir = ptr;
+ }
+ return 0;
+}
diff --git a/pttbbs/innbbsd/echobbslib.c b/pttbbs/innbbsd/echobbslib.c
new file mode 100644
index 00000000..0d34a443
--- /dev/null
+++ b/pttbbs/innbbsd/echobbslib.c
@@ -0,0 +1,777 @@
+#include <stdlib.h>
+#if defined( LINUX )
+#include "innbbsconf.h"
+#include "bbslib.h"
+#include <stdarg.h>
+#else
+#include <stdarg.h>
+#include "innbbsconf.h"
+#include "bbslib.h"
+#endif
+#include "config.h"
+
+#include "externs.h"
+
+char INNBBSCONF[MAXPATHLEN];
+char INNDHOME[MAXPATHLEN];
+char HISTORY[MAXPATHLEN];
+char LOGFILE[MAXPATHLEN];
+char MYBBSID[MAXPATHLEN];
+char ECHOMAIL[MAXPATHLEN];
+char BBSFEEDS[MAXPATHLEN];
+char LOCALDAEMON[MAXPATHLEN];
+
+int His_Maint_Min = HIS_MAINT_MIN;
+int His_Maint_Hour = HIS_MAINT_HOUR;
+int Expiredays = EXPIREDAYS;
+
+nodelist_t *NODELIST = NULL, **NODELIST_BYNODE = NULL;
+newsfeeds_t *NEWSFEEDS = NULL, **NEWSFEEDS_BYBOARD = NULL;
+static char *NODELIST_BUF, *NEWSFEEDS_BUF;
+int NFCOUNT, NLCOUNT;
+int LOCALNODELIST = 0, NONENEWSFEEDS = 0;
+
+#ifndef _PATH_BBSHOME
+#define _PATH_BBSHOME "/u/staff/bbsroot/csie_util/bntpd/home"
+#endif
+
+static FILE *bbslogfp;
+
+static int
+ verboseFlag = 0;
+
+static char *
+ verboseFilename = NULL;
+static char verbosename[MAXPATHLEN];
+
+void
+verboseon(filename)
+ char *filename;
+{
+ verboseFlag = 1;
+ if (filename != NULL) {
+ if (strchr(filename, '/') == NULL) {
+ sprintf(verbosename, "%s/innd/%s", BBSHOME, filename);
+ filename = verbosename;
+ }
+ }
+ verboseFilename = filename;
+}
+void
+verboseoff()
+{
+ verboseFlag = 0;
+}
+
+void
+setverboseon()
+{
+ verboseFlag = 1;
+}
+
+int
+isverboselog()
+{
+ return verboseFlag;
+}
+
+void
+setverboseoff()
+{
+ verboseoff();
+ if (bbslogfp != NULL) {
+ fclose(bbslogfp);
+ bbslogfp = NULL;
+ }
+}
+
+void
+verboselog(char *fmt,...)
+{
+ va_list ap;
+ char datebuf[40];
+ time_t now;
+
+ if (verboseFlag == 0)
+ return;
+
+ va_start(ap, fmt);
+
+ time(&now);
+ strftime(datebuf, sizeof(datebuf), "%b %d %X ", localtime(&now));
+
+ if (bbslogfp == NULL) {
+ if (verboseFilename != NULL)
+ bbslogfp = fopen(verboseFilename, "a");
+ else
+ bbslogfp = fdopen(1, "a");
+ }
+ if (bbslogfp == NULL) {
+ va_end(ap);
+ return;
+ }
+ fprintf(bbslogfp, "%s[%d] ", datebuf, getpid());
+ vfprintf(bbslogfp, fmt, ap);
+ fflush(bbslogfp);
+ va_end(ap);
+}
+
+void
+#ifdef PalmBBS
+xbbslog(char *fmt,...)
+#else
+bbslog(char *fmt,...)
+#endif
+{
+ va_list ap;
+ char datebuf[40];
+ time_t now;
+
+ va_start(ap, fmt);
+
+ time(&now);
+ strftime(datebuf, sizeof(datebuf), "%b %d %X ", localtime(&now));
+
+ if (bbslogfp == NULL) {
+ bbslogfp = fopen(LOGFILE, "a");
+ }
+ if (bbslogfp == NULL) {
+ va_end(ap);
+ return;
+ }
+ fprintf(bbslogfp, "%s[%d] ", datebuf, getpid());
+ vfprintf(bbslogfp, fmt, ap);
+ fflush(bbslogfp);
+ va_end(ap);
+}
+
+int
+initial_bbs(outgoing)
+ char *outgoing;
+{
+ FILE *FN;
+ char *bbsnameptr = NULL;
+
+ /* reopen bbslog */
+ if (bbslogfp != NULL) {
+ fclose(bbslogfp);
+ bbslogfp = NULL;
+ }
+#ifdef WITH_ECHOMAIL
+ init_echomailfp();
+ init_bbsfeedsfp();
+#endif
+
+ LOCALNODELIST = 0, NONENEWSFEEDS = 0;
+ sprintf(INNDHOME, "%s/innd", BBSHOME);
+ sprintf(HISTORY, "%s/history", INNDHOME);
+ sprintf(LOGFILE, "%s/bbslog", INNDHOME);
+ sprintf(ECHOMAIL, "%s/echomail.log", BBSHOME);
+ sprintf(LOCALDAEMON, "%s/.innbbsd", INNDHOME);
+ sprintf(INNBBSCONF, "%s/innbbs.conf", INNDHOME);
+ sprintf(BBSFEEDS, "%s/bbsfeeds.log", INNDHOME);
+
+ if (isfile(INNBBSCONF)) {
+ FILE *conf;
+ char buffer[MAXPATHLEN];
+ conf = fopen(INNBBSCONF, "r");
+ if (conf != NULL) {
+ while (fgets(buffer, sizeof buffer, conf) != NULL) {
+ char *ptr, *front = NULL, *value = NULL, *value2 = NULL,
+ *value3 = NULL;
+ if (buffer[0] == '#' || buffer[0] == '\n')
+ continue;
+ for (front = buffer; *front && isspace(*front); front++);
+ for (ptr = front; *ptr && !isspace(*ptr); ptr++);
+ if (*ptr == '\0')
+ continue;
+ *ptr++ = '\0';
+ for (; *ptr && isspace(*ptr); ptr++);
+ if (*ptr == '\0')
+ continue;
+ value = ptr++;
+ for (; *ptr && !isspace(*ptr); ptr++);
+ if (*ptr) {
+ *ptr++ = '\0';
+ for (; *ptr && isspace(*ptr); ptr++);
+ value2 = ptr++;
+ for (; *ptr && !isspace(*ptr); ptr++);
+ if (*ptr) {
+ *ptr++ = '\0';
+ for (; *ptr && isspace(*ptr); ptr++);
+ value3 = ptr++;
+ for (; *ptr && !isspace(*ptr); ptr++);
+ if (*ptr) {
+ *ptr++ = '\0';
+ }
+ }
+ }
+ if (strcasecmp(front, "expiredays") == 0) {
+ Expiredays = atoi(value);
+ if (Expiredays < 0) {
+ Expiredays = EXPIREDAYS;
+ }
+ } else if (strcasecmp(front, "expiretime") == 0) {
+ ptr = strchr(value, ':');
+ if (ptr == NULL) {
+ fprintf(stderr, "Syntax error in innbbs.conf\n");
+ } else {
+ *ptr++ = '\0';
+ His_Maint_Hour = atoi(value);
+ His_Maint_Min = atoi(ptr);
+ if (His_Maint_Hour < 0)
+ His_Maint_Hour = HIS_MAINT_HOUR;
+ if (His_Maint_Min < 0)
+ His_Maint_Min = HIS_MAINT_MIN;
+ }
+ } else if (strcasecmp(front, "newsfeeds") == 0) {
+ if (strcmp(value, "none") == 0)
+ NONENEWSFEEDS = 1;
+ } else if (strcasecmp(front, "nodelist") == 0) {
+ if (strcmp(value, "local") == 0)
+ LOCALNODELIST = 1;
+ } /* else if ( strcasecmp(front,"newsfeeds") ==
+ * 0) { printf("newsfeeds %s\n", value); }
+ * else if ( strcasecmp(front,"nodelist") ==
+ * 0) { printf("nodelist %s\n", value); }
+ * else if ( strcasecmp(front,"bbsname") ==
+ * 0) { printf("bbsname %s\n", value); } */
+ }
+ fclose(conf);
+ }
+ }
+#ifdef WITH_ECHOMAIL
+ bbsnameptr = fileglue("%s/bbsname.bbs", INNDHOME);
+ if ((FN = fopen(bbsnameptr, "r")) == NULL) {
+ fprintf(stderr, "can't open file %s\n", bbsnameptr);
+ return 0;
+ }
+ while (fscanf(FN, "%s", MYBBSID) != EOF);
+ fclose(FN);
+ if (!isdir(fileglue("%s/out.going", BBSHOME))) {
+ mkdir((char *)fileglue("%s/out.going", BBSHOME), 0750);
+ }
+ if (NONENEWSFEEDS == 0)
+ readnffile(INNDHOME);
+ if (LOCALNODELIST == 0) {
+ if (readnlfile(INNDHOME, outgoing) != 0)
+ return 0;
+ }
+#endif
+ return 1;
+}
+
+static int
+nf_byboardcmp(a, b)
+ newsfeeds_t **a, **b;
+{
+ /*
+ * if (!a || !*a || !(*a)->board) return -1; if (!b || !*b ||
+ * !(*b)->board) return 1;
+ */
+ return strcasecmp((*a)->board, (*b)->board);
+}
+
+static int
+nfcmp(a, b)
+ newsfeeds_t *a, *b;
+{
+ /*
+ * if (!a || !a->newsgroups) return -1; if (!b || !b->newsgroups) return
+ * 1;
+ */
+ return strcasecmp(a->newsgroups, b->newsgroups);
+}
+
+static int
+nlcmp(a, b)
+ nodelist_t *a, *b;
+{
+ /*
+ * if (!a || !a->host) return -1; if (!b || !b->host) return 1;
+ */
+ return strcasecmp(a->host, b->host);
+}
+
+static int
+nl_bynodecmp(a, b)
+ nodelist_t **a, **b;
+{
+ /*
+ * if (!a || !*a || !(*a)->node) return -1; if (!b || !*b || !(*b)->node)
+ * return 1;
+ */
+ return strcasecmp((*a)->node, (*b)->node);
+}
+
+/* read in newsfeeds.bbs and nodelist.bbs */
+int
+readnlfile(inndhome, outgoing)
+ char *inndhome;
+ char *outgoing;
+{
+ FILE *fp;
+ char buff[1024];
+ struct stat st;
+ int i, count;
+ char *ptr, *nodelistptr;
+ static int lastcount = 0;
+
+ sprintf(buff, "%s/nodelist.bbs", inndhome);
+ fp = fopen(buff, "r");
+ if (fp == NULL) {
+ fprintf(stderr, "open fail %s", buff);
+ return -1;
+ }
+ if (fstat(fileno(fp), &st) != 0) {
+ fprintf(stderr, "stat fail %s", buff);
+ return -1;
+ }
+ if (NODELIST_BUF == NULL) {
+ NODELIST_BUF = (char *)mymalloc(st.st_size + 1);
+ } else {
+ NODELIST_BUF = (char *)myrealloc(NODELIST_BUF, st.st_size + 1);
+ }
+ i = 0, count = 0;
+ while (fgets(buff, sizeof buff, fp) != NULL) {
+ if (buff[0] == '#')
+ continue;
+ if (buff[0] == '\n')
+ continue;
+ strcpy(NODELIST_BUF + i, buff);
+ i += strlen(buff);
+ count++;
+ }
+ fclose(fp);
+ if (NODELIST == NULL) {
+ NODELIST = (nodelist_t *) mymalloc(sizeof(nodelist_t) * (count + 1));
+ NODELIST_BYNODE = (nodelist_t **) mymalloc(sizeof(nodelist_t *) * (count + 1));
+ } else {
+ NODELIST = (nodelist_t *) myrealloc(NODELIST, sizeof(nodelist_t) * (count + 1));
+ NODELIST_BYNODE = (nodelist_t **) myrealloc(NODELIST_BYNODE, sizeof(nodelist_t *) * (count + 1));
+ }
+ for (i = lastcount; i < count; i++) {
+ NODELIST[i].feedfp = NULL;
+ }
+ lastcount = count;
+ NLCOUNT = 0;
+ for (ptr = NODELIST_BUF; (nodelistptr = (char *)strchr(ptr, '\n')) != NULL; ptr = nodelistptr + 1, NLCOUNT++) {
+ char *nptr, *tptr;
+ *nodelistptr = '\0';
+ NODELIST[NLCOUNT].host = "";
+ NODELIST[NLCOUNT].exclusion = "";
+ NODELIST[NLCOUNT].node = "";
+ NODELIST[NLCOUNT].protocol = "IHAVE(119)";
+ NODELIST[NLCOUNT].comments = "";
+ NODELIST_BYNODE[NLCOUNT] = NODELIST + NLCOUNT;
+ for (nptr = ptr; *nptr && isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0') {
+ bbslog("nodelist.bbs %d entry read error\n", NLCOUNT);
+ return -1;
+ }
+ /* NODELIST[NLCOUNT].id = nptr; */
+ NODELIST[NLCOUNT].node = nptr;
+ for (nptr++; *nptr && !isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0') {
+ bbslog("nodelist.bbs node %d entry read error\n", NLCOUNT);
+ return -1;
+ }
+ *nptr = '\0';
+ if ((tptr = strchr(NODELIST[NLCOUNT].node, '/'))) {
+ *tptr = '\0';
+ NODELIST[NLCOUNT].exclusion = tptr + 1;
+ } else {
+ NODELIST[NLCOUNT].exclusion = "";
+ }
+ for (nptr++; *nptr && isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ if (*nptr == '+' || *nptr == '-') {
+ NODELIST[NLCOUNT].feedtype = *nptr;
+ if (NODELIST[NLCOUNT].feedfp != NULL) {
+ fclose(NODELIST[NLCOUNT].feedfp);
+ }
+ if (NODELIST[NLCOUNT].feedtype == '+')
+ if (outgoing != NULL) {
+ NODELIST[NLCOUNT].feedfp = fopen((char *)fileglue("%s/out.going/%s.%s", BBSHOME, NODELIST[NLCOUNT].node, outgoing), "a");
+ }
+ nptr++;
+ } else {
+ NODELIST[NLCOUNT].feedtype = ' ';
+ }
+ NODELIST[NLCOUNT].host = nptr;
+ for (nptr++; *nptr && !isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0') {
+ continue;
+ }
+ *nptr = '\0';
+ for (nptr++; *nptr && isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ NODELIST[NLCOUNT].protocol = nptr;
+ for (nptr++; *nptr && !isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ *nptr = '\0';
+ for (nptr++; *nptr && strchr(" \t\r\n", *nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ NODELIST[NLCOUNT].comments = nptr;
+ }
+ qsort(NODELIST, NLCOUNT, sizeof(nodelist_t), nlcmp);
+ qsort(NODELIST_BYNODE, NLCOUNT, sizeof(nodelist_t *), nl_bynodecmp);
+ return 0;
+}
+
+int
+readnffile(inndhome)
+ char *inndhome;
+{
+ FILE *fp;
+ char buff[1024];
+ struct stat st;
+ int i, count;
+ char *ptr, *newsfeedsptr;
+
+ sprintf(buff, "%s/newsfeeds.bbs", inndhome);
+ fp = fopen(buff, "r");
+ if (fp == NULL) {
+ fprintf(stderr, "open fail %s", buff);
+ return -1;
+ }
+ if (fstat(fileno(fp), &st) != 0) {
+ fprintf(stderr, "stat fail %s", buff);
+ return -1;
+ }
+ if (NEWSFEEDS_BUF == NULL) {
+ NEWSFEEDS_BUF = (char *)mymalloc(st.st_size + 1);
+ } else {
+ NEWSFEEDS_BUF = (char *)myrealloc(NEWSFEEDS_BUF, st.st_size + 1);
+ }
+ i = 0, count = 0;
+ while (fgets(buff, sizeof buff, fp) != NULL) {
+ if (buff[0] == '#')
+ continue;
+ if (buff[0] == '\n')
+ continue;
+ strcpy(NEWSFEEDS_BUF + i, buff);
+ i += strlen(buff);
+ count++;
+ }
+ fclose(fp);
+ if (NEWSFEEDS == NULL) {
+ NEWSFEEDS = (newsfeeds_t *) mymalloc(sizeof(newsfeeds_t) * (count + 1));
+ NEWSFEEDS_BYBOARD = (newsfeeds_t **) mymalloc(sizeof(newsfeeds_t *) * (count + 1));
+ } else {
+ NEWSFEEDS = (newsfeeds_t *) myrealloc(NEWSFEEDS, sizeof(newsfeeds_t) * (count + 1));
+ NEWSFEEDS_BYBOARD = (newsfeeds_t **) myrealloc(NEWSFEEDS_BYBOARD, sizeof(newsfeeds_t *) * (count + 1));
+ }
+ NFCOUNT = 0;
+ for (ptr = NEWSFEEDS_BUF; (newsfeedsptr = (char *)strchr(ptr, '\n')) != NULL; ptr = newsfeedsptr + 1, NFCOUNT++) {
+ char *nptr;
+ *newsfeedsptr = '\0';
+ NEWSFEEDS[NFCOUNT].newsgroups = "";
+ NEWSFEEDS[NFCOUNT].board = "";
+ NEWSFEEDS[NFCOUNT].path = NULL;
+ NEWSFEEDS_BYBOARD[NFCOUNT] = NEWSFEEDS + NFCOUNT;
+ for (nptr = ptr; *nptr && isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ NEWSFEEDS[NFCOUNT].newsgroups = nptr;
+ for (nptr++; *nptr && !isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ *nptr = '\0';
+ for (nptr++; *nptr && isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ NEWSFEEDS[NFCOUNT].board = nptr;
+ for (nptr++; *nptr && !isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ *nptr = '\0';
+ for (nptr++; *nptr && isspace(*nptr);)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ NEWSFEEDS[NFCOUNT].path = nptr;
+ for (nptr++; *nptr && !strchr("\r\n", *nptr);)
+ nptr++;
+ /* if (*nptr == '\0') continue; */
+ *nptr = '\0';
+ }
+ qsort(NEWSFEEDS, NFCOUNT, sizeof(newsfeeds_t), nfcmp);
+ qsort(NEWSFEEDS_BYBOARD, NFCOUNT, sizeof(newsfeeds_t *), nf_byboardcmp);
+ return 0;
+}
+
+newsfeeds_t *
+search_board(board)
+ char *board;
+{
+ newsfeeds_t nft, *nftptr, **find;
+ if (NONENEWSFEEDS)
+ return NULL;
+ nft.board = board;
+ nftptr = &nft;
+ find = (newsfeeds_t **) bsearch((char *)&nftptr, NEWSFEEDS_BYBOARD, NFCOUNT, sizeof(newsfeeds_t *), nf_byboardcmp);
+ if (find != NULL)
+ return *find;
+ return NULL;
+}
+
+nodelist_t *
+search_nodelist_bynode(node)
+ char *node;
+{
+ nodelist_t nlt, *nltptr, **find;
+ if (LOCALNODELIST)
+ return NULL;
+ nlt.node = node;
+ nltptr = &nlt;
+ find = (nodelist_t **) bsearch((char *)&nltptr, NODELIST_BYNODE, NLCOUNT, sizeof(nodelist_t *), nl_bynodecmp);
+ if (find != NULL)
+ return *find;
+ return NULL;
+}
+
+
+nodelist_t *
+search_nodelist(site, identuser)
+ char *site;
+ char *identuser;
+{
+ nodelist_t nlt, *find;
+ char buffer[1024];
+ if (LOCALNODELIST)
+ return NULL;
+ nlt.host = site;
+ find = (nodelist_t *) bsearch((char *)&nlt, NODELIST, NLCOUNT, sizeof(nodelist_t), nlcmp);
+ if (find == NULL && identuser != NULL) {
+ sprintf(buffer, "%s@%s", identuser, site);
+ nlt.host = buffer;
+ find = (nodelist_t *) bsearch((char *)&nlt, NODELIST, NLCOUNT, sizeof(nodelist_t), nlcmp);
+ }
+ return find;
+}
+
+newsfeeds_t *
+search_group(newsgroup)
+ char *newsgroup;
+{
+ newsfeeds_t nft, *find;
+ if (NONENEWSFEEDS)
+ return NULL;
+ nft.newsgroups = newsgroup;
+ find = (newsfeeds_t *) bsearch((char *)&nft, NEWSFEEDS, NFCOUNT, sizeof(newsfeeds_t), nfcmp);
+ return find;
+}
+
+char *
+ascii_date(now)
+ time_t now;
+{
+ static char datebuf[40];
+ /*
+ * time_t now; time(&now);
+ */
+ strftime(datebuf, sizeof(datebuf), "%d %b %Y %X " INNTIMEZONE, gmtime(&now));
+ return datebuf;
+}
+
+char *
+restrdup(ptr, string)
+ char *ptr;
+ char *string;
+{
+ int len;
+ if (string == NULL) {
+ if (ptr != NULL)
+ *ptr = '\0';
+ return ptr;
+ }
+ len = strlen(string) + 1;
+ if (ptr != NULL) {
+ ptr = (char *)myrealloc(ptr, len);
+ } else
+ ptr = (char *)mymalloc(len);
+ strcpy(ptr, string);
+ return ptr;
+}
+
+
+
+void *
+mymalloc(size)
+ int size;
+{
+ char *ptr = (char *)malloc(size);
+ if (ptr == NULL) {
+ fprintf(stderr, "cant allocate memory\n");
+ syslog(LOG_ERR, "cant allocate memory %m");
+ exit(1);
+ }
+ return ptr;
+}
+
+void *
+myrealloc(optr, size)
+ void *optr;
+ int size;
+{
+ char *ptr = (char *)realloc(optr, size);
+ if (ptr == NULL) {
+ fprintf(stderr, "cant allocate memory\n");
+ syslog(LOG_ERR, "cant allocate memory %m");
+ exit(1);
+ }
+ return ptr;
+}
+
+void
+testandmkdir(dir)
+ char *dir;
+{
+ if (!isdir(dir)) {
+ char path[MAXPATHLEN + 12];
+ sprintf(path, "mkdir -p %s", dir);
+ system(path);
+ }
+}
+
+static char splitbuf[2048];
+static char joinbuf[1024];
+#define MAXTOK 50
+static char *Splitptr[MAXTOK];
+char **
+split(line, pat)
+ char *line, *pat;
+{
+ char *p;
+ int i;
+
+ for (i = 0; i < MAXTOK; ++i)
+ Splitptr[i] = NULL;
+ strncpy(splitbuf, line, sizeof splitbuf - 1);
+ /* printf("%d %d\n",strlen(line),strlen(splitbuf)); */
+ splitbuf[sizeof splitbuf - 1] = '\0';
+ for (i = 0, p = splitbuf; *p && i < MAXTOK - 1;) {
+ for (Splitptr[i++] = p; *p && !strchr(pat, *p); p++);
+ if (*p == '\0')
+ break;
+ for (*p++ = '\0'; *p && strchr(pat, *p); p++);
+ }
+ return Splitptr;
+}
+
+char **
+BNGsplit(line)
+ char *line;
+{
+ char **ptr = split(line, ",");
+ newsfeeds_t *nf1, *nf2;
+ char *n11, *n12, *n21, *n22;
+ int i, j;
+ for (i = 0; ptr[i] != NULL; i++) {
+ nf1 = (newsfeeds_t *) search_group(ptr[i]);
+ for (j = i + 1; ptr[j] != NULL; j++) {
+ if (strcmp(ptr[i], ptr[j]) == 0) {
+ *ptr[j] = '\0';
+ continue;
+ }
+ nf2 = (newsfeeds_t *) search_group(ptr[j]);
+ if (nf1 && nf2) {
+ if (strcmp(nf1->board, nf2->board) == 0) {
+ *ptr[j] = '\0';
+ continue;
+ }
+ for (n11 = nf1->board, n12 = (char *)strchr(n11, ',');
+ n11 && *n11; n12 = (char *)strchr(n11, ',')) {
+ if (n12)
+ *n12 = '\0';
+ for (n21 = nf2->board, n22 = (char *)strchr(n21, ',');
+ n21 && *n21; n22 = (char *)strchr(n21, ',')) {
+ if (n22)
+ *n22 = '\0';
+ if (strcmp(n11, n21) == 0) {
+ *n21 = '\t';
+ }
+ if (n22) {
+ *n22 = ',';
+ n21 = n22 + 1;
+ } else
+ break;
+ }
+ if (n12) {
+ *n12 = ',';
+ n11 = n12 + 1;
+ } else
+ break;
+ }
+ }
+ }
+ }
+ return ptr;
+}
+
+char **
+ssplit(line, pat)
+ char *line, *pat;
+{
+ char *p;
+ int i;
+ for (i = 0; i < MAXTOK; ++i)
+ Splitptr[i] = NULL;
+ strncpy(splitbuf, line, 1024);
+ for (i = 0, p = splitbuf; *p && i < MAXTOK;) {
+ for (Splitptr[i++] = p; *p && !strchr(pat, *p); p++);
+ if (*p == '\0')
+ break;
+ *p = 0;
+ p++;
+ /* for (*p='\0'; strchr(pat,*p);p++); */
+ }
+ return Splitptr;
+}
+
+char *
+join(lineptr, pat, num)
+ char **lineptr, *pat;
+ int num;
+{
+ int i;
+ joinbuf[0] = '\0';
+ if (lineptr[0] != NULL)
+ strncpy(joinbuf, lineptr[0], 1024);
+ else {
+ joinbuf[0] = '\0';
+ return joinbuf;
+ }
+ for (i = 1; i < num; i++) {
+ strcat(joinbuf, pat);
+ if (lineptr[i] != NULL)
+ strcat(joinbuf, lineptr[i]);
+ else
+ break;
+ }
+ return joinbuf;
+}
+
+#ifdef BBSLIB
+main()
+{
+ initial_bbs("feed");
+ printf("%s\n", ascii_date());
+}
+#endif
diff --git a/pttbbs/innbbsd/externs.h b/pttbbs/innbbsd/externs.h
new file mode 100644
index 00000000..7fe63b71
--- /dev/null
+++ b/pttbbs/innbbsd/externs.h
@@ -0,0 +1,88 @@
+#ifndef EXTERNS_H
+#define EXTERNS_H
+
+#ifndef ARG
+#ifdef __STDC__
+#define ARG(x) x
+#else
+#define ARG(x) ()
+#endif
+#endif
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include "bbslib.h"
+#include "nocem.h"
+#include "dbz.h"
+#include "daemon.h"
+#include "his.h"
+#include "bbs.h"
+
+char *fileglue ARG((char *,...));
+char *ascii_date ARG(());
+char **split ARG((char *, char *));
+char *my_rfc931_name(int, struct sockaddr_in *);
+int isreturn(unsigned char);
+nodelist_t *search_nodelist_bynode(char *node);
+int isfile(char *);
+void str_decode_M3(unsigned char *str);
+int headervalue(char *);
+int open_listen(char *, char *, int (*) ARG((int)));
+int open_unix_listen(char *, char *, int (*) ARG((int)));
+int unixclient(char *, char *);
+int pmain(char *port);
+void docompletehalt(int);
+int p_unix_main(char *);
+int INNBBSDshutdown(void);
+void HISclose(void);
+void HISmaint(void);
+newsfeeds_t *search_board(char *board);
+long filesize(char *);
+int inetclient(char *, char *, char *);
+int iszerofile(char *);
+void init_echomailfp(void);
+void init_bbsfeedsfp(void);
+int isdir(char *);
+int readnffile(char *);
+int readnlfile(char *, char *);
+int tryaccept(int);
+void verboselog(char *fmt,...);
+int argify(char *, char ***);
+void deargify ARG((char ***));
+void mkhistory(char *);
+int cancel_article_front(char *);
+ncmperm_t *search_issuer(char *);
+int myHISsetup(char *);
+void closeOnExec(int, int);
+int dbzwritethrough(int);
+char *HISfilesfor(datum *, datum *);
+int myHISwrite(datum *, char *);
+void CloseOnExec(int, int);
+void verboseon(char *);
+daemoncmd_t *searchcmd(char *);
+void hisincore(int);
+void startfrominetd(int);
+void HISsetup(void);
+void installinnbbsd(void);
+void sethaltfunction(int (*) (int));
+int innbbsdstartup(void);
+int isverboselog(void);
+time_t gethisinfo(void);
+void setverboseoff(void);
+void setverboseon(void);
+char *DBfetch(char *);
+int storeDB(char *, char *);
+void readlines(ClientType *);
+int receive_control(void);
+int receive_nocem(void);
+void clearfdset(int);
+void channeldestroy(ClientType *);
+BOOL HISwrite(datum *, long, char *);
+void mkhistory(char *);
+void testandmkdir(char *);
+void feedfplog(newsfeeds_t *, char *, int);
+char **BNGsplit(char *);
+void bbsfeedslog(char *, int);
+
+#endif
diff --git a/pttbbs/innbbsd/file.c b/pttbbs/innbbsd/file.c
new file mode 100644
index 00000000..29b6384a
--- /dev/null
+++ b/pttbbs/innbbsd/file.c
@@ -0,0 +1,205 @@
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <stdarg.h>
+#define MAXARGS 100
+
+/*
+ * isfile is called by isfile(filenamecomp1, filecomp2, filecomp3, ...,
+ * (char *)0); extern "C" int isfile(const char *, const char *[]) ;
+ */
+
+
+char FILEBUF[4096];
+
+
+static char DOLLAR_[8192];
+char *
+getstream(fp)
+ FILE *fp;
+{
+ return fgets(DOLLAR_, sizeof(DOLLAR_) - 1, fp);
+}
+
+/*
+ * The same as sprintf, but return the new string
+ * fileglue("%s/%s",home,".newsrc");
+ */
+
+char *
+fileglue(char *fmt,...)
+{
+ va_list ap;
+ static char gluebuffer[8192];
+ va_start(ap, fmt);
+ vsprintf(gluebuffer, fmt, ap);
+ va_end(ap);
+ return gluebuffer;
+}
+
+long
+filesize(filename)
+ char *filename;
+{
+ struct stat st;
+
+ if (stat(filename, &st))
+ return 0;
+ return st.st_size;
+}
+
+int
+iszerofile(filename)
+ char *filename;
+{
+ struct stat st;
+
+ if (stat(filename, &st))
+ return 0;
+ if (st.st_size == 0)
+ return 1;
+ return 0;
+}
+
+int
+isfile(filename)
+ char *filename;
+{
+ struct stat st;
+
+ if (stat(filename, &st))
+ return 0;
+ if (!S_ISREG(st.st_mode))
+ return 0;
+ return 1;
+}
+
+#ifdef TEST
+int
+isfilev(va_alist)
+{
+ va_list ap;
+ struct stat st;
+ char *p;
+ va_start(ap);
+
+ FILEBUF[0] = '\0';
+ while ((p = va_arg(ap, char *)) != (char *)0) {
+ strcat(FILEBUF, p);
+ }
+ printf("file %s\n", FILEBUF);
+
+ va_end(ap);
+ return isfile(FILEBUF);
+}
+#endif
+
+
+int
+isdir(filename)
+ char *filename;
+{
+ struct stat st;
+
+ if (stat(filename, &st))
+ return 0;
+ if (!S_ISDIR(st.st_mode))
+ return 0;
+ return 1;
+}
+
+#ifdef TEST
+int
+isdirv(va_alist)
+{
+ va_list ap;
+ struct stat st;
+ char *p;
+ va_start(ap);
+
+ FILEBUF[0] = '\0';
+ while ((p = va_arg(ap, char *)) != (char *)0) {
+ strcat(FILEBUF, p);
+ }
+
+ va_end(ap);
+ return isdir(FILEBUF);
+}
+#endif
+
+unsigned long
+mtime(filename)
+ char *filename;
+{
+ struct stat st;
+ if (stat(filename, &st))
+ return 0;
+ return st.st_mtime;
+}
+
+#ifdef TEST
+unsigned long
+mtimev(va_alist)
+{
+ va_list ap;
+ struct stat st;
+ char *p;
+ va_start(ap);
+
+ FILEBUF[0] = '\0';
+ while ((p = va_arg(ap, char *)) != (char *)0) {
+ strcat(FILEBUF, p);
+ }
+
+ va_end(ap);
+ return mtime(FILEBUF);
+}
+#endif
+
+unsigned long
+atime(filename)
+ char *filename;
+{
+ struct stat st;
+ if (stat(filename, &st))
+ return 0;
+ return st.st_atime;
+}
+
+#ifdef TEST
+unsigned long
+atimev(va_alist)
+{
+ va_list ap;
+ struct stat st;
+ char *p;
+ va_start(ap);
+
+ FILEBUF[0] = '\0';
+ while ((p = va_arg(ap, char *)) != (char *)0) {
+ strcat(FILEBUF, p);
+ }
+
+ va_end(ap);
+ return atime(FILEBUF);
+}
+#endif
+
+/* #undef TEST */
+#ifdef TEST
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ int i;
+ if (argc > 3) {
+ if (isfilev(argv[1], argv[2], (char *)0))
+ printf("%s %s %s is file\n", argv[1], argv[2], argv[3]);
+ if (isdirv(argv[1], argv[2], (char *)0))
+ printf("%s %s %s is dir\n", argv[1], argv[2], argv[3]);
+ printf("mtime %d\n", mtimev(argv[1], argv[2], (char *)0));
+ printf("atime %d\n", atimev(argv[1], argv[2], (char *)0));
+ }
+ printf("fileglue %s\n", fileglue("%s/%s", "home", ".test"));
+}
+#endif
diff --git a/pttbbs/innbbsd/his.c b/pttbbs/innbbsd/his.c
new file mode 100644
index 00000000..773fb78c
--- /dev/null
+++ b/pttbbs/innbbsd/his.c
@@ -0,0 +1,477 @@
+/*
+ * $Revision: 1.1 $ *
+ *
+ * History file routines.
+ */
+#include <stdlib.h>
+#include "innbbsconf.h"
+#include "bbslib.h"
+#include "his.h"
+#include "externs.h"
+
+#define STATIC static
+/* STATIC char HIShistpath[] = _PATH_HISTORY; */
+STATIC FILE *HISwritefp;
+STATIC int HISreadfd;
+STATIC int HISdirty;
+STATIC int HISincore = XINDEX_DBZINCORE;
+STATIC char *LogName = "xindexchan";
+
+#ifndef EXPIREDAYS
+#define EXPIREDAYS 4
+#endif
+
+#ifndef DEFAULT_HIST_SIZE
+#define DEFAULT_HIST_SIZE 100000
+#endif
+
+void
+hisincore(flag)
+ int flag;
+{
+ HISincore = flag;
+}
+
+void
+makedbz(histpath, entry)
+ char *histpath;
+ long entry;
+{
+ long size;
+ size = dbzsize(entry);
+ dbzfresh(histpath, size, '\t', 0, 1);
+ dbmclose();
+}
+
+void HISsetup();
+void HISclose();
+
+void
+mkhistory(srchist)
+ char *srchist;
+{
+ FILE *hismaint;
+ char maintbuff[256];
+ char *ptr;
+ hismaint = fopen(srchist, "r");
+ if (hismaint == NULL) {
+ return;
+ } {
+ char newhistpath[1024];
+ char newhistdirpath[1024];
+ char newhistpagpath[1024];
+ sprintf(newhistpath, "%s.n", srchist);
+ sprintf(newhistdirpath, "%s.n.dir", srchist);
+ sprintf(newhistpagpath, "%s.n.pag", srchist);
+ if (!isfile(newhistdirpath) || !isfile(newhistpagpath)) {
+ makedbz(newhistpath, DEFAULT_HIST_SIZE);
+ }
+ myHISsetup(newhistpath);
+ while (fgets(maintbuff, sizeof(maintbuff), hismaint) != NULL) {
+ datum key;
+ ptr = (char *)strchr(maintbuff, '\t');
+ if (ptr != NULL) {
+ *ptr = '\0';
+ ptr++;
+ }
+ key.dptr = maintbuff;
+ key.dsize = strlen(maintbuff);
+ myHISwrite(&key, ptr);
+ }
+ (void)HISclose();
+ /*
+ * rename(newhistpath, srchist); rename(newhistdirpath,
+ * fileglue("%s.dir", srchist)); rename(newhistpagpath,
+ * fileglue("%s.pag", srchist));
+ */
+ }
+ fclose(hismaint);
+}
+
+time_t
+gethisinfo(void)
+{
+ FILE *hismaint;
+ time_t lasthist;
+ char maintbuff[4096];
+ char *ptr;
+ hismaint = fopen(HISTORY, "r");
+ if (hismaint == NULL) {
+ return 0;
+ }
+ fgets(maintbuff, sizeof(maintbuff), hismaint);
+ fclose(hismaint);
+ ptr = (char *)strchr(maintbuff, '\t');
+ if (ptr != NULL) {
+ ptr++;
+ lasthist = atol(ptr);
+ return lasthist;
+ }
+ return 0;
+}
+
+void
+HISmaint(void)
+{
+ FILE *hismaint;
+ time_t lasthist, now;
+ char maintbuff[4096];
+ char *ptr;
+
+ if (!isfile(HISTORY)) {
+ makedbz(HISTORY, DEFAULT_HIST_SIZE);
+ }
+ hismaint = fopen(HISTORY, "r");
+ if (hismaint == NULL) {
+ return;
+ }
+ fgets(maintbuff, sizeof(maintbuff), hismaint);
+ ptr = (char *)strchr(maintbuff, '\t');
+ if (ptr != NULL) {
+ ptr++;
+ lasthist = atol(ptr);
+ time(&now);
+ if (lasthist + 86400 * Expiredays * 2 < now) {
+ char newhistpath[1024];
+ char newhistdirpath[1024];
+ char newhistpagpath[1024];
+ (void)HISclose();
+ sprintf(newhistpath, "%s.n", HISTORY);
+ sprintf(newhistdirpath, "%s.n.dir", HISTORY);
+ sprintf(newhistpagpath, "%s.n.pag", HISTORY);
+ if (!isfile(newhistdirpath)) {
+ makedbz(newhistpath, DEFAULT_HIST_SIZE);
+ }
+ myHISsetup(newhistpath);
+ while (fgets(maintbuff, sizeof(maintbuff), hismaint) != NULL) {
+ datum key;
+ ptr = (char *)strchr(maintbuff, '\t');
+ if (ptr != NULL) {
+ *ptr = '\0';
+ ptr++;
+ lasthist = atol(ptr);
+ } else {
+ continue;
+ }
+ if (lasthist + 99600 * Expiredays < now)
+ continue;
+ key.dptr = maintbuff;
+ key.dsize = strlen(maintbuff);
+ myHISwrite(&key, ptr);
+ }
+ (void)HISclose();
+ rename(HISTORY, (char *)fileglue("%s.o", HISTORY));
+ rename(newhistpath, HISTORY);
+ rename(newhistdirpath, (char *)fileglue("%s.dir", HISTORY));
+ rename(newhistpagpath, (char *)fileglue("%s.pag", HISTORY));
+ (void)HISsetup();
+ }
+ }
+ fclose(hismaint);
+}
+
+
+/*
+ * * Set up the history files.
+ */
+void
+HISsetup(void)
+{
+ myHISsetup(HISTORY);
+}
+
+int
+myHISsetup(histpath)
+ char *histpath;
+{
+ if (HISwritefp == NULL) {
+ /* Open the history file for appending formatted I/O. */
+ if ((HISwritefp = fopen(histpath, "a")) == NULL) {
+ syslog(LOG_CRIT, "%s cant fopen %s %m", LogName, histpath);
+ exit(1);
+ }
+ CloseOnExec((int)fileno(HISwritefp), TRUE);
+
+ /* Open the history file for reading. */
+ if ((HISreadfd = open(histpath, O_RDONLY)) < 0) {
+ syslog(LOG_CRIT, "%s cant open %s %m", LogName, histpath);
+ exit(1);
+ }
+ CloseOnExec(HISreadfd, TRUE);
+
+ /* Open the DBZ file. */
+ /* (void)dbzincore(HISincore); */
+ (void)dbzincore(HISincore);
+ (void)dbzwritethrough(1);
+ if (dbminit(histpath) < 0) {
+ syslog(LOG_CRIT, "%s cant dbminit %s %m", histpath, LogName);
+ exit(1);
+ }
+ }
+ return 0;
+}
+
+
+/*
+ * * Synchronize the in-core history file (flush it).
+ */
+void
+HISsync()
+{
+ if (HISdirty) {
+ if (dbzsync()) {
+ syslog(LOG_CRIT, "%s cant dbzsync %m", LogName);
+ exit(1);
+ }
+ HISdirty = 0;
+ }
+}
+
+
+/*
+ * * Close the history files.
+ */
+void
+HISclose(void)
+{
+ if (HISwritefp != NULL) {
+ /*
+ * Since dbmclose calls dbzsync we could replace this line with
+ * "HISdirty = 0;". Oh well, it keeps the abstraction clean.
+ */
+ HISsync();
+ if (dbmclose() < 0)
+ syslog(LOG_ERR, "%s cant dbmclose %m", LogName);
+ if (fclose(HISwritefp) == EOF)
+ syslog(LOG_ERR, "%s cant fclose history %m", LogName);
+ HISwritefp = NULL;
+ if (close(HISreadfd) < 0)
+ syslog(LOG_ERR, "%s cant close history %m", LogName);
+ HISreadfd = -1;
+ }
+}
+
+
+#ifdef HISset
+/*
+ * * File in the DBZ datum for a Message-ID, making sure not to copy any *
+ * illegal characters.
+ */
+STATIC void
+HISsetkey(p, keyp)
+ register char *p;
+ datum *keyp;
+{
+ static BUFFER MessageID;
+ register char *dest;
+ register int i;
+
+ /* Get space to hold the ID. */
+ i = strlen(p);
+ if (MessageID.Data == NULL) {
+ MessageID.Data = NEW(char, i + 1);
+ MessageID.Size = i;
+ } else if (MessageID.Size < i) {
+ RENEW(MessageID.Data, char, i + 1);
+ MessageID.Size = i;
+ }
+ for (keyp->dptr = dest = MessageID.Data; *p; p++)
+ if (*p == HIS_FIELDSEP || *p == '\n')
+ *dest++ = HIS_BADCHAR;
+ else
+ *dest++ = *p;
+ *dest = '\0';
+
+ keyp->dsize = dest - MessageID.Data + 1;
+}
+
+#endif
+/*
+ * * Get the list of files under which a Message-ID is stored.
+ */
+char *
+HISfilesfor(key, output)
+ datum *key;
+ datum *output;
+{
+ char *dest;
+ datum val;
+ long offset;
+ register char *p;
+ register int i;
+ int Used;
+
+ /* Get the seek value into the history file. */
+ val = dbzfetch(*key);
+ if (val.dptr == NULL || val.dsize != sizeof offset) {
+ /* printf("fail here val.dptr %d\n",val.dptr); */
+ return NULL;
+ }
+ /* Get space. */
+ if (output->dptr == NULL) {
+ printf("fail here output->dptr null\n");
+ return NULL;
+ }
+ /* Copy the value to an aligned spot. */
+ for (p = val.dptr, dest = (char *)&offset, i = sizeof offset; --i >= 0;)
+ *dest++ = *p++;
+ if (lseek(HISreadfd, offset, SEEK_SET) == -1) {
+ printf("fail here lseek %d\n", offset);
+ return NULL;
+ }
+ /* Read the text until \n or EOF. */
+ for (output->dsize = 0, Used = 0;;) {
+ i = read(HISreadfd,
+ &output->dptr[output->dsize], LEN - 1);
+ if (i <= 0) {
+ printf("fail here i %d\n", i);
+ return NULL;
+ }
+ Used += i;
+ output->dptr[Used] = '\0';
+ if ((p = (char *)strchr(output->dptr, '\n')) != NULL) {
+ *p = '\0';
+ break;
+ }
+ }
+
+ /* Move past the first two fields -- Message-ID and date info. */
+ if ((p = (char *)strchr(output->dptr, HIS_FIELDSEP)) == NULL) {
+ printf("fail here no HIS_FILE\n");
+ return NULL;
+ }
+ return p + 1;
+ /*
+ * if ((p = (char*)strchr(p + 1, HIS_FIELDSEP)) == NULL) return NULL;
+ */
+
+ /* Translate newsgroup separators to slashes, return the fieldstart. */
+}
+
+/*
+ * * Have we already seen an article?
+ */
+#ifdef HISh
+BOOL
+HIShavearticle(MessageID)
+ char *MessageID;
+{
+ datum key;
+ datum val;
+
+ HISsetkey(MessageID, &key);
+ val = dbzfetch(key);
+ return val.dptr != NULL;
+}
+#endif
+
+
+/*
+ * * Turn a history filename entry from slashes to dots. It's a pity * we
+ * have to do this.
+ */
+STATIC void
+HISslashify(p)
+ register char *p;
+{
+ register char *last;
+
+ for (last = NULL; *p; p++) {
+ if (*p == '/') {
+ *p = '.';
+ last = p;
+ } else if (*p == ' ' && last != NULL)
+ *last = '/';
+ }
+ if (last)
+ *last = '/';
+}
+
+
+void
+IOError(error)
+ char *error;
+{
+ fprintf(stderr, "%s\n", error);
+}
+
+/* BOOL */
+int
+myHISwrite(key, remain)
+ datum *key;
+ char *remain;
+{
+ long offset;
+ datum val;
+ int i;
+
+ val = dbzfetch(*key);
+ if (val.dptr != NULL) {
+ return FALSE;
+ }
+ flock(fileno(HISwritefp), LOCK_EX);
+ offset = ftell(HISwritefp);
+ i = fprintf(HISwritefp, "%s%c%s",
+ key->dptr, HIS_FIELDSEP, remain);
+ if (i == EOF || fflush(HISwritefp) == EOF) {
+ /* The history line is now an orphan... */
+ IOError("history");
+ syslog(LOG_ERR, "%s cant write history %m", LogName);
+ flock(fileno(HISwritefp), LOCK_UN);
+ return FALSE;
+ }
+ /* Set up the database values and write them. */
+ val.dptr = (char *)&offset;
+ val.dsize = sizeof offset;
+ if (dbzstore(*key, val) < 0) {
+ IOError("my history database");
+ syslog(LOG_ERR, "%s cant dbzstore %m", LogName);
+ flock(fileno(HISwritefp), LOCK_UN);
+ return FALSE;
+ }
+ if (++HISdirty >= ICD_SYNC_COUNT)
+ HISsync();
+ flock(fileno(HISwritefp), LOCK_UN);
+ return TRUE;
+}
+
+
+/*
+ * * Write a history entry.
+ */
+BOOL
+HISwrite(key, date, paths)
+ datum *key;
+ long date;
+ char *paths;
+{
+ long offset;
+ datum val;
+ int i;
+
+ flock(fileno(HISwritefp), LOCK_EX);
+ offset = ftell(HISwritefp);
+ i = fprintf(HISwritefp, "%s%c%ld%c%s\n",
+ key->dptr, HIS_FIELDSEP, (long)date, HIS_FIELDSEP,
+ paths);
+ if (i == EOF || fflush(HISwritefp) == EOF) {
+ /* The history line is now an orphan... */
+ IOError("history");
+ syslog(LOG_ERR, "%s cant write history %m", LogName);
+ flock(fileno(HISwritefp), LOCK_UN);
+ return FALSE;
+ }
+ /* Set up the database values and write them. */
+ val.dptr = (char *)&offset;
+ val.dsize = sizeof offset;
+ if (dbzstore(*key, val) < 0) {
+ IOError("history database");
+ syslog(LOG_ERR, "%s cant dbzstore %m", LogName);
+ flock(fileno(HISwritefp), LOCK_UN);
+ return FALSE;
+ }
+ if (++HISdirty >= ICD_SYNC_COUNT)
+ HISsync();
+ flock(fileno(HISwritefp), LOCK_UN);
+ return TRUE;
+}
diff --git a/pttbbs/innbbsd/his.h b/pttbbs/innbbsd/his.h
new file mode 100644
index 00000000..fab0f76e
--- /dev/null
+++ b/pttbbs/innbbsd/his.h
@@ -0,0 +1,77 @@
+#ifndef HIS_H
+#define HIS_H
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#ifndef SEEK_SET
+#include <unistd.h>
+#endif
+#include "dbz.h"
+
+#ifndef XINDEXDIR
+#define XINDEXDIR "/homec/xindex"
+#endif
+#ifndef _PATH_HISTORY
+#define _PATH_HISTORY "/u/staff/bbsroot/csie_util/bntpd/history"
+#endif
+
+#ifndef _PATH_COVERVIEW
+#define _PATH_COVERVIEW ".coverview"
+#endif
+
+#ifndef _PATH_COVERVIEWDIR
+#define _PATH_COVERVIEWDIR "/homec/xindex"
+#endif
+
+#ifndef XINDEX_DBZINCORE
+#define XINDEX_DBZINCORE 1
+#endif
+#ifndef XINDEXNAME
+#define XINDEXNAME ".index"
+#endif
+#ifndef XINDEXDBM
+#define XINDEXDBM ".dbm"
+#endif
+#ifndef XINDEXINFO
+#define XINDEXINFO ".info"
+#endif
+
+#define LEN 1024
+struct t_article {
+ long artnum;
+ char subject[LEN]; /* Subject: line from mail header */
+ char from[LEN]; /* From: line from mail header (address) */
+ char name[LEN]; /* From: line from mail header (full nam e) */
+ long date; /* Date: line from header in seconds */
+ char xref[LEN]; /* Xref: cross posted article reference line */
+ int lines; /* Lines: number of lines in article */
+ char *archive; /* Archive-name: line from mail header */
+ char *part; /* part no. of archive */
+ char *patch; /* patch no. of archive */
+};
+
+typedef struct t_article art_t;
+
+#define HIS_BADCHAR '_'
+#define HIS_FIELDSEP '\t'
+#define HIS_NOEXP "-"
+#define HIS_SUBFIELDSEP '~'
+/* #define HIS_FIELDSEP2 '\034' */
+#define HIS_FIELDSEP2 'I'
+
+#ifndef TRUE
+#define TRUE 1
+#define FALSE 0
+#endif
+
+#ifndef BOOL
+typedef unsigned char BOOL;
+#endif
+
+#ifndef ICD_SYNC_COUNT
+#define ICD_SYNC_COUNT 1
+#endif
+
+#endif
diff --git a/pttbbs/innbbsd/innbbsconf.h b/pttbbs/innbbsd/innbbsconf.h
new file mode 100644
index 00000000..dcdc5f21
--- /dev/null
+++ b/pttbbs/innbbsd/innbbsconf.h
@@ -0,0 +1,186 @@
+#ifndef INNBBSCONF_H
+#define INNBBSCONF_H
+#include <stdio.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/un.h>
+#include <sys/param.h>
+#include <sys/wait.h>
+
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h>
+#ifndef BSD44
+#include <malloc.h>
+#endif
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+
+/* #include "bbs.h" */
+#if defined(AIX)
+#include <sys/select.h>
+#endif
+
+/*
+ * BBS home directory It has been overridden in Makefile
+ */
+#ifndef _PATH_BBSHOME
+#define _PATH_BBSHOME "/u/staff/bbsroot/csie_util/bntpd/home"
+/* # define _PATH_BBSHOME "/home/bbs" */
+#endif
+
+#ifndef EXPIREDAYS
+#define EXPIREDAYS 7
+#endif
+
+#ifndef DEFAULT_HIST_SIZE
+#define DEFAULT_HIST_SIZE 100000
+#endif
+
+/*
+ * Maximum number of connections accepted by innbbsd
+ */
+#ifndef MAXCLIENT
+#define MAXCLIENT 500
+#endif
+
+/*
+ * Maximum number of articles received for a newsgroup by bbsnnrp each time
+ */
+#ifndef MAX_ARTS
+#define MAX_ARTS 100
+#endif
+
+/*
+ * Maximum size of articles received
+ */
+#ifndef MAX_ART_SIZE
+#define MAX_ART_SIZE 1000000L
+#endif
+
+
+/*
+ * Maximum number of articles stated for a newsgroup by bbsnnrp each time
+ */
+#ifndef MAX_STATS
+#define MAX_STATS 1000
+#endif
+
+/*
+ * Mininum wait interval for bbsnnrp
+ */
+#ifndef MIN_WAIT
+#define MIN_WAIT 60
+#endif
+
+
+#ifndef DefaultINNBBSPort
+#define DefaultINNBBSPort "7777"
+#endif
+
+/*
+ * time to maintain history database
+ */
+#ifndef HIS_MAINT
+#define HIS_MAINT
+#define HIS_MAINT_HOUR 4
+#define HIS_MAINT_MIN 30
+#endif
+
+#ifndef ChannelSize
+#define ChannelSize 4096
+#endif
+
+#ifndef ReadSize
+#define ReadSize 1024
+#endif
+
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 1024
+#endif
+
+#ifndef CLX_IOCTL
+#define CLX_IOCTL
+#endif
+
+#define DEFAULTSERVER "your.favorite.news.server"
+#define DEFAULTPORT "nntp"
+#define DEFAULTPROTOCOL "tcp"
+#define DEFAULTPATH ".innbbsd"
+
+#ifndef INADDR_NONE
+#define INADDR_NONE 0xffffffff
+#endif
+
+/*
+ * # ifndef ARG # ifdef __STDC__ # define ARG(x) (x) # else # define
+ * ARG(x) () # endif # endif
+ */
+/* machine dependend */
+#if defined(__linux)
+#ifndef LINUX
+#define LINUX
+#endif
+#endif
+
+#if !defined(__svr4__) || defined(sun)
+#define WITH_TM_GMTOFF
+#endif
+#if (defined(__svr4__) && defined(sun)) || defined(Solaris)
+#ifndef Solaris
+#define Solaris
+#endif
+#define NO_getdtablesize
+//#define NO_bcopy
+//#define NO_bzero
+//#define NO_flock
+#define WITH_lockf
+#endif
+
+#if defined(AIX)
+#define NO_flock
+#define WITH_lockf
+#endif
+
+#if defined(HPUX)
+#define NO_getdtablesize
+#define NO_flock
+#define WITH_lockf
+#endif
+
+#ifdef NO_bcopy
+#ifndef bcopy
+#define bcopy(a,b,c) memcpy(b,a,c)
+#endif
+#endif
+
+#ifdef NO_bzero
+#ifndef bzero
+#define bzero(mem, size) memset(mem,'\0',size)
+#endif
+#endif
+
+#ifndef LOCK_EX
+#define LOCK_EX 2 /* exclusive lock */
+#define LOCK_UN 8 /* unlock */
+#endif
+
+#ifdef DEC_ALPHA
+#define ULONG unsigned int
+#else
+#define ULONG unsigned long
+#endif
+
+#ifdef PalmBBS
+#undef WITH_RECORD_O
+#endif
+
+#endif
diff --git a/pttbbs/innbbsd/innbbsd.c b/pttbbs/innbbsd/innbbsd.c
new file mode 100644
index 00000000..f71ab30c
--- /dev/null
+++ b/pttbbs/innbbsd/innbbsd.c
@@ -0,0 +1,794 @@
+#include "innbbsconf.h"
+#include "daemon.h"
+#include "innbbsd.h"
+#include <dirent.h>
+#include "bbslib.h"
+#include "inntobbs.h"
+#include "nntp.h"
+#include "externs.h"
+
+#ifdef GETRUSAGE
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif
+
+#ifdef STDC
+#ifndef ARG
+#define ARG(x) (x)
+#else
+#define ARG(x) ()
+#endif
+#endif
+
+/*
+ * < add <mid> <recno> ... > 200 OK < quit 500 BYE
+ *
+ * > 300 DBZ Server ... < query <mid> > 250 <recno> ... > 450 NOT FOUND!
+ */
+
+static int CMDhelp ARG((ClientType *));
+static int CMDquit ARG((ClientType *));
+static int CMDihave ARG((ClientType *));
+static int CMDstat ARG((ClientType *));
+static int CMDaddhist ARG((ClientType *));
+static int CMDgrephist ARG((ClientType *));
+static int CMDmidcheck ARG((ClientType *));
+static int CMDshutdown ARG((ClientType *));
+static int CMDmode ARG((ClientType *));
+static int CMDreload ARG((ClientType *));
+static int CMDhismaint ARG((ClientType *));
+static int CMDverboselog ARG((ClientType *));
+static int CMDlistnodelist ARG((ClientType *));
+static int CMDlistnewsfeeds ARG((ClientType *));
+
+#ifdef GETRUSAGE
+static int CMDgetrusage ARG((ClientType *));
+static int CMDmallocmap ARG((ClientType *));
+#endif
+
+static daemoncmd_t cmds[] =
+/* cmd-name, cmd-usage, min-argc, max-argc, errorcode, normalcode, cmd-func */
+{{"help", "help [cmd]", 1, 2, 99, 100, CMDhelp},
+{"quit", "quit", 1, 0, 99, 100, CMDquit},
+#ifndef DBZSERVER
+{"ihave", "ihave mid", 2, 2, 435, 335, CMDihave},
+#endif
+{"stat", "stat mid", 2, 2, 223, 430, CMDstat},
+{"addhist", "addhist <mid> <path>", 3, 3, NNTP_ADDHIST_BAD, NNTP_ADDHIST_OK, CMDaddhist},
+{"grephist", "grephist <mid>", 2, 2, NNTP_GREPHIST_BAD, NNTP_GREPHIST_OK, CMDgrephist},
+{"midcheck", "midcheck [on|off]", 1, 2, NNTP_MIDCHECK_BAD, NNTP_MIDCHECK_OK, CMDmidcheck},
+{"shutdown", "shutdown (local)", 1, 1, NNTP_SHUTDOWN_BAD, NNTP_SHUTDOWN_OK, CMDshutdown},
+{"mode", "mode (local)", 1, 1, NNTP_MODE_BAD, NNTP_MODE_OK, CMDmode},
+{"listnodelist", "listnodelist (local)", 1, 1, NNTP_MODE_BAD, NNTP_MODE_OK, CMDlistnodelist},
+{"listnewsfeeds", "listnewsfeeds (local)", 1, 1, NNTP_MODE_BAD, NNTP_MODE_OK, CMDlistnewsfeeds},
+{"reload", "reload (local)", 1, 1, NNTP_RELOAD_BAD, NNTP_RELOAD_OK, CMDreload},
+{"hismaint", "hismaint (local)", 1, 1, NNTP_RELOAD_BAD, NNTP_RELOAD_OK, CMDhismaint},
+{"verboselog", "verboselog [on|off](local)", 1, 2, NNTP_VERBOSELOG_BAD, NNTP_VERBOSELOG_OK, CMDverboselog},
+#ifdef GETRUSAGE
+{"getrusage", "getrusage (local)", 1, 1, NNTP_MODE_BAD, NNTP_MODE_OK, CMDgetrusage},
+#endif
+#ifdef MALLOCMAP
+{"mallocmap", "mallocmap (local)", 1, 1, NNTP_MODE_BAD, NNTP_MODE_OK, CMDmallocmap},
+#endif
+{NULL, NULL, 0, 0, 99, 100, NULL}
+};
+
+void
+installinnbbsd(void)
+{
+ installdaemon(cmds, 100, NULL);
+}
+
+#ifdef OLDLIBINBBSINND
+void
+testandmkdir(dir)
+ char *dir;
+{
+ if (!isdir(dir)) {
+ char path[MAXPATHLEN + 12];
+ sprintf(path, "mkdir -p %s", dir);
+ system(path);
+ }
+}
+
+static char splitbuf[2048];
+static char joinbuf[1024];
+#define MAXTOK 50
+static char *Splitptr[MAXTOK];
+char **
+split(line, pat)
+ char *line, *pat;
+{
+ char *p;
+ int i;
+
+ for (i = 0; i < MAXTOK; ++i)
+ Splitptr[i] = NULL;
+ strncpy(splitbuf, line, sizeof splitbuf - 1);
+ /* printf("%d %d\n",strlen(line),strlen(splitbuf)); */
+ splitbuf[sizeof splitbuf - 1] = '\0';
+ for (i = 0, p = splitbuf; *p && i < MAXTOK - 1;) {
+ for (Splitptr[i++] = p; *p && !strchr(pat, *p); p++);
+ if (*p == '\0')
+ break;
+ for (*p++ = '\0'; *p && strchr(pat, *p); p++);
+ }
+ return Splitptr;
+}
+
+char **
+BNGsplit(line)
+ char *line;
+{
+ char **ptr = split(line, ",");
+ newsfeeds_t *nf1, *nf2;
+ char *n11, *n12, *n21, *n22;
+ int i, j;
+ for (i = 0; ptr[i] != NULL; i++) {
+ nf1 = (newsfeeds_t *) search_group(ptr[i]);
+ for (j = i + 1; ptr[j] != NULL; j++) {
+ if (strcmp(ptr[i], ptr[j]) == 0) {
+ *ptr[j] = '\0';
+ continue;
+ }
+ nf2 = (newsfeeds_t *) search_group(ptr[j]);
+ if (nf1 && nf2) {
+ if (strcmp(nf1->board, nf2->board) == 0) {
+ *ptr[j] = '\0';
+ continue;
+ }
+ for (n11 = nf1->board, n12 = (char *)strchr(n11, ',');
+ n11 && *n11; n12 = (char *)strchr(n11, ',')) {
+ if (n12)
+ *n12 = '\0';
+ for (n21 = nf2->board, n22 = (char *)strchr(n21, ',');
+ n21 && *n21; n22 = (char *)strchr(n21, ',')) {
+ if (n22)
+ *n22 = '\0';
+ if (strcmp(n11, n21) == 0) {
+ *n21 = '\t';
+ }
+ if (n22) {
+ *n22 = ',';
+ n21 = n22 + 1;
+ } else
+ break;
+ }
+ if (n12) {
+ *n12 = ',';
+ n11 = n12 + 1;
+ } else
+ break;
+ }
+ }
+ }
+ }
+ return ptr;
+}
+
+char **
+ssplit(line, pat)
+ char *line, *pat;
+{
+ char *p;
+ int i;
+ for (i = 0; i < MAXTOK; ++i)
+ Splitptr[i] = NULL;
+ strncpy(splitbuf, line, 1024);
+ for (i = 0, p = splitbuf; *p && i < MAXTOK;) {
+ for (Splitptr[i++] = p; *p && !strchr(pat, *p); p++);
+ if (*p == '\0')
+ break;
+ *p = 0;
+ p++;
+ /* for (*p='\0'; strchr(pat,*p);p++); */
+ }
+ return Splitptr;
+}
+
+char *
+join(lineptr, pat, num)
+ char **lineptr, *pat;
+ int num;
+{
+ int i;
+ joinbuf[0] = '\0';
+ if (lineptr[0] != NULL)
+ strncpy(joinbuf, lineptr[0], 1024);
+ else {
+ joinbuf[0] = '\0';
+ return joinbuf;
+ }
+ for (i = 1; i < num; i++) {
+ strcat(joinbuf, pat);
+ if (lineptr[i] != NULL)
+ strcat(joinbuf, lineptr[i]);
+ else
+ break;
+ }
+ return joinbuf;
+}
+
+#endif
+
+static int
+CMDtnrpd(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ fprintf(argv->out, "%d %s\n", argv->dc->usage);
+ return 0;
+}
+
+int
+islocalconnect(client)
+ ClientType *client;
+{
+ if (strcmp(client->username, "localuser") != 0 ||
+ strcmp(client->hostname, "localhost") != 0)
+ return 0;
+ return 1;
+}
+
+static int shutdownflag = 0;
+void
+INNBBSDhalt()
+{
+ shutdownflag = 1;
+}
+
+int
+INNBBSDshutdown(void)
+{
+ return shutdownflag;
+}
+
+static int
+CMDshutdown(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ daemoncmd_t *p = argv->dc;
+ if (!islocalconnect(client)) {
+ fprintf(argv->out, "%d shutdown access denied\r\n", p->errorcode);
+ fflush(argv->out);
+ verboselog("Shutdown Put: %d shutdown access denied\n", p->errorcode);
+ return 1;
+ }
+ shutdownflag = 1;
+ fprintf(argv->out, "%d shutdown starting\r\n", p->normalcode);
+ fflush(argv->out);
+ verboselog("Shutdown Put: %d shutdown starting\n", p->normalcode);
+ return 1;
+}
+
+static int
+CMDmode(client)
+ ClientType *client;
+{
+ /* char cwdpath[MAXPATHLEN+1]; */
+ argv_t *argv = &client->Argv;
+ extern ClientType INNBBSD_STAT;
+ daemoncmd_t *p = argv->dc;
+ time_t uptime, now;
+ int i, j;
+ time_t lasthist;
+ ClientType *client1 = &INNBBSD_STAT;
+
+ if (!islocalconnect(client)) {
+ fprintf(argv->out, "%d mode access denied\r\n", p->errorcode);
+ fflush(argv->out);
+ verboselog("Mode Put: %d mode access denied\n", p->errorcode);
+ return 1;
+ }
+ fprintf(argv->out, "%d mode\r\n", p->normalcode);
+ fflush(argv->out);
+ verboselog("Mode Put: %d mode\n", p->normalcode);
+ uptime = innbbsdstartup();
+ time(&now);
+ fprintf(argv->out, "up since %salive %.2f days\r\n", ctime(&uptime), (double)(now - innbbsdstartup()) / 86400);
+ fprintf(argv->out, "BBSHOME %s\r\n", BBSHOME);
+ fprintf(argv->out, "MYBBSID %s\r\n", MYBBSID);
+ fprintf(argv->out, "ECHOMAIL %s\r\n", ECHOMAIL);
+ fprintf(argv->out, "INNDHOME %s\r\n", INNDHOME);
+ fprintf(argv->out, "HISTORY %s\r\n", HISTORY);
+ fprintf(argv->out, "LOGFILE %s\r\n", LOGFILE);
+ fprintf(argv->out, "INNBBSCONF %s\r\n", INNBBSCONF);
+ fprintf(argv->out, "BBSFEEDS %s\r\n", BBSFEEDS);
+ fprintf(argv->out, "Verbose log: %s\r\n", isverboselog() ? "ON" : "OFF");
+ fprintf(argv->out, "History Expire Days %d\r\n", Expiredays);
+ fprintf(argv->out, "History Expire Time %d:%d\r\n", His_Maint_Hour, His_Maint_Min);
+ lasthist = gethisinfo();
+ if (lasthist > 0) {
+ time_t keep = lasthist, keep1;
+ time(&now);
+ fprintf(argv->out, "Oldest history entry created: %s", (char *)ctime(&keep));
+ keep = Expiredays * 86400 * 2 + lasthist;
+ keep1 = keep - now;
+ fprintf(argv->out, "Next time to maintain history: (%.2f days later) %s", (double)keep1 / 86400, (char *)ctime(&keep));
+ }
+ fprintf(argv->out, "PID is %d\r\n", getpid());
+ fprintf(argv->out, "LOCAL ONLY %d\r\n", LOCALNODELIST);
+ fprintf(argv->out, "NONE NEWSFEEDS %d\r\n", NONENEWSFEEDS);
+ fprintf(argv->out, "Max connections %d\r\n", Maxclient);
+#ifdef DEBUGCWD
+ getwd(cwdpath);
+ fprintf(argv->out, "Working directory %s\r\n", cwdpath);
+#endif
+ if (Channel)
+ for (i = 0, j = 0; i < Maxclient; ++i) {
+ if (Channel[i].fd == -1)
+ continue;
+ if (Channel + i == client)
+ continue;
+ j++;
+ fprintf(argv->out, " %d) in->used %d, in->left %d %s@%s\r\n", i,
+ Channel[i].in.used, Channel[i].in.left,
+ Channel[i].username, Channel[i].hostname);
+ }
+ fprintf(argv->out, "Total connections %d\r\n", j);
+ fprintf(argv->out, "Total rec: %d dup: %d fail: %d size: %d, stat rec: %d fail: %d\n", client1->ihavecount, client1->ihaveduplicate, client1->ihavefail, client1->ihavesize, client1->statcount, client1->statfail);
+ fprintf(argv->out, ".\r\n");
+ fflush(argv->out);
+ return 1;
+}
+
+static int
+CMDlistnodelist(client)
+ ClientType *client;
+{
+ int nlcount;
+ argv_t *argv = &client->Argv;
+ daemoncmd_t *p = argv->dc;
+ if (!islocalconnect(client)) {
+ fprintf(argv->out, "%d listnodelist access denied\r\n", p->errorcode);
+ fflush(argv->out);
+ verboselog("Mallocmap Put: %d listnodelist access denied\n", p->errorcode);
+ return 1;
+ }
+ fprintf(argv->out, "%d listnodelist\r\n", p->normalcode);
+ for (nlcount = 0; nlcount < NLCOUNT; nlcount++) {
+ nodelist_t *nl = NODELIST + nlcount;
+ fprintf(argv->out, "%2d %s /\\/\\ %s\r\n", nlcount + 1, nl->node == NULL ? "" : nl->node, nl->exclusion == NULL ? "" : nl->exclusion);
+ fprintf(argv->out, " %s:%s:%s\r\n", nl->host == NULL ? "" : nl->host, nl->protocol == NULL ? "" : nl->protocol, nl->comments == NULL ? "" : nl->comments);
+ }
+ fprintf(argv->out, ".\r\n");
+ fflush(argv->out);
+ verboselog("Listnodelist Put: %d listnodelist complete\n", p->normalcode);
+ return 1;
+}
+
+static int
+CMDlistnewsfeeds(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ daemoncmd_t *p = argv->dc;
+ int nfcount;
+ if (!islocalconnect(client)) {
+ fprintf(argv->out, "%d listnewsfeeds access denied\r\n", p->errorcode);
+ fflush(argv->out);
+ verboselog("Mallocmap Put: %d listnewsfeeds access denied\n", p->errorcode);
+ return 1;
+ }
+ fprintf(argv->out, "%d listnewsfeeds\r\n", p->normalcode);
+ for (nfcount = 0; nfcount < NFCOUNT; nfcount++) {
+ newsfeeds_t *nf = NEWSFEEDS + nfcount;
+ fprintf(argv->out, "%3d %s<=>%s\r\n", nfcount + 1, nf->newsgroups, nf->board);
+ fprintf(argv->out, " %s\r\n", nf->path == NULL ? "(Null)" : nf->path);
+ }
+ fprintf(argv->out, ".\r\n");
+ fflush(argv->out);
+ verboselog("Listnewsfeeds Put: %d listnewsfeeds complete\n", p->normalcode);
+ return 1;
+}
+
+#ifdef MALLOCMAP
+static int
+CMDmallocmap(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ buffer_t *in = &client->in;
+ daemoncmd_t *p = argv->dc;
+ struct rusage ru;
+ int savefd;
+ if (!islocalconnect(client)) {
+ fprintf(argv->out, "%d mallocmap access denied\r\n", p->errorcode);
+ fflush(argv->out);
+ verboselog("Mallocmap Put: %d mallocmap access denied\n", p->errorcode);
+ return 1;
+ }
+ fprintf(argv->out, "%d mallocmap\r\n", p->normalcode);
+ savefd = dup(1);
+ dup2(client->fd, 1);
+ mallocmap();
+ dup2(savefd, 1);
+ close(savefd);
+ fprintf(argv->out, ".\r\n");
+ fflush(argv->out);
+ verboselog("Mallocmap Put: %d mallocmap complete\n", p->normalcode);
+ return 1;
+}
+#endif
+
+#ifdef GETRUSAGE
+static int
+CMDgetrusage(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ daemoncmd_t *p = argv->dc;
+ struct rusage ru;
+ if (!islocalconnect(client)) {
+ fprintf(argv->out, "%d getrusage access denied\r\n", p->errorcode);
+ fflush(argv->out);
+ verboselog("Getrusage Put: %d getrusage access denied\n", p->errorcode);
+ return 1;
+ }
+ fprintf(argv->out, "%d getrusage\r\n", p->normalcode);
+ if (getrusage(RUSAGE_SELF, &ru) == 0) {
+ fprintf(argv->out, "user time used: %.6f\r\n", (double)ru.ru_utime.tv_sec + (double)ru.ru_utime.tv_usec / 1000000.0);
+ fprintf(argv->out, "system time used: %.6f\r\n", (double)ru.ru_stime.tv_sec + (double)ru.ru_stime.tv_usec / 1000000.0);
+ fprintf(argv->out, "maximum resident set size: %lu\r\n", ru.ru_maxrss * getpagesize());
+ fprintf(argv->out, "integral resident set size: %lu\r\n", ru.ru_idrss * getpagesize());
+ fprintf(argv->out, "page faults not requiring physical I/O: %d\r\n", ru.ru_minflt);
+ fprintf(argv->out, "page faults requiring physical I/O: %d\r\n", ru.ru_majflt);
+ fprintf(argv->out, "swaps: %d\r\n", ru.ru_nswap);
+ fprintf(argv->out, "block input operations: %d\r\n", ru.ru_inblock);
+ fprintf(argv->out, "block output operations: %d\r\n", ru.ru_oublock);
+ fprintf(argv->out, "messages sent: %d\r\n", ru.ru_msgsnd);
+ fprintf(argv->out, "messages received: %d\r\n", ru.ru_msgrcv);
+ fprintf(argv->out, "signals received: %d\r\n", ru.ru_nsignals);
+ fprintf(argv->out, "voluntary context switches: %d\r\n", ru.ru_nvcsw);
+ fprintf(argv->out, "involuntary context switches: %d\r\n", ru.ru_nivcsw);
+ }
+ fprintf(argv->out, ".\r\n");
+ fflush(argv->out);
+ verboselog("Getrusage Put: %d getrusage complete\n", p->normalcode);
+ return 1;
+}
+
+#endif
+
+static int
+CMDhismaint(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ daemoncmd_t *p = argv->dc;
+ if (!islocalconnect(client)) {
+ fprintf(argv->out, "%d hismaint access denied\r\n", p->errorcode);
+ fflush(argv->out);
+ verboselog("Hismaint Put: %d hismaint access denied\n", p->errorcode);
+ return 1;
+ }
+ verboselog("Hismaint Put: %d hismaint start\n", p->normalcode);
+ HISmaint();
+ fprintf(argv->out, "%d hismaint complete\r\n", p->normalcode);
+ fflush(argv->out);
+ verboselog("Hismaint Put: %d hismaint complete\n", p->normalcode);
+ return 1;
+}
+
+static int
+CMDreload(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ daemoncmd_t *p = argv->dc;
+ if (!islocalconnect(client)) {
+ fprintf(argv->out, "%d reload access denied\r\n", p->errorcode);
+ fflush(argv->out);
+ verboselog("Reload Put: %d reload access denied\n", p->errorcode);
+ return 1;
+ }
+ initial_bbs("feed");
+ fprintf(argv->out, "%d reload complete\r\n", p->normalcode);
+ fflush(argv->out);
+ verboselog("Reload Put: %d reload complete\n", p->normalcode);
+ return 1;
+}
+
+static int
+CMDverboselog(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ daemoncmd_t *p = argv->dc;
+ if (!islocalconnect(client)) {
+ fprintf(argv->out, "%d verboselog access denied\r\n", p->errorcode);
+ fflush(argv->out);
+ verboselog("Reload Put: %d verboselog access denied\n", p->errorcode);
+ return 1;
+ }
+ if (client->mode == 0) {
+ if (argv->argc > 1) {
+ if (strcasecmp(argv->argv[1], "off") == 0) {
+ setverboseoff();
+ } else {
+ setverboseon();
+ }
+ }
+ }
+ fprintf(argv->out, "%d verboselog %s\r\n", p->normalcode,
+ isverboselog() ? "ON" : "OFF");
+ fflush(argv->out);
+ verboselog("%d verboselog %s\r\n", p->normalcode,
+ isverboselog() ? "ON" : "OFF");
+}
+
+static int
+CMDmidcheck(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ daemoncmd_t *p = argv->dc;
+ if (client->mode == 0) {
+ if (argv->argc > 1) {
+ if (strcasecmp(argv->argv[1], "off") == 0) {
+ client->midcheck = 0;
+ } else {
+ client->midcheck = 1;
+ }
+ }
+ }
+ fprintf(argv->out, "%d mid check %s\r\n", p->normalcode,
+ client->midcheck == 1 ? "ON" : "OFF");
+ fflush(argv->out);
+ verboselog("%d mid check %s\r\n", p->normalcode,
+ client->midcheck == 1 ? "ON" : "OFF");
+}
+
+static int
+CMDgrephist(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ daemoncmd_t *p = argv->dc;
+ if (client->mode == 0) {
+ if (argv->argc > 1) {
+ char *ptr;
+ ptr = (char *)DBfetch(argv->argv[1]);
+ if (ptr != NULL) {
+ fprintf(argv->out, "%d %s OK\r\n", p->normalcode, ptr);
+ fflush(argv->out);
+ verboselog("Addhist Put: %d %s OK\n", p->normalcode, ptr);
+ return 0;
+ } else {
+ fprintf(argv->out, "%d %s not found\r\n", p->errorcode, argv->argv[1]);
+ fflush(argv->out);
+ verboselog("Addhist Put: %d %s not found\n", p->errorcode, argv->argv[1]);
+ return 1;
+ }
+ }
+ }
+ fprintf(argv->out, "%d grephist error\r\n", p->errorcode);
+ fflush(argv->out);
+ verboselog("Addhist Put: %d grephist error\n", p->errorcode);
+ return 1;
+}
+
+
+static int
+CMDaddhist(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ daemoncmd_t *p = argv->dc;
+ /*
+ * if (strcmp(client->username,"localuser") != 0 ||
+ * strcmp(client->hostname,"localhost") != 0) { fprintf(argv->out,"%d add
+ * hist access denied\r\n", p->errorcode); fflush(argv->out);
+ * verboselog("Addhist Put: %d add hist access denied\n", p->errorcode);
+ * return 1; }
+ */
+ if (client->mode == 0) {
+ if (argv->argc > 2) {
+ char *ptr;
+ ptr = (char *)DBfetch(argv->argv[1]);
+ if (ptr == NULL) {
+ if (storeDB(argv->argv[1], argv->argv[2]) < 0) {
+ fprintf(argv->out, "%d add hist store DB error\r\n", p->errorcode);
+ fflush(argv->out);
+ verboselog("Addhist Put: %d add hist store DB error\n", p->errorcode);
+ return 1;
+ } else {
+ fprintf(argv->out, "%d add hist OK\r\n", p->normalcode);
+ fflush(argv->out);
+ verboselog("Addhist Put: %d add hist OK\n", p->normalcode);
+ return 0;
+ }
+ } else {
+ fprintf(argv->out, "%d add hist duplicate error\r\n", p->errorcode);
+ fflush(argv->out);
+ verboselog("Addhist Put: %d add hist duplicate error\n", p->errorcode);
+ return 1;
+ }
+ }
+ }
+ fprintf(argv->out, "%d add hist error\r\n", p->errorcode);
+ fflush(argv->out);
+ verboselog("Addhist Put: %d add hist error\n", p->errorcode);
+ return 1;
+}
+
+static int
+CMDstat(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ char *ptr;
+ if (client->mode == 0) {
+ client->statcount++;
+ if (argv->argc > 1) {
+ if (argv->argv[1][0] != '<') {
+ fprintf(argv->out, "430 No such article\r\n");
+ fflush(argv->out);
+ verboselog("Stat Put: 430 No such article\n");
+ client->statfail++;
+ return 0;
+ }
+ ptr = (char *)DBfetch(argv->argv[1]);
+ if (ptr != NULL) {
+ fprintf(argv->out, "223 0 status %s\r\n", argv->argv[1]);
+ fflush(argv->out);
+ client->mode = 0;
+ verboselog("Stat Put: 223 0 status %s\n", argv->argv[1]);
+ return 1;
+ } else {
+ fprintf(argv->out, "430 No such article\r\n");
+ fflush(argv->out);
+ verboselog("Stat Put: 430 No such article\n");
+ client->mode = 0;
+ client->statfail++;
+ }
+ }
+ }
+}
+
+#ifndef DBZSERVER
+static int
+CMDihave(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ char *ptr = NULL;
+ if (client->mode == 0) {
+ client->ihavecount++;
+ if (argv->argc > 1) {
+ if (argv->argv[1][0] != '<') {
+ fprintf(argv->out, "435 Bad Message-ID\r\n");
+ fflush(argv->out);
+ verboselog("Ihave Put: 435 Bad Message-ID\n");
+ client->ihavefail++;
+ return 0;
+ }
+ if (client->midcheck == 1)
+ ptr = (char *)DBfetch(argv->argv[1]);
+ if (ptr != NULL && client->midcheck == 1) {
+ fprintf(argv->out, "435 Duplicate\r\n");
+ fflush(argv->out);
+ client->mode = 0;
+ verboselog("Ihave Put: 435 Duplicate\n");
+ client->ihaveduplicate++;
+ client->ihavefail++;
+ return 1;
+ } else {
+ fprintf(argv->out, "335\r\n");
+ fflush(argv->out);
+ client->mode = 1;
+ verboselog("Ihave Put: 335\n");
+ }
+ }
+ } else {
+ client->mode = 0;
+ readlines(client);
+ if (HEADER[SUBJECT_H] && HEADER[FROM_H] && HEADER[DATE_H] &&
+ HEADER[MID_H] && HEADER[NEWSGROUPS_H]) {
+ char *path1, *path2;
+ int rel;
+ str_decode_M3(HEADER[SUBJECT_H]);
+ str_decode_M3(HEADER[FROM_H]);
+ str_decode_M3(HEADER[DATE_H]);
+ str_decode_M3(HEADER[MID_H]);
+ str_decode_M3(HEADER[NEWSGROUPS_H]);
+ rel = 0;
+ path1 = (char *)mymalloc(strlen(HEADER[PATH_H]) + 3);
+ path2 = (char *)mymalloc(strlen(MYBBSID) + 3);
+ sprintf(path1, "!%s!", HEADER[PATH_H]);
+ sprintf(path2, "!%s!", MYBBSID);
+ if (HEADER[CONTROL_H]) {
+ bbslog("Control: %s\n", HEADER[CONTROL_H]);
+ if (strncasecmp(HEADER[CONTROL_H], "cancel ", 7) == 0) {
+ rel = cancel_article_front(HEADER[CONTROL_H] + 7);
+ } else {
+ rel = receive_control();
+ }
+ } else if ((char *)strstr(path1, path2) != NULL) {
+ bbslog(":Warn: Loop back article: %s!%s\n", MYBBSID, HEADER[PATH_H]);
+ } else if (strstr(SUBJECT, "@@") && strstr(BODY, "NCM") && strstr(BODY, "PGP")) {
+ rel = receive_nocem();
+ } else {
+ rel = receive_article();
+ }
+ free(path1);
+ free(path2);
+ if (rel == -1) {
+ fprintf(argv->out, "400 server side failed\r\n");
+ fflush(argv->out);
+ verboselog("Ihave Put: 400\n");
+ clearfdset(client->fd);
+ fclose(client->Argv.in);
+ fclose(client->Argv.out);
+ close(client->fd);
+ client->fd = -1;
+ client->mode = 0;
+ client->ihavefail++;
+ return;
+ } else {
+ fprintf(argv->out, "235\r\n");
+ verboselog("Ihave Put: 235\n");
+ }
+ fflush(argv->out);
+ } else if (!HEADER[PATH_H]) {
+ fprintf(argv->out, "437 No Path in \"ihave %s\" header\r\n", HEADER[MID_H]);
+ fflush(argv->out);
+ verboselog("Put: 437 No Path in \"ihave %s\" header\n", HEADER[MID_H]);
+ client->ihavefail++;
+ } else {
+ fprintf(argv->out, "437 No colon-space in \"ihave %s\" header\r\n", HEADER[MID_H]);
+ fflush(argv->out);
+ verboselog("Ihave Put: 437 No colon-space in \"ihave %s\" header\n", HEADER[MID_H]);
+ client->ihavefail++;
+ }
+#ifdef DEBUG
+ printf("subject is %s\n", HEADER[SUBJECT_H]);
+ printf("from is %s\n", HEADER[FROM_H]);
+ printf("Date is %s\n", HEADER[DATE_H]);
+ printf("Newsgroups is %s\n", HEADER[NEWSGROUPS_H]);
+ printf("mid is %s\n", HEADER[MID_H]);
+ printf("path is %s\n", HEADER[PATH_H]);
+#endif
+ }
+ fflush(argv->out);
+ return 0;
+}
+#endif
+
+static int
+CMDhelp(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ daemoncmd_t *p;
+ if (argv->argc >= 1) {
+ fprintf(argv->out, "%d Available Commands\r\n", argv->dc->normalcode);
+ for (p = cmds; p->name != NULL; p++) {
+ fprintf(argv->out, " %s\r\n", p->usage);
+ }
+ fprintf(argv->out, "Report problems to %s\r\n", ADMINUSER);
+ }
+ fputs(".\r\n", argv->out);
+ fflush(argv->out);
+ client->mode = 0;
+ return 0;
+}
+
+static int
+CMDquit(client)
+ ClientType *client;
+{
+ argv_t *argv = &client->Argv;
+ fprintf(argv->out, "205 quit\r\n");
+ fflush(argv->out);
+ verboselog("Quit Put: 205 quit\n");
+ clearfdset(client->fd);
+ fclose(client->Argv.in);
+ fclose(client->Argv.out);
+ close(client->fd);
+ client->fd = -1;
+ client->mode = 0;
+ channeldestroy(client);
+ /* exit(0); */
+}
diff --git a/pttbbs/innbbsd/innbbsd.h b/pttbbs/innbbsd/innbbsd.h
new file mode 100644
index 00000000..90a019d1
--- /dev/null
+++ b/pttbbs/innbbsd/innbbsd.h
@@ -0,0 +1,9 @@
+#ifndef INNBBSD_H
+#define INNBBSD_H
+#include "daemon.h"
+
+#ifndef ADMINUSER
+#define ADMINUSER "usenet@csie.nctu.edu.tw"
+#endif
+
+#endif
diff --git a/pttbbs/innbbsd/inncheck.pl b/pttbbs/innbbsd/inncheck.pl
new file mode 100644
index 00000000..2b98a305
--- /dev/null
+++ b/pttbbs/innbbsd/inncheck.pl
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+use IO::Socket::INET;
+$BBSHOME = '/home/bbs';
+
+open NODES, "<$BBSHOME/innd/nodelist.bbs";
+while( <NODES> ){
+ next if( /^\#/ );
+
+ ($nodename, $host) = $_ =~ /^(\S+)\s+(\S+)/;
+ next if( !$nodename );
+
+ $sock = IO::Socket::INET->new(PeerAddr => $host,
+ PeerPort => 119,
+ Proto => 'tcp');
+ next if( !$sock );
+ $sock->write("list\r\nquit\r\n");
+ $sock->read($data, 104857600);
+
+ foreach( split("\n", $data) ){
+ $group{$nodename}{$1} = 1
+ if( /^([A-Za-z0-9\.]+) \d+ \d+ y/ );
+ }
+}
+
+open FEEDS, "<$BBSHOME/innd/newsfeeds.bbs";
+while( <FEEDS> ){
+ ++$line;
+ next if( /^\#/ );
+
+ next if( !(($gname, $board, $nodename) =
+ $_ =~ /^([\w\.]+)\s+(\w+)\s+(\w+)/) );
+
+ if( !-d ("$BBSHOME/boards/". substr($board, 0, 1). "/$board") ){
+ print "$line: board not found ($board)\n";
+ next;
+ }
+
+ if( !$group{$nodename} ){
+ print "$line: node not found ($nodename)\n";
+ next;
+ }
+
+ if( !$group{$nodename}{$gname} ){
+ print "$line: group not found ($gname)\n";
+ }
+}
+
diff --git a/pttbbs/innbbsd/inndchannel.c b/pttbbs/innbbsd/inndchannel.c
new file mode 100644
index 00000000..fe5b74ef
--- /dev/null
+++ b/pttbbs/innbbsd/inndchannel.c
@@ -0,0 +1,681 @@
+#include <stdlib.h>
+#include "innbbsconf.h"
+#include "daemon.h"
+#include "bbslib.h"
+#include "config.h"
+#include "externs.h"
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include "bbs.h"
+
+#define DEBUG
+#undef DEBUG
+
+#ifndef MAXCLIENT
+#define MAXCLIENT 500
+#endif
+
+#ifndef ChannelSize
+#define ChannelSize 4096
+#endif
+
+#ifndef ReadSize
+#define ReadSize 1024
+#endif
+
+#ifndef DefaultINNBBSPort
+#define DefaultINNBBSPort "7777"
+#endif
+
+#ifndef HIS_MAINT
+#define HIS_MAINT
+#define HIS_MAINT_HOUR 5
+#define HIS_MAINT_MIN 30
+#endif
+
+int Maxclient = MAXCLIENT;
+ClientType *Channel = NULL;
+ClientType INNBBSD_STAT;
+
+int Max_Art_Size = MAX_ART_SIZE;
+
+int inetdstart = 0;
+
+int Junkhistory = 0;
+
+char *REMOTEUSERNAME, *REMOTEHOSTNAME;
+
+static fd_set rfd, wfd, efd, orfd, owfd, oefd;
+
+int channelreader(ClientType *);
+
+void
+clearfdset(fd)
+ int fd;
+{
+ FD_CLR(fd, &rfd);
+}
+
+static void
+channelcreate(client)
+ ClientType *client;
+{
+ buffer_t *in, *out;
+ in = &client->in;
+ out = &client->out;
+ if (in->data != NULL)
+ free(in->data);
+ in->data = (char *)mymalloc(ChannelSize);
+ in->left = ChannelSize;
+ in->used = 0;
+ if (out->data != NULL)
+ free(out->data);
+ out->data = (char *)mymalloc(ChannelSize);
+ out->used = 0;
+ out->left = ChannelSize;
+ client->ihavecount = 0;
+ client->ihaveduplicate = 0;
+ client->ihavefail = 0;
+ client->ihavesize = 0;
+ client->statcount = 0;
+ client->statfail = 0;
+ client->begin = time(NULL);
+}
+
+void
+channeldestroy(client)
+ ClientType *client;
+{
+ if (client->in.data != NULL) {
+ free(client->in.data);
+ client->in.data = NULL;
+ }
+ if (client->out.data != NULL) {
+ free(client->out.data);
+ client->out.data = NULL;
+ }
+#if !defined(PowerBBS) && !defined(DBZSERVER)
+ if (client->ihavecount > 0 || client->statcount > 0) {
+ bbslog("%s@%s rec: %d dup: %d fail: %d size: %d, stat rec: %d fail: %d, time sec: %d\n",
+ client->username, client->hostname, client->ihavecount,
+ client->ihaveduplicate, client->ihavefail, client->ihavesize,
+ client->statcount, client->statfail, time(NULL) - client->begin);
+ INNBBSD_STAT.ihavecount += client->ihavecount;
+ INNBBSD_STAT.ihaveduplicate += client->ihaveduplicate;
+ INNBBSD_STAT.ihavefail += client->ihavefail;
+ INNBBSD_STAT.ihavesize += client->ihavesize;
+ INNBBSD_STAT.statcount += client->statcount;
+ INNBBSD_STAT.statfail += client->statfail;
+ }
+#endif
+}
+
+void
+inndchannel(port, path)
+ char *port, *path;
+{
+ time_t tvec;
+ int i;
+ int bbsinnd;
+ int localbbsinnd;
+ struct timeval tout;
+ ClientType *client = (ClientType *) mymalloc(sizeof(ClientType) * Maxclient);
+ int localdaemonready = 0;
+ Channel = client;
+
+ bbsinnd = pmain(port);
+ if (bbsinnd < 0) {
+ perror("pmain, existing");
+ docompletehalt(0);
+ return;
+ }
+ FD_ZERO(&rfd);
+ FD_ZERO(&wfd);
+ FD_ZERO(&efd);
+
+ localbbsinnd = p_unix_main(path);
+ if (localbbsinnd < 0) {
+ perror("local pmain, existing");
+ /*
+ * Kaede if (!inetdstart) fprintf(stderr, "if no other innbbsd
+ * running, try to remove %s\n",path);
+ */
+ close(bbsinnd);
+ return;
+ } else {
+ FD_SET(localbbsinnd, &rfd);
+ localdaemonready = 1;
+ }
+
+ FD_SET(bbsinnd, &rfd);
+ tvec = time((time_t *) 0);
+ for (i = 0; i < Maxclient; ++i) {
+ client[i].fd = -1;
+ client[i].access = 0;
+ client[i].buffer[0] = '\0';
+ client[i].mode = 0;
+ client[i].in.left = 0;
+ client[i].in.used = 0;
+ client[i].in.data = NULL;
+ client[i].out.left = 0;
+ client[i].out.used = 0;
+ client[i].out.data = NULL;
+ client[i].midcheck = 1;
+ }
+ for (;;) {
+ int nsel, i;
+
+ /*
+ * When to maintain history files.
+ */
+ time_t now;
+ static int maint = 0;
+ struct tm *local;
+
+ if (INNBBSDshutdown()) {
+ HISclose();
+ bbslog(" Shutdown Complete \n");
+ docompletehalt(0);
+ exit(0);
+ }
+ time(&now);
+ local = localtime(&now);
+ if (local != NULL & local->tm_hour == His_Maint_Hour &&
+ local->tm_min >= His_Maint_Min) {
+ if (!maint) {
+ bbslog(":Maint: start (%d:%d).\n", local->tm_hour, local->tm_min);
+ HISmaint();
+ time(&now);
+ local = localtime(&now);
+ if (local != NULL)
+ bbslog(":Maint: end (%d:%d).\n", local->tm_hour, local->tm_min);
+ maint = 1;
+ }
+ } else {
+ maint = 0;
+ }
+ /*
+ * */
+ /*
+ * in order to maintain history, timeout every 60 seconds in case no
+ * connections
+ */
+ tout.tv_sec = 60;
+ tout.tv_usec = 0;
+ orfd = rfd;
+ if ((nsel = select(FD_SETSIZE, &orfd, NULL, NULL, &tout)) < 0) {
+ continue;
+ }
+ if (localdaemonready && FD_ISSET(localbbsinnd, &orfd)) {
+ int ns;
+ ns = tryaccept(localbbsinnd);
+ if (ns < 0)
+ continue;
+ for (i = 0; i < Maxclient; ++i) {
+ if (client[i].fd == -1)
+ break;
+ }
+ if (i == Maxclient) {
+ static char msg[] = "502 no free descriptors\r\n";
+ printf("%s", msg);
+ write(ns, msg, sizeof(msg));
+ close(ns);
+ continue;
+ }
+ client[i].fd = ns;
+ client[i].buffer[0] = '\0';
+ client[i].mode = 0;
+ client[i].midcheck = 1;
+ channelcreate(&client[i]);
+ FD_SET(ns, &rfd); /* FD_SET(ns,&wfd); */
+ {
+ strncpy(client[i].username, "localuser", 20);
+ strncpy(client[i].hostname, "localhost", 128);
+ client[i].Argv.in = fdopen(ns, "r");
+ client[i].Argv.out = fdopen(ns, "w");
+#if !defined(PowerBBS) && !defined(DBZSERVER)
+ bbslog("connected from (%s@%s).\n", client[i].username, client[i].hostname);
+#endif
+#ifdef INNBBSDEBUG
+ printf("connected from (%s@%s).\n", client[i].username, client[i].hostname);
+#endif
+#ifdef DBZSERVER
+ fprintf(client[i].Argv.out, "200 %s InterNetNews DBZSERVER server %s (%s@%s).\r\n", MYBBSID, VERSION, client[i].username, client[i].hostname);
+#else
+ fprintf(client[i].Argv.out, "200 %s InterNetNews INNBBSD server %s (%s@%s).\r\n", MYBBSID, VERSION, client[i].username, client[i].hostname);
+#endif
+ fflush(client[i].Argv.out);
+ verboselog("UNIX Connect from %s@%s\n", client[i].username, client[i].hostname);
+ }
+ }
+ if (FD_ISSET(bbsinnd, &orfd)) {
+ int ns = tryaccept(bbsinnd), length;
+ struct sockaddr_in there;
+ char *name;
+ struct hostent *hp;
+ if (ns < 0)
+ continue;
+ for (i = 0; i < Maxclient; ++i) {
+ if (client[i].fd == -1)
+ break;
+ }
+ if (i == Maxclient) {
+ static char msg[] = "502 no free descriptors\r\n";
+ printf("%s", msg);
+ write(ns, msg, sizeof(msg));
+ close(ns);
+ continue;
+ }
+ client[i].fd = ns;
+ client[i].buffer[0] = '\0';
+ client[i].mode = 0;
+ client[i].midcheck = 1;
+ channelcreate(&client[i]);
+ FD_SET(ns, &rfd); /* FD_SET(ns,&wfd); */
+ length = sizeof(there);
+ if (getpeername(ns, (struct sockaddr *) & there, &length) >= 0) {
+ name = (char *)my_rfc931_name(ns, (struct sockaddr_in *)&there);
+ strncpy(client[i].username, name, 20);
+ hp = (struct hostent *) gethostbyaddr((char *)&there.sin_addr, sizeof(struct in_addr), there.sin_family);
+ if (hp)
+ strncpy(client[i].hostname, hp->h_name, 128);
+ else
+ strncpy(client[i].hostname, (char *)inet_ntoa(there.sin_addr), 128);
+
+ client[i].Argv.in = fdopen(ns, "r");
+ client[i].Argv.out = fdopen(ns, "w");
+ if ((char *)search_nodelist(client[i].hostname, client[i].username) == NULL) {
+ bbslog(":Err: invalid connection (%s@%s).\n", client[i].username, client[i].hostname);
+ fprintf(client[i].Argv.out, "502 You are not in my access file. (%s@%s)\r\n", client[i].username, client[i].hostname);
+ fflush(client[i].Argv.out);
+ fclose(client[i].Argv.in);
+ fclose(client[i].Argv.out);
+ close(client[i].fd);
+ FD_CLR(client[i].fd, &rfd);
+ client[i].fd = -1;
+ continue;
+ }
+ bbslog("connected from (%s@%s).\n", client[i].username, client[i].hostname);
+#ifdef INNBBSDEBUG
+ printf("connected from (%s@%s).\n", client[i].username, client[i].hostname);
+#endif
+#ifdef DBZSERVER
+ fprintf(client[i].Argv.out, "200 %s InterNetNews DBZSERVER server %s (%s@%s).\r\n", MYBBSID, VERSION, client[i].username, client[i].hostname);
+#else
+ fprintf(client[i].Argv.out, "200 %s InterNetNews INNBBSD server %s (%s@%s).\r\n", MYBBSID, VERSION, client[i].username, client[i].hostname);
+#endif
+ fflush(client[i].Argv.out);
+ verboselog("INET Connect from %s@%s\n", client[i].username, client[i].hostname);
+ } else {
+ }
+
+ }
+ for (i = 0; i < Maxclient; ++i) {
+ int fd = client[i].fd;
+ if (fd < 0) {
+ continue;
+ }
+ if (FD_ISSET(fd, &orfd)) {
+ int nr;
+#ifdef DEBUG
+ printf("before read i %d in.used %d in.left %d\n", i, client[i].in.used, client[i].in.left);
+#endif
+ nr = channelreader(client + i);
+#ifdef DEBUG
+ printf("after read i %d in.used %d in.left %d\n", i, client[i].in.used, client[i].in.left);
+#endif
+ /* int nr=read(fd,client[i].buffer,1024); */
+ if (nr <= 0) {
+ FD_CLR(fd, &rfd);
+ fclose(client[i].Argv.in);
+ fclose(client[i].Argv.out);
+ close(fd);
+ client[i].fd = -1;
+ channeldestroy(client + i);
+ continue;
+ }
+#ifdef DEBUG
+ printf("nr %d %.*s", nr, nr, client[i].buffer);
+#endif
+ if (client[i].access == 0) {
+ continue;
+ }
+ }
+ }
+ }
+}
+
+void
+commandparse(client)
+ ClientType *client;
+{
+ char *ptr, *lastend;
+ argv_t *Argv = &client->Argv;
+ int (*Main) ();
+ char *buffer = client->in.data;
+ buffer_t *in = &client->in;
+ int dataused;
+ int dataleft;
+
+#ifdef DEBUG
+ printf("%s %s buffer %s", client->username, client->hostname, buffer);
+#endif
+ ptr = (char *)strchr(in->data + in->used, '\n');
+ if (client->mode == 0) {
+ if (ptr == NULL) {
+ in->used += in->lastread;
+ in->left -= in->lastread;
+ return;
+ } else {
+ dataused = ptr - (in->data + in->used) + 1;
+ dataleft = in->lastread - dataused;
+ lastend = ptr + 1;
+ }
+ } else {
+ if (in->used >= 5) {
+ ptr = (char *)strstr(in->data + in->used - 5, "\r\n.\r\n");
+ } else if (strncmp(in->data, ".\r\n", 3) == 0) {
+ ptr = in->data;
+ } else {
+ ptr = (char *)strstr(in->data + in->used, "\r\n.\r\n");
+ }
+ if (ptr == NULL) {
+ in->used += in->lastread;
+ in->left -= in->lastread;
+ return;
+ } else {
+ ptr[2] = '\0';
+ if (strncmp(in->data, ".\r\n", 3) == 0)
+ dataused = 3;
+ else
+ dataused = ptr - (in->data + in->used) + 5;
+ dataleft = in->lastread - dataused;
+ lastend = ptr + 5;
+ verboselog("Get: %s@%s end of data . size %d\n", client->username, client->hostname, in->used + dataused);
+ client->ihavesize += in->used + dataused;
+ }
+ }
+ if (client->mode == 0) {
+ struct Daemoncmd *dp;
+ Argv->argc = 0, Argv->argv = NULL,
+ Argv->inputline = buffer;
+ if (ptr != NULL)
+ *ptr = '\0';
+ verboselog("Get: %s\n", Argv->inputline);
+ Argv->argc = argify(in->data + in->used, &Argv->argv);
+ if (ptr != NULL)
+ *ptr = '\n';
+ dp = (struct Daemoncmd *) searchcmd(Argv->argv[0]);
+ Argv->dc = dp;
+ if (Argv->dc) {
+#ifdef DEBUG
+ printf("enter command %s\n", Argv->argv[0]);
+#endif
+ if (Argv->argc < dp->argc) {
+ fprintf(Argv->out, "%d Usage: %s\r\n", dp->errorcode, dp->usage);
+ fflush(Argv->out);
+ verboselog("Put: %d Usage: %s\n", dp->errorcode, dp->usage);
+ } else if (dp->argno != 0 && Argv->argc > dp->argno) {
+ fprintf(Argv->out, "%d Usage: %s\r\n", dp->errorcode, dp->usage);
+ fflush(Argv->out);
+ verboselog("Put: %d Usage: %s\n", dp->errorcode, dp->usage);
+ } else {
+ Main = Argv->dc->main;
+ if (Main) {
+ fflush(stdout);
+ (*Main) (client);
+ }
+ }
+ } else {
+ fprintf(Argv->out, "500 Syntax error or bad command\r\n");
+ fflush(Argv->out);
+ verboselog("Put: 500 Syntax error or bad command\r\n");
+ }
+ deargify(&Argv->argv);
+ } else {
+ if (Argv->dc) {
+#ifdef DEBUG
+ printf("enter data mode\n");
+#endif
+ Main = Argv->dc->main;
+ if (Main) {
+ fflush(stdout);
+ (*Main) (client);
+ }
+ }
+ }
+ if (client->mode == 0) {
+ if (dataleft > 0) {
+ strncpy(in->data, lastend, dataleft);
+#ifdef INNBBSDEBUG
+ printf("***** try to copy %x %x %d bytes\n", in->data, lastend, dataleft);
+#endif
+ } else {
+ dataleft = 0;
+ }
+ in->left += in->used - dataleft;
+ in->used = dataleft;
+ }
+}
+
+int
+channelreader(client)
+ ClientType *client;
+{
+ int len;
+ char *ptr;
+ buffer_t *in = &client->in;
+
+ if (in->left < ReadSize + 3) {
+ int need = in->used + in->left + ReadSize + 3;
+ need += need / 5;
+ in->data = (char *)myrealloc(in->data, need);
+ in->left = need - in->used;
+ verboselog("channelreader realloc %d\n", need);
+ }
+ len = read(client->fd, in->data + in->used, ReadSize);
+
+ if (len <= 0)
+ return len;
+
+ in->data[len + in->used] = '\0';
+ in->lastread = len;
+#ifdef DEBUG
+ printf("after read lastread %d\n", in->lastread);
+ printf("len %d client %d\n", len, strlen(in->data + in->used));
+#endif
+
+ REMOTEHOSTNAME = client->hostname;
+ REMOTEUSERNAME = client->username;
+ if (client->mode == 0) {
+ if ((ptr = (char *)strchr(in->data, '\n')) != NULL) {
+ if (in->data[0] != '\r')
+ commandparse(client);
+ }
+ } else {
+ commandparse(client);
+ }
+ return len;
+}
+
+void
+do_command()
+{
+}
+
+void
+dopipesig(s)
+ int s;
+{
+ printf("catch sigpipe\n");
+ signal(SIGPIPE, dopipesig);
+}
+
+int
+standaloneinit(port)
+ char *port;
+{
+ int ndescriptors;
+ FILE *pf;
+ char pidfile[24];
+ ndescriptors = getdtablesize();
+#ifndef NOFORK
+ if (!inetdstart)
+ if (fork())
+ exit(0);
+#endif
+
+ sprintf(pidfile, "/tmp/innbbsd-%s.pid", port);
+ /*
+ * Kaede if (!inetdstart) fprintf(stderr, "PID file is in %s\n",
+ * pidfile);
+ */
+ {
+ int s;
+ for (s = 3; s < ndescriptors; s++)
+ (void)close(s);
+ }
+ pf = fopen(pidfile, "w");
+ if (pf != NULL) {
+ fprintf(pf, "%d\n", getpid());
+ fclose(pf);
+ }
+ return 0;
+}
+
+extern char *optarg;
+extern int opterr, optind;
+
+void
+innbbsusage(name)
+ char *name;
+{
+ fprintf(stderr, "Usage: %s [options] [port [path]]\n", name);
+ fprintf(stderr, " -v (verbose log)\n");
+ fprintf(stderr, " -h|? (help)\n");
+ fprintf(stderr, " -n (not to use in core dbz)\n");
+ fprintf(stderr, " -i (start from inetd with wait option)\n");
+ fprintf(stderr, " -c connections (maximum number of connections accepted)\n");
+ fprintf(stderr, " default=%d\n", Maxclient);
+ fprintf(stderr, " -j (keep history of junk article, default=none)\n");
+}
+
+
+#ifdef DEBUGNGSPLIT
+main()
+{
+ char **ngptr;
+ char buf[1024];
+ gets(buf);
+ ngptr = (char **)BNGsplit(buf);
+ printf("line %s\n", buf);
+ while (*ngptr != NULL) {
+ printf("%s\n", *ngptr);
+ ngptr++;
+ }
+}
+#endif
+
+static time_t INNBBSDstartup;
+int
+innbbsdstartup(void)
+{
+ return INNBBSDstartup;
+}
+
+int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+
+ char *port, *path;
+ int c, errflag = 0;
+ extern INNBBSDhalt();
+ /*
+ * woju
+ */
+ setgid(BBSGID);
+ setuid(BBSUID);
+ chdir(BBSHOME);
+ attach_SHM();
+ resolve_boards();
+
+ port = DefaultINNBBSPort;
+ path = LOCALDAEMON;
+ Junkhistory = 0;
+
+ time(&INNBBSDstartup);
+ openlog("innbbsd", LOG_PID | LOG_ODELAY, LOG_DAEMON);
+ while ((c = getopt(argc, argv, "c:f:s:vhidn?j")) != -1)
+ switch (c) {
+ case 'j':
+ Junkhistory = 1;
+ break;
+ case 'v':
+ verboseon("innbbsd.log");
+ break;
+ case 'n':
+ hisincore(0);
+ break;
+ case 'c':
+ Maxclient = atoi(optarg);
+ if (Maxclient < 0)
+ Maxclient = 0;
+ break;
+ case 'i':{
+ struct sockaddr_in there;
+ int len = sizeof(there);
+ int rel;
+ if ((rel = getsockname(0, (struct sockaddr *) & there, &len)) < 0) {
+ fprintf(stdout, "You must run -i from inetd with inetd.conf line: \n");
+ fprintf(stdout, "service-port stream tcp wait bbs " BBSHOME "/innd/innbbsd innbbsd -i port\n");
+ fflush(stdout);
+ exit(5);
+ }
+ inetdstart = 1;
+ startfrominetd(1);
+ }
+ break;
+ case 'd':
+ dbzdebug(1);
+ break;
+ case 's':
+ Max_Art_Size = atol(optarg);
+ if (Max_Art_Size < 0)
+ Max_Art_Size = 0;
+ break;
+ case 'h':
+ case '?':
+ default:
+ errflag++;
+ }
+ if (errflag > 0) {
+ innbbsusage(argv[0]);
+ return (1);
+ }
+ if (argc - optind >= 1) {
+ port = argv[optind];
+ }
+ if (argc - optind >= 2) {
+ path = argv[optind + 1];
+ }
+ standaloneinit(port);
+
+ initial_bbs("feed");
+
+ /*
+ * Kaede if (!inetdstart) fprintf(stderr, "Try to listen in port %s and
+ * path %s\n", port, path);
+ */
+ HISmaint();
+ HISsetup();
+ installinnbbsd();
+ sethaltfunction(INNBBSDhalt);
+
+ signal(SIGPIPE, dopipesig);
+ inndchannel(port, path);
+ HISclose();
+ return 0;
+}
diff --git a/pttbbs/innbbsd/inntobbs.c b/pttbbs/innbbsd/inntobbs.c
new file mode 100644
index 00000000..fca7c3d5
--- /dev/null
+++ b/pttbbs/innbbsd/inntobbs.c
@@ -0,0 +1,342 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "daemon.h"
+#include "bbslib.h"
+#include <time.h>
+#include "externs.h"
+
+#define INNTOBBS
+#include "inntobbs.h"
+
+typedef struct Header {
+ char *name;
+ int id;
+} header_t;
+
+/*
+ * enum HeaderValue { SUBJECT_H, FROM_H, DATE_H, MID_H, NEWSGROUPS_H,
+ * NNTPPOSTINGHOST_H, NNTPHOST_H, CONTROL_H, PATH_H, ORGANIZATION_H,
+ * LASTHEADER, };
+ */
+
+#include <string.h>
+
+header_t headertable[] = {
+ "Subject", SUBJECT_H,
+ "From", FROM_H,
+ "Date", DATE_H,
+ "Message-ID", MID_H,
+ "Newsgroups", NEWSGROUPS_H,
+ "NNTP-Posting-Host", NNTPPOSTINGHOST_H,
+ "NNTP-Host", NNTPHOST_H,
+ "Control", CONTROL_H,
+ "Path", PATH_H,
+ "Organization", ORGANIZATION_H,
+ "X-Auth-From", X_Auth_From_H,
+ "Approved", APPROVED_H,
+ "Distribution", DISTRIBUTION_H,
+ "Keywords", KEYWORDS_H,
+ "Summary", SUMMARY_H,
+ "References", REFERENCES_H,
+};
+
+char *HEADER[LASTHEADER];
+char *BODY;
+char *FROM, *SUBJECT, *SITE, *DATE, *POSTHOST, *NNTPHOST, *PATH,
+ *GROUPS, *MSGID, *CONTROL;
+
+#ifdef PalmBBS
+char **XHEADER;
+char *XPATH;
+#endif
+
+
+int
+isexcluded(path1, nl)
+ char *path1;
+ nodelist_t *nl;
+{
+ char path2[1024];
+ /* path2 = (char*)mymalloc(strlen(nl->node) + 3); */
+ sprintf(path2, "!%.*s!", sizeof path2 - 3, nl->node);
+ if (strstr(path1, path2) != NULL)
+ return 1;
+ if (nl->exclusion && *nl->exclusion) {
+ char *exclude, *ptr;
+ for (exclude = nl->exclusion, ptr = strchr(exclude, ',');
+ exclude && *exclude; ptr = strchr(exclude, ',')) {
+ if (ptr)
+ *ptr = '\0';
+ sprintf(path2, "!%.*s!", sizeof path2 - 3, exclude);
+ if (strstr(path1, path2) != NULL)
+ return 1;
+ if (ptr) {
+ *ptr = ',';
+ exclude = ptr + 1;
+ } else {
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+void
+feedfplog(nf, filepath, type)
+ newsfeeds_t *nf;
+ char *filepath;
+ int type;
+{
+ char *path1;
+ nodelist_t *nl;
+ if (nf == NULL)
+ return;
+ if (nf->path != NULL) {
+ char *ptr1, *ptr2;
+ char savech;
+ path1 = (char *)mymalloc(strlen(HEADER[PATH_H]) + 3);
+ sprintf(path1, "!%s!", HEADER[PATH_H]);
+ for (ptr1 = nf->path; ptr1 && *ptr1;) {
+ for (; *ptr1 && isspace(*ptr1); ptr1++);
+ if (!*ptr1)
+ break;
+ for (ptr2 = ptr1; *ptr2 && !isspace(*ptr2); ptr2++);
+ savech = *ptr2;
+ *ptr2 = '\0';
+ /*
+ * bbslog("search node %s\n",ptr1);
+ */
+ nl = (nodelist_t *) search_nodelist_bynode(ptr1);
+ /*
+ * bbslog("search node node %s, host %s fp %d\n",nl->node,
+ * nl->host, nl->feedfp);
+ */
+ *ptr2 = savech;
+ ptr1 = ptr2++;
+ if (nl == NULL)
+ continue;
+ if (nl->feedfp == NULL)
+ continue;
+ if (isexcluded(path1, nl))
+ continue;
+ /*
+ * path2 = (char*)mymalloc(strlen(nl->node) + 3); sprintf(path2,
+ * "!%s!",nl->node); free(path2);
+ */
+ /*
+ * bbslog("path1 %s path2 %s\n",path1, path2);
+ */
+ /* if (strstr(path1, path2) != NULL) return; */
+ /* to conform to the bntplink batch file */
+ {
+ char *slash = strrchr(filepath, '/');
+ if (slash != NULL)
+ *slash = '\t';
+ fprintf(nl->feedfp, "%s\t%s\t\t%s\t%s\t%c\t%s\t%s!%s\n",
+ filepath == NULL ? "" : filepath,
+ GROUPS, FROM, SUBJECT, type, MSGID, MYBBSID, HEADER[PATH_H]);
+ if (slash != NULL)
+ *slash = '/';
+ }
+ fflush(nl->feedfp);
+ if (savech == '\0')
+ break;
+ }
+ free(path1);
+ }
+}
+
+static FILE *bbsfeedsfp = NULL;
+static int bbsfeedson = -1;
+
+void
+init_bbsfeedsfp(void)
+{
+ if (bbsfeedsfp != NULL) {
+ fclose(bbsfeedsfp);
+ bbsfeedsfp = NULL;
+ }
+ bbsfeedson = -1;
+}
+
+void
+bbsfeedslog(filepath, type)
+ char *filepath;
+ int type;
+{
+
+ char datebuf[40];
+ time_t now;
+
+ if (bbsfeedson == 0)
+ return;
+ if (bbsfeedson == -1) {
+ if (!isfile(BBSFEEDS)) {
+ bbsfeedson = 0;
+ return;
+ }
+ bbsfeedson = 1;
+ }
+ if (bbsfeedsfp == NULL) {
+ bbsfeedsfp = fopen(BBSFEEDS, "a");
+ }
+ time(&now);
+ strftime(datebuf, sizeof(datebuf), "%b %d %X ", localtime(&now));
+
+ if (bbsfeedsfp != NULL) {
+ fprintf(bbsfeedsfp, "%s %c %s %s %s %s!%s %s\n", datebuf, type,
+ REMOTEHOSTNAME, GROUPS, MSGID, MYBBSID, HEADER[PATH_H], filepath == NULL ? "" : filepath);
+ fflush(bbsfeedsfp);
+ }
+}
+
+static FILE *echomailfp = NULL;
+static int echomaillogon = -1;
+
+void
+init_echomailfp(void)
+{
+ if (echomailfp != NULL) {
+ fclose(echomailfp);
+ echomailfp = NULL;
+ }
+ echomaillogon = -1;
+}
+
+void
+echomaillog()
+{
+
+ if (echomaillogon == 0)
+ return;
+ if (echomaillogon == -1) {
+ if (!isfile(ECHOMAIL)) {
+ echomaillogon = 0;
+ return;
+ }
+ echomaillogon = 1;
+ }
+ if (echomailfp == NULL) {
+ echomailfp = fopen(ECHOMAIL, "a");
+ }
+ if (echomailfp != NULL) {
+ fprintf(echomailfp, "\n");
+ fprintf(echomailfp, "發信人: %s, 信區: %s\n", FROM, GROUPS);
+ str_decode_M3(SUBJECT);
+ fprintf(echomailfp, "標 題: %s\n", SUBJECT);
+ fprintf(echomailfp, "發信站: %s (%s)\n", SITE, DATE);
+ fprintf(echomailfp, "轉信站: %s (%s)\n", PATH, REMOTEHOSTNAME);
+ fflush(echomailfp);
+ }
+}
+
+int
+headercmp(a, b)
+ header_t *a, *b;
+{
+ return strcasecmp(a->name, b->name);
+}
+
+void
+readlines(client)
+ ClientType *client;
+{
+ buffer_t *in = &client->in;
+ char *front = in->data, *ptr, *hptr;
+ int i;
+
+ for (i = 0; i < LASTHEADER; i++)
+ HEADER[i] = NULL;
+ for (ptr = (char *)strchr(in->data, '\n'); ptr != NULL && *ptr != '\0'; front = ptr + 1, ptr = (char *)strchr(front, '\n')) {
+ *ptr = '\0';
+ if (front[0] == '\r' || front[1] == '\n') {
+ BODY = front + 2;
+ break;
+ }
+ hptr = (char *)strchr(front, ':');
+ if (hptr != NULL && hptr[1] == ' ') {
+ int value;
+ *hptr = '\0';
+ value = headervalue(front);
+ if (value != -1) {
+ char *tp;
+ HEADER[value] = hptr + 2;
+ if ((tp = (char *)strchr(HEADER[value], '\r')) != NULL)
+ *tp = '\0';
+ }
+ *hptr = ':';
+ }
+ /**ptr = '\n';*/
+ }
+ NNTPHOST = HEADER[NNTPHOST_H];
+ PATH = HEADER[PATH_H];
+ FROM = HEADER[FROM_H];
+ GROUPS = HEADER[NEWSGROUPS_H];
+ SUBJECT = HEADER[SUBJECT_H];
+ DATE = HEADER[DATE_H];
+ SITE = HEADER[ORGANIZATION_H];
+ MSGID = HEADER[MID_H];
+ CONTROL = HEADER[CONTROL_H];
+ POSTHOST = HEADER[NNTPPOSTINGHOST_H];
+ if (POSTHOST == NULL) {
+ if (HEADER[X_Auth_From_H] != NULL) {
+ POSTHOST = HEADER[X_Auth_From_H];
+ HEADER[NNTPPOSTINGHOST_H] = POSTHOST;
+ }
+ }
+#ifdef PalmBBS
+ XPATH = PATH;
+ XHEADER = HEADER;
+#endif
+}
+
+void
+article_init()
+{
+ int i;
+ static int article_inited = 0;
+
+ if (article_inited)
+ return;
+ article_inited = 1;
+
+ qsort(headertable, sizeof(headertable) / sizeof(header_t), sizeof(header_t),
+ headercmp);
+ for (i = 0; i < LASTHEADER; i++)
+ HEADER[i] = NULL;
+}
+
+int
+headervalue(inputheader)
+ char *inputheader;
+{
+ header_t key, *findkey;
+ static int hasinit = 0;
+
+ if (hasinit == 0) {
+ article_init();
+ hasinit = 1;
+ }
+ key.name = inputheader;
+ findkey = (header_t *) bsearch(
+ (char *)&key, (char *)headertable,
+ sizeof(headertable) / sizeof(header_t), sizeof(key),
+ headercmp);
+ if (findkey != NULL)
+ return findkey->id;
+ return -1;
+}
+
+#ifdef INNTOBBS_MAIN
+main()
+{
+ int i, j, k, l, m, n, o, p, q;
+ article_init();
+ i = headervalue("Subject");
+ j = headervalue("From");
+ k = headervalue("Date");
+ l = headervalue("NNTP-Posting-Host");
+ m = headervalue("Newsgroups");
+ n = headervalue("Message-ID");
+}
+#endif
diff --git a/pttbbs/innbbsd/inntobbs.h b/pttbbs/innbbsd/inntobbs.h
new file mode 100644
index 00000000..6d910252
--- /dev/null
+++ b/pttbbs/innbbsd/inntobbs.h
@@ -0,0 +1,39 @@
+#ifndef INNTOBBS_H
+#define INNTOBBS_H
+
+enum HeaderValue {
+ SUBJECT_H, FROM_H, DATE_H, MID_H, NEWSGROUPS_H,
+ NNTPPOSTINGHOST_H, NNTPHOST_H, CONTROL_H, PATH_H,
+ ORGANIZATION_H, X_Auth_From_H, APPROVED_H, DISTRIBUTION_H,
+ REFERENCES_H, KEYWORDS_H, SUMMARY_H,
+ LASTHEADER,
+};
+
+#if !defined(PalmBBS)
+extern char *HEADER[];
+extern char *BODY;
+extern char *FROM, *SUBJECT, *SITE, *DATE, *POSTHOST, *NNTPHOST, *PATH,
+ *GROUPS, *MSGID, *CONTROL;
+extern char *REMOTEHOSTNAME, *REMOTEUSERNAME;
+#else
+extern char **XHEADER;
+extern char *BODY;
+extern char *FROM, *SUBJECT, *SITE, *DATE, *POSTHOST, *NNTPHOST, *XPATH,
+ *GROUPS, *MSGID, *CONTROL;
+extern char *REMOTEHOSTNAME, *REMOTEUSERNAME;
+#endif
+
+int receive_article();
+
+#if defined(PalmBBS)
+#ifndef INNTOBBS
+#ifndef PATH
+#define PATH XPATH
+#endif
+#ifndef HEADER
+#define HEADER XHEADER
+#endif
+#endif
+#endif
+
+#endif
diff --git a/pttbbs/innbbsd/mkhistory.c b/pttbbs/innbbsd/mkhistory.c
new file mode 100644
index 00000000..c1278adb
--- /dev/null
+++ b/pttbbs/innbbsd/mkhistory.c
@@ -0,0 +1,18 @@
+#include <stdlib.h>
+#include "externs.h"
+#include "innbbsconf.h"
+#include "bbslib.h"
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s history-file\n", argv[0]);
+ exit(1);
+ }
+ initial_bbs(NULL);
+ mkhistory(argv[1]);
+ return 0;
+}
diff --git a/pttbbs/innbbsd/nntp.h b/pttbbs/innbbsd/nntp.h
new file mode 100644
index 00000000..78129d7c
--- /dev/null
+++ b/pttbbs/innbbsd/nntp.h
@@ -0,0 +1,141 @@
+/*
+ * $Revision: 1.1 $ *
+ *
+ * Here be a set of NNTP response codes as defined in RFC977 and elsewhere. *
+ * The reponse codes are three digits, RFI, defined like this: * R,
+ * Response: * 1xx Informative message * 2xx
+ * Command ok * 3xx Command ok so far, send the rest of it. *
+ * xx Command was correct, but couldn't be performed for *
+ * ome reason. * 5xx Command unimplemented, or incorrect,
+ * or a serious * program error occurred. * F,
+ * Function: * x0x Connection, setup, and miscellaneous messages *
+ * 1x Newsgroup selection * x2x Article selection *
+ * 3x Distribution functions * x4x Posting *
+ * 8x Nonstandard extensions (AUTHINFO, XGTITLE) * x9x
+ * Debugging output * I, Information: * No defined semantics
+ */
+#define NNTP_HELPOK_VAL 100
+#define NNTP_BAD_COMMAND_VAL 500
+#define NNTP_BAD_COMMAND "500 Syntax error or bad command"
+#define NNTP_TEMPERR_VAL 503
+#define NNTP_ACCESS "502 Permission denied"
+#define NNTP_ACCESS_VAL 502
+#define NNTP_GOODBYE_ACK "205"
+#define NNTP_GOODBYE_ACK_VAL 205
+#define NNTP_GOODBYE "400"
+#define NNTP_GOODBYE_VAL 400
+#define NNTP_HAVEIT "435 Duplicate"
+#define NNTP_HAVEIT_BADID "435 Bad Message-ID"
+#define NNTP_HAVEIT_VAL 435
+#define NNTP_LIST_FOLLOWS "215"
+#define NNTP_LIST_FOLLOWS_VAL 215
+#define NNTP_HELP_FOLLOWS "100 Legal commands"
+#define NNTP_HELP_FOLLOWS_VAL 100
+#define NNTP_NOTHING_FOLLOWS_VAL 223
+#define NNTP_ARTICLE_FOLLOWS "220"
+#define NNTP_ARTICLE_FOLLOWS_VAL 220
+#define NNTP_NEWGROUPS_FOLLOWS_VAL 231
+#define NNTP_HEAD_FOLLOWS "221"
+#define NNTP_HEAD_FOLLOWS_VAL 221
+#define NNTP_BODY_FOLLOWS_VAL 222
+#define NNTP_OVERVIEW_FOLLOWS_VAL 224
+#define NNTP_DATE_FOLLOWS_VAL 111
+#define NNTP_POSTOK "200"
+#define NNTP_POSTOK_VAL 200
+#define NNTP_START_POST_VAL 340
+#define NNTP_NOPOSTOK_VAL 201
+#define NNTP_SLAVEOK_VAL 202
+#define NNTP_REJECTIT_VAL 437
+#define NNTP_REJECTIT_EMPTY "437 Empty article"
+#define NNTP_DONTHAVEIT "430"
+#define NNTP_DONTHAVEIT_VAL 430
+#define NNTP_RESENDIT_NOHIST "436 Can't write history"
+#define NNTP_RESENDIT_NOSPACE "436 No space"
+#define NNTP_RESENDIT_VAL 436
+#define NNTP_POSTEDOK "240 Article posted"
+#define NNTP_POSTEDOK_VAL 240
+#define NNTP_POSTFAIL_VAL 441
+#define NNTP_GROUPOK_VAL 211
+#define NNTP_SENDIT "335"
+#define NNTP_SENDIT_VAL 335
+#define NNTP_SYNTAX_USE "501 Bad command use"
+#define NNTP_SYNTAX_VAL 501
+#define NNTP_TOOKIT "235"
+#define NNTP_TOOKIT_VAL 235
+#define NNTP_NOTINGROUP "412 Not in a newsgroup"
+#define NNTP_NOTINGROUP_VAL 412
+#define NNTP_NOSUCHGROUP "411 No such group"
+#define NNTP_NOSUCHGROUP_VAL 411
+#define NNTP_NEWNEWSOK "230 New news follows"
+#define NNTP_NOARTINGRP "423 Bad article number"
+#define NNTP_NOARTINGRP_VAL 423
+#define NNTP_NOCURRART "420 No current article"
+#define NNTP_NOCURRART_VAL 420
+#define NNTP_NONEXT_VAL 421
+#define NNTP_NOPREV_VAL 422
+#define NNTP_CANTPOST "440 Posting not allowed"
+#define NNTP_CANTPOST_VAL 440
+
+
+/*
+ * * The first character of an NNTP reply can be used as a category class.
+ */
+#define NNTP_CLASS_OK '2'
+#define NNTP_CLASS_ERROR '4'
+#define NNTP_CLASS_FATAL '5'
+
+
+/*
+ * * The NNTP protocol currently has no way to say "offer me this article *
+ * later, but don't close the connection." That will be fixed in NNTP2.
+ * #define NNTP_RESENDIT_LATER "?" #define NNTP_RESENDIT_LATER_VAL
+ * */
+
+
+/*
+ * * Authentication commands from the RFC update (not official).
+ */
+#define NNTP_AUTH_NEEDED "480"
+#define NNTP_AUTH_NEEDED_VAL 480
+#define NNTP_AUTH_BAD "481"
+#define NNTP_AUTH_NEXT "381"
+#define NNTP_AUTH_NEXT_VAL 381
+#define NNTP_AUTH_OK "281"
+#define NNTP_AUTH_OK_VAL 281
+#define NNTP_AUTH_REJECT_VAL 482
+
+/*
+ * * XGTITLE, from ANU news.
+ */
+#define NNTP_XGTITLE_BAD 481 /* Yes, 481. */
+#define NNTP_XGTITLE_OK 282
+
+#define NNTP_STRLEN 512
+
+/*
+ * * For tin newsreader
+ */
+#define OK_XINDEX 218 /* Tin style group index file
+ * follows */
+#define OK_XMOTD 217 /* Motd (message of the day)
+ * file follows */
+#define ERR_XINDEX 418 /* No tin style index file
+ * for newsgroup */
+#define ERR_XMOTD 417 /* No motd (message of the
+ * day) file */
+
+/* For DBZ server */
+#define NNTP_ADDHIST_OK 283 /* addhist OK */
+#define NNTP_GREPHIST_OK 284 /* grephist OK */
+#define NNTP_MIDCHECK_OK 285 /* grephist OK */
+#define NNTP_SHUTDOWN_OK 286 /* grephist OK */
+#define NNTP_RELOAD_OK 287 /* grephist OK */
+#define NNTP_MODE_OK 101 /* grephist OK */
+#define NNTP_VERBOSELOG_OK 289 /* grephist OK */
+#define NNTP_ADDHIST_BAD 483 /* addhist fail */
+#define NNTP_GREPHIST_BAD 484 /* grephist fail */
+#define NNTP_MIDCHECK_BAD 485 /* grephist fail */
+#define NNTP_SHUTDOWN_BAD 486 /* grephist fail */
+#define NNTP_RELOAD_BAD 487 /* grephist fail */
+#define NNTP_MODE_BAD 488 /* grephist fail */
+#define NNTP_VERBOSELOG_BAD 489 /* grephist fail */
diff --git a/pttbbs/innbbsd/nocem.c b/pttbbs/innbbsd/nocem.c
new file mode 100644
index 00000000..595ecb1d
--- /dev/null
+++ b/pttbbs/innbbsd/nocem.c
@@ -0,0 +1,636 @@
+/*
+ * NoCeM-INNBBSD Yen-Ming Lee <leeym@cae.ce.ntu.edu.tw> NCMparse(),
+ * NCMverify(), NCMcancel(): return 0 success, otherwise fail;
+ */
+
+#include <stdlib.h>
+#include "externs.h"
+#include "nocem.h"
+#define PGP5
+#undef PGP2
+
+int ncmdebug = 0;
+
+char NCMVER[8];
+char ISSUER[STRLEN];
+char TYPE[8];
+char ACTION[8];
+char NCMID[STRLEN];
+char COUNT[8];
+char THRESHOLD[STRLEN];
+char KEYID[16];
+char SPAMMID_NOW[STRLEN];
+char SPAMMID[MAXSPAMMID][STRLEN];
+
+int NNTP = -1;
+FILE *NNTPrfp = NULL;
+FILE *NNTPwfp = NULL;
+char NNTPbuffer[1024];
+int num_spammid = 0;
+char errmsg[1024] = "nothing";
+int NCMCOUNT = 0;
+ncmperm_t *NCMPERM=NULL, **NCMPERM_BYTYPE=NULL;
+static char *NCMPERM_BUF;
+
+/* ------------------------------------------------------------------ */
+/* NCM initial and maintain */
+/* ------------------------------------------------------------------ */
+
+static int
+ncm_bytypecmp(a, b)
+ ncmperm_t **a, **b;
+{
+ return strcasecmp((*a)->type, (*b)->type);
+}
+
+static int
+ncmcmp(a, b)
+ ncmperm_t *a, *b;
+{
+ return strcasecmp(a->issuer, b->issuer);
+}
+
+#if 0
+ncmperm_t *
+search_issuer(issuer)
+ char *issuer;
+{
+ ncmperm_t ncmt, *find;
+ ncmt.issuer = "*";
+ find = (ncmperm_t *) bsearch((char *) &ncmt, NCMPERM, NCMCOUNT, sizeof(ncmperm_t), ncmcmp);
+ if (find)
+ return find;
+ ncmt.issuer = issuer;
+ find = (ncmperm_t *) bsearch((char *) &ncmt, NCMPERM, NCMCOUNT, sizeof(ncmperm_t), ncmcmp);
+ return find;
+}
+#else
+ncmperm_t *
+search_issuer(issuer)
+ char *issuer;
+{
+ ncmperm_t *find;
+ int i;
+ for (i = 0; i < NCMCOUNT; i++)
+ {
+ find = &NCMPERM[i];
+ if (strstr(find->issuer, "*"))
+ return find;
+ if (strstr(issuer, find->issuer))
+ return find;
+ }
+ return NULL;
+}
+#endif
+
+ncmperm_t *
+search_issuer_type(issuer, type)
+ char *issuer, *type;
+{
+ ncmperm_t *find;
+ int i;
+ for (i = 0; i < NCMCOUNT; i++)
+ {
+ find = &NCMPERM[i];
+ if ((!strcmp(find->issuer, "*") || strstr(issuer, find->issuer)) &&
+ (!strcmp(find->type, "*") || !strcasecmp(find->type, type)))
+ return find;
+ }
+ return NULL;
+}
+
+int
+readNCMfile(inndhome)
+ char *inndhome;
+{
+ FILE *fp;
+ char buff[LINELEN];
+ struct stat st;
+ int i, count;
+ char *ptr, *ncmpermptr;
+
+ sprintf(buff, "%s/ncmperm.bbs", inndhome);
+ fp = fopen(buff, "r");
+ if (fp == NULL)
+ {
+ fprintf(stderr, "read fail %s", buff);
+ return -1;
+ }
+ if (fstat(fileno(fp), &st) != 0)
+ {
+ fprintf(stderr, "stat fail %s", buff);
+ return -1;
+ }
+ if (NCMPERM_BUF == NULL)
+ {
+ NCMPERM_BUF = (char *) mymalloc(st.st_size + 1);
+ }
+ else
+ {
+ NCMPERM_BUF = (char *) myrealloc(NCMPERM_BUF, st.st_size + 1);
+ }
+ i = 0, count = 0;
+ while (fgets(buff, sizeof buff, fp) != NULL)
+ {
+ if (buff[0] == '#')
+ continue;
+ if (buff[0] == '\n')
+ continue;
+ strcpy(NCMPERM_BUF + i, buff);
+ i += strlen(buff);
+ count++;
+ }
+ fclose(fp);
+ if (NCMPERM == NULL)
+ {
+ NCMPERM = (ncmperm_t *) mymalloc(sizeof(ncmperm_t) * (count + 1));
+ NCMPERM_BYTYPE = (ncmperm_t **) mymalloc(sizeof(ncmperm_t *) * (count + 1));
+ }
+ else
+ {
+ NCMPERM = (ncmperm_t *) myrealloc(NCMPERM, sizeof(ncmperm_t) * (count + 1));
+ NCMPERM_BYTYPE = (ncmperm_t **) myrealloc(NCMPERM_BYTYPE, sizeof(ncmperm_t *) * (count + 1));
+ }
+ NCMCOUNT = 0;
+ for (ptr = NCMPERM_BUF; (ncmpermptr = (char *) strchr(ptr, '\n')) != NULL; ptr = ncmpermptr + 1, NCMCOUNT++)
+ {
+ char *nptr;
+ *ncmpermptr = '\0';
+ NCMPERM[NCMCOUNT].issuer = "";
+ NCMPERM[NCMCOUNT].type = "";
+ NCMPERM[NCMCOUNT].perm = 0;
+ NCMPERM_BYTYPE[NCMCOUNT] = NCMPERM + NCMCOUNT;
+ for (nptr = ptr; *nptr && (*nptr == '\t');)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ NCMPERM[NCMCOUNT].issuer = nptr;
+ for (nptr++; *nptr && !(*nptr == '\t');)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ *nptr = '\0';
+ for (nptr++; *nptr && (*nptr == '\t');)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ NCMPERM[NCMCOUNT].type = nptr;
+ for (nptr++; *nptr && !(*nptr == '\t');)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ *nptr = '\0';
+ for (nptr++; *nptr && (*nptr == '\t');)
+ nptr++;
+ if (*nptr == '\0')
+ continue;
+ NCMPERM[NCMCOUNT].perm = (strstr(nptr, "y") || strstr(nptr, "Y"));
+ for (nptr++; *nptr && !strchr("\r\n", *nptr);)
+ nptr++;
+ /* if (*nptr == '\0') continue; */
+ *nptr = '\0';
+ }
+ qsort(NCMPERM, NCMCOUNT, sizeof(ncmperm_t), ncmcmp);
+ qsort(NCMPERM_BYTYPE, NCMCOUNT, sizeof(ncmperm_t *), ncm_bytypecmp);
+#if 0
+ NCMregister();
+#endif
+ return 0;
+}
+
+int
+NCMupdate(char *issuer, char *type)
+{
+ FILE *fp;
+ char buff[LINELEN];
+
+ sprintf(buff, "%s/ncmperm.bbs", INNDHOME);
+ if (!isfile(buff))
+ {
+ if ((fp = fopen(buff, "w")) == NULL)
+ {
+ fprintf(stderr, "write fail %s", buff);
+ return -1;
+ }
+ fprintf(fp, "# This is ncmperm.bbs, it's auto-generated by program for first time\n");
+ fprintf(fp, "# The columns *MUST* be separated by [TAB]\n");
+ fprintf(fp, "# If you wanna accept someone's NoCeM notice, change his perm from \'no\' to \'yes\'\n");
+ fprintf(fp, "# put \"*\" in Issuer column means to match all\n");
+ fprintf(fp, "# Any questions ? please e-mail %s\n", LeeymEMAIL);
+ fprintf(fp, "# Issuer\t\tType\tPerm\n");
+ fflush(fp);
+ fclose(fp);
+ bbslog("NCMupdate create %s\n", buff);
+ }
+ if ((fp = fopen(buff, "a")) == NULL)
+ {
+ fprintf(stderr, "attach fail %s", buff);
+ return -1;
+ }
+ fprintf(fp, "%s\t\t%s\tno\n", issuer, type);
+ fflush(fp);
+ fclose(fp);
+ bbslog("NCMupdate add Issuer: %s , Type: %s\n", ISSUER, TYPE);
+ sleep(1);
+ if (readNCMfile(INNDHOME) == -1)
+ bbslog("fail to readNCMfile\n");
+ return 0;
+}
+
+int tcpcommand(char *fmt, ...)
+{
+ va_list ap;
+ char *ptr;
+ va_start(ap, fmt);
+ vfprintf(NNTPwfp, fmt, ap);
+ va_end(ap);
+ fprintf(NNTPwfp, "\r\n");
+ fflush(NNTPwfp);
+
+ fgets(NNTPbuffer, sizeof NNTPbuffer, NNTPrfp);
+ ptr = strchr(NNTPbuffer, '\r');
+ if (ptr)
+ *ptr = '\0';
+ ptr = strchr(NNTPbuffer, '\n');
+ if (ptr)
+ *ptr = '\0';
+ return atoi(NNTPbuffer);
+}
+
+int
+NCMregister()
+{
+ int status;
+ time_t now = time(NULL);
+ char hbuf[80];
+
+ gethostname(hbuf, 80);
+
+ if (!strcmp(hbuf, LeeymBBS))
+ return 1;
+
+ if ((NNTP = inetclient(LeeymBBS, "7777", "tcp")) < 0)
+ {
+ bbslog("NCMregister :Err: server %s %s error: cant connect\n", LeeymBBS, "7777");
+ return 0;
+ }
+
+ if (!(NNTPrfp = fdopen(NNTP, "r")) || !(NNTPwfp = fdopen(NNTP, "w")))
+ {
+ bbslog("NCMregister :Err: fdopen failed\n");
+ return 0;
+ }
+
+ fgets(NNTPbuffer, sizeof NNTPbuffer, NNTPrfp);
+ if (atoi(NNTPbuffer) != 200)
+ {
+ bbslog("NCMregister :Err: server error: %s", NNTPbuffer);
+ return 0;
+ }
+ status = tcpcommand("ADDHIST <%d-%s> NCMregister/%s/%s",
+ now, hbuf, VERSION, NCMINNBBSVER);
+ status = tcpcommand("QUIT");
+ fclose(NNTPwfp);
+ fclose(NNTPrfp);
+ close(NNTP);
+ return 1;
+}
+
+/* ------------------------------------------------------------------ */
+/* PGP verify */
+/* ------------------------------------------------------------------ */
+
+#ifdef PGP5
+int
+run_pgp(char *cmd, FILE ** in, FILE ** out)
+{
+ int pin[2], pout[2], child_pid;
+ char PGPPATH[80];
+
+ sprintf(PGPPATH, "%s/.pgp", BBSHOME);
+ setenv("PGPPATH", PGPPATH, 1);
+
+ *in = *out = NULL;
+
+ pipe(pin);
+ pipe(pout);
+
+ if (!(child_pid = fork()))
+ {
+ /* We're the child. */
+ close(pin[1]);
+ dup2(pin[0], 0);
+ close(pin[0]);
+
+ close(pout[0]);
+ dup2(pout[1], 1);
+ close(pout[1]);
+
+ execl("/bin/sh", "sh", "-c", cmd, NULL);
+ _exit(127);
+ }
+ /* Only get here if we're the parent. */
+ close(pout[1]);
+ *out = fdopen(pout[0], "r");
+
+ close(pin[0]);
+ *in = fdopen(pin[1], "w");
+
+ return (child_pid);
+}
+
+int
+verify_buffer(char *buf, char *passphrase)
+{
+ FILE *pgpin, *pgpout;
+ char tmpbuf[1024] = " ";
+ int ans = NOPGP;
+
+ setenv("PGPPASSFD", "0", 1);
+ run_pgp("/usr/local/bin/pgpv -f +batchmode=1 +OutputInformationFD=1",
+ &pgpin, &pgpout);
+ if (pgpin && pgpout)
+ {
+ fprintf(pgpin, "%s\n", passphrase); /* Send the passphrase in, first */
+ bzero(passphrase, strlen(passphrase)); /* Burn the passphrase */
+ fprintf(pgpin, "%s", buf);
+ fclose(pgpin);
+
+ *buf = '\0';
+ fgets(tmpbuf, sizeof(tmpbuf), pgpout);
+ while (!feof(pgpout))
+ {
+ strcat(buf, tmpbuf);
+ fgets(tmpbuf, sizeof(tmpbuf), pgpout);
+ }
+
+ wait(NULL);
+
+ fclose(pgpout);
+ }
+
+ if (strstr(buf, "BAD signature made"))
+ {
+ strcpy(errmsg, "BAD signature");
+ ans = PGPBAD;
+ }
+ else if (strstr(buf, "Good signature made"))
+ {
+ strcpy(errmsg, "Good signature");
+ ans = PGPGOOD;
+ }
+ else if (strcpy(tmpbuf, strstr(buf, "Signature by unknown keyid:")))
+ {
+ sprintf(errmsg, "%s ", strtok(tmpbuf, "\r\n"));
+ strcpy(KEYID, strrchr(tmpbuf, ' ') + 1);
+ ans = PGPUN;
+ }
+
+ unsetenv("PGPPASSFD");
+ return ans;
+}
+
+int
+NCMverify()
+{
+ int ans;
+ char passphrase[80] = "Haha, I am Leeym..";
+ ans = verify_buffer(BODY, passphrase);
+ return ans;
+}
+#endif
+/* ------------------------------------------------------------------ */
+/* parse NoCeM Notice Headers/Body */
+/* ------------------------------------------------------------------ */
+
+int
+readNCMheader(char *line)
+{
+ if (!strncasecmp(line, "Version", strlen("Version")))
+ {
+ strcpy(NCMVER, line + strlen("Version") + 2);
+ if (!strstr(NCMVER, "0.9"))
+ {
+ sprintf(errmsg, "unknown version: %s", NCMVER);
+ return P_FAIL;
+ }
+ }
+ else if (!strncasecmp(line, "Issuer", strlen("Issuer")))
+ {
+ strcpy(ISSUER, line + strlen("Issuer") + 2);
+ FROM = ISSUER;
+ }
+ else if (!strncasecmp(line, "Type", strlen("Type")))
+ {
+ strcpy(TYPE, line + strlen("Type") + 2);
+ }
+ else if (!strncasecmp(line, "Action", strlen("Action")))
+ {
+ strcpy(ACTION, line + strlen("Action") + 2);
+ if (!strstr(ACTION, "hide"))
+ {
+ sprintf(errmsg, "unsupported action: %s", ACTION);
+ return P_FAIL;
+ }
+ }
+ else if (!strncasecmp(line, "Notice-ID", strlen("Notice-ID")))
+ {
+ strcpy(NCMID, line + strlen("Notice-ID") + 2);
+ }
+ else if (!strncasecmp(line, "Count", strlen("Count")))
+ {
+ strcpy(COUNT, line + strlen("Count") + 2);
+ }
+ else if (!strncasecmp(line, "Threshold", strlen("Threshold")))
+ {
+ strcpy(THRESHOLD, line + strlen("Threshold") + 2);
+ }
+
+ return P_OKAY;
+}
+
+int
+readNCMbody(char *line)
+{
+ char buf[LINELEN], *group;
+ strcpy(buf, line);
+
+ if (!strstr(buf, "\t"))
+ return P_FAIL;
+
+ group = strrchr(line, '\t') + 1;
+
+ if (buf[0] == '<' && strstr(buf, ">"))
+ {
+ strtok(buf, "\t");
+ strcpy(SPAMMID_NOW, buf);
+ }
+
+ if (num_spammid && !strcmp(SPAMMID[num_spammid - 1], SPAMMID_NOW))
+ return 0;
+
+ if (search_group(group))
+ strcpy(SPAMMID[num_spammid++], SPAMMID_NOW);
+}
+
+int
+NCMparse()
+{
+ char *fptr, *ptr;
+ int type = TEXT;
+
+ if (!(fptr = strstr(BODY, "-----BEGIN PGP SIGNED MESSAGE-----")))
+ {
+ strcpy(errmsg, "notice isn't signed");
+ return P_FAIL;
+ }
+
+ for (ptr = strchr(fptr, '\n'); ptr != NULL && *ptr != '\0'; fptr = ptr + 1, ptr = strchr(fptr, '\n'))
+ {
+ int ch = *ptr;
+ int ch2 = *(ptr - 1);
+
+ *ptr = '\0';
+ if (*(ptr - 1) == '\r')
+ *(ptr - 1) = '\0';
+
+ if (num_spammid > MAXSPAMMID)
+ return P_OKAY;
+
+ if (ncmdebug >= 2)
+ bbslog("NCMparse: %s\n", fptr);
+
+ if (!strncmp(fptr, "@@", 2))
+ {
+ if (strstr(fptr, "BEGIN NCM HEADERS"))
+ type = NCMHDR;
+ else if (strstr(fptr, "BEGIN NCM BODY"))
+ {
+ if (NCMVER && ISSUER && TYPE && ACTION && COUNT && NCMID)
+ {
+ ncmperm_t *ncmt;
+ ncmt = (ncmperm_t *) search_issuer_type(ISSUER, TYPE);
+ if (ncmt == NULL)
+ {
+ NCMupdate(ISSUER, TYPE);
+ sprintf(errmsg, "unknown issuer: %s, %s", ISSUER, MSGID);
+ return P_UNKNOWN;
+ }
+ if (ncmt->perm == NULL)
+ {
+ sprintf(errmsg, "disallow issuer: %s, %s", ISSUER, MSGID);
+ return P_DISALLOW;
+ }
+ }
+ else
+ {
+ strcpy(errmsg, "HEADERS syntax not correct");
+ return P_FAIL;
+ }
+ type = NCMBDY;
+ }
+ else if (strstr(fptr, "END NCM BODY"))
+ {
+ *ptr = ch;
+ *(ptr - 1) = ch2;
+ break;
+ }
+ else
+ {
+ strcpy(errmsg, "NCM Notice syntax not correct");
+ return P_FAIL;
+ }
+ *ptr = ch;
+ *(ptr - 1) = ch2;
+ continue;
+ }
+
+ if (type == NCMHDR && readNCMheader(fptr) == P_FAIL)
+ return P_FAIL;
+ else if (type == NCMBDY)
+ readNCMbody(fptr);
+ *ptr = ch;
+ *(ptr - 1) = ch2;
+ }
+ if (NCMVER && ISSUER && TYPE && ACTION && COUNT && NCMID)
+ return P_OKAY;
+ else
+ {
+ strcpy(errmsg, "HEADERS syntax not correct");
+ return P_FAIL;
+ }
+
+ strcpy(errmsg, "I don't know..");
+ return P_FAIL;
+}
+
+int
+NCMcancel()
+{
+ int i, rel, num_ok, num_fail;
+ for (i = rel = num_ok = num_fail = 0; i < num_spammid; i++)
+ {
+ rel = cancel_article_front(SPAMMID[i]);
+ if (rel)
+ num_fail++;
+ else
+ num_ok++;
+ }
+ bbslog("NCMcancel %s %s, count:%d spam:%d, ok:%d fail:%d\n",
+ ISSUER, MSGID, atoi(COUNT), num_spammid, num_ok, num_fail);
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+/* NoCeM-innbbsd */
+/* ------------------------------------------------------------------ */
+
+void
+initial_nocem()
+{
+ bzero(SPAMMID[0], strlen(SPAMMID[0]) * num_spammid);
+ num_spammid = 0;
+ bzero(SPAMMID_NOW, strlen(SPAMMID_NOW));
+}
+
+int
+receive_nocem(void)
+{
+ int rel;
+
+ if (ncmdebug)
+ bbslog("NCM: receive %s\n", MSGID);
+
+ initial_nocem();
+
+ rel = NCMparse();
+
+ if (rel != P_OKAY)
+ {
+ if (rel != P_DISALLOW)
+ bbslog("NCMparse %s\n", errmsg);
+ return 0;
+ }
+
+ if (!num_spammid)
+ {
+ bbslog("NCMparse: nothing to cancel\n");
+ return 0;
+ }
+#ifdef PGP5
+ if (ncmdebug)
+ bbslog("NCM: verifying PGP sign\n");
+
+ rel = NCMverify();
+
+ if (rel != PGPGOOD)
+ {
+ bbslog("NCMverify %s, %s, %s\n", errmsg, MSGID, ISSUER);
+ return 0;
+ }
+#endif
+
+ if (ncmdebug)
+ bbslog("NCM: canceling spam in NoCeM Notice\n");
+ return NCMcancel();
+}
diff --git a/pttbbs/innbbsd/nocem.h b/pttbbs/innbbsd/nocem.h
new file mode 100644
index 00000000..18a4c5e9
--- /dev/null
+++ b/pttbbs/innbbsd/nocem.h
@@ -0,0 +1,57 @@
+/*
+ NoCeM-INNBBSD
+ Yen-Ming Lee <leeym@cae.ce.ntu.edu.tw>
+*/
+
+#ifndef NOCEM_H
+#define NOCEM_H
+
+#include "innbbsconf.h"
+#include "bbslib.h"
+#include "inntobbs.h"
+
+#include <stdarg.h> /* for va_start() problem */
+
+typedef struct ncmperm_t
+{
+ char *issuer;
+ char *type;
+ int perm;
+} ncmperm_t;
+
+#define TEXT 0
+#define NCMHDR 1
+#define NCMBDY 2
+
+#define NOPGP -1
+#define PGPGOOD 0
+#define PGPBAD 1
+#define PGPUN 2
+
+#define P_OKAY 0
+#define P_FAIL -1
+#define P_UNKNOWN -2
+#define P_DISALLOW -3
+
+#define STRLEN 80
+#define MAXSPAMMID 10000
+#define LINELEN 512
+
+#define LeeymBBS "bbs.civil.ncku.edu.tw"
+#define LeeymEMAIL "leeym@cae.ce.ntu.edu.tw"
+#define NCMINNBBSVER "NoCeM-INNBBSD-0.71"
+
+#undef DONT_REGISTER
+
+extern char NCMVER[];
+extern char ISSUER[];
+extern char TYPE[];
+extern char ACTION[];
+extern char NCMID[];
+extern char COUNT[];
+extern char THRESHOLD[];
+extern char KEYID[];
+extern char SPAMMID_NOW[];
+extern char SPAMMID[MAXSPAMMID][STRLEN];
+
+#endif /* NOCEM_H */
diff --git a/pttbbs/innbbsd/pmain.c b/pttbbs/innbbsd/pmain.c
new file mode 100644
index 00000000..1b039c48
--- /dev/null
+++ b/pttbbs/innbbsd/pmain.c
@@ -0,0 +1,64 @@
+#include "innbbsconf.h"
+#include "daemon.h"
+#include "externs.h"
+
+/* char *AccessFile=ACCESSFILE; */
+#define INNBBSDPORT1 "1904"
+#define INNBBSDPORT2 "1234"
+#define INNBBSDPATH1 ".innbbsd1"
+#define INNBBSDPATH2 ".innbbsd2"
+
+int
+pmain(port)
+ char *port;
+{
+ if (port == NULL) {
+ int rel;
+ /* installbbstalkd(); */
+ fprintf(stderr, "Trying to listen in port %s\n", INNBBSDPORT1);
+ rel = open_listen(INNBBSDPORT1, "tcp", NULL);
+#ifdef DEBUG
+ printf("port fd %d allocated\n", rel);
+#endif
+ if (rel < 0) {
+ fprintf(stderr, "Trying to listen in port %s\n", INNBBSDPORT2);
+ return open_listen(INNBBSDPORT2, "tcp", NULL);
+ }
+ return rel;
+ } else {
+#ifdef DEBUG
+ printf("start to allocate port\n");
+#endif
+ return open_listen(port, "tcp", NULL);
+ }
+}
+
+int
+p_unix_main(path)
+ char *path;
+{
+ if (path == NULL) {
+ int rel;
+ /* installbbstalkd(); */
+ fprintf(stderr, "Trying to listen in port %s\n", INNBBSDPATH1);
+ rel = open_unix_listen(INNBBSDPATH1, "tcp", NULL);
+#ifdef DEBUG
+ printf("port fd %d allocated\n", rel);
+#endif
+ if (rel < 0) {
+ fprintf(stderr, "Trying to listen in port %s\n", INNBBSDPATH2);
+ return open_listen(INNBBSDPATH2, "tcp", NULL);
+ }
+ return rel;
+ } else {
+#ifdef DEBUG
+ printf("start to allocate path %s\n", path);
+#endif
+ int fd = unixclient(path, "tcp");
+ if (fd < 0)
+ unlink(path);
+ else
+ close(fd);
+ return open_unix_listen(path, "tcp", NULL);
+ }
+}
diff --git a/pttbbs/innbbsd/port.c b/pttbbs/innbbsd/port.c
new file mode 100644
index 00000000..fab82771
--- /dev/null
+++ b/pttbbs/innbbsd/port.c
@@ -0,0 +1,35 @@
+#include "innbbsconf.h"
+
+#ifdef NO_getdtablesize
+#include <sys/time.h>
+#include <sys/resource.h>
+getdtablesize()
+{
+ struct rlimit limit;
+ if (getrlimit(RLIMIT_NOFILE, &limit) >= 0) {
+ return limit.rlim_cur;
+ }
+ return -1;
+}
+#endif
+
+#if 0
+#if defined(SYSV) && !defined(WITH_RECORD_O)
+#include <fcntl.h>
+flock(fd, op)
+ int fd, op;
+{
+ switch (op) {
+ case LOCK_EX:
+ op = F_LOCK;
+ break;
+ case LOCK_UN:
+ op = F_ULOCK;
+ break;
+ default:
+ return -1;
+ }
+ return lockf(fd, op, 0L);
+}
+#endif
+#endif
diff --git a/pttbbs/innbbsd/receive_article.c b/pttbbs/innbbsd/receive_article.c
new file mode 100644
index 00000000..87726ab1
--- /dev/null
+++ b/pttbbs/innbbsd/receive_article.c
@@ -0,0 +1,1123 @@
+
+/*
+ * BBS implementation dependendent part
+ *
+ * The only two interfaces you must provide
+ *
+ * #include "inntobbs.h" int receive_article(); 0 success not 0 fail
+ *
+ * if (storeDB(HEADER[MID_H], hispaths) < 0) { .... fail }
+ *
+ * int cancel_article_front( char *msgid ); 0 success not 0 fail
+ *
+ * char *ptr = (char*)DBfetch(msgid);
+ *
+ * 收到之文章內容 (body)在 char *BODY, 檔頭 (header)在 char *HEADER[] SUBJECT_H,
+ * FROM_H, DATE_H, MID_H, NEWSGROUPS_H, NNTPPOSTINGHOST_H, NNTPHOST_H,
+ * CONTROL_H, PATH_H, ORGANIZATION_H
+ */
+
+/*
+ * Sample Implementation
+ *
+ * receive_article() --> post_article() --> bbspost_write_post();
+ * cacnel_article_front(mid) --> cancel_article() --> bbspost_write_cancel();
+ */
+
+#include "externs.h"
+#include <stdlib.h>
+#define _XOPEN_SOURCE /* glibc2 needs this */
+#include <time.h>
+#ifndef PowerBBS
+#include "innbbsconf.h"
+#include "daemon.h"
+#include "bbslib.h"
+#include "inntobbs.h"
+#include "antisplam.h"
+
+extern int Junkhistory;
+
+char *post_article ARG((char *, char *, char *, int (*) (), char *, char *));
+int cancel_article ARG((char *, char *, char *));
+
+
+#ifdef MapleBBS
+#include "config.h"
+#include "pttstruct.h"
+#define _BBS_UTIL_C_
+#else
+report()
+{
+ /* Function called from record.o */
+ /* Please leave this function empty */
+}
+#endif
+
+
+#if defined(PalmBBS)
+
+#ifndef PATH
+#define PATH XPATH
+#endif
+
+#ifndef HEADER
+#define HEADER XHEADER
+#endif
+
+#endif
+
+/* process post write */
+int
+bbspost_write_post(fh, board, filename)
+ int fh;
+ char *board;
+ char *filename;
+{
+ char *fptr, *ptr;
+ FILE *fhfd = fdopen(fh, "w");
+
+ if (fhfd == NULL) {
+ bbslog("can't fdopen, maybe disk full\n");
+ return -1;
+ }
+ fprintf(fhfd, "發信人: %.60s, 看板: %s\n", FROM, board);
+ fprintf(fhfd, "標 題: %.70s\n", SUBJECT);
+ fprintf(fhfd, "發信站: %.43s (%s)\n", SITE, DATE);
+ fprintf(fhfd, "轉信站: %.70s\n", PATH);
+
+#ifndef MapleBBS
+ if (POSTHOST != NULL) {
+ fprintf(fhfd, "Origin: %.70s\n", POSTHOST);
+ }
+#endif
+
+ fprintf(fhfd, "\n");
+ for (fptr = BODY, ptr = strchr(fptr, '\r'); ptr != NULL && *ptr != '\0'; fptr = ptr + 1, ptr = strchr(fptr, '\r')) {
+ int ch = *ptr;
+ *ptr = '\0';
+ fputs(fptr, fhfd);
+ *ptr = ch;
+ }
+ fputs(fptr, fhfd);
+
+ fflush(fhfd);
+ fclose(fhfd);
+ return 0;
+}
+
+#ifdef KEEP_NETWORK_CANCEL
+/* process cancel write */
+bbspost_write_cancel(fh, board, filename)
+ int fh;
+ char *board, *filename;
+{
+ char *fptr, *ptr;
+ FILE *fhfd = fdopen(fh, "w"), *fp;
+ char buffer[256];
+
+ if (fhfd == NULL) {
+ bbslog("can't fdopen, maybe disk full\n");
+ return -1;
+ }
+ fprintf(fhfd, "發信人: %s, 信區: %s\n", FROM, board);
+ fprintf(fhfd, "標 題: %s\n", SUBJECT);
+ fprintf(fhfd, "發信站: %.43s (%s)\n", SITE, DATE);
+ fprintf(fhfd, "轉信站: %.70s\n", PATH);
+ if (HEADER[CONTROL_H] != NULL) {
+ fprintf(fhfd, "Control: %s\n", HEADER[CONTROL_H]);
+ }
+ if (POSTHOST != NULL) {
+ fprintf(fhfd, "Origin: %s\n", POSTHOST);
+ }
+ fprintf(fhfd, "\n");
+ for (fptr = BODY, ptr = strchr(fptr, '\r'); ptr != NULL && *ptr != '\0'; fptr = ptr + 1, ptr = strchr(fptr, '\r')) {
+ int ch = *ptr;
+ *ptr = '\0';
+ fputs(fptr, fhfd);
+ *ptr = ch;
+ }
+ fputs(fptr, fhfd);
+ if (POSTHOST != NULL) {
+ fprintf(fhfd, "\n * Origin: ● %.26s ● From: %.40s\n", SITE, POSTHOST);
+ }
+ fprintf(fhfd, "\n---------------------\n");
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+ bbslog("can't open %s\n", filename);
+ return -1;
+ }
+ while (fgets(buffer, sizeof buffer, fp) != NULL) {
+ fputs(buffer, fhfd);
+ }
+ fclose(fp);
+ fflush(fhfd);
+ fclose(fhfd);
+
+ {
+ fp = fopen(filename, "w");
+ if (fp == NULL) {
+ bbslog("can't write %s\n", filename);
+ return -1;
+ }
+ fprintf(fp, "發信人: %s, 信區: %s\n", FROM, board);
+ fprintf(fp, "標 題: %.70s\n", SUBJECT);
+ fprintf(fp, "發信站: %.43s (%s)\n", SITE, DATE);
+ fprintf(fp, "轉信站: %.70s\n", PATH);
+ if (POSTHOST != NULL) {
+ fprintf(fhfd, "Origin: %s\n", POSTHOST);
+ }
+ if (HEADER[CONTROL_H] != NULL) {
+ fprintf(fhfd, "Control: %s\n", HEADER[CONTROL_H]);
+ }
+ fprintf(fp, "\n");
+ for (fptr = BODY, ptr = strchr(fptr, '\r'); ptr != NULL && *ptr != '\0'; fptr = ptr + 1, ptr = strchr(fptr, '\r')) {
+ *ptr = '\0';
+ fputs(fptr, fp);
+ }
+ fputs(fptr, fp);
+ if (POSTHOST != NULL) {
+ fprintf(fp, "\n * Origin: ● %.26s ● From: %.40s\n", SITE, POSTHOST);
+ }
+ fclose(fp);
+ }
+ return 0;
+}
+#endif
+
+
+int
+bbspost_write_control(fh, board, filename)
+ int fh;
+ char *board;
+ char *filename;
+{
+ char *fptr, *ptr;
+ FILE *fhfd = fdopen(fh, "w");
+
+ if (fhfd == NULL) {
+ bbslog("can't fdopen, maybe disk full\n");
+ return -1;
+ }
+ fprintf(fhfd, "Path: %s!%s\n", MYBBSID, HEADER[PATH_H]);
+ fprintf(fhfd, "From: %s\n", FROM);
+ fprintf(fhfd, "Newsgroups: %s\n", GROUPS);
+ fprintf(fhfd, "Subject: %s\n", SUBJECT);
+ fprintf(fhfd, "Date: %s\n", DATE);
+ fprintf(fhfd, "Organization: %s\n", SITE);
+ if (POSTHOST != NULL) {
+ fprintf(fhfd, "NNTP-Posting-Host: %.70s\n", POSTHOST);
+ }
+ if (HEADER[CONTROL_H] != NULL) {
+ fprintf(fhfd, "Control: %s\n", HEADER[CONTROL_H]);
+ }
+ if (HEADER[APPROVED_H] != NULL) {
+ fprintf(fhfd, "Approved: %s\n", HEADER[APPROVED_H]);
+ }
+ if (HEADER[DISTRIBUTION_H] != NULL) {
+ fprintf(fhfd, "Distribution: %s\n", HEADER[DISTRIBUTION_H]);
+ }
+ fprintf(fhfd, "\n");
+ for (fptr = BODY, ptr = strchr(fptr, '\r'); ptr != NULL && *ptr != '\0'; fptr = ptr + 1, ptr = strchr(fptr, '\r')) {
+ int ch = *ptr;
+ *ptr = '\0';
+ fputs(fptr, fhfd);
+ *ptr = ch;
+ }
+ fputs(fptr, fhfd);
+
+
+ fflush(fhfd);
+ fclose(fhfd);
+ return 0;
+}
+
+
+time_t datevalue;
+
+
+/* process cancel write */
+int
+receive_article()
+{
+ char *user, *userptr;
+ char *ngptr, *pathptr;
+ char **splitptr;
+ static char userid[32];
+ static char xdate[32];
+ static char xpath[180];
+ newsfeeds_t *nf;
+ char *boardhome;
+ char hispaths[4096];
+ char firstpath[MAXPATHLEN], *firstpathbase;
+ char *lesssym, *nameptrleft, *nameptrright;
+
+#ifdef HMM_USE_ANTI_SPAM
+ int i;
+ char *notitle[] =
+ {NULL},
+ *nofrom[] =
+ {NULL},
+ *nocont[] =
+ {"http://msi-team.com", "http://affluence.rithosts.net",
+ "http://www.fly-team.com", "http://fly-team.com",
+ "http://whymis.com", "http://www.msihk.com",
+ "http://Autopost.e8d8d.com", "http://www.e8d8d.com",
+ "http://whymsi.com", "httpwww.e8d8d.com", "http://freeway.163.to/",
+ "MSI團隊", "MSI系統", "MSI經營", "本訊息由AUTO POST發送",
+ "MSI團隊", "USANA", "http://uuu.to/ommplan",
+ "靠一份網路事業創業與加盟圓夢", "http://www.usanaomm.net",
+ "優莎娜MSI", "Robert G.Allen", "http://www.whyomm.cn",
+ "http://home.pchome.com.tw/store/deryes/",
+ "http://xyzoo.hkoo.net/", "mypiece.com/",
+ "【新舊歐盟醫學院招生】", "《台鹽多寶在家工作系統》",
+ "jin-hua@hotmail.com", "010-82330590", "http://www.s-bus.com",
+ "http://fone.4hk.cc/mkslk", "http://kiki.hkoo.net/fongy",
+ "http://myokay.nowgo.net/jojo/", "0911685648",
+ "http://digi.hkgo.cc/mile", "http://love520.hk852.cc/mytw",
+ "美容保養品公司USANA", "Robert G. Allen", "Multiple Streams of Income",
+ "http://www.twstars.com", "36005081", "3123835",
+ ".hkgo.cc/", ".hk852.cc/", ".4hk.cc/", ".2hk.cc/", ".xdd.cc/",
+ ".hkoo.net/", ".nowgo.net/", "http://www.taconet.com.tw/jscha/",
+ "www.ejiajia.net", "ufjt0356@ms9.hinet.net", "jt0356@yahoo.com.tw",
+ "http://www.IT-Test.Net", "http://uuu.to/", "greenhouse6688",
+ "http://www.s-bus.com", "http://goods.sytes.net/",
+ "http://www.1-care.com", "美商優莎納生技公司", "Http://www.It-Test.Net",
+ "http://home.pchome.com.tw/happy/eykk6767/", "http://www.agelopp.com/",
+ "http://www.togetrich.net", "http://www.newchance.ligsystem.com/",
+ "jimtist@yahoo.com", "http://fleamarket.mine.nu", "http://e-car.mine.nu",
+ "http://www.whymsi.com", "http://www.msi-team.com/", NULL};
+#endif
+
+ if (FROM == NULL) {
+ bbslog(":Err: article without usrid %s\n", MSGID);
+ return 0;
+ } else {
+#ifdef HMM_USE_ANTI_SPAM
+ for (i = 0; nofrom[i]; i++)
+ if (strstr(FROM, nofrom[i])) {
+ bbslog(":Ptt: spam from [%s]: %s\n", nofrom[i], FROM);
+ return 0;
+ }
+#endif
+ }
+
+ if (!BODY) {
+ bbslog(":Err: article without body %s\n", MSGID);
+ return 0;
+ } else {
+#ifdef HMM_USE_ANTI_SPAM
+ for (i = 0; nocont[i]; i++)
+ if (strstr(BODY, nocont[i])) {
+ bbslog(":Ptt: spam body: %s\n", nocont[i]);
+ return 0;
+ }
+#endif
+ }
+
+ if (!SUBJECT) {
+ bbslog(":Err: article without subject %s\n", MSGID);
+ return 0;
+ } else {
+#ifdef HMM_USE_ANTI_SPAM
+ for (i = 0; notitle[i]; i++)
+ if (strstr(SUBJECT, notitle[i])) {
+ bbslog(":Ptt: spam title [%s]: %s\n", notitle[i], SUBJECT);
+ return 0;
+ }
+#endif
+ }
+
+
+ user = (char *)strchr(FROM, '@');
+ lesssym = (char *)strchr(FROM, '<');
+ nameptrleft = NULL, nameptrright = NULL;
+ if (lesssym == NULL || lesssym >= user) {
+ lesssym = FROM;
+ nameptrleft = strchr(FROM, '(');
+ if (nameptrleft != NULL)
+ nameptrleft++;
+ nameptrright = strrchr(FROM, ')');
+ } else {
+ nameptrleft = FROM;
+ nameptrright = strrchr(FROM, '<');
+ lesssym++;
+ }
+ if (user != NULL) {
+ *user = '\0';
+ userptr = (char *)strchr(FROM, '.');
+ if (userptr != NULL) {
+ *userptr = '\0';
+ strncpy(userid, lesssym, sizeof userid);
+ *userptr = '.';
+ } else {
+ strncpy(userid, lesssym, sizeof userid);
+ }
+ *user = '@';
+ } else {
+ strncpy(userid, lesssym, sizeof userid);
+ }
+ strcat(userid, ".");
+
+ {
+ struct tm tmbuf;
+
+ /* RFC-1036 says the second format is the correct one. But we keep
+ * the first format for backward compatible.
+ */
+ if (strptime(DATE, "%d %b %Y %X ", &tmbuf) != NULL)
+ datevalue = timegm(&tmbuf);
+ else if (strptime(DATE, "%a, %d %b %Y %X ", &tmbuf) != NULL)
+ datevalue = timegm(&tmbuf);
+ else
+ datevalue = -1;
+ }
+
+ if (datevalue > 0) {
+ char *p;
+ strncpy(xdate, ctime(&datevalue), sizeof(xdate));
+ p = (char *)strchr(xdate, '\n');
+ if (p != NULL)
+ *p = '\0';
+ DATE = xdate;
+ }
+#ifndef MapleBBS
+ if (SITE == NULL || *SITE == '\0') {
+ if (nameptrleft != NULL && nameptrright != NULL) {
+ char savech = *nameptrright;
+ *nameptrright = '\0';
+ strncpy(sitebuf, nameptrleft, sizeof sitebuf);
+ *nameptrright = savech;
+ SITE = sitebuf;
+ } else
+ /* SITE = "(Unknown)"; */
+ SITE = "";
+ }
+ if (strlen(MYBBSID) > 70) {
+ bbslog(" :Err: your bbsid %s too long\n", MYBBSID);
+ return 0;
+ }
+#endif
+
+ sprintf(xpath, "%s!%.*s", MYBBSID, sizeof(xpath) - strlen(MYBBSID) - 2, PATH);
+ PATH = xpath;
+ for (pathptr = PATH; pathptr != NULL && (pathptr = strstr(pathptr, ".edu.tw")) != NULL;) {
+ if (pathptr != NULL) {
+ strcpy(pathptr, pathptr + 7);
+ }
+ }
+ xpath[71] = '\0';
+
+#ifndef MapleBBS
+ echomaillog();
+#endif
+
+ *hispaths = '\0';
+ splitptr = (char **)BNGsplit(GROUPS);
+ firstpath[0] = '\0';
+ firstpathbase = firstpath;
+
+ for (ngptr = *splitptr; ngptr != NULL; ngptr = *(++splitptr)) {
+ char *boardptr, *nboardptr;
+
+ if (*ngptr == '\0')
+ continue;
+ nf = (newsfeeds_t *) search_group(ngptr);
+ if (nf == NULL) {
+ bbslog("unwanted \'%s\'\n", ngptr);
+ continue;
+ }
+ if (nf->board == NULL || !*nf->board)
+ continue;
+ if (nf->path == NULL || !*nf->path)
+ continue;
+ for (boardptr = nf->board, nboardptr = (char *)strchr(boardptr, ','); boardptr != NULL && *boardptr != '\0'; nboardptr = (char *)strchr(boardptr, ',')) {
+ if (nboardptr != NULL) {
+ *nboardptr = '\0';
+ }
+ if (*boardptr == '\t') {
+ goto boardcont;
+ }
+ boardhome = (char *)fileglue("%s/boards/%c/%s", BBSHOME, boardptr[0], boardptr);
+ if (!isdir(boardhome)) {
+ bbslog(":Err: unable to write %s\n", boardhome);
+ } else {
+ char *fname;
+ /*
+ * if ( !isdir( boardhome )) { bbslog( ":Err: unable to write
+ * %s\n",boardhome); testandmkdir(boardhome); }
+ */
+ fname = (char *)post_article(boardhome, userid, boardptr,
+ bbspost_write_post, NULL, firstpath);
+ if (fname != NULL) {
+ fname = (char *)fileglue("%s/%s", boardptr, fname);
+ if (firstpath[0] == '\0') {
+ sprintf(firstpath, "%s/boards/%c, %s", BBSHOME, fname[0], fname);
+ firstpathbase = firstpath + strlen(BBSHOME) + strlen("/boards/x/");
+ }
+ if (strlen(fname) + strlen(hispaths) + 1 < sizeof(hispaths)) {
+ strcat(hispaths, fname);
+ strcat(hispaths, " ");
+ }
+ } else {
+ bbslog("fname is null %s\n", boardhome);
+ return -1;
+ }
+ }
+
+ boardcont:
+ if (nboardptr != NULL) {
+ *nboardptr = ',';
+ boardptr = nboardptr + 1;
+ } else
+ break;
+
+ } /* for board1,board2,... */
+ /*
+ * if (nngptr != NULL) ngptr = nngptr + 1; else break;
+ */
+ if (*firstpathbase)
+ feedfplog(nf, firstpathbase, 'P');
+ }
+ if (*hispaths)
+ bbsfeedslog(hispaths, 'P');
+
+ if (Junkhistory || *hispaths) {
+ if (storeDB(HEADER[MID_H], hispaths) < 0) {
+ bbslog("store DB fail\n");
+ /* I suspect here will introduce duplicated articles */
+ /* return -1; */
+ }
+ }
+ return 0;
+}
+
+int
+receive_control(void)
+{
+ char *boardhome, *fname;
+ char firstpath[MAXPATHLEN], *firstpathbase;
+ char **splitptr, *ngptr;
+ newsfeeds_t *nf;
+
+ bbslog("control post %s\n", HEADER[CONTROL_H]);
+ boardhome = (char *)fileglue("%s/boards/c/control", BBSHOME);
+ testandmkdir(boardhome);
+ *firstpath = '\0';
+ if (isdir(boardhome)) {
+ fname = (char *)post_article(boardhome, FROM, "control", bbspost_write_control, NULL, firstpath);
+ if (fname != NULL) {
+ if (firstpath[0] == '\0')
+ sprintf(firstpath, "%s/boards/c/control/%s", BBSHOME, fname);
+ if (storeDB(HEADER[MID_H], (char *)fileglue("control/%s", fname)) < 0) {
+ }
+ bbsfeedslog(fileglue("control/%s", fname), 'C');
+ firstpathbase = firstpath + strlen(BBSHOME) + strlen("/boards/x/");
+ splitptr = (char **)BNGsplit(GROUPS);
+ for (ngptr = *splitptr; ngptr != NULL; ngptr = *(++splitptr)) {
+ if (*ngptr == '\0')
+ continue;
+ nf = (newsfeeds_t *) search_group(ngptr);
+ if (nf == NULL)
+ continue;
+ if (nf->board == NULL)
+ continue;
+ if (nf->path == NULL)
+ continue;
+ feedfplog(nf, firstpathbase, 'C');
+ }
+ }
+ }
+ return 0;
+}
+
+int
+cancel_article_front(msgid)
+ char *msgid;
+{
+ char *ptr = (char *)DBfetch(msgid);
+ char *filelist, filename[2048];
+ char histent[4096];
+ char firstpath[MAXPATHLEN], *firstpathbase;
+ if (ptr == NULL) {
+ bbslog("cancel failed(DBfetch): %s\n", msgid);
+ return 0;
+ }
+ strncpy(histent, ptr, sizeof histent);
+ ptr = histent;
+
+#ifdef DEBUG
+ printf("**** try to cancel %s *****\n", ptr);
+#endif
+
+ filelist = strchr(ptr, '\t');
+ if (filelist != NULL) {
+ filelist++;
+ }
+ *firstpath = '\0';
+ for (ptr = filelist; ptr && *ptr;) {
+ char *file;
+ for (; *ptr && isspace(*ptr); ptr++);
+ if (*ptr == '\0')
+ break;
+ file = ptr;
+ for (ptr++; *ptr && !isspace(*ptr); ptr++);
+ if (*ptr != '\0') {
+ *ptr++ = '\0';
+ }
+ sprintf(filename, "%s/boards/%c/%s", BBSHOME, file[0], file);
+ bbslog("cancel post %s\n", filename);
+ if (isfile(filename)) {
+ FILE *fp = fopen(filename, "r");
+ char buffer[1024];
+ char xfrom0[100], xfrom[100], xpath[1024];
+
+ if (fp == NULL)
+ continue;
+ strncpy(xfrom0, HEADER[FROM_H], 99);
+ xfrom0[99] = 0;
+ strtok(xfrom0, ", ");
+ while (fgets(buffer, sizeof buffer, fp) != NULL) {
+ char *hptr;
+ if (buffer[0] == '\n')
+ break;
+ hptr = strchr(buffer, '\n');
+ if (hptr != NULL)
+ *hptr = '\0';
+ if (strncmp(buffer, "發信人: ", 8) == 0) {
+ strncpy(xfrom, buffer + 8, 99);
+ xfrom[99] = 0;
+ strtok(xfrom, ", ");
+ } else if (strncmp(buffer, "轉信站: ", 8) == 0) {
+ strcpy(xpath, buffer + 8);
+ }
+ }
+ fclose(fp);
+ if (strcmp(xfrom0, xfrom) && !search_issuer(FROM)) {
+ bbslog("Invalid cancel %s, path: %s!%s, [`%s` != `%s`]\n",
+ FROM, MYBBSID, PATH, xfrom0, xfrom);
+ return 0;
+ }
+#ifdef KEEP_NETWORK_CANCEL
+ bbslog("cancel post %s\n", filename);
+ boardhome = (char *)fileglue("%s/boards/d/deleted", BBSHOME);
+ testandmkdir(boardhome);
+ if (isdir(boardhome)) {
+ char subject[1024];
+ char *fname;
+ if (POSTHOST) {
+ sprintf(subject, "cancel by: %.1000s", POSTHOST);
+ } else {
+ char *body, *body2;
+ body = strchr(BODY, '\r');
+ if (body != NULL)
+ *body = '\0';
+ body2 = strchr(BODY, '\n');
+ if (body2 != NULL)
+ *body = '\0';
+ sprintf(subject, "%.1000s", BODY);
+ if (body != NULL)
+ *body = '\r';
+ if (body2 != NULL)
+ *body = '\n';
+ }
+ if (*subject) {
+ SUBJECT = subject;
+ }
+ fname = (char *)post_article(boardhome, FROM, "deleted", bbspost_write_cancel, filename, firstpath);
+ if (fname != NULL) {
+ if (firstpath[0] == '\0') {
+ sprintf(firstpath, "%s/boards/d/deleted/%s", BBSHOME, fname);
+ firstpathbase = firstpath + strlen(BBSHOME) + strlen("/boards/x/");
+ }
+ if (storeDB(HEADER[MID_H], (char *)fileglue("deleted/%s", fname)) < 0) {
+ /* should do something */
+ bbslog("store DB fail\n");
+ /* return -1; */
+ }
+ bbsfeedslog(fileglue("deleted/%s", fname), 'D');
+
+#ifdef OLDDISPATCH
+ {
+ char board[256];
+ newsfeeds_t *nf;
+ char *filebase = filename + strlen(BBSHOME) + strlen("/boards/x/");
+ char *filetail = strrchr(filename, '/');
+ if (filetail != NULL) {
+ strncpy(board, filebase, filetail - filebase);
+ nf = (newsfeeds_t *) search_board(board);
+ if (nf != NULL && nf->board && nf->path) {
+ feedfplog(nf, firstpathbase, 'D');
+ }
+ }
+ }
+#endif
+ } else {
+ bbslog(" fname is null %s %s\n", boardhome, filename);
+ return -1;
+ }
+ }
+#else
+ /* bbslog("**** %s should be removed\n", filename); */
+ /*
+ * unlink(filename);
+ */
+#endif
+
+ {
+ char *fp = strrchr(file, '/');
+ if (fp != NULL) {
+ *fp = '\0';
+ cancel_article(BBSHOME, file, fp + 1);
+ *fp = '/';
+ }
+ }
+ }
+ }
+ if (*firstpath) {
+ char **splitptr, *ngptr;
+ newsfeeds_t *nf;
+ splitptr = (char **)BNGsplit(GROUPS);
+ for (ngptr = *splitptr; ngptr != NULL; ngptr = *(++splitptr)) {
+ if (*ngptr == '\0')
+ continue;
+ nf = (newsfeeds_t *) search_group(ngptr);
+ if (nf == NULL)
+ continue;
+ if (nf->board == NULL)
+ continue;
+ if (nf->path == NULL)
+ continue;
+ feedfplog(nf, firstpathbase, 'D');
+ }
+ }
+ return 0;
+}
+
+
+#if defined(PhoenixBBS) || defined(SecretBBS) || defined(PivotBBS) || defined(MapleBBS)
+/* for PhoenixBBS's post article and cancel article */
+#include "config.h"
+
+
+char *
+post_article(homepath, userid, board, writebody, pathname, firstpath)
+ char *homepath;
+ char *userid, *board;
+ int (*writebody) ();
+ char *pathname, *firstpath;
+{
+ struct fileheader_t header;
+ char *subject = SUBJECT;
+ char index[MAXPATHLEN];
+ static char name[MAXPATHLEN];
+ char article[MAXPATHLEN];
+ FILE *fidx;
+ int fh, bid;
+ time_t now;
+ int linkflag;
+ /*
+ * Ptt if(bad_subject(subject)) return NULL;
+ */
+ sprintf(index, "%s/.DIR", homepath);
+ if ((fidx = fopen(index, "r")) == NULL) {
+ if ((fidx = fopen(index, "w")) == NULL) {
+ bbslog(":Err: Unable to post in %s.\n", homepath);
+ return NULL;
+ }
+ }
+ fclose(fidx);
+
+ now = time(NULL);
+ while (1) {
+ sprintf(name, "M.%d.A", ++now);
+ sprintf(article, "%s/%s", homepath, name);
+ fh = open(article, O_CREAT | O_EXCL | O_WRONLY, 0644);
+ if (fh >= 0)
+ break;
+ if (errno != EEXIST) {
+ bbslog(" Err: can't writable or other errors\n");
+ return NULL;
+ }
+ }
+
+#ifdef DEBUG
+ printf("post to %s\n", article);
+#endif
+
+ linkflag = 1;
+ if (firstpath && *firstpath) {
+ close(fh);
+ unlink(article);
+
+#ifdef DEBUGLINK
+ bbslog("try to link %s to %s", firstpath, article);
+#endif
+
+ linkflag = link(firstpath, article);
+ if (linkflag) {
+ fh = open(article, O_CREAT | O_EXCL | O_WRONLY, 0644);
+ }
+ }
+ if (linkflag) {
+ if (writebody) {
+ if ((*writebody) (fh, board, pathname) < 0)
+ return NULL;
+ } else {
+ if (bbspost_write_post(fh, board, pathname) < 0)
+ return NULL;
+ }
+ close(fh);
+ }
+ bzero((void *)&header, sizeof(header));
+
+#ifndef MapleBBS
+ strcpy(header.filename, name);
+ strncpy(header.owner, userid, IDLEN);
+ strncpy(header.title, subject, STRLEN);
+ header.filename[STRLEN - 1] = 'M';
+#else
+
+ strcpy(header.filename, name);
+ if (userid[IDLEN-1])
+ {
+ userid[IDLEN-1] = '.';
+ userid[IDLEN] = '\0';
+ }
+ strcpy(header.owner, userid);
+ strncpy(header.title, subject, TTLEN);
+ /* no need to apply this... FILE_MULTI is used for mail group reply only now. */
+ // header.filemode |= FILE_MULTI;
+ {
+ struct tm *ptime;
+ ptime = localtime(&datevalue);
+ sprintf(header.date, "%2d/%02d", ptime->tm_mon + 1, ptime->tm_mday);
+ }
+#endif
+ {
+ int i;
+ for( i = 0 ; header.title[i] != 0 && i < sizeof(header.title) ; ++i )
+ if( header.title[i] == '\n' ||
+ header.title[i] == '\r' ||
+ header.title[i] == '\033' ){
+ header.title[i] = 0;
+ break;
+ }
+ }
+ append_record(index, &header, sizeof(header));
+
+ if ((bid = getbnum(board)) > 0) {
+ touchbtotal(bid);
+ }
+ return name;
+}
+
+/*
+ * woju Cross-fs rename()
+ */
+
+int
+Rename(const char *src, const char *dst)
+{
+ char cmd[200];
+
+ bbslog("Rename: %s -> %s\n", src, dst);
+ if (rename(src, dst) == 0)
+ return 0;
+
+ sprintf(cmd, "/bin/mv %s %s", src, dst);
+ return system(cmd);
+}
+
+
+void
+cancelpost(fileheader_t * fhdr, char *boardname)
+{
+ int fd;
+ char fpath[MAXPATHLEN];
+
+ sprintf(fpath, BBSHOME "/boards/%c/%s/%s", boardname[0], boardname, fhdr->filename);
+ if ((fd = open(fpath, O_RDONLY)) >= 0) {
+ fileheader_t postfile;
+ char fn2[MAXPATHLEN] = BBSHOME "/boards/d/deleted",
+ *junkdir;
+
+ stampfile(fn2, &postfile);
+ memcpy(postfile.owner, fhdr->owner, IDLEN + TTLEN + 10);
+ close(fd);
+ Rename(fpath, fn2);
+ strcpy(strrchr(fn2, '/') + 1, ".DIR");
+ append_record(fn2, &postfile, sizeof(postfile));
+ } else
+ bbslog("cancelpost: %s opened error\n", fpath);
+}
+
+
+/* ---------------------------- */
+/* new/old/lock file processing */
+/* ---------------------------- */
+
+typedef struct {
+ char newfn[MAXPATHLEN];
+ char oldfn[MAXPATHLEN];
+ char lockfn[MAXPATHLEN];
+} nol;
+
+
+static void
+nolfilename(n, fpath)
+ nol *n;
+ char *fpath;
+{
+ sprintf(n->newfn, "%s.new", fpath);
+ sprintf(n->oldfn, "%s.old", fpath);
+ sprintf(n->lockfn, "%s.lock", fpath);
+}
+
+#if 0
+int
+delete_record(const char *fpath, int size, int id)
+{
+ nol my;
+ char abuf[512];
+ int fdr, fdw, fd;
+ int count;
+ fileheader_t fhdr;
+
+ nolfilename(&my, fpath);
+
+ if ((fd = open(my.lockfn, O_RDWR | O_CREAT | O_APPEND, 0644)) == -1)
+ return -1;
+ flock(fd, LOCK_EX);
+
+ if ((fdr = open(fpath, O_RDONLY, 0)) == -1) {
+
+#ifdef HAVE_REPORT
+ report("delete_record failed!!! (open)");
+#endif
+
+ flock(fd, LOCK_UN);
+ close(fd);
+ return -1;
+ }
+ if ((fdw = open(my.newfn, O_WRONLY | O_CREAT | O_EXCL, 0644)) == -1) {
+ flock(fd, LOCK_UN);
+
+#ifdef HAVE_REPORT
+ report("delete_record failed!!! (open tmpfile)");
+#endif
+
+ close(fd);
+ close(fdr);
+ return -1;
+ }
+ count = 1;
+ while (read(fdr, abuf, size) == size) {
+ if (id == count) {
+ memcpy(&fhdr, abuf, sizeof(fhdr));
+ bbslog("delete_record: %d, %s, %s\n", count, fhdr.owner, fhdr.title);
+ }
+ if (id != count++ && (write(fdw, abuf, size) == -1)) {
+
+ bbslog("delete_record: %s failed!!! (write)\n", fpath);
+#ifdef HAVE_REPORT
+ report("delete_record failed!!! (write)");
+#endif
+
+ unlink(my.newfn);
+ close(fdr);
+ close(fdw);
+ flock(fd, LOCK_UN);
+ close(fd);
+ return -1;
+ }
+ }
+ close(fdr);
+ close(fdw);
+ if (Rename(fpath, my.oldfn) == -1 || Rename(my.newfn, fpath) == -1) {
+
+#ifdef HAVE_REPORT
+ report("delete_record failed!!! (Rename)");
+#endif
+
+ flock(fd, LOCK_UN);
+ close(fd);
+ return -1;
+ }
+ flock(fd, LOCK_UN);
+ close(fd);
+ return 0;
+}
+#endif
+
+int
+cancel_article(homepath, board, file)
+ char *homepath;
+ char *board, *file;
+{
+ struct fileheader_t header;
+ struct stat state;
+ char dirname[MAXPATHLEN];
+ long size, time, now;
+ int fd, lower, ent;
+
+
+ if (file == NULL || file[0] != 'M' || file[1] != '.' ||
+ (time = atoi(file + 2)) <= 0) {
+ bbslog("cancel_article: invalid filename `%s`\n", file);
+ return 0;
+ }
+ size = sizeof(header);
+ sprintf(dirname, "%s/boards/%c/%s/.DIR", homepath, board[0], board);
+ if ((fd = open(dirname, O_RDONLY)) == -1) {
+ bbslog("cancel_article: open `%s` error\n", dirname);
+ return 0;
+ }
+ fstat(fd, &state);
+ ent = ((long)state.st_size) / size;
+ lower = 0;
+ while (1) {
+ ent -= 8;
+ if (ent <= 0 || lower >= 2)
+ break;
+ lseek(fd, size * ent, SEEK_SET);
+ if (read(fd, &header, size) != size) {
+ ent = 0;
+ break;
+ }
+ now = atoi(header.filename + 2);
+ lower = (now < time) ? lower + 1 : 0;
+ }
+ if (ent < 0)
+ ent = 0;
+ while (read(fd, &header, size) == size) {
+ if (strcmp(file, header.filename) == 0) {
+ if ((header.filemode & FILE_MARKED)
+ || (header.filemode & FILE_DIGEST) || (header.owner[0] == '-')
+ || !strchr(header.owner,'.'))
+ break;
+ delete_record(dirname, sizeof(fileheader_t), lseek(fd, 0, SEEK_CUR) / size);
+ cancelpost(&header, board);
+ break;
+ }
+ now = atoi(header.filename + 2);
+ if (now > time)
+ break;
+ }
+ close(fd);
+ return 0;
+}
+
+#elif defined(PalmBBS)
+#undef PATH XPATH
+#undef HEADER XHEADER
+#include "server.h"
+
+char *
+post_article(homepath, userid, board, writebody, pathname, firstpath)
+ char *homepath;
+ char *userid, *board;
+ int (*writebody) ();
+ char *pathname, *firstpath;
+{
+ PATH msgdir, msgfile;
+ static PATH name;
+
+ READINFO readinfo;
+ SHORT fileid;
+ char buf[MAXPATHLEN];
+ struct stat stbuf;
+ int fh;
+
+ strcpy(msgdir, homepath);
+ if (stat(msgdir, &stbuf) == -1 || !S_ISDIR(stbuf.st_mode)) {
+ /* A directory is missing! */
+ bbslog(":Err: Unable to post in %s.\n", msgdir);
+ return NULL;
+ }
+ get_filelist_ids(msgdir, &readinfo);
+
+ for (fileid = 1; fileid <= BBS_MAX_FILES; fileid++) {
+ int oumask;
+ if (test_readbit(&readinfo, fileid))
+ continue;
+ fileid_to_fname(msgdir, fileid, msgfile);
+ sprintf(name, "%04x", fileid);
+
+#ifdef DEBUG
+ printf("post to %s\n", msgfile);
+#endif
+
+ if (firstpath && *firstpath) {
+
+#ifdef DEBUGLINK
+ bbslog("try to link %s to %s", firstpath, msgfile);
+#endif
+
+ if (link(firstpath, msgfile) == 0)
+ break;
+ }
+ oumask = umask(0);
+ fh = open(msgfile, O_CREAT | O_EXCL | O_WRONLY, 0664);
+ umask(oumask);
+ if (writebody) {
+ if ((*writebody) (fh, board, pathname) < 0)
+ return NULL;
+ } else {
+ if (bbspost_write_post(fh, board, pathname) < 0)
+ return NULL;
+ }
+ close(fh);
+ break;
+ }
+
+#ifdef CACHED_OPENBOARD
+ {
+ char *bname;
+ bname = strrchr(msgdir, '/');
+ if (bname)
+ notify_new_post(++bname, 1, fileid, stbuf.st_mtime);
+ }
+#endif
+
+ return name;
+}
+
+cancel_article(homepath, board, file)
+ char *homepath;
+ char *board, *file;
+{
+ PATH fname;
+
+#ifdef CACHED_OPENBOARD
+ PATH bdir;
+ struct stat stbuf;
+
+ sprintf(bdir, "%s/boards/%c/%s", homepath, board[0], board);
+ stat(bdir, &stbuf);
+#endif
+
+ sprintf(fname, "%s/boards/%c/%s/%s", homepath, board[0], board, file);
+ unlink(fname);
+ /* kill it now! the function is far small then original.. :) */
+ /* because it won't make system load heavy like before */
+
+#ifdef CACHED_OPENBOARD
+ notify_new_post(board, -1, hex2SHORT(file), stbuf.st_mtime);
+#endif
+}
+
+#else
+error("You should choose one of the systems: PhoenixBBS, PowerBBS, or PalmBBS")
+#endif
+
+#else
+
+receive_article()
+{
+}
+
+cancel_article_front(msgid)
+ char *msgid;
+{
+}
+#endif
diff --git a/pttbbs/innbbsd/rfc931.c b/pttbbs/innbbsd/rfc931.c
new file mode 100644
index 00000000..33ee49fd
--- /dev/null
+++ b/pttbbs/innbbsd/rfc931.c
@@ -0,0 +1,147 @@
+/*
+ * rfc931_user() speaks a common subset of the RFC 931, AUTH, TAP and IDENT
+ * protocols. It consults an RFC 931 etc. compatible daemon on the client
+ * host to look up the remote user name. The information should not be used
+ * for authentication purposes.
+ *
+ * Diagnostics are reported through syslog(3).
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ *
+ * Inspired by the authutil package (comp.sources.unix volume 22) by Dan
+ * Bernstein (brnstnd@kramden.acf.nyu.edu).
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#) rfc931.c 1.4 93/03/07 22:47:52";
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <setjmp.h>
+#include <signal.h>
+#include "osdep.h"
+
+/* #include "log_tcp.h" */
+
+#define RFC931_PORT 113 /* Semi-well-known port */
+
+#ifndef RFC931_TIMEOUT
+#define RFC931_TIMEOUT 30 /* wait for at most 30 seconds */
+#endif
+
+extern char *strchr();
+extern char *inet_ntoa();
+
+static jmp_buf timebuf;
+
+/* timeout - handle timeouts */
+
+static void
+timeout(sig)
+ int sig;
+{
+ longjmp(timebuf, sig);
+}
+
+/* rfc931_name - return remote user name */
+
+char *
+my_rfc931_name(herefd, there)
+ int herefd;
+ struct sockaddr_in *there; /* remote link information */
+{
+ struct sockaddr_in here; /* local link information */
+ struct sockaddr_in sin; /* for talking to RFC931 daemon */
+ int length;
+ int s;
+ unsigned remote;
+ unsigned local;
+ static char user[256]; /* XXX */
+ char buffer[512];/* YYY */
+ FILE *fp;
+ char *cp;
+ char *result = "unknown";
+
+ /* Find out local address and port number of stdin. */
+
+ length = sizeof(here);
+ if (getsockname(herefd, (struct sockaddr *) & here, &length) == -1) {
+ syslog(LOG_ERR, "getsockname: %m");
+ return (result);
+ }
+ /*
+ * The socket that will be used for user name lookups should be bound to
+ * the same local IP address as stdin. This will automagically happen on
+ * hosts that have only one IP network address. When the local host has
+ * more than one IP network address, we must do an explicit bind() call.
+ */
+
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1)
+ return (result);
+
+ sin = here;
+ sin.sin_port = 0;
+ if (bind(s, (struct sockaddr *) & sin, sizeof sin) < 0) {
+ syslog(LOG_ERR, "bind: %s: %m", inet_ntoa(here.sin_addr));
+ return (result);
+ }
+ /* Set up timer so we won't get stuck. */
+
+ Signal(SIGALRM, timeout);
+ if (setjmp(timebuf)) {
+ close(s); /* not: fclose(fp) */
+ return (result);
+ }
+ alarm(RFC931_TIMEOUT);
+
+ /* Connect to the RFC931 daemon. */
+
+ sin = *there;
+ sin.sin_port = htons(RFC931_PORT);
+ if (connect(s, (struct sockaddr *) & sin, sizeof(sin)) == -1
+ || (fp = fdopen(s, "w+")) == 0) {
+ close(s);
+ alarm(0);
+ return (result);
+ }
+ /*
+ * Use unbuffered I/O or we may read back our own query. setbuf() must be
+ * called before doing any I/O on the stream. Thanks for the reminder,
+ * Paul Kranenburg <pk@cs.few.eur.nl>!
+ */
+
+ setbuf(fp, (char *)0);
+
+ /* Query the RFC 931 server. Would 13-byte writes ever be broken up? */
+
+ fprintf(fp, "%u,%u\r\n", ntohs(there->sin_port), ntohs(here.sin_port));
+ fflush(fp);
+
+ /*
+ * Read response from server. Use fgets()/sscanf() instead of fscanf()
+ * because there is no buffer for pushback. Thanks, Chris Turbeville
+ * <turbo@cse.uta.edu>.
+ */
+
+ if (fgets(buffer, sizeof(buffer), fp) != 0
+ && ferror(fp) == 0 && feof(fp) == 0
+ && sscanf(buffer, "%u , %u : USERID :%*[^:]:%255s",
+ &remote, &local, user) == 3
+ && ntohs(there->sin_port) == remote
+ && ntohs(here.sin_port) == local) {
+ /* Strip trailing carriage return. */
+
+ if (cp = strchr(user, '\r'))
+ *cp = 0;
+ result = user;
+ }
+ alarm(0);
+ fclose(fp);
+ return (result);
+}
diff --git a/pttbbs/innbbsd/str_decode.c b/pttbbs/innbbsd/str_decode.c
new file mode 100644
index 00000000..72a1f225
--- /dev/null
+++ b/pttbbs/innbbsd/str_decode.c
@@ -0,0 +1,291 @@
+/*
+ * 使用方法大致如下: innbbsd 中 在 SUBJECT 從 news 讀進來後, 呼叫
+ * str_decode_M3(SUBJECT) 就行了
+ */
+
+/*
+ * bsd 底下使用要編譯時要加 -I/usr/local/include -L/usr/local/lib -liconv
+ * 並安裝 libiconv, 若真的沒有iconv就把底下的 #define USE_ICONV 1 刪了
+ */
+
+/*-------------------------------------------------------*/
+/* lib/str_decode.c ( NTHU CS MapleBBS Ver 3.00 ) */
+/*-------------------------------------------------------*/
+/* target : included C for QP/BASE64 decoding */
+/* create : 95/03/29 */
+/* update : 97/03/29 */
+/*-------------------------------------------------------*/
+
+
+/* ----------------------------------------------------- */
+/* QP code : "0123456789ABCDEF" */
+/* ----------------------------------------------------- */
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h> /* isspace() */
+
+#define USE_ICONV 1
+/*
+ * bsd 底下使用要編譯時要加 -I/usr/local/include -L/usr/local/lib -liconv
+ * 若真的沒有iconv就把上面這行 #define 刪了
+ */
+
+#ifdef USE_ICONV
+#include <iconv.h>
+#endif
+
+static int
+qp_code(int x)
+{
+ if (x >= '0' && x <= '9')
+ return x - '0';
+ if (x >= 'a' && x <= 'f')
+ return x - 'a' + 10;
+ if (x >= 'A' && x <= 'F')
+ return x - 'A' + 10;
+ return -1;
+}
+
+
+/* ------------------------------------------------------------------ */
+/* BASE64 : */
+/* "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" */
+/* ------------------------------------------------------------------ */
+
+
+static int
+base64_code(int x)
+{
+ if (x >= 'A' && x <= 'Z')
+ return x - 'A';
+ if (x >= 'a' && x <= 'z')
+ return x - 'a' + 26;
+ if (x >= '0' && x <= '9')
+ return x - '0' + 52;
+ if (x == '+')
+ return 62;
+ if (x == '/')
+ return 63;
+ return -1;
+}
+
+
+/* ----------------------------------------------------- */
+/* judge & decode QP / BASE64 */
+/* ----------------------------------------------------- */
+
+inline int
+isreturn(unsigned char c)
+{
+ return c == '\r' || c == '\n';
+}
+
+#if 0 /* in glibc */
+inline int
+isspace(unsigned char c)
+{
+ return c == ' ' || c == '\t' || isreturn(c);
+}
+#endif
+
+/* static inline */
+int
+mmdecode(unsigned char *src, unsigned char encode, unsigned char *dst)
+{
+ /* Thor.980901: src和dst可相同, 但src 一定有?或\0結束 */
+ /* Thor.980901: 注意, decode出的結果不會自己加上 \0 */
+ unsigned char *t = dst;
+ int pattern = 0, bits = 0;
+ encode |= 0x20; /* Thor: to lower */
+ switch (encode) {
+ case 'q': /* Thor: quoted-printable */
+ while (*src && *src != '?') { /* Thor: delimiter *//* Thor.980901:
+ * 0 算是 delimiter */
+ if (*src == '=') {
+ int x = *++src, y = x ? *++src : 0;
+ if (isreturn(x))
+ continue;
+ if ((x = qp_code(x)) < 0 || (y = qp_code(y)) < 0)
+ return -1;
+ *t++ = (x << 4) + y, src++;
+ } else if (*src == '_')
+ *t++ = ' ', src++;
+#if 0
+ else if (!*src) /* Thor: no delimiter is not successful */
+ return -1;
+#endif
+ else /* Thor: *src != '=' '_' */
+ *t++ = *src++;
+ }
+ return t - dst;
+ case 'b': /* Thor: base 64 */
+ while (*src && *src != '?') { /* Thor: delimiter */
+ /*
+ * Thor.980901: 0也算 *//* Thor: pattern & bits are cleared
+ * outside
+ */
+ int x;
+#if 0
+ if (!*src)
+ return -1; /* Thor: no delimiter is not successful */
+#endif
+ x = base64_code(*src++);
+ if (x < 0)
+ continue; /* Thor: ignore everything not in the
+ * base64,=,.. */
+ pattern = (pattern << 6) | x;
+ bits += 6; /* Thor: 1 code gains 6 bits */
+ if (bits >= 8) { /* Thor: enough to form a byte */
+ bits -= 8;
+ *t++ = (pattern >> bits) & 0xff;
+ }
+ }
+ return t - dst;
+ }
+ return -1;
+}
+
+#ifdef USE_ICONV
+size_t
+str_iconv(
+ const char *fromcode, /* charset of source string */
+ const char *tocode, /* charset of destination string */
+ char *src, /* source string */
+ size_t srclen, /* source string length */
+ char *dst, /* destination string */
+ size_t dstlen)
+{ /* destination string length */
+ /*
+ * 這個函式會將一個字串 (src) 從 charset=fromcode 轉成 charset=tocode,
+ * srclen 是 src 的長度, dst 是輸出的buffer, dstlen 則指定了 dst 的大小,
+ * 最後會補 '\0', 所以要留一個byte給'\0'. 如果遇到 src 中有非字集的字,
+ * 或是 src 中有未完整的 byte, 都會砍掉.
+ */
+ iconv_t iconv_descriptor;
+ size_t iconv_ret, dstlen_old;
+
+ dstlen--; /* keep space for '\0' */
+
+ dstlen_old = dstlen;
+
+ /* Open a descriptor for iconv */
+ iconv_descriptor = iconv_open(tocode, fromcode);
+
+ if (iconv_descriptor == ((iconv_t) (-1))) { /* if open fail */
+ strncpy(dst, src, dstlen);
+ return dstlen;
+ }
+ /* Start translation */
+ while (srclen > 0 && dstlen > 0) {
+ iconv_ret = iconv(iconv_descriptor, (const char **)&src, &srclen,
+ &dst, &dstlen);
+ if (iconv_ret != 0) {
+ switch (errno) {
+ /* invalid multibyte happened */
+ case EILSEQ:
+ /* forward that byte */
+ *dst = *src;
+ src++;
+ srclen--;
+ dst++;
+ dstlen--;
+ break;
+ /* incomplete multibyte happened */
+ case EINVAL:
+ /* forward that byte (maybe wrong) */
+ *dst = *src;
+ src++;
+ srclen--;
+ dst++;
+ dstlen--;
+ break;
+ /* dst no rooms */
+ case E2BIG:
+ /* break out the while loop */
+ srclen = 0;
+ break;
+ }
+ }
+ }
+ *dst = '\0';
+ /* close descriptor of iconv */
+ iconv_close(iconv_descriptor);
+
+ return (dstlen_old - dstlen);
+}
+#endif
+
+
+void
+str_decode_M3(unsigned char *str)
+{
+ int adj;
+ int i;
+ unsigned char *src, *dst;
+ unsigned char buf[512];
+ unsigned char charset[512], dst1[512];
+
+
+ src = str;
+ dst = buf;
+ adj = 0;
+
+ while (*src && (dst - buf) < sizeof(buf) - 1) {
+ if (*src != '=') { /* Thor: not coded */
+ unsigned char *tmp = src;
+ while (adj && *tmp && isspace(*tmp))
+ tmp++;
+ if (adj && *tmp == '=') { /* Thor: jump over space */
+ adj = 0;
+ src = tmp;
+ } else
+ *dst++ = *src++;
+ /* continue; *//* Thor: take out */
+ } else { /* Thor: *src == '=' */
+ unsigned char *tmp = src + 1;
+ if (*tmp == '?') { /* Thor: =? coded */
+ /* "=?%s?Q?" for QP, "=?%s?B?" for BASE64 */
+ tmp++;
+ i = 0;
+ while (*tmp && *tmp != '?') {
+ if (i + 1 < sizeof(charset)) {
+ charset[i] = *tmp;
+ charset[i + 1] = '\0';
+ i++;
+ }
+ tmp++;
+ }
+ if (*tmp && tmp[1] && tmp[2] == '?') { /* Thor: *tmp == '?' */
+#ifdef USE_ICONV
+ int i = mmdecode(tmp + 3, tmp[1], dst1);
+ i = str_iconv(charset, "big5", dst1, i, dst,
+ sizeof(buf) - ((int)(dst - buf)));
+#else
+ int i = mmdecode(tmp + 3, tmp[1], dst);
+#endif
+ if (i >= 0) {
+ tmp += 3; /* Thor: decode's src */
+#if 0
+ while (*tmp++ != '?'); /* Thor: no ? end, mmdecode
+ * -1 */
+#endif
+ while (*tmp && *tmp++ != '?'); /* Thor: no ? end,
+ * mmdecode -1 */
+ /* Thor.980901: 0 也算 decode 結束 */
+ if (*tmp == '=')
+ tmp++;
+ src = tmp; /* Thor: decode over */
+ dst += i;
+ adj = 1;/* Thor: adjcent */
+ }
+ }
+ }
+ while (src != tmp) /* Thor: not coded */
+ *dst++ = *src++;
+ }
+ }
+ *dst = 0;
+ strcpy(str, buf);
+}
diff --git a/pttbbs/mbbsd/Makefile b/pttbbs/mbbsd/Makefile
new file mode 100644
index 00000000..eb26c91f
--- /dev/null
+++ b/pttbbs/mbbsd/Makefile
@@ -0,0 +1,69 @@
+# $Id$
+
+.include "../pttbbs.mk"
+
+.if defined(WITH_BLOG)
+CFLAGS+= -DBLOG
+LDFLAGS+= -L/usr/local/lib/mysql -lmysqlclient
+.endif
+
+
+PROG= mbbsd
+OBJS= admin.o announce.o args.o assess.o bbs.o board.o cache.o cal.o card.o\
+ chat.o chc.o chc_tab.o chicken.o convert.o crypt.o dark.o edit.o fav.o friend.o gamble.o\
+ gomo.o guess.o indict.o io.o kaede.o lovepaper.o mail.o mbbsd.o menu.o\
+ more.o name.o osdep.o othello.o read.o record.o register.o\
+ screen.o stuff.o talk.o term.o topsong.o user.o brc.o vice.o vote.o\
+ xyz.o voteboard.o syspost.o var.o passwd.o calendar.o go.o file.o \
+ pmore.o chess.o reversi.o
+
+.if defined(DIET)
+OBJS+= random.o time.o alloc.o
+DIETCC= diet -Os
+.endif
+#CFLAGS+=-g
+#CFLAGS+=-std=c99
+
+# reduce .bss align overhead
+.if !defined(DEBUG)
+LDFLAGS+=-Wl,--sort-common
+.endif
+
+.if defined(MERGEBBS)
+CFLAGS+= -DMERGEBBS
+OBJS+= merge.o
+.endif
+
+.SUFFIXES: .c .o
+.c.o: ../include/var.h
+ $(CCACHE) $(DIETCC) $(CC) $(CFLAGS) -c $*.c
+
+all: $(PROG)
+
+$(PROG): $(OBJS)
+ sh ../util/newvers.sh
+ $(DIETCC) $(CC) $(LDFLAGS) -o $(PROG) $(OBJS) $(LIBS) $(EXT_LIBS) vers.c
+
+../include/var.h: var.c
+ perl ../util/parsevar.pl < var.c > ../include/var.h
+
+../include/banip.h: ../util/banip.pl
+ perl ../util/banip.pl > $@
+
+mbbsd.o: mbbsd.c ../include/var.h ../include/banip.h
+ $(CCACHE) $(DIETCC) $(CC) $(CFLAGS) -c $<
+
+test: $(PROG)
+ killall -9 testmbbsd || true
+ cp mbbsd testmbbsd
+ ./testmbbsd 9000
+ rm -f testmbbsd
+
+install: $(PROG)
+ install -d $(BBSHOME)/bin/
+ install -c -m 755 $(PROG) $(BBSHOME)/bin/
+ mv -f $(BBSHOME)/bin/mbbsd $(BBSHOME)/bin/mbbsd.`date '+%m%d%H%M'`
+ ln -sv $(BBSHOME)/bin/mbbsd.`date '+%m%d%H%M'` $(BBSHOME)/bin/mbbsd
+
+clean:
+ rm -f $(OBJS) $(PROG)
diff --git a/pttbbs/mbbsd/admin.c b/pttbbs/mbbsd/admin.c
new file mode 100644
index 00000000..41b776f7
--- /dev/null
+++ b/pttbbs/mbbsd/admin.c
@@ -0,0 +1,1537 @@
+/* $Id$ */
+#include "bbs.h"
+
+/* 進站水球宣傳 */
+int
+m_loginmsg(void)
+{
+ char msg[100];
+ move(21,0);
+ clrtobot();
+ if(SHM->loginmsg.pid && SHM->loginmsg.pid != currutmp->pid)
+ {
+ outs("目前已經有以下的 進站水球設定請先協調好再設定..");
+ getmessage(SHM->loginmsg);
+ }
+ getdata(22, 0,
+ "進站水球:本站活動,不干擾使用者為限,設定者離站自動取消,確定要設?(y/N)",
+ msg, 3, LCECHO);
+
+ if(msg[0]=='y' &&
+
+ getdata_str(23, 0, "設定進站水球:", msg, 56, DOECHO, SHM->loginmsg.last_call_in))
+ {
+ SHM->loginmsg.pid=currutmp->pid; /*站長不多 就不管race condition */
+ strcpy(SHM->loginmsg.last_call_in, msg);
+ strcpy(SHM->loginmsg.userid, cuser.userid);
+ }
+ return 0;
+}
+
+/* 使用者管理 */
+int
+m_user(void)
+{
+ userec_t xuser;
+ int id;
+ char genbuf[200];
+
+ stand_title("使用者設定");
+ usercomplete(msg_uid, genbuf);
+ if (*genbuf) {
+ move(2, 0);
+ if ((id = getuser(genbuf, &xuser))) {
+ user_display(&xuser, 1);
+ if( HasUserPerm(PERM_ACCOUNTS) )
+ uinfo_query(&xuser, 1, id);
+ else
+ pressanykey();
+ } else {
+ outs(err_uid);
+ clrtoeol();
+ pressanykey();
+ }
+ }
+ return 0;
+}
+
+static int retrieve_backup(userec_t *user)
+{
+ int uid;
+ char src[PATHLEN], dst[PATHLEN];
+ char ans;
+
+ if ((uid = searchuser(user->userid, user->userid))) {
+ userec_t orig;
+ passwd_query(uid, &orig);
+ strlcpy(user->passwd, orig.passwd, sizeof(orig.passwd));
+ setumoney(uid, user->money);
+ passwd_update(uid, user);
+ return 0;
+ }
+
+ ans = getans("目前的 PASSWD 檔沒有此 ID,新增嗎?[y/N]");
+
+ if (ans != 'y') {
+ vmsg("目前的 PASSWDS 檔沒有此 ID,請先新增此帳號");
+ return -1;
+ }
+
+ if (setupnewuser((const userec_t *)user) >= 0) {
+ sethomepath(dst, user->userid);
+ if (!dashd(dst)) {
+ snprintf(src, sizeof(src), "tmp/%s", user->userid);
+ if (!dashd(src) || !Rename(src, dst))
+ mkuserdir(user->userid);
+ }
+ return 0;
+ }
+ return -1;
+}
+
+static int
+search_key_user(const char *passwdfile, int mode)
+{
+ userec_t user;
+ int ch;
+ int coun = 0;
+ FILE *fp1 = fopen(passwdfile, "r");
+ char friendfile[128]="", key[22], *keymatch;
+
+
+ assert(fp1);
+ clear();
+ getdata(0, 0, mode ? "請輸入使用者關鍵字[電話|地址|姓名|上站地點|"
+ "email|小雞id] :" : "請輸入id :", key, sizeof(key), DOECHO);
+ if(!key[0]) {
+ fclose(fp1);
+ return 0;
+ }
+ while ((fread(&user, sizeof(user), 1, fp1)) > 0 && coun < MAX_USERS) {
+ if (!(++coun & 15)) {
+ move(1, 0);
+ prints("第 [%d] 筆資料\n", coun);
+ refresh();
+ }
+ keymatch = NULL;
+ if (!strcasecmp(user.userid, key))
+ keymatch = user.userid;
+ else if(mode) {
+ if(strstr(user.realname, key))
+ keymatch = user.realname;
+ else if(strstr(user.nickname, key))
+ keymatch = user.nickname;
+ else if(strstr(user.lasthost, key))
+ keymatch = user.lasthost;
+ else if(strcasestr(user.email, key))
+ keymatch = user.email;
+ else if(strstr(user.address, key))
+ keymatch = user.address;
+ else if(strstr(user.justify, key))
+ keymatch = user.justify;
+ else if(strstr(user.mychicken.name, key))
+ keymatch = user.mychicken.name;
+ }
+ if(keymatch) {
+ move(1, 0);
+ prints("第 [%d] 筆資料\n", coun);
+ refresh();
+
+ user_display(&user, 1);
+ if (HasUserPerm(PERM_ACCOUNTS))
+ uinfo_query(&user, 1, coun);
+
+ outs(ANSI_COLOR(44) " 空白鍵" \
+ ANSI_COLOR(37) ":搜尋下一個 " \
+ ANSI_COLOR(33)" Q" ANSI_COLOR(37)": 離開");
+ outs(mode ?
+ " A: add to namelist " ANSI_RESET " " :
+ " S: 取用備份資料 " ANSI_RESET " ");
+ while (1) {
+ while ((ch = igetch()) == 0);
+ if (ch == 'a' || ch=='A' )
+ {
+ if(!friendfile[0])
+ {
+ friend_special();
+ setfriendfile(friendfile, FRIEND_SPECIAL);
+ }
+ friend_add(user.userid, FRIEND_SPECIAL, keymatch);
+ break;
+ }
+ if (ch == ' ')
+ break;
+ if (ch == 'q' || ch == 'Q') {
+ fclose(fp1);
+ return 0;
+ }
+ if (ch == 's' && !mode) {
+ if (retrieve_backup(&user) >= 0) {
+ fclose(fp1);
+ return 0;
+ }
+ }
+ }
+ }
+ }
+
+ fclose(fp1);
+ return 0;
+}
+
+/* 以任意 key 尋找使用者 */
+int
+search_user_bypwd(void)
+{
+ search_key_user(FN_PASSWD, 1);
+ return 0;
+}
+
+/* 尋找備份的使用者資料 */
+int
+search_user_bybakpwd(void)
+{
+ char *choice[] = {
+ "PASSWDS.NEW1", "PASSWDS.NEW2", "PASSWDS.NEW3",
+ "PASSWDS.NEW4", "PASSWDS.NEW5", "PASSWDS.NEW6",
+ "PASSWDS.BAK"
+ };
+ int ch;
+
+ clear();
+ move(1, 1);
+ outs("請輸入你要用來尋找備份的檔案 或按 'q' 離開\n");
+ outs(" [" ANSI_COLOR(1;31) "1" ANSI_RESET "]一天前,"
+ " [" ANSI_COLOR(1;31) "2" ANSI_RESET "]兩天前,"
+ " [" ANSI_COLOR(1;31) "3" ANSI_RESET "]三天前\n");
+ outs(" [" ANSI_COLOR(1;31) "4" ANSI_RESET "]四天前,"
+ " [" ANSI_COLOR(1;31) "5" ANSI_RESET "]五天前,"
+ " [" ANSI_COLOR(1;31) "6" ANSI_RESET "]六天前\n");
+ outs(" [7]備份的\n");
+ do {
+ move(5, 1);
+ outs("選擇 => ");
+ ch = igetch();
+ if (ch == 'q' || ch == 'Q')
+ return 0;
+ } while (ch < '1' || ch > '7');
+ ch -= '1';
+ if( access(choice[ch], R_OK) != 0 )
+ vmsg("檔案不存在");
+ else
+ search_key_user(choice[ch], 0);
+ return 0;
+}
+
+static void
+bperm_msg(const boardheader_t * board)
+{
+ prints("\n設定 [%s] 看板之(%s)權限:", board->brdname,
+ board->brdattr & BRD_POSTMASK ? "發表" : "閱\讀");
+}
+
+unsigned int
+setperms(unsigned int pbits, const char * const pstring[])
+{
+ register int i;
+
+ move(4, 0);
+ for (i = 0; i < NUMPERMS / 2; i++) {
+ prints("%c. %-20s %-15s %c. %-20s %s\n",
+ 'A' + i, pstring[i],
+ ((pbits >> i) & 1 ? "ˇ" : "X"),
+ i < 10 ? 'Q' + i : '0' + i - 10,
+ pstring[i + 16],
+ ((pbits >> (i + 16)) & 1 ? "ˇ" : "X"));
+ }
+ clrtobot();
+ while (
+ (i = getkey("請按 [A-5] 切換設定,按 [Return] 結束:"))!='\r')
+ {
+ if (isdigit(i))
+ i = i - '0' + 26;
+ else if (isalpha(i))
+ i = tolower(i) - 'a';
+ else {
+ bell();
+ continue;
+ }
+
+ pbits ^= (1 << i);
+ move(i % 16 + 4, i <= 15 ? 24 : 64);
+ outs((pbits >> i) & 1 ? "ˇ" : "X");
+ }
+ return pbits;
+}
+
+#ifdef CHESSCOUNTRY
+static void
+AddingChessCountryFiles(const char* apath)
+{
+ char filename[PATHLEN];
+ char symbolicname[PATHLEN];
+ char adir[PATHLEN];
+ FILE* fp;
+ fileheader_t fh;
+
+ setadir(adir, apath);
+
+ /* creating chess country regalia */
+ snprintf(filename, sizeof(filename), "%s/chess_ensign", apath);
+ close(open(filename, O_CREAT | O_WRONLY, 0644));
+
+ strlcpy(symbolicname, apath, sizeof(symbolicname));
+ stampfile(symbolicname, &fh);
+ symlink("chess_ensign", symbolicname);
+
+ strcpy(fh.title, "◇ 棋國國徽 (不能刪除,系統需要)");
+ strcpy(fh.owner, str_sysop);
+ append_record(adir, &fh, sizeof(fileheader_t));
+
+ /* creating member list */
+ snprintf(filename, sizeof(filename), "%s/chess_list", apath);
+ if (!dashf(filename)) {
+ fp = fopen(filename, "w");
+ assert(fp);
+ fputs("棋國國名\n"
+ "帳號 階級 加入日期 等級或被誰俘虜\n"
+ "────── ─── ───── ───────\n",
+ fp);
+ fclose(fp);
+ }
+
+ strlcpy(symbolicname, apath, sizeof(symbolicname));
+ stampfile(symbolicname, &fh);
+ symlink("chess_list", symbolicname);
+
+ strcpy(fh.title, "◇ 棋國成員表 (不能刪除,系統需要)");
+ strcpy(fh.owner, str_sysop);
+ append_record(adir, &fh, sizeof(fileheader_t));
+
+ /* creating profession photos' dir */
+ snprintf(filename, sizeof(filename), "%s/chess_photo", apath);
+ mkdir(filename, 0755);
+
+ strlcpy(symbolicname, apath, sizeof(symbolicname));
+ stampfile(symbolicname, &fh);
+ symlink("chess_photo", symbolicname);
+
+ strcpy(fh.title, "◆ 棋國照片檔 (不能刪除,系統需要)");
+ strcpy(fh.owner, str_sysop);
+ append_record(adir, &fh, sizeof(fileheader_t));
+}
+#endif /* defined(CHESSCOUNTRY) */
+
+/* 自動設立精華區 */
+void
+setup_man(const boardheader_t * board, const boardheader_t * oldboard)
+{
+ char genbuf[200];
+
+ setapath(genbuf, board->brdname);
+ mkdir(genbuf, 0755);
+
+#ifdef CHESSCOUNTRY
+ if (oldboard == NULL || oldboard->chesscountry != board->chesscountry)
+ if (board->chesscountry != CHESSCODE_NONE)
+ AddingChessCountryFiles(genbuf);
+ // else // doesn't remove files..
+#endif
+}
+
+void delete_symbolic_link(boardheader_t *bh, int bid)
+{
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ memset(bh, 0, sizeof(boardheader_t));
+ substitute_record(fn_board, bh, sizeof(boardheader_t), bid);
+ reset_board(bid);
+ sort_bcache();
+ log_usies("DelLink", bh->brdname);
+}
+
+int dir_cmp(const void *a, const void *b)
+{
+ return (atoi( &((fileheader_t *)a)->filename[2] ) -
+ atoi( &((fileheader_t *)b)->filename[2] ));
+}
+
+void merge_dir(const char *dir1, const char *dir2, int isoutter)
+{
+ int i, pn, sn;
+ fileheader_t *fh;
+ char *p1, *p2, bakdir[128], file1[128], file2[128];
+ strcpy(file1,dir1);
+ strcpy(file2,dir2);
+ if((p1=strrchr(file1,'/')))
+ p1 ++;
+ else
+ p1 = file1;
+ if((p2=strrchr(file2,'/')))
+ p2 ++;
+ else
+ p2 = file2;
+
+ pn=get_num_records(dir1, sizeof(fileheader_t));
+ sn=get_num_records(dir2, sizeof(fileheader_t));
+ if(!sn) return;
+ fh= (fileheader_t *)malloc( (pn+sn)*sizeof(fileheader_t));
+ get_records(dir1, fh, sizeof(fileheader_t), 1, pn);
+ get_records(dir2, fh+pn, sizeof(fileheader_t), 1, sn);
+ if(isoutter)
+ {
+ for(i=0; i<sn; i++)
+ if(fh[pn+i].owner[0])
+ strcat(fh[pn+i].owner, ".");
+ }
+ qsort(fh, pn+sn, sizeof(fileheader_t), dir_cmp);
+ sprintf(bakdir,"%s.bak", dir1);
+ Rename(dir1, bakdir);
+ for(i=1; i<=pn+sn; i++ )
+ {
+ if(!fh[i-1].filename[0]) continue;
+ if(i == pn+sn || strcmp(fh[i-1].filename, fh[i].filename))
+ {
+ fh[i-1].recommend =0;
+ fh[i-1].filemode |= 1;
+ append_record(dir1, &fh[i-1], sizeof(fileheader_t));
+ strcpy(p1, fh[i-1].filename);
+ if(!dashf(file1))
+ {
+ strcpy(p2, fh[i-1].filename);
+ Copy(file2, file1);
+ }
+ }
+ else
+ fh[i].filemode |= fh[i-1].filemode;
+ }
+
+ free(fh);
+}
+
+int
+m_mod_board(char *bname)
+{
+ boardheader_t bh, newbh;
+ int bid;
+ char genbuf[256], ans[4];
+
+ bid = getbnum(bname);
+ if (!bid || !bname[0] || get_record(fn_board, &bh, sizeof(bh), bid) == -1) {
+ vmsg(err_bid);
+ return -1;
+ }
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ prints("看板名稱:%s\n看板說明:%s\n看板bid:%d\n看板GID:%d\n"
+ "板主名單:%s", bh.brdname, bh.title, bid, bh.gid, bh.BM);
+ bperm_msg(&bh);
+
+ /* Ptt 這邊斷行會檔到下面 */
+ move(9, 0);
+ snprintf(genbuf, sizeof(genbuf), "(E)設定 (V)違法/解除%s%s [Q]取消?",
+ HasUserPerm(PERM_SYSOP |
+ PERM_BOARD) ? " (B)Vote (S)救回 (C)合併 (G)賭盤解卡" : "",
+ HasUserPerm(PERM_SYSSUBOP | PERM_SYSSUPERSUBOP | PERM_BOARD) ? " (D)刪除" : "");
+ getdata(10, 0, genbuf, ans, 3, LCECHO);
+
+ switch (*ans) {
+ case 'g':
+ if (HasUserPerm(PERM_SYSOP | PERM_BOARD)) {
+ char path[PATHLEN];
+ setbfile(genbuf, bname, FN_TICKET_LOCK);
+ setbfile(path, bname, FN_TICKET_END);
+ rename(genbuf, path);
+ }
+ break;
+ case 's':
+ if (HasUserPerm(PERM_SYSOP | PERM_BOARD)) {
+ snprintf(genbuf, sizeof(genbuf),
+ BBSHOME "/bin/buildir boards/%c/%s &",
+ bh.brdname[0], bh.brdname);
+ system(genbuf);
+ }
+ break;
+ case 'c':
+ if (HasUserPerm(PERM_SYSOP)) {
+ char frombname[20], fromdir[256];
+#ifdef MERGEBBS
+ if(getans("是否匯入SOB看板? (y/N)")=='y')
+ {
+ setbdir(genbuf, bname);
+ m_sob_brd(bname, fromdir);
+ if(!fromdir[0]) break;
+ merge_dir(genbuf, fromdir, 1);
+ }
+ else{
+#endif
+ CompleteBoard(MSG_SELECT_BOARD, frombname);
+ if (frombname[0] == '\0' || !getbnum(frombname) ||
+ !strcmp(frombname,bname))
+ break;
+ setbdir(genbuf, bname);
+ setbdir(fromdir, frombname);
+ merge_dir(genbuf, fromdir, 0);
+#ifdef MERGEBBS
+ }
+#endif
+ touchbtotal(bid);
+ }
+ break;
+ case 'b':
+ if (HasUserPerm(PERM_SYSOP | PERM_BOARD)) {
+ char bvotebuf[10];
+
+ memcpy(&newbh, &bh, sizeof(bh));
+ snprintf(bvotebuf, sizeof(bvotebuf), "%d", newbh.bvote);
+ move(20, 0);
+ prints("看板 %s 原來的 BVote:%d", bh.brdname, bh.bvote);
+ getdata_str(21, 0, "新的 Bvote:", genbuf, 5, LCECHO, bvotebuf);
+ newbh.bvote = atoi(genbuf);
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ substitute_record(fn_board, &newbh, sizeof(newbh), bid);
+ reset_board(bid);
+ log_usies("SetBoardBvote", newbh.brdname);
+ break;
+ } else
+ break;
+ case 'v':
+ memcpy(&newbh, &bh, sizeof(bh));
+ outs("看板目前為");
+ outs((bh.brdattr & BRD_BAD) ? "違法" : "正常");
+ getdata(21, 0, "確定更改?", genbuf, 5, LCECHO);
+ if (genbuf[0] == 'y') {
+ if (newbh.brdattr & BRD_BAD)
+ newbh.brdattr = newbh.brdattr & (!BRD_BAD);
+ else
+ newbh.brdattr = newbh.brdattr | BRD_BAD;
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ substitute_record(fn_board, &newbh, sizeof(newbh), bid);
+ reset_board(bid);
+ log_usies("ViolateLawSet", newbh.brdname);
+ }
+ break;
+ case 'd':
+ if (!(HasUserPerm(PERM_SYSOP | PERM_BOARD) ||
+ (HasUserPerm(PERM_SYSSUPERSUBOP) && GROUPOP())))
+ break;
+ getdata_str(9, 0, msg_sure_ny, genbuf, 3, LCECHO, "N");
+ if (genbuf[0] != 'y' || !bname[0])
+ outs(MSG_DEL_CANCEL);
+ else if (bh.brdattr & BRD_SYMBOLIC) {
+ delete_symbolic_link(&bh, bid);
+ }
+ else {
+ strlcpy(bname, bh.brdname, sizeof(bh.brdname));
+ snprintf(genbuf, sizeof(genbuf),
+ "/bin/tar zcvf tmp/board_%s.tgz boards/%c/%s man/boards/%c/%s >/dev/null 2>&1;"
+ "/bin/rm -fr boards/%c/%s man/boards/%c/%s",
+ bname, bname[0], bname, bname[0],
+ bname, bname[0], bname, bname[0], bname);
+ system(genbuf);
+ memset(&bh, 0, sizeof(bh));
+ snprintf(bh.title, sizeof(bh.title),
+ " %s 看板 %s 刪除", bname, cuser.userid);
+ post_msg("Security", bh.title, "請注意刪除的合法性", "[系統安全局]");
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ substitute_record(fn_board, &bh, sizeof(bh), bid);
+ reset_board(bid);
+ sort_bcache();
+ log_usies("DelBoard", bh.title);
+ outs("刪板完畢");
+ }
+ break;
+ case 'e':
+ if( bh.brdattr & BRD_SYMBOLIC ){
+ vmsg("禁止更動連結看板,請直接修正原看板");
+ break;
+ }
+ move(8, 0);
+ outs("直接按 [Return] 不修改該項設定");
+ memcpy(&newbh, &bh, sizeof(bh));
+
+ while (getdata(9, 0, "新看板名稱:", genbuf, IDLEN + 1, DOECHO)) {
+ if (getbnum(genbuf)) {
+ move(3, 0);
+ outs("錯誤! 板名雷同");
+ } else if ( !invalid_brdname(genbuf) ){
+ strlcpy(newbh.brdname, genbuf, sizeof(newbh.brdname));
+ break;
+ }
+ }
+
+ do {
+ getdata_str(12, 0, "看板類別:", genbuf, 5, DOECHO, bh.title);
+ if (strlen(genbuf) == 4)
+ break;
+ } while (1);
+
+ strcpy(newbh.title, genbuf);
+
+ newbh.title[4] = ' ';
+
+ getdata_str(14, 0, "看板主題:", genbuf, BTLEN + 1, DOECHO,
+ bh.title + 7);
+ if (genbuf[0])
+ strlcpy(newbh.title + 7, genbuf, sizeof(newbh.title) - 7);
+ if (getdata_str(15, 0, "新板主名單:", genbuf, IDLEN * 3 + 3, DOECHO,
+ bh.BM)) {
+ trim(genbuf);
+ strlcpy(newbh.BM, genbuf, sizeof(newbh.BM));
+ }
+#ifdef CHESSCOUNTRY
+ if (HasUserPerm(PERM_SYSOP)) {
+ snprintf(genbuf, sizeof(genbuf), "%d", bh.chesscountry);
+ if (getdata_str(16, 0, "設定棋國 (0)無 (1)五子棋 (2)象棋 (3)圍棋", ans,
+ sizeof(ans), LCECHO, genbuf)){
+ newbh.chesscountry = atoi(ans);
+ if (newbh.chesscountry > CHESSCODE_MAX ||
+ newbh.chesscountry < CHESSCODE_NONE)
+ newbh.chesscountry = bh.chesscountry;
+ }
+ }
+#endif /* defined(CHESSCOUNTRY) */
+ if (HasUserPerm(PERM_SYSOP|PERM_BOARD)) {
+ move(1, 0);
+ clrtobot();
+ newbh.brdattr = setperms(newbh.brdattr, str_permboard);
+ move(1, 0);
+ clrtobot();
+ }
+ {
+ const char* brd_symbol;
+ if (newbh.brdattr & BRD_GROUPBOARD)
+ brd_symbol = "Σ";
+ else if (newbh.brdattr & BRD_NOTRAN)
+ brd_symbol = "◎";
+ else
+ brd_symbol = "●";
+
+ newbh.title[5] = brd_symbol[0];
+ newbh.title[6] = brd_symbol[1];
+ }
+
+ if (HasUserPerm(PERM_SYSOP|PERM_BOARD) && !(newbh.brdattr & BRD_HIDE)) {
+ getdata_str(14, 0, "設定讀寫權限(Y/N)?", ans, sizeof(ans), LCECHO, "N");
+ if (*ans == 'y') {
+ getdata_str(15, 0, "限制 [R]閱\讀 (P)發表?", ans, sizeof(ans), LCECHO,
+ "R");
+ if (*ans == 'p')
+ newbh.brdattr |= BRD_POSTMASK;
+ else
+ newbh.brdattr &= ~BRD_POSTMASK;
+
+ move(1, 0);
+ clrtobot();
+ bperm_msg(&newbh);
+ newbh.level = setperms(newbh.level, str_permid);
+ clear();
+ }
+ }
+
+ getdata(b_lines - 1, 0, "請您確定(Y/N)?[Y]", genbuf, 4, LCECHO);
+
+ if ((*genbuf != 'n') && memcmp(&newbh, &bh, sizeof(bh))) {
+ char buf[64];
+
+ if (strcmp(bh.brdname, newbh.brdname)) {
+ char src[60], tar[60];
+
+ setbpath(src, bh.brdname);
+ setbpath(tar, newbh.brdname);
+ Rename(src, tar);
+
+ setapath(src, bh.brdname);
+ setapath(tar, newbh.brdname);
+ Rename(src, tar);
+ }
+ setup_man(&newbh, &bh);
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ substitute_record(fn_board, &newbh, sizeof(newbh), bid);
+ reset_board(bid);
+ sort_bcache();
+ log_usies("SetBoard", newbh.brdname);
+
+ snprintf(buf, sizeof(buf), "[看板變更] %s (by %s)", bh.brdname, cuser.userid);
+ snprintf(genbuf, sizeof(genbuf),
+ "板名: %s => %s\n"
+ "板主: %s => %s\n",
+ bh.brdname, newbh.brdname, bh.BM, newbh.BM);
+ post_msg("Security", buf, genbuf, "[系統安全局]");
+ }
+ }
+ return 0;
+}
+
+/* 設定看板 */
+int
+m_board(void)
+{
+ char bname[32];
+
+ stand_title("看板設定");
+ CompleteBoardAndGroup(msg_bid, bname);
+ if (!*bname)
+ return 0;
+ m_mod_board(bname);
+ return 0;
+}
+
+/* 設定系統檔案 */
+int
+x_file(void)
+{
+ int aborted, num;
+ char ans[4], *fpath, buf[256];
+
+ move(b_lines - 7, 0);
+ /* Ptt */
+ outs("設定 (1)身份確認信 (4)post注意事項 (5)錯誤登入訊息 (6)註冊範例 (7)通過確認通知\n");
+ outs(" (8)email post通知 (9)系統功\能精靈 (A)茶樓 (B)站長名單 (C)email通過確認\n");
+ outs(" (D)新使用者需知 (E)身份確認方法 (F)歡迎畫面 (G)進站畫面 "
+#ifdef MULTI_WELCOME_LOGIN
+ "(X)刪除進站畫面"
+#endif
+ "\n");
+ outs(" (H)看板期限 (I)故鄉 (J)出站畫面 (K)生日卡 (L)節日 (M)外籍使用者認證通知\n");
+ outs(" (N)外籍使用者過期警告通知 (O)看板列表 help (P)文章列表 help\n");
+#ifdef PLAY_ANGEL
+ outs(" (R)小天使認證通知 (S)小天使功\能說明\n");
+#endif
+ getdata(b_lines - 1, 0, "[Q]取消[1-9 A-P]?", ans, sizeof(ans), LCECHO);
+
+ switch (ans[0]) {
+ case '1':
+ fpath = "etc/confirm";
+ break;
+ case '4':
+ fpath = "etc/post.note";
+ break;
+ case '5':
+ fpath = "etc/goodbye";
+ break;
+ case '6':
+ fpath = "etc/register";
+ break;
+ case '7':
+ fpath = "etc/registered";
+ break;
+ case '8':
+ fpath = "etc/emailpost";
+ break;
+ case '9':
+ fpath = "etc/hint";
+ break;
+ case 'b':
+ fpath = "etc/sysop";
+ break;
+ case 'c':
+ fpath = "etc/bademail";
+ break;
+ case 'd':
+ fpath = "etc/newuser";
+ break;
+ case 'e':
+ fpath = "etc/justify";
+ break;
+ case 'f':
+ fpath = "etc/Welcome";
+ break;
+ case 'g':
+#ifdef MULTI_WELCOME_LOGIN
+ getdata(b_lines - 1, 0, "第幾個進站畫面[0-4]", ans, sizeof(ans), LCECHO);
+ if (ans[0] == '1') {
+ fpath = "etc/Welcome_login.1";
+ } else if (ans[0] == '2') {
+ fpath = "etc/Welcome_login.2";
+ } else if (ans[0] == '3') {
+ fpath = "etc/Welcome_login.3";
+ } else if (ans[0] == '4') {
+ fpath = "etc/Welcome_login.4";
+ } else {
+ fpath = "etc/Welcome_login.0";
+ }
+#else
+ fpath = "etc/Welcome_login";
+#endif
+ break;
+
+#ifdef MULTI_WELCOME_LOGIN
+ case 'x':
+ getdata(b_lines - 1, 0, "第幾個進站畫面[1-4]", ans, sizeof(ans), LCECHO);
+ if (ans[0] == '1') {
+ unlink("etc/Welcome_login.1");
+ vmsg("ok");
+ } else if (ans[0] == '2') {
+ unlink("etc/Welcome_login.2");
+ vmsg("ok");
+ } else if (ans[0] == '3') {
+ unlink("etc/Welcome_login.3");
+ vmsg("ok");
+ } else if (ans[0] == '4') {
+ unlink("etc/Welcome_login.4");
+ vmsg("ok");
+ } else {
+ vmsg("所指定的進站畫面無法刪除");
+ }
+ return FULLUPDATE;
+
+#endif
+
+ case 'h':
+ fpath = "etc/expire.conf";
+ break;
+ case 'i':
+ fpath = "etc/domain_name_query.cidr";
+ break;
+ case 'j':
+ fpath = "etc/Logout";
+ break;
+ case 'k':
+ mouts(b_lines - 3, 0, "1.摩羯 2.水瓶 3.雙魚 4.牡羊 5.金牛 6.雙子");
+ mouts(b_lines - 2, 0, "7.巨蟹 8.獅子 9.處女 10.天秤 11.天蠍 12.射手");
+ getdata(b_lines - 1, 0, "請選擇 [1-12]", ans, sizeof(ans), LCECHO);
+ num = atoi(ans);
+ if (num <= 0 || num > 12)
+ return FULLUPDATE;
+ snprintf(buf, sizeof(buf), "etc/Welcome_birth.%d", num);
+ fpath = buf;
+ break;
+ case 'l':
+ fpath = "etc/feast";
+ break;
+ case 'm':
+ fpath = "etc/foreign_welcome";
+ break;
+ case 'n':
+ fpath = "etc/foreign_expired_warn";
+ break;
+ case 'o':
+ fpath = "etc/boardlist.help";
+ break;
+ case 'p':
+ fpath = "etc/board.help";
+ break;
+
+#ifdef PLAY_ANGEL
+ case 'r':
+ fpath = "etc/angel_notify";
+ break;
+
+ case 's':
+ fpath = "etc/angel_usage";
+ break;
+#endif
+
+ default:
+ return FULLUPDATE;
+ }
+ aborted = vedit(fpath, NA, NULL);
+ vmsgf("\n\n系統檔案[%s]:%s", fpath,
+ (aborted == -1) ? "未改變" : "更新完畢");
+ return FULLUPDATE;
+}
+
+static int add_board_record(const boardheader_t *board)
+{
+ int bid;
+ if ((bid = getbnum("")) > 0) {
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ substitute_record(fn_board, board, sizeof(boardheader_t), bid);
+ reset_board(bid);
+ sort_bcache();
+ } else if (append_record(fn_board, (fileheader_t *)board, sizeof(boardheader_t)) == -1) {
+ return -1;
+ } else {
+ addbrd_touchcache();
+ }
+ return 0;
+}
+
+/**
+ * open a new board
+ * @param whatclass In which sub class
+ * @param recover Forcely open a new board, often used for recovery.
+ * @return -1 if failed
+ */
+int
+m_newbrd(int whatclass, int recover)
+{
+ boardheader_t newboard;
+ char ans[4];
+ char genbuf[200];
+
+ stand_title("建立新板");
+ memset(&newboard, 0, sizeof(newboard));
+
+ newboard.gid = whatclass;
+ if (newboard.gid == 0) {
+ vmsg("請先選擇一個類別再開板!");
+ return -1;
+ }
+ do {
+ if (!getdata(3, 0, msg_bid, newboard.brdname,
+ sizeof(newboard.brdname), DOECHO))
+ return -1;
+ } while (invalid_brdname(newboard.brdname));
+
+ do {
+ getdata(6, 0, "看板類別:", genbuf, 5, DOECHO);
+ if (strlen(genbuf) == 4)
+ break;
+ } while (1);
+
+ strcpy(newboard.title, genbuf);
+
+ newboard.title[4] = ' ';
+
+ getdata(8, 0, "看板主題:", genbuf, BTLEN + 1, DOECHO);
+ if (genbuf[0])
+ strlcpy(newboard.title + 7, genbuf, sizeof(newboard.title) - 7);
+ setbpath(genbuf, newboard.brdname);
+
+ if (!recover &&
+ (getbnum(newboard.brdname) > 0 || mkdir(genbuf, 0755) == -1)) {
+ vmsg("此看板已經存在! 請取不同英文板名");
+ return -1;
+ }
+ newboard.brdattr = BRD_NOTRAN;
+#ifdef DEFAULT_AUTOCPLOG
+ newboard.brdattr |= BRD_CPLOG;
+#endif
+
+ if (HasUserPerm(PERM_SYSOP)) {
+ move(1, 0);
+ clrtobot();
+ newboard.brdattr = setperms(newboard.brdattr, str_permboard);
+ move(1, 0);
+ clrtobot();
+ }
+ getdata(9, 0, "是看板? (N:目錄) (Y/n):", genbuf, 3, LCECHO);
+ if (genbuf[0] == 'n')
+ {
+ newboard.brdattr |= BRD_GROUPBOARD;
+ newboard.brdattr &= ~BRD_CPLOG;
+ }
+
+ {
+ const char* brd_symbol;
+ if (newboard.brdattr & BRD_GROUPBOARD)
+ brd_symbol = "Σ";
+ else if (newboard.brdattr & BRD_NOTRAN)
+ brd_symbol = "◎";
+ else
+ brd_symbol = "●";
+
+ newboard.title[5] = brd_symbol[0];
+ newboard.title[6] = brd_symbol[1];
+ }
+
+ newboard.level = 0;
+ getdata(11, 0, "板主名單:", newboard.BM, sizeof(newboard.BM), DOECHO);
+#ifdef CHESSCOUNTRY
+ if (getdata_str(12, 0, "設定棋國 (0)無 (1)五子棋 (2)象棋 (3)圍棋", ans,
+ sizeof(ans), LCECHO, "0")){
+ newboard.chesscountry = atoi(ans);
+ if (newboard.chesscountry > CHESSCODE_MAX ||
+ newboard.chesscountry < CHESSCODE_NONE)
+ newboard.chesscountry = CHESSCODE_NONE;
+ }
+#endif /* defined(CHESSCOUNTRY) */
+
+ if (HasUserPerm(PERM_SYSOP) && !(newboard.brdattr & BRD_HIDE)) {
+ getdata_str(14, 0, "設定讀寫權限(Y/N)?", ans, sizeof(ans), LCECHO, "N");
+ if (*ans == 'y') {
+ getdata_str(15, 0, "限制 [R]閱\讀 (P)發表?", ans, sizeof(ans), LCECHO, "R");
+ if (*ans == 'p')
+ newboard.brdattr |= BRD_POSTMASK;
+ else
+ newboard.brdattr &= (~BRD_POSTMASK);
+
+ move(1, 0);
+ clrtobot();
+ bperm_msg(&newboard);
+ newboard.level = setperms(newboard.level, str_permid);
+ clear();
+ }
+ }
+
+ add_board_record(&newboard);
+ getbcache(whatclass)->childcount = 0;
+ pressanykey();
+ setup_man(&newboard, NULL);
+ outs("\n新板成立");
+ post_newboard(newboard.title, newboard.brdname, newboard.BM);
+ log_usies("NewBoard", newboard.title);
+ pressanykey();
+ return 0;
+}
+
+int make_symbolic_link(const char *bname, int gid)
+{
+ boardheader_t newboard;
+ int bid;
+
+ bid = getbnum(bname);
+ if(bid==0) return -1;
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ memset(&newboard, 0, sizeof(newboard));
+
+ /*
+ * known issue:
+ * These two stuff will be used for sorting. But duplicated brdnames
+ * may cause wrong binary-search result. So I replace the last
+ * letters of brdname to '~'(ascii code 126) in order to correct the
+ * resuilt, thought I think it's a dirty solution.
+ *
+ * Duplicate entry with same brdname may cause wrong result, if
+ * searching by key brdname. But people don't need to know a board
+ * is symbolic, so just let SYSOP know it. You may want to read
+ * board.c:load_boards().
+ */
+
+ strlcpy(newboard.brdname, bname, sizeof(newboard.brdname));
+ newboard.brdname[strlen(bname) - 1] = '~';
+ strlcpy(newboard.title, bcache[bid - 1].title, sizeof(newboard.title));
+ strcpy(newboard.title + 5, "@看板連結");
+
+ newboard.gid = gid;
+ BRD_LINK_TARGET(&newboard) = bid;
+ newboard.brdattr = BRD_NOTRAN | BRD_SYMBOLIC;
+
+ if (add_board_record(&newboard) < 0)
+ return -1;
+ return bid;
+}
+
+int make_symbolic_link_interactively(int gid)
+{
+ char buf[32];
+
+ CompleteBoard(msg_bid, buf);
+ if (!buf[0])
+ return -1;
+
+ stand_title("建立看板連結");
+
+ if (make_symbolic_link(buf, gid) < 0) {
+ vmsg("看板連結建立失敗");
+ return -1;
+ }
+ log_usies("NewSymbolic", buf);
+ 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)
+/* 處理 Register Form */
+int
+scan_register_form(const char *regfile, int automode, int neednum)
+{
+ char genbuf[200];
+ char *logfile = "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;
+ int nSelf = 0, nAuto = 0;
+
+ uid = cuser.userid;
+ snprintf(fname, sizeof(fname), "%s.tmp", regfile);
+ move(2, 0);
+ if (dashf(fname)) {
+ if (neednum == 0) { /* 自己進 Admin 來審的 */
+ vmsg("其他 SYSOP 也在審核註冊申請單");
+ }
+ return -1;
+ }
+ Rename(regfile, fname);
+ if ((fn = fopen(fname, "r")) == NULL) {
+ vmsgf("系統錯誤,無法讀取註冊資料檔: %s", fname);
+ return -1;
+ }
+ if (neednum) { /* 被強迫審的 */
+ move(1, 0);
+ clrtobot();
+ prints("各位具有站長權限的人,註冊單累積超過一百份了,麻煩您幫忙審 %d 份\n", neednum);
+ outs("也就是大概二十分之一的數量,當然,您也可以多審\n"
+ "沒審完之前,系統不會讓你跳出喲!謝謝");
+ pressanykey();
+ }
+ 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) );
+
+ 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();
+ neednum--;
+ } else {
+ neednum--;
+ 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", neednum);
+ 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';
+ if ('A' <= ans[0] && ans[0] <= 'Z')
+ ans[0] += 32;
+ if (ans[0] != 'y' && ans[0] != 'n' && ans[0] != 'q' &&
+ ans[0] != 'd' && !('0' <= ans[0] && ans[0] <= '4'))
+ ans[0] = 's';
+ ans[1] = 0;
+ }
+ nSelf++;
+ } else
+ nAuto++;
+
+ if (neednum > 0 && ans[0] == 'q') {
+ move(2, 0);
+ clrtobot();
+ vmsg("沒審完不能退出");
+ ans[0] = 's';
+ }
+ 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':
+ /* please confirm match REJECT_REASONS here */
+ case 'n':
+ if (ans[0] == 'n') {
+ int nf = 0;
+ move(8, 0);
+ clrtobot();
+ outs("請提出退回申請表原因,按 <enter> 取消\n");
+ for (n = 0; reason[n]; 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) {
+ 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);
+ }
+ 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);
+
+ 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);
+}
+
+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, "開始審核嗎(Auto/Yes/No)?[N] ", ans, sizeof(ans), LCECHO);
+ if (ans[0] == 'a')
+ scan_register_form(fn_register, 1, 0);
+ else if (ans[0] == 'y')
+ scan_register_form(fn_register, 0, 0);
+
+ 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)
+{
+ char tt[TTLEN + 1] = {0};
+
+ if (deumoney(searchuser(user_id, NULL), money) < 0) { // TODO if searchuser() return 0
+ move(12, 0);
+ clrtoeol();
+ prints("id:%s money:%d 不對吧!!", user_id, money);
+ pressanykey();
+ } else {
+ snprintf(tt, sizeof(tt), "%s : %d ptt 幣", mail_title, money);
+ mail_id(user_id, tt, "etc/givemoney.why", "[PTT 銀行]");
+ }
+}
+
+int
+give_money(void)
+{
+ FILE *fp, *fp2;
+ char *ptr, *id, *mn;
+ char buf[200] = "", reason[100], tt[TTLEN + 1] = "";
+ int to_all = 0, money = 0;
+ int total_money=0, count=0;
+
+ getdata(0, 0, "指定使用者(S) 全站使用者(A) 取消(Q)?[S]", buf, 3, LCECHO);
+ if (buf[0] == 'q')
+ return 1;
+ else if (buf[0] == 'a') {
+ to_all = 1;
+ getdata(1, 0, "發多少錢呢?", buf, 20, DOECHO);
+ money = atoi(buf);
+ if (money <= 0) {
+ move(2, 0);
+ vmsg("輸入錯誤!!");
+ return 1;
+ }
+ } else {
+ if (vedit("etc/givemoney.txt", NA, NULL) < 0)
+ return 1;
+ }
+
+ clear();
+
+ unlink("etc/givemoney.log");
+ if (!(fp2 = fopen("etc/givemoney.log", "w")))
+ return 1;
+
+ getdata(0, 0, "動用國庫!請輸入正當理由(如活動名稱):", reason, 40, LCECHO);
+ fprintf(fp2,"\n使用理由: %s\n", reason);
+
+ getdata(1, 0, "要發錢了嗎(Y/N)[N]", buf, 3, LCECHO);
+ if (buf[0] != 'y')
+ {
+ fclose(fp2);
+ return 1;
+ }
+
+ getdata(1, 0, "紅包袋標題 :", tt, TTLEN, DOECHO);
+ fprintf(fp2,"\n紅包袋標題: %s\n", tt);
+ move(2, 0);
+
+ vmsg("編紅包袋內容");
+ if (vedit("etc/givemoney.why", NA, NULL) < 0) {
+ fclose(fp2);
+ return 1;
+ }
+
+ stand_title("發錢中...");
+ if (to_all) {
+ int i, unum;
+ for (unum = SHM->number, i = 0; i < unum; i++) {
+ if (bad_user_id(SHM->userid[i]))
+ continue;
+ id = SHM->userid[i];
+ give_id_money(id, money, tt);
+ fprintf(fp2,"給 %s : %d\n", id, money);
+ count++;
+ }
+ sprintf(buf, "(%d人:%dP幣)", count, count*money);
+ strcat(reason, buf);
+ } else {
+ if (!(fp = fopen("etc/givemoney.txt", "r+"))) {
+ fclose(fp2);
+ return 1;
+ }
+ while (fgets(buf, sizeof(buf), fp)) {
+ clear();
+ if (!(ptr = strchr(buf, ':')))
+ continue;
+ *ptr = '\0';
+ id = buf;
+ mn = ptr + 1;
+ money = atoi(mn);
+ give_id_money(id, money, tt);
+ fprintf(fp2,"給 %s : %d\n", id, money);
+ total_money += money;
+ count++;
+ }
+ fclose(fp);
+ sprintf(buf, "(%d人:%dP幣)", count, total_money);
+ strcat(reason, buf);
+
+ }
+
+ fclose(fp2);
+
+ sprintf(buf, "%s 紅包機: %s", cuser.userid, reason);
+ post_file("Security", buf, "etc/givemoney.log", "[紅包機報告]");
+ pressanykey();
+ return FULLUPDATE;
+}
diff --git a/pttbbs/mbbsd/alloc.c b/pttbbs/mbbsd/alloc.c
new file mode 100644
index 00000000..de676ce4
--- /dev/null
+++ b/pttbbs/mbbsd/alloc.c
@@ -0,0 +1,254 @@
+/*
+ * malloc/free by O.Dreesen
+ *
+ * first TRY:
+ * lists w/magics
+ * and now the second TRY
+ * let the kernel map all the stuff (if there is something to do)
+ */
+
+#include <unistd.h>
+#include <sys/mman.h>
+#include <errno.h>
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/shm.h> /* for PAGE_SIZE */
+
+
+/* -- HELPER CODE --------------------------------------------------------- */
+
+#ifndef MAP_FAILED
+#define MAP_FAILED ((void*)-1)
+#endif
+
+#ifndef NULL
+#define NULL ((void*)0)
+#endif
+
+typedef struct {
+ void* next;
+ size_t size;
+} __alloc_t;
+
+#define BLOCK_START(b) (((void*)(b))-sizeof(__alloc_t))
+#define BLOCK_RET(b) (((void*)(b))+sizeof(__alloc_t))
+
+#define MEM_BLOCK_SIZE PAGE_SIZE
+#define PAGE_ALIGN(s) (((s)+MEM_BLOCK_SIZE-1)&(unsigned long)(~(MEM_BLOCK_SIZE-1)))
+
+/* a simple mmap :) */
+#if defined(__i386__)
+#define REGPARM(x) __attribute__((regparm(x)))
+#else
+#define REGPARM(x)
+#endif
+
+static void REGPARM(1) *do_mmap(size_t size) {
+ return mmap(0, size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (size_t)0);
+}
+
+/* -- SMALL MEM ----------------------------------------------------------- */
+
+static __alloc_t* __small_mem[8];
+
+static int smallalloc[8];
+static int smallalloc_max[8];
+
+#define __SMALL_NR(i) (MEM_BLOCK_SIZE/(i))
+
+#define __MIN_SMALL_SIZE __SMALL_NR(256) /* 16 / 32 */
+#define __MAX_SMALL_SIZE __SMALL_NR(2) /* 2048 / 4096 */
+
+#define GET_SIZE(s) (__MIN_SMALL_SIZE<<get_index((s)))
+
+#define FIRST_SMALL(p) (((unsigned long)(p))&(~(MEM_BLOCK_SIZE-1)))
+
+static inline int __ind_shift() { return (MEM_BLOCK_SIZE==4096)?4:5; }
+
+static size_t REGPARM(1) get_index(size_t _size) {
+ register size_t idx=0;
+// if (_size) { /* we already check this in the callers */
+ register size_t size=((_size-1)&(MEM_BLOCK_SIZE-1))>>__ind_shift();
+ while(size) { size>>=1; ++idx; }
+// }
+ return idx;
+}
+
+/* small mem */
+static void __small_free(void*_ptr,size_t _size) REGPARM(2);
+
+static void REGPARM(2) __small_free(void*_ptr,size_t _size) {
+ __alloc_t* ptr=BLOCK_START(_ptr);
+ size_t size=_size;
+ size_t idx=get_index(size);
+
+ memset(ptr,0,size); /* allways zero out small mem */
+
+ ptr->next=__small_mem[idx];
+ __small_mem[idx]=ptr;
+
+ smallalloc[idx]--;
+
+ if (MEM_BLOCK_SIZE == PAGE_SIZE &&
+ smallalloc[idx] == 0 &&
+ smallalloc_max[idx] < __SMALL_NR(size)) {
+ __alloc_t* p = __small_mem[idx];
+ __alloc_t* ph = p - (size_t)p%PAGE_SIZE;
+ munmap(ph, MEM_BLOCK_SIZE);
+ __small_mem[idx] = 0;
+ }
+}
+
+static void* REGPARM(1) __small_malloc(size_t _size) {
+ __alloc_t *ptr;
+ size_t size=_size;
+ size_t idx;
+
+ idx=get_index(size);
+ ptr=__small_mem[idx];
+
+ if (ptr==0) { /* no free blocks ? */
+ register int i,nr;
+ ptr=do_mmap(MEM_BLOCK_SIZE);
+ if (ptr==MAP_FAILED) return MAP_FAILED;
+
+ __small_mem[idx]=ptr;
+
+ nr=__SMALL_NR(size)-1;
+ for (i=0;i<nr;i++) {
+ ptr->next=(((void*)ptr)+size);
+ ptr=ptr->next;
+ }
+ ptr->next=0;
+
+ ptr=__small_mem[idx];
+ }
+
+ /* get a free block */
+ __small_mem[idx]=ptr->next;
+ ptr->next=0;
+
+ smallalloc[idx]++;
+ if(smallalloc[idx] > smallalloc_max[idx])
+ smallalloc_max[idx] = smallalloc[idx];
+
+ return ptr;
+}
+
+/* -- PUBLIC FUNCTIONS ---------------------------------------------------- */
+
+static void _alloc_libc_free(void *ptr) {
+ register size_t size;
+ if (ptr) {
+ size=((__alloc_t*)BLOCK_START(ptr))->size;
+ if (size) {
+ if (size<=__MAX_SMALL_SIZE)
+ __small_free(ptr,size);
+ else
+ munmap(BLOCK_START(ptr),size);
+ }
+ }
+}
+void __libc_free(void *ptr) __attribute__((alias("_alloc_libc_free")));
+void free(void *ptr) __attribute__((weak,alias("_alloc_libc_free")));
+void if_freenameindex(void* ptr) __attribute__((alias("free")));
+
+#ifdef WANT_MALLOC_ZERO
+static __alloc_t zeromem[2];
+#endif
+
+static void* _alloc_libc_malloc(size_t size) {
+ __alloc_t* ptr;
+ size_t need;
+#ifdef WANT_MALLOC_ZERO
+ if (!size) return BLOCK_RET(zeromem);
+#else
+ if (!size) goto err_out;
+#endif
+ size+=sizeof(__alloc_t);
+ if (size<sizeof(__alloc_t)) goto err_out;
+ if (size<=__MAX_SMALL_SIZE) {
+ need=GET_SIZE(size);
+ ptr=__small_malloc(need);
+ }
+ else {
+ need=PAGE_ALIGN(size);
+ if (!need) ptr=MAP_FAILED; else ptr=do_mmap(need);
+ }
+ if (ptr==MAP_FAILED) goto err_out;
+ ptr->size=need;
+ return BLOCK_RET(ptr);
+err_out:
+ (*__errno_location())=ENOMEM;
+ return 0;
+}
+void* __libc_malloc(size_t size) __attribute__((alias("_alloc_libc_malloc")));
+void* malloc(size_t size) __attribute__((weak,alias("_alloc_libc_malloc")));
+
+void* __libc_calloc(size_t nmemb, size_t _size);
+void* __libc_calloc(size_t nmemb, size_t _size) {
+ register size_t size=_size*nmemb;
+ if (nmemb && size/nmemb!=_size) {
+ (*__errno_location())=ENOMEM;
+ return 0;
+ }
+ return malloc(size);
+}
+void* calloc(size_t nmemb, size_t _size) __attribute__((weak,alias("__libc_calloc")));
+
+void* __libc_realloc(void* ptr, size_t _size);
+void* __libc_realloc(void* ptr, size_t _size) {
+ register size_t size=_size;
+ if (ptr) {
+ if (size) {
+ __alloc_t* tmp=BLOCK_START(ptr);
+ size+=sizeof(__alloc_t);
+ if (size<sizeof(__alloc_t)) goto retzero;
+ size=(size<=__MAX_SMALL_SIZE)?GET_SIZE(size):PAGE_ALIGN(size);
+ if (tmp->size!=size) {
+ if ((tmp->size<=__MAX_SMALL_SIZE)) {
+ void *new=_alloc_libc_malloc(_size);
+ if (new) {
+ register __alloc_t* foo=BLOCK_START(new);
+ size=foo->size;
+ if (size>tmp->size) size=tmp->size;
+ if (size) memcpy(new,ptr,size-sizeof(__alloc_t));
+ _alloc_libc_free(ptr);
+ }
+ ptr=new;
+ }
+ else {
+ register __alloc_t* foo;
+ size=PAGE_ALIGN(size);
+ foo=mremap(tmp,tmp->size,size,MREMAP_MAYMOVE);
+ if (foo==MAP_FAILED) {
+retzero:
+ (*__errno_location())=ENOMEM;
+ ptr=0;
+ }
+ else {
+ foo->size=size;
+ ptr=BLOCK_RET(foo);
+ }
+ }
+ }
+ }
+ else { /* size==0 */
+ _alloc_libc_free(ptr);
+ ptr = NULL;
+ }
+ }
+ else { /* ptr==0 */
+ if (size) {
+ ptr=_alloc_libc_malloc(size);
+ }
+ }
+ return ptr;
+}
+void* realloc(void* ptr, size_t size) __attribute__((weak,alias("__libc_realloc")));
+
diff --git a/pttbbs/mbbsd/announce.c b/pttbbs/mbbsd/announce.c
new file mode 100644
index 00000000..b5421325
--- /dev/null
+++ b/pttbbs/mbbsd/announce.c
@@ -0,0 +1,1511 @@
+/* $Id$ */
+#include "bbs.h"
+
+/* copy temp queue operation -------------------------------------- */
+
+/* TODO
+ * change this to char* instead of char[]
+ */
+typedef struct {
+ char copyfile[PATHLEN];
+ char copytitle[TTLEN + 1];
+ char copyowner[IDLEN + 2];
+} CopyQueue ;
+
+#define COPYQUEUE_COMMON_SIZE (10)
+
+static CopyQueue *copyqueue;
+static int allocated_copyqueue = 0, used_copyqueue = 0, head_copyqueue = 0;
+
+int copyqueue_testin(CopyQueue *pcq)
+{
+ int i = 0;
+ for (i = head_copyqueue; i < used_copyqueue; i++)
+ if (strcmp(pcq->copyfile, copyqueue[i].copyfile) == 0)
+ return 1;
+ return 0;
+}
+
+int copyqueue_locate(CopyQueue *pcq)
+{
+ int i = 0;
+ for (i = head_copyqueue; i < used_copyqueue; i++)
+ if (strcmp(pcq->copyfile, copyqueue[i].copyfile) == 0)
+ return i;
+ return -1;
+}
+
+int copyqueue_fileinqueue(const char *fn)
+{
+ int i = 0;
+ for (i = head_copyqueue; i < used_copyqueue; i++)
+ if (strcmp(fn, copyqueue[i].copyfile) == 0)
+ return 1;
+ return 0;
+}
+
+void copyqueue_reset()
+{
+ allocated_copyqueue = 0;
+ used_copyqueue = 0;
+ head_copyqueue = 0;
+}
+
+int copyqueue_append(CopyQueue *pcq)
+{
+ if(copyqueue_testin(pcq))
+ return 0;
+ if(head_copyqueue == used_copyqueue)
+ {
+ // empty queue, happy happy reset
+ if(allocated_copyqueue > COPYQUEUE_COMMON_SIZE)
+ {
+ // let's reduce it
+ allocated_copyqueue = COPYQUEUE_COMMON_SIZE;
+ copyqueue = (CopyQueue*)realloc( copyqueue,
+ allocated_copyqueue * sizeof(CopyQueue));
+ }
+ head_copyqueue = used_copyqueue = 0;
+ }
+ used_copyqueue ++;
+
+ if(used_copyqueue > allocated_copyqueue)
+ {
+ allocated_copyqueue =
+ used_copyqueue + COPYQUEUE_COMMON_SIZE; // half page
+ copyqueue = (CopyQueue*) realloc (copyqueue,
+ sizeof(CopyQueue) * allocated_copyqueue);
+ if(!copyqueue)
+ {
+ vmsg("記憶體不足,拷貝失敗");
+ // try to reset
+ copyqueue_reset();
+ if(copyqueue) free(copyqueue);
+ copyqueue = NULL;
+ return 0;
+ }
+ }
+ memcpy(&(copyqueue[used_copyqueue-1]), pcq, sizeof(CopyQueue));
+ return 1;
+}
+
+int copyqueue_toggle(CopyQueue *pcq)
+{
+ int i = copyqueue_locate(pcq);
+ if(i >= 0)
+ {
+#if 0
+ if (getans("已標記過此檔,要取消標記嗎 [y/N]: ") != 'y')
+ return 1;
+#endif
+ /* remove it */
+ used_copyqueue --;
+ if(head_copyqueue > used_copyqueue)
+ head_copyqueue =used_copyqueue;
+ if (i < used_copyqueue)
+ {
+ memcpy(copyqueue + i, copyqueue+i+1,
+ sizeof(CopyQueue) * (used_copyqueue - i));
+ }
+ return 0;
+ } else {
+ copyqueue_append(pcq);
+ }
+ return 1;
+}
+
+CopyQueue *copyqueue_gethead()
+{
+ if( used_copyqueue <= 0 ||
+ head_copyqueue >= used_copyqueue)
+ return NULL;
+ return &(copyqueue[head_copyqueue++]);
+}
+
+int copyqueue_querysize()
+{
+ if( used_copyqueue <= 0 ||
+ head_copyqueue >= used_copyqueue)
+ return 0;
+ return (used_copyqueue - head_copyqueue);
+}
+
+/* end copy temp queue operation ----------------------------------- */
+
+void
+a_copyitem(const char *fpath, const char *title, const char *owner, int mode)
+{
+ CopyQueue cq;
+ static int flFirstAlert = 1;
+
+ memset(&cq, 0, sizeof(CopyQueue));
+ strcpy(cq.copyfile, fpath);
+ strcpy(cq.copytitle, title);
+ if (owner)
+ strcpy(cq.copyowner, owner);
+
+ //copyqueue_append(&cq);
+ copyqueue_toggle(&cq);
+ if (mode && flFirstAlert) {
+#if 0
+ move(b_lines-2, 0); clrtoeol();
+ prints("目前已標記 %d 個檔案。[注意] 拷貝後才能刪除原文!",
+ copyqueue_querysize());
+#else
+ vmsg("[注意] 提醒您複製/標記後要貼上(p)或附加(a)後才能刪除原文!");
+ flFirstAlert = 0;
+#endif
+ }
+}
+
+#define FHSZ sizeof(fileheader_t)
+
+static void
+a_loadname(menu_t * pm)
+{
+ char buf[PATHLEN];
+ int len;
+
+ if(p_lines != pm->header_size) {
+ pm->header_size = p_lines;
+ pm->header = (fileheader_t *) realloc(pm->header, pm->header_size*FHSZ);
+ assert(pm->header);
+ }
+
+ setadir(buf, pm->path);
+ len = get_records(buf, pm->header, FHSZ, pm->page + 1, pm->header_size); // XXX if get_records() return -1
+ assert(len!=-1);
+ if (len < pm->header_size)
+ bzero(&pm->header[len], FHSZ * (pm->header_size - len));
+}
+
+static void
+a_timestamp(char *buf, const time4_t *time)
+{
+ struct tm *pt = localtime4(time);
+
+ sprintf(buf, "%02d/%02d/%02d", pt->tm_mon + 1, pt->tm_mday, (pt->tm_year + 1900) % 100);
+}
+
+static void
+a_showmenu(menu_t * pm)
+{
+ char *title, *editor;
+ int n;
+ fileheader_t *item;
+ char buf[PATHLEN];
+ time4_t dtime;
+
+ showtitle("精華文章", pm->mtitle);
+ prints(" " ANSI_COLOR(1;36) "編號 標 題%56s" ANSI_COLOR(0),
+ "編 選 日 期");
+
+ if (pm->num) {
+ setadir(buf, pm->path);
+ a_loadname(pm);
+ for (n = 0; n < p_lines && pm->page + n < pm->num; n++) {
+ int flTagged = 0;
+ item = &pm->header[n];
+ title = item->title;
+ editor = item->owner;
+ /*
+ * Ptt 把時間改為取檔案時間 dtime = atoi(&item->filename[2]);
+ */
+ snprintf(buf, sizeof(buf), "%s/%s", pm->path, item->filename);
+ if(copyqueue_querysize() > 0 && copyqueue_fileinqueue(buf))
+ {
+ flTagged = 1;
+ }
+ dtime = dasht(buf);
+ a_timestamp(buf, &dtime);
+ prints("\n%6d%c%c%-47.46s%-13s[%s]", pm->page + n + 1,
+ (item->filemode & FILE_BM) ? 'X' :
+ (item->filemode & FILE_HIDE) ? ')' : '.',
+ flTagged ? 'c' : ' ',
+ title, editor,
+ buf);
+ }
+ } else
+ outs("\n 《精華區》尚在吸取天地間的日月精華中... :)");
+
+ move(b_lines, 0);
+ if(copyqueue_querysize() > 0)
+ { // something in queue
+ prints(
+ ANSI_COLOR(37;44) "【已標記(複製) %d 項】"
+ ANSI_COLOR(31;47) " (c)" ANSI_COLOR(30) "標記/複製 "
+ , copyqueue_querysize());
+
+ if(pm->level == 0)
+ outs(" - 無管理權限,無法貼上 " ANSI_RESET);
+ else
+ outs( ANSI_COLOR(31) "(p)" ANSI_COLOR(30) "貼上/取消/重設標記 "
+ ANSI_COLOR(31) "(a)" ANSI_COLOR(30) "附加至文章後 "
+ ANSI_RESET);
+ }
+ else if(pm->level)
+ { // BM
+ outs(
+ ANSI_COLOR(34;46) " 【板 主】 "
+ ANSI_COLOR(31;47) " (h)" ANSI_COLOR(30) "說明 "
+ ANSI_COLOR(31) "(q/←)" ANSI_COLOR(30) "離開 "
+ ANSI_COLOR(31) "(n)" ANSI_COLOR(30) "新增文章 "
+ ANSI_COLOR(31) "(g)" ANSI_COLOR(30) "新增目錄 "
+ ANSI_COLOR(31) "(e)" ANSI_COLOR(30) "編輯檔案 " ANSI_RESET
+ );
+ }
+ else
+ { // normal user
+ outs(
+ ANSI_COLOR(34;46) " 【功\能鍵】 "
+ ANSI_COLOR(31;47) " (h)" ANSI_COLOR(30) "說明 "
+ ANSI_COLOR(31) "(q/←)" ANSI_COLOR(30) "離開 "
+ ANSI_COLOR(31) "(k↑j↓)" ANSI_COLOR(30) "移動游標 "
+ ANSI_COLOR(31) "(enter/→)" ANSI_COLOR(30) "讀取資料 " ANSI_RESET);
+ }
+}
+
+static int
+a_searchtitle(menu_t * pm, int rev)
+{
+ static char search_str[40] = "";
+ int pos;
+
+ getdata(b_lines - 1, 1, "[搜尋]關鍵字:", search_str, sizeof(search_str), DOECHO);
+
+ if (!*search_str)
+ return pm->now;
+
+ str_lower(search_str, search_str);
+
+ rev = rev ? -1 : 1;
+ pos = pm->now;
+ do {
+ pos += rev;
+ if (pos == pm->num)
+ pos = 0;
+ else if (pos < 0)
+ pos = pm->num - 1;
+ if (pos < pm->page || pos >= pm->page + p_lines) {
+ pm->page = pos - pos % p_lines;
+ a_loadname(pm);
+ }
+ if (strcasestr(pm->header[pos - pm->page].title, search_str))
+ return pos;
+ } while (pos != pm->now);
+ return pm->now;
+}
+
+enum {
+ NOBODY, MANAGER, SYSOP
+};
+
+static void
+a_showhelp(int level)
+{
+ clear();
+ outs(ANSI_COLOR(36) "【 " BBSNAME "公佈欄使用說明 】" ANSI_RESET "\n\n"
+ "[←][q] 離開到上一層目錄\n"
+ "[↑][k] 上一個選項\n"
+ "[↓][j] 下一個選項\n"
+ "[→][r][enter] 進入目錄/讀取文章\n"
+ "[^B][PgUp] 上頁選單\n"
+ "[^F][PgDn][Spc] 下頁選單\n"
+ "[##] 移到該選項\n"
+ "[F][U] 將文章寄回 Internet 郵箱/"
+ "將文章 uuencode 後寄回郵箱\n");
+ if (level >= MANAGER) {
+ outs("\n" ANSI_COLOR(36) "【 板主專用鍵 】" ANSI_RESET "\n"
+ "[H] 切換為 公開/會員/板主 才能閱\讀\n"
+ "[n/g/G] 收錄精華文章/開闢目錄/建立連線\n"
+ "[m/d/D] 移動/刪除文章/刪除一個範圍的文章\n"
+ "[f/T/e] 編輯標題符號/修改文章標題/內容\n"
+ "[c/p/a] 精華區內 標記(複製)/貼上(可多篇)/附加單篇文章\n"
+ "[^P/^A] 貼上/附加精華區外已用't'標記文章\n");
+ }
+ if (level >= SYSOP) {
+ outs("\n" ANSI_COLOR(36) "【 站長專用鍵 】" ANSI_RESET "\n"
+ "[l] 建 symbolic link\n"
+ "[N] 查詢檔名\n");
+ }
+ pressanykey();
+}
+
+static void
+a_forward(const char *path, const fileheader_t * pitem, int mode)
+{
+ fileheader_t fhdr;
+
+ strlcpy(fhdr.filename, pitem->filename, sizeof(fhdr.filename));
+ strlcpy(fhdr.title, pitem->title, sizeof(fhdr.title));
+ switch (doforward(path, &fhdr, mode)) {
+ case 0:
+ outmsg(msg_fwd_ok);
+ break;
+ case -1:
+ outmsg(msg_fwd_err1);
+ break;
+ case -2:
+ outmsg(msg_fwd_err2);
+ break;
+ }
+}
+
+static void
+a_additem(menu_t * pm, const fileheader_t * myheader)
+{
+ char buf[PATHLEN];
+
+ setadir(buf, pm->path);
+ if (append_record(buf, myheader, FHSZ) == -1)
+ return;
+ pm->now = pm->num++;
+
+ if (pm->now >= pm->page + p_lines) {
+ pm->page = pm->now - ((pm->page == 10000 && pm->now > p_lines / 2) ?
+ (p_lines / 2) : (pm->now % p_lines));
+ }
+ /* Ptt */
+ strlcpy(pm->header[pm->now - pm->page].filename,
+ myheader->filename,
+ sizeof(pm->header[pm->now - pm->page].filename));
+}
+
+#define ADDITEM 0
+#define ADDGROUP 1
+#define ADDLINK 2
+
+static void
+a_newitem(menu_t * pm, int mode)
+{
+ char *mesg[3] = {
+ "[新增文章] 請輸入標題:", /* ADDITEM */
+ "[新增目錄] 請輸入標題:", /* ADDGROUP */
+ "請輸入標題:" /* ADDLINK */
+ };
+
+ char fpath[PATHLEN], buf[PATHLEN], lpath[PATHLEN];
+ fileheader_t item;
+ int d;
+
+ strlcpy(fpath, pm->path, sizeof(fpath));
+
+ switch (mode) {
+ case ADDITEM:
+ stampfile(fpath, &item);
+ strlcpy(item.title, "◇ ", sizeof(item.title)); /* A1BA */
+ break;
+
+ case ADDGROUP:
+ stampdir(fpath, &item);
+ strlcpy(item.title, "◆ ", sizeof(item.title)); /* A1BB */
+ break;
+ case ADDLINK:
+ stamplink(fpath, &item);
+ if (!getdata(b_lines - 2, 1, "新增連線:", buf, 61, DOECHO))
+ return;
+ if (invalid_pname(buf)) {
+ unlink(fpath);
+ outs("目的地路徑不合法!");
+ igetch();
+ return;
+ }
+ item.title[0] = 0;
+ // XXX Is it alright?
+ // ex: path= "PASSWD"
+ for (d = 0; d <= 3; d++) {
+ switch (d) {
+ case 0:
+ snprintf(lpath, sizeof(lpath), BBSHOME "/man/boards/%c/%s/%s",
+ currboard[0], currboard, buf);
+ break;
+ case 1:
+ snprintf(lpath, sizeof(lpath), BBSHOME "/man/boards/%c/%s",
+ buf[0], buf);
+ break;
+ case 2:
+ snprintf(lpath, sizeof(lpath), BBSHOME "/%s",
+ buf);
+ break;
+ case 3:
+ snprintf(lpath, sizeof(lpath), BBSHOME "/etc/%s",
+ buf);
+ break;
+ }
+ if (dashf(lpath)) {
+ strlcpy(item.title, "☆ ", sizeof(item.title)); /* A1B3 */
+ break;
+ } else if (dashd(lpath)) {
+ strlcpy(item.title, "★ ", sizeof(item.title)); /* A1B4 */
+ break;
+ }
+ if (!HasUserPerm(PERM_BBSADM) && d == 1)
+ break;
+ }
+
+ if (!item.title[0]) {
+ unlink(fpath);
+ outs("目的地路徑不合法!");
+ igetch();
+ return;
+ }
+ }
+
+ if (!getdata(b_lines - 1, 1, mesg[mode], &item.title[3], 55, DOECHO)) {
+ if (mode == ADDGROUP)
+ rmdir(fpath);
+ else
+ unlink(fpath);
+ return;
+ }
+ switch (mode) {
+ case ADDITEM:
+ if (vedit(fpath, 0, NULL) == -1) {
+ unlink(fpath);
+ pressanykey();
+ return;
+ }
+ break;
+ case ADDLINK:
+ unlink(fpath);
+ if (symlink(lpath, fpath) == -1) {
+ outs("無法建立 symbolic link");
+ igetch();
+ return;
+ }
+ break;
+ }
+
+ strlcpy(item.owner, cuser.userid, sizeof(item.owner));
+ a_additem(pm, &item);
+}
+
+void
+a_pasteitem(menu_t * pm, int mode)
+{
+ char newpath[PATHLEN];
+ char buf[PATHLEN];
+ char ans[2], skipAll = 0, multiple = 0;
+ int i, copied = 0;
+ fileheader_t item;
+
+ CopyQueue *cq;
+
+ move(b_lines - 1, 0);
+ if(copyqueue_querysize() <= 0)
+ {
+ vmsg("請先執行複製(copy)命令後再貼上(paste)");
+ return;
+ }
+ if(mode && copyqueue_querysize() > 1)
+ {
+ multiple = 1;
+ move(b_lines-2, 0); clrtobot();
+ outs("c: 對各項目個別確認是否要貼上, z: 全部不貼,同時重設並取消全部標記\n");
+ snprintf(buf, sizeof(buf),
+ "確定要貼上全部共 %d 個項目嗎 (c/z/y/N)? ",
+ copyqueue_querysize());
+ getdata(b_lines - 1, 0, buf, ans, sizeof(ans), LCECHO);
+ if(ans[0] == 'y')
+ skipAll = 1;
+ else if(ans[0] == 'z')
+ {
+ copyqueue_reset();
+ vmsg("已重設複製記錄。");
+ return;
+ }
+ else if (ans[0] != 'c')
+ return;
+ clear();
+ }
+ while (copyqueue_querysize() > 0)
+ {
+ cq = copyqueue_gethead();
+ if(!cq->copyfile[0])
+ continue;
+ if(mode && multiple)
+ {
+ scroll();
+ move(b_lines-2, 0); clrtobot();
+ prints("%d. %s\n", ++copied,cq->copytitle);
+
+ }
+
+ if (dashd(cq->copyfile)) {
+ for (i = 0; cq->copyfile[i] && cq->copyfile[i] == pm->path[i]; i++);
+ if (!cq->copyfile[i]) {
+ vmsg("將目錄拷進自己的子目錄中,會造成無窮迴圈!");
+ continue;
+ }
+ }
+ if (mode && !skipAll) {
+ snprintf(buf, sizeof(buf),
+ "確定要拷貝[%s]嗎(Y/N)?[N] ", cq->copytitle);
+ getdata(b_lines - 1, 0, buf, ans, sizeof(ans), LCECHO);
+ } else
+ ans[0] = 'y';
+ if (ans[0] == 'y') {
+ strlcpy(newpath, pm->path, sizeof(newpath));
+
+ if (*cq->copyowner) {
+ char *fname = strrchr(cq->copyfile, '/');
+
+ if (fname)
+ strcat(newpath, fname);
+ else
+ return;
+ if (access(pm->path, X_OK | R_OK | W_OK))
+ mkdir(pm->path, 0755);
+ memset(&item, 0, sizeof(fileheader_t));
+ strlcpy(item.filename, fname + 1, sizeof(item.filename));
+ memcpy(cq->copytitle, "◎", 2);
+ Copy(cq->copyfile, newpath);
+ } else if (dashf(cq->copyfile)) {
+ stampfile(newpath, &item);
+ memcpy(cq->copytitle, "◇", 2);
+ Copy(cq->copyfile, newpath);
+ } else if (dashd(cq->copyfile)) {
+ stampdir(newpath, &item);
+ memcpy(cq->copytitle, "◆", 2);
+ copy_file(cq->copyfile, newpath);
+ } else {
+ copyqueue_reset();
+ vmsg("無法拷貝!");
+ return;
+ }
+ strlcpy(item.owner, *cq->copyowner ? cq->copyowner : cuser.userid,
+ sizeof(item.owner));
+ strlcpy(item.title, cq->copytitle, sizeof(item.title));
+ a_additem(pm, &item);
+ cq->copyfile[0] = '\0';
+ }
+ }
+}
+
+static void
+a_appenditem(const menu_t * pm, int isask)
+{
+ char fname[PATHLEN];
+ char buf[ANSILINELEN];
+ char ans[2] = "y";
+ FILE *fp, *fin;
+
+ move(b_lines - 1, 0);
+ if(copyqueue_querysize() <= 0)
+ {
+ vmsg("請先執行 copy 命令後再 append");
+ copyqueue_reset();
+ return;
+ }
+ else
+ {
+ CopyQueue *cq = copyqueue_gethead();
+
+ if (dashf(cq->copyfile)) {
+ snprintf(fname, sizeof(fname), "%s/%s", pm->path,
+ pm->header[pm->now - pm->page].filename);
+ if (dashf(fname)) {
+ if (isask) {
+ snprintf(buf, sizeof(buf),
+ "確定要將[%s]附加於此嗎(Y/N)?[N] ", cq->copytitle);
+ getdata(b_lines - 2, 1, buf, ans, sizeof(ans), LCECHO);
+ }
+ if (ans[0] == 'y') {
+ if ((fp = fopen(fname, "a+"))) {
+ if ((fin = fopen(cq->copyfile, "r"))) {
+ memset(buf, '-', 74);
+ buf[74] = '\0';
+ fprintf(fp, "\n> %s <\n\n", buf);
+ if (isask)
+ getdata(b_lines - 1, 1,
+ "是否收錄簽名檔部份(Y/N)?[Y] ",
+ ans, sizeof(ans), LCECHO);
+ while (fgets(buf, sizeof(buf), fin)) {
+ if ((ans[0] == 'n') &&
+ !strcmp(buf, "--\n"))
+ break;
+ fputs(buf, fp);
+ }
+ fclose(fin);
+ cq->copyfile[0] = '\0';
+ }
+ fclose(fp);
+ }
+ }
+ } else {
+ vmsg("檔案不得附加於此!");
+ }
+ } else {
+ vmsg("目錄不得附加於檔案後!");
+ }
+ }
+}
+
+static int
+a_pastetagpost(menu_t * pm, int mode)
+{
+ fileheader_t fhdr;
+ boardheader_t *bh = NULL;
+ int ans = 0, ent = 0, tagnum;
+ char title[TTLEN + 1] = "◇ ";
+ char dirname[200], buf[200];
+
+ if (TagBoard == 0){
+ sethomedir(dirname, cuser.userid);
+ }
+ else{
+ bh = getbcache(TagBoard);
+ setbdir(dirname, bh->brdname);
+ }
+ tagnum = TagNum;
+
+ if (!tagnum)
+ return ans;
+
+ /* since we use different tag features,
+ * copyqueue is not required/used. */
+ copyqueue_reset();
+
+ while (tagnum--) {
+ EnumTagFhdr(&fhdr, dirname, ent++);
+ if (TagBoard == 0)
+ sethomefile(buf, cuser.userid, fhdr.filename);
+ else
+ setbfile(buf, bh->brdname, fhdr.filename);
+
+ if (dashf(buf)) {
+ strlcpy(title + 3, fhdr.title, sizeof(title) - 3);
+ a_copyitem(buf, title, 0, 0);
+ if (mode) {
+ mode--;
+ a_pasteitem(pm, 0);
+ } else
+ a_appenditem(pm, 0);
+ ++ans;
+ UnTagger(tagnum);
+ }
+ }
+
+ return ans;
+}
+
+static void
+a_moveitem(menu_t * pm)
+{
+ fileheader_t *tmp;
+ char newnum[5];
+ int num, max, min;
+ char buf[PATHLEN];
+ int fail;
+
+ snprintf(buf, sizeof(buf), "請輸入第 %d 選項的新次序:", pm->now + 1);
+ if (!getdata(b_lines - 1, 1, buf, newnum, sizeof(newnum), DOECHO))
+ return;
+ num = (newnum[0] == '$') ? 9999 : atoi(newnum) - 1;
+ if (num >= pm->num)
+ num = pm->num - 1;
+ else if (num < 0)
+ num = 0;
+ setadir(buf, pm->path);
+ min = num < pm->now ? num : pm->now;
+ max = num > pm->now ? num : pm->now;
+ tmp = (fileheader_t *) calloc(max + 1, FHSZ);
+
+ fail = 0;
+ if (get_records(buf, tmp, FHSZ, 1, min) != min)
+ fail = 1;
+ if (num > pm->now) {
+ if (get_records(buf, &tmp[min], FHSZ, pm->now + 2, max - min) != max - min)
+ fail = 1;
+ if (get_records(buf, &tmp[max], FHSZ, pm->now + 1, 1) != 1)
+ fail = 1;
+ } else {
+ if (get_records(buf, &tmp[min], FHSZ, pm->now + 1, 1) != 1)
+ fail = 1;
+ if (get_records(buf, &tmp[min + 1], FHSZ, num + 1, max - min) != max - min)
+ fail = 1;
+ }
+ if (!fail)
+ substitute_record(buf, tmp, FHSZ * (max + 1), 1);
+ pm->now = num;
+ free(tmp);
+}
+
+static void
+a_delrange(menu_t * pm)
+{
+ char fname[PATHLEN];
+
+ snprintf(fname, sizeof(fname), "%s/.DIR", pm->path);
+ del_range(0, NULL, fname);
+ pm->num = get_num_records(fname, FHSZ);
+}
+
+static void
+a_delete(menu_t * pm)
+{
+ char fpath[PATHLEN], buf[PATHLEN], cmd[PATHLEN];
+ char ans[4];
+ fileheader_t backup;
+
+ snprintf(fpath, sizeof(fpath),
+ "%s/%s", pm->path, pm->header[pm->now - pm->page].filename);
+ setadir(buf, pm->path);
+
+ if (pm->header[pm->now - pm->page].filename[0] == 'H' &&
+ pm->header[pm->now - pm->page].filename[1] == '.') {
+ getdata(b_lines - 1, 1, "您確定要刪除此精華區連線嗎(Y/N)?[N] ",
+ ans, sizeof(ans), LCECHO);
+ if (ans[0] != 'y')
+ return;
+ if (delete_record(buf, FHSZ, pm->now + 1) == -1)
+ return;
+ } else if (dashl(fpath)) {
+ getdata(b_lines - 1, 1, "您確定要刪除此 symbolic link 嗎(Y/N)?[N] ",
+ ans, sizeof(ans), LCECHO);
+ if (ans[0] != 'y')
+ return;
+ if (delete_record(buf, FHSZ, pm->now + 1) == -1)
+ return;
+ unlink(fpath);
+ } else if (dashf(fpath)) {
+ getdata(b_lines - 1, 1, "您確定要刪除此檔案嗎(Y/N)?[N] ", ans,
+ sizeof(ans), LCECHO);
+ if (ans[0] != 'y')
+ return;
+ if (delete_record(buf, FHSZ, pm->now + 1) == -1)
+ return;
+
+ setbpath(buf, "deleted");
+ stampfile(buf, &backup);
+ strlcpy(backup.owner, cuser.userid, sizeof(backup.owner));
+ strlcpy(backup.title,
+ pm->header[pm->now - pm->page].title + 2,
+ sizeof(backup.title));
+
+ snprintf(cmd, sizeof(cmd),
+ "mv -f %s %s", fpath, buf);
+ system(cmd);
+ setbdir(buf, "deleted");
+ append_record(buf, &backup, sizeof(backup));
+ } else if (dashd(fpath)) {
+ getdata(b_lines - 1, 1, "您確定要刪除整個目錄嗎(Y/N)?[N] ", ans,
+ sizeof(ans), LCECHO);
+ if (ans[0] != 'y')
+ return;
+ if (delete_record(buf, FHSZ, pm->now + 1) == -1)
+ return;
+
+ setapath(buf, "deleted");
+ stampdir(buf, &backup);
+
+ snprintf(cmd, sizeof(cmd),
+ "rm -rf %s;/bin/mv -f %s %s", buf, fpath, buf);
+ system(cmd);
+
+ strlcpy(backup.owner, cuser.userid, sizeof(backup.owner));
+ strcpy(backup.title, "◆");
+ strlcpy(backup.title + 2,
+ pm->header[pm->now - pm->page].title + 2,
+ sizeof(backup.title) - 3);
+
+ /* merge setapath(buf, "deleted"); setadir(buf, buf); */
+ snprintf(buf, sizeof(buf), "man/boards/%c/%s/.DIR",
+ 'd', "deleted");
+ append_record(buf, &backup, sizeof(backup));
+ } else { /* Ptt 損毀的項目 */
+ getdata(b_lines - 1, 1, "您確定要刪除此損毀的項目嗎(Y/N)?[N] ",
+ ans, sizeof(ans), LCECHO);
+ if (ans[0] != 'y')
+ return;
+ if (delete_record(buf, FHSZ, pm->now + 1) == -1)
+ return;
+ }
+ pm->num--;
+}
+
+static void
+a_newtitle(const menu_t * pm)
+{
+ char buf[PATHLEN];
+ fileheader_t item;
+
+ memcpy(&item, &pm->header[pm->now - pm->page], FHSZ);
+ strlcpy(buf, item.title + 3, sizeof(buf));
+ if (getdata_buf(b_lines - 1, 1, "新標題:", buf, 60, DOECHO)) {
+ strlcpy(item.title + 3, buf, sizeof(item.title) - 3);
+ setadir(buf, pm->path);
+ substitute_record(buf, &item, FHSZ, pm->now + 1);
+ }
+}
+static void
+a_hideitem(const menu_t * pm)
+{
+ fileheader_t *item = &pm->header[pm->now - pm->page];
+ char buf[PATHLEN];
+ if (item->filemode & FILE_BM) {
+ item->filemode &= ~FILE_BM;
+ item->filemode &= ~FILE_HIDE;
+ } else if (item->filemode & FILE_HIDE)
+ item->filemode |= FILE_BM;
+ else
+ item->filemode |= FILE_HIDE;
+ setadir(buf, pm->path);
+ substitute_record(buf, item, FHSZ, pm->now + 1);
+}
+static void
+a_editsign(const menu_t * pm)
+{
+ char buf[PATHLEN];
+ fileheader_t item;
+
+ memcpy(&item, &pm->header[pm->now - pm->page], FHSZ);
+ snprintf(buf, sizeof(buf), "%c%c", item.title[0], item.title[1]);
+ if (getdata_buf(b_lines - 1, 1, "符號", buf, 5, DOECHO)) {
+ item.title[0] = buf[0] ? buf[0] : ' ';
+ item.title[1] = buf[1] ? buf[1] : ' ';
+ item.title[2] = buf[2] ? buf[2] : ' ';
+ setadir(buf, pm->path);
+ substitute_record(buf, &item, FHSZ, pm->now + 1);
+ }
+}
+
+static void
+a_showname(const menu_t * pm)
+{
+ char buf[PATHLEN];
+ int len;
+ int i;
+ int sym;
+
+ move(b_lines - 1, 0);
+ snprintf(buf, sizeof(buf),
+ "%s/%s", pm->path, pm->header[pm->now - pm->page].filename);
+ if (dashl(buf)) {
+ prints("此 symbolic link 名稱為 %s\n",
+ pm->header[pm->now - pm->page].filename);
+ if ((len = readlink(buf, buf, PATHLEN - 1)) >= 0) {
+ buf[len] = '\0';
+ for (i = 0; BBSHOME[i] && buf[i] == BBSHOME[i]; i++);
+ if (!BBSHOME[i] && buf[i] == '/') {
+ if (HasUserPerm(PERM_BBSADM))
+ sym = 1;
+ else {
+ sym = 0;
+ for (i++; BBSHOME "/man"[i] && buf[i] == BBSHOME "/man"[i];
+ i++);
+ if (!BBSHOME "/man"[i] && buf[i] == '/')
+ sym = 1;
+ }
+ if (sym) {
+ vmsgf("此 symbolic link 指向 %s\n", &buf[i + 1]);
+ }
+ }
+ }
+ } else if (dashf(buf))
+ prints("此文章名稱為 %s", pm->header[pm->now - pm->page].filename);
+ else if (dashd(buf))
+ prints("此目錄名稱為 %s", pm->header[pm->now - pm->page].filename);
+ else
+ outs("此項目已損毀, 建議將其刪除!");
+ pressanykey();
+}
+#ifdef CHESSCOUNTRY
+static void
+a_setchesslist(const menu_t * me)
+{
+ char buf[4];
+ char buf_list[PATHLEN];
+ char buf_photo[PATHLEN];
+ char buf_this[PATHLEN];
+ char buf_real[PATHLEN];
+ int list_exist, photo_exist;
+ fileheader_t* fhdr = me->header + me->now - me->page;
+ int n;
+
+ snprintf(buf_this, sizeof(buf_this), "%s/%s", me->path, fhdr->filename);
+ if((n = readlink(buf_this, buf_real, sizeof(buf_real) - 1)) == -1)
+ strcpy(buf_real, fhdr->filename);
+ else
+ // readlink doesn't garentee zero-ended
+ buf_real[n] = 0;
+
+ if (strcmp(buf_real, "chess_list") == 0
+ || strcmp(buf_real, "chess_photo") == 0) {
+ vmsg("不需重設!");
+ return;
+ }
+
+ snprintf(buf_list, sizeof(buf_list), "%s/chess_list", me->path);
+ snprintf(buf_photo, sizeof(buf_photo), "%s/chess_photo", me->path);
+
+ list_exist = dashf(buf_list);
+ photo_exist = dashd(buf_photo);
+
+ if (!list_exist && !photo_exist) {
+ vmsg("此看板非棋國!");
+ return;
+ }
+
+ getdata(b_lines, 0, "將此項目設定為 (1) 棋國名單 (2) 棋國照片檔目錄:",
+ buf, sizeof(buf), 1);
+ if (buf[0] == '1') {
+ if (list_exist)
+ getdata(b_lines, 0, "原有之棋國名單將被取代,請確認 (y/N)",
+ buf, sizeof(buf), 1);
+ else
+ buf[0] = 'y';
+
+ if (buf[0] == 'y' || buf[0] == 'Y') {
+ Rename(buf_this, buf_list);
+ symlink("chess_list", buf_this);
+ }
+ } else if (buf[0] == '2') {
+ if (photo_exist)
+ getdata(b_lines, 0, "原有之棋國照片將被取代,請確認 (y/N)",
+ buf, sizeof(buf), 1);
+ else
+ buf[0] = 'y';
+
+ if (buf[0] == 'y' || buf[0] == 'Y') {
+ if(strncmp(buf_photo, "man/boards/", 11) == 0 && // guarding
+ buf_photo[11] && buf_photo[12] == '/' && // guarding
+ snprintf(buf_list, sizeof(buf_list), "rm -rf %s", buf_photo)
+ == strlen(buf_photo) + 7)
+ system(buf_list);
+ Rename(buf_this, buf_photo);
+ symlink("chess_photo", buf_this);
+ }
+ }
+}
+#endif /* defined(CHESSCOUNTRY) */
+
+static int
+isvisible_man(const menu_t * me)
+{
+ fileheader_t *fhdr = &me->header[me->now - me->page];
+ if (me->level < MANAGER && ((fhdr->filemode & FILE_BM) ||
+ ((fhdr->filemode & FILE_HIDE) &&
+ /* board friend only effact when
+ * in board reading mode */
+ (currstat == ANNOUNCE ||
+ hbflcheck(currbid, currutmp->uid))
+ )))
+ return 0;
+ return 1;
+}
+int
+a_menu(const char *maintitle, const char *path, int lastlevel, char *trans_buffer)
+{
+ static char Fexit; // 用來跳出 recursion
+ menu_t me;
+ char fname[PATHLEN];
+ int ch, returnvalue = FULLUPDATE;
+
+ if(trans_buffer)
+ trans_buffer[0] = '\0';
+
+ Fexit = 0;
+ me.header_size = p_lines;
+ me.header = (fileheader_t *) calloc(me.header_size, FHSZ);
+ me.path = path;
+ strlcpy(me.mtitle, maintitle, sizeof(me.mtitle));
+ setadir(fname, me.path);
+ me.num = get_num_records(fname, FHSZ);
+
+ /* 精華區-tree 中部份結構屬於 cuser ==> BM */
+
+ if (!(me.level = lastlevel)) {
+ char *ptr;
+
+ if ((ptr = strrchr(me.mtitle, '[')))
+ me.level = is_BM(ptr + 1);
+ }
+ me.page = 9999;
+ me.now = 0;
+ for (;;) {
+ if (me.now >= me.num)
+ me.now = me.num - 1;
+ if (me.now < 0)
+ me.now = 0;
+
+ if (me.now < me.page || me.now >= me.page + me.header_size) {
+ me.page = me.now - ((me.page == 10000 && me.now > p_lines / 2) ?
+ (p_lines / 2) : (me.now % p_lines));
+ a_showmenu(&me);
+ }
+ ch = cursor_key(2 + me.now - me.page, 0);
+
+ if (ch == 'q' || ch == 'Q' || ch == KEY_LEFT)
+ break;
+
+ if (ch >= '1' && ch <= '9') {
+ if ((ch = search_num(ch, me.num)) != -1)
+ me.now = ch;
+ me.page = 10000;
+ continue;
+ }
+ switch (ch) {
+ case KEY_UP:
+ case 'k':
+ if (--me.now < 0)
+ me.now = me.num - 1;
+ break;
+
+ case KEY_DOWN:
+ case 'j':
+ if (++me.now >= me.num)
+ me.now = 0;
+ break;
+
+ case KEY_PGUP:
+ case Ctrl('B'):
+ if (me.now >= p_lines)
+ me.now -= p_lines;
+ else if (me.now > 0)
+ me.now = 0;
+ else
+ me.now = me.num - 1;
+ break;
+
+ case ' ':
+ case KEY_PGDN:
+ case Ctrl('F'):
+ if (me.now < me.num - p_lines)
+ me.now += p_lines;
+ else if (me.now < me.num - 1)
+ me.now = me.num - 1;
+ else
+ me.now = 0;
+ break;
+
+ case '0':
+ me.now = 0;
+ break;
+ case '?':
+ case '/':
+ if(me.num) {
+ me.now = a_searchtitle(&me, ch == '?');
+ me.page = 9999;
+ }
+ break;
+ case '$':
+ me.now = me.num - 1;
+ break;
+ case 'h':
+ a_showhelp(me.level);
+ me.page = 9999;
+ break;
+
+ case Ctrl('I'):
+ t_idle();
+ me.page = 9999;
+ break;
+
+ case 'e':
+ case 'E':
+ snprintf(fname, sizeof(fname),
+ "%s/%s", path, me.header[me.now - me.page].filename);
+ if (dashf(fname) && me.level >= MANAGER) {
+ *quote_file = 0;
+ if (vedit(fname, NA, NULL) != -1) {
+ char fpath[200];
+ fileheader_t fhdr;
+
+ strlcpy(fpath, path, sizeof(fpath));
+ stampfile(fpath, &fhdr);
+ unlink(fpath);
+ Rename(fname, fpath);
+ strlcpy(me.header[me.now - me.page].filename,
+ fhdr.filename,
+ sizeof(me.header[me.now - me.page].filename));
+ strlcpy(me.header[me.now - me.page].owner,
+ cuser.userid,
+ sizeof(me.header[me.now - me.page].owner));
+ setadir(fpath, path);
+ substitute_record(fpath, me.header + me.now - me.page,
+ sizeof(fhdr), me.now + 1);
+ }
+ me.page = 9999;
+ }
+ break;
+
+ case 't':
+ case 'c':
+ if (me.now < me.num) {
+ if (!isvisible_man(&me))
+ break;
+
+ snprintf(fname, sizeof(fname), "%s/%s", path,
+ me.header[me.now - me.page].filename);
+
+ /* XXX: dirty fix
+ 應該要改成如果發現該目錄裡面有隱形目錄的話才拒絕.
+ 不過這樣的話須要整個搜一遍, 而且目前判斷該資料是目錄
+ 還是檔案竟然是用 fstat(2) 而不是直接存在 .DIR 內 |||b
+ 須等該資料寫入 .DIR 內再 implement才有效率.
+ */
+ if( !lastlevel && !HasUserPerm(PERM_SYSOP) &&
+ (currbid==0 || !is_BM_cache(currbid)) && dashd(fname) )
+ vmsg("只有板主才可以拷貝目錄唷!");
+ else
+ a_copyitem(fname, me.header[me.now - me.page].title, 0, 1);
+ me.page = 9999;
+ /* move down */
+ if (++me.now >= me.num)
+ me.now = 0;
+ break;
+ }
+ case '\n':
+ case '\r':
+ case KEY_RIGHT:
+ case 'r':
+ if (me.now < me.num) {
+ fileheader_t *fhdr = &me.header[me.now - me.page];
+ if (!isvisible_man(&me))
+ break;
+#ifdef DEBUG
+ vmsgf("%s/%s", &path[11], fhdr->filename);;
+#endif
+ snprintf(fname, sizeof(fname), "%s/%s", path, fhdr->filename);
+ if (*fhdr->filename == 'H' && fhdr->filename[1] == '.') {
+ vmsg("不再支援 gopher mode, 請使用瀏覽器直接瀏覽");
+ vmsgf("gopher://%s/1/",fhdr->filename+2);
+ } else if (dashf(fname)) {
+ int more_result;
+
+ while ((more_result = more(fname, YEA))) {
+ /* Ptt 範本精靈 plugin */
+ if (trans_buffer &&
+ (currstat == EDITEXP || currstat == OSONG)) {
+ char ans[4];
+
+ move(22, 0);
+ clrtoeol();
+ getdata(22, 1,
+ currstat == EDITEXP ?
+ "要把範例 Plugin 到文章嗎?[y/N]" :
+ "確定要點這首歌嗎?[y/N]",
+ ans, sizeof(ans), LCECHO);
+ if (ans[0] == 'y') {
+ strlcpy(trans_buffer, fname, PATHLEN);
+ Fexit = 1;
+ if (currstat == OSONG) {
+ log_file(FN_USSONG, LOG_CREAT | LOG_VF,
+ "%s\n", fhdr->title);
+ }
+ free(me.header);
+ return FULLUPDATE;
+ }
+ }
+ if (more_result == READ_PREV) {
+ if (--me.now < 0) {
+ me.now = 0;
+ break;
+ }
+ } else if (more_result == READ_NEXT) {
+ if (++me.now >= me.num) {
+ me.now = me.num - 1;
+ break;
+ }
+ /* we only load me.header_size pages */
+ if (me.now - me.page >= me.header_size)
+ break;
+ } else
+ break;
+ if (!isvisible_man(&me))
+ break;
+ snprintf(fname, sizeof(fname), "%s/%s", path,
+ me.header[me.now - me.page].filename);
+ if (!dashf(fname))
+ break;
+ }
+ } else if (dashd(fname)) {
+ a_menu(me.header[me.now - me.page].title, fname, me.level, trans_buffer);
+ /* Ptt 強力跳出recursive */
+ if (Fexit) {
+ free(me.header);
+ return FULLUPDATE;
+ }
+ }
+ me.page = 9999;
+ }
+ break;
+
+ case 'F':
+ case 'U':
+ if (me.now < me.num) {
+ fileheader_t *fhdr = &me.header[me.now - me.page];
+ if (!isvisible_man(&me))
+ break;
+ snprintf(fname, sizeof(fname),
+ "%s/%s", path, fhdr->filename);
+ if (HasUserPerm(PERM_LOGINOK) && dashf(fname)) {
+ a_forward(path, fhdr, ch /* == 'U' */ );
+ /* By CharlieL */
+ } else
+ vmsg("無法轉寄此項目");
+ me.page = 9999;
+ }
+
+ break;
+
+#ifdef BLOG
+ case 'b':
+ if( !HasUserPerm(PERM_SYSOP) && !is_BM_cache(currbid) )
+ vmsg("只有板主才可以用唷!");
+ else{
+ char genbuf[128];
+ snprintf(genbuf, sizeof(genbuf),
+ "bin/builddb.pl -f -n %d %s", me.now, currboard);
+ system(genbuf);
+ vmsg("資料更新完成");
+ }
+ me.page = 9999;
+ break;
+
+ case 'B':
+ if( !HasUserPerm(SYSOP) && !is_BM_cache(currbid) )
+ vmsg("只有板主才可以用唷!");
+ else
+ BlogMain(me.now);
+ me.page = 9999;
+
+ break;
+#endif
+ }
+
+ if (me.level >= MANAGER) {
+ int page0 = me.page;
+
+ switch (ch) {
+ case 'n':
+ a_newitem(&me, ADDITEM);
+ me.page = 9999;
+ break;
+ case 'g':
+ a_newitem(&me, ADDGROUP);
+ me.page = 9999;
+ break;
+ case 'p':
+ a_pasteitem(&me, 1);
+ me.page = 9999;
+ break;
+ case 'f':
+ a_editsign(&me);
+ me.page = 9999;
+ break;
+ case Ctrl('P'):
+ a_pastetagpost(&me, -1);
+ returnvalue = DIRCHANGED;
+ me.page = 9999;
+ break;
+ case Ctrl('A'):
+ a_pastetagpost(&me, 1);
+ returnvalue = DIRCHANGED;
+ me.page = 9999;
+ break;
+ case 'a':
+ a_appenditem(&me, 1);
+ me.page = 9999;
+ break;
+ default:
+ me.page = page0;
+ break;
+ }
+
+ if (me.num)
+ switch (ch) {
+ case 'm':
+ a_moveitem(&me);
+ me.page = 9999;
+ break;
+
+ case 'D':
+ /* Ptt me.page = -1; */
+ a_delrange(&me);
+ me.page = 9999;
+ break;
+ case 'd':
+ a_delete(&me);
+ me.page = 9999;
+ break;
+ case 'H':
+ a_hideitem(&me);
+ me.page = 9999;
+ break;
+ case 'T':
+ a_newtitle(&me);
+ me.page = 9999;
+ break;
+#ifdef CHESSCOUNTRY
+ case 'L':
+ a_setchesslist(&me);
+ break;
+#endif
+ }
+ }
+ if (me.level == SYSOP) {
+ switch (ch) {
+ case 'l':
+ a_newitem(&me, ADDLINK);
+ me.page = 9999;
+ break;
+ case 'N':
+ a_showname(&me);
+ me.page = 9999;
+ break;
+ }
+ }
+ }
+ free(me.header);
+ return returnvalue;
+}
+
+int
+Announce(void)
+{
+ setutmpmode(ANNOUNCE);
+ a_menu(BBSNAME "佈告欄", "man",
+ ((HasUserPerm(PERM_SYSOP) ) ? SYSOP : NOBODY),
+ NULL);
+ return 0;
+}
+
+#ifdef BLOG
+#include <mysql/mysql.h>
+void BlogMain(int num)
+{
+ int oldmode = currutmp->mode;
+ char genbuf[128], exit = 0;
+
+ //setutmpmode(BLOGGING); /* will crash someone using old program */
+ sprintf(genbuf, "%s的部落格", currboard);
+ showtitle("部落格", genbuf);
+ while( !exit ){
+ move(1, 0);
+ outs("請選擇您要執行的重作:\n"
+ "0.回到上一層\n"
+ "1.製作部落格樣板格式\n"
+ " 使用新的 config 目錄下樣板資料\n"
+ " 通常在第一次使用部落格或樣板更新的時候使用\n"
+ "\n"
+ "2.重新製作部落格\n"
+ " 只在部落格資料整個亂掉的時候才使用\n"
+ "\n"
+ "3.將本文加入部落格\n"
+ " 將游標所在位置的文章加入部落格\n"
+ "\n"
+ "4.刪除迴響\n"
+ "\n"
+ "5.刪除一篇部落格\n"
+ "\n"
+ "C.建立部落格目錄 (您只有第一次時需要)\n"
+ );
+ switch( getans("請選擇(0-5,C)?[0]") ){
+ case '1':
+ snprintf(genbuf, sizeof(genbuf),
+ "bin/builddb.pl -c %s", currboard);
+ system(genbuf);
+ break;
+ case '2':
+ snprintf(genbuf, sizeof(genbuf),
+ "bin/builddb.pl -a %s", currboard);
+ system(genbuf);
+ break;
+ case '3':
+ snprintf(genbuf, sizeof(genbuf),
+ "bin/builddb.pl -f -n %d %s", num, currboard);
+ system(genbuf);
+ break;
+ case '4':{
+ char hash[33];
+ int i;
+ getdata(16, 0, "請輸入該篇的雜湊值: ",
+ hash, sizeof(hash), DOECHO);
+ for( i = 0 ; hash[i] != 0 ; ++i ) /* 前面用 getdata() 保證有 \0 */
+ if( !islower(hash[i]) && !isdigit(hash[i]) )
+ break;
+ if( i != 32 ){
+ vmsg("輸入錯誤");
+ break;
+ }
+ if( hash[0] != 0 &&
+ getans("請確定刪除(Y/N)?[N] ") == 'y' ){
+ MYSQL mysql;
+ char cmd[256];
+
+ snprintf(cmd, sizeof(cmd), "delete from comment where "
+ "hash='%s'&&brdname='%s'", hash, currboard);
+#ifdef DEBUG
+ vmsg(cmd);
+#endif
+ if( !(!mysql_init(&mysql) ||
+ !mysql_real_connect(&mysql, BLOGDB_HOST, BLOGDB_USER,
+ BLOGDB_PASSWD, BLOGDB_DB,
+ BLOGDB_PORT, BLOGDB_SOCK, 0) ||
+ mysql_query(&mysql, cmd)) )
+ vmsg("資料刪除完成");
+ else
+ vmsg(
+#ifdef DEBUG
+ mysql_error(&mysql)
+#else
+ "database internal error"
+#endif
+ );
+ mysql_close(&mysql);
+ }
+ }
+ break;
+
+ case '5': {
+ char date[9];
+ int i;
+ getdata(16, 0, "請輸入該篇的日期(yyyymmdd): ",
+ date, sizeof(date), DOECHO);
+ for( i = 0 ; i < 9 ; ++i )
+ if( !isdigit(date[i]) )
+ break;
+ if( i != 8 ){
+ vmsg("輸入錯誤");
+ break;
+ }
+ snprintf(genbuf, sizeof(genbuf),
+ "bin/builddb.pl -D %s %s", date, currboard);
+ system(genbuf);
+ }
+ break;
+
+ case 'C': case 'c': {
+ fileheader_t item;
+ char fpath[PATHLEN], adir[PATHLEN], buf[256];
+ setapath(fpath, currboard);
+ stampdir(fpath, &item);
+ strlcpy(item.title, "◆ Blog", sizeof(item.title));
+ strlcpy(item.owner, cuser.userid, sizeof(item.owner));
+
+ setapath(adir, currboard);
+ strcat(adir, "/.DIR");
+ append_record(adir, &item, FHSZ);
+
+ snprintf(buf, sizeof(buf),
+ "cp -R etc/Blog.Default/.DIR etc/Blog.Default/* %s/",
+ fpath);
+ system(buf);
+
+ more("etc/README.BLOG", YEA);
+ }
+ break;
+
+ default:
+ exit = 1;
+ break;
+ }
+ if( !exit )
+ vmsg("部落格完成");
+ }
+ currutmp->mode = oldmode;
+ pressanykey();
+}
+#endif
diff --git a/pttbbs/mbbsd/args.c b/pttbbs/mbbsd/args.c
new file mode 100644
index 00000000..8d6ee98d
--- /dev/null
+++ b/pttbbs/mbbsd/args.c
@@ -0,0 +1,71 @@
+/* $Id$ */
+#include "bbs.h"
+#ifdef HAVE_SETPROCTITLE
+
+void
+initsetproctitle(int argc, char **argv, char **envp)
+{
+}
+
+#else
+
+
+char **Argv = NULL; /* pointer to argument vector */
+char *LastArgv = NULL;/* end of argv */
+extern char **environ;
+
+void
+initsetproctitle(int argc, char **argv, char **envp)
+{
+ register int i;
+ int len=0,nenv=0;
+
+ /*
+ * Move the environment so setproctitle can use the space at the top of
+ * memory.
+ */
+ for (i = 0; envp[i]; i++)
+ len+=strlen(envp[i])+1;
+ nenv=i+1;
+ len+=sizeof(char*)*nenv;
+ environ = malloc(len);
+ len=0;
+ for (i = 0; envp[i]; i++) {
+ environ[i] = (char*)environ+nenv*sizeof(char*)+len;
+ strcpy(environ[i], envp[i]);
+ len+=strlen(envp[i])+1;
+ }
+ environ[i] = NULL;
+
+ /* Save start and extent of argv for setproctitle. */
+ Argv = argv;
+ if (i > 0)
+ LastArgv = envp[i - 1] + strlen(envp[i - 1]);
+ else
+ LastArgv = argv[argc - 1] + strlen(argv[argc - 1]);
+}
+
+static void
+do_setproctitle(const char *cmdline)
+{
+ int len;
+
+ len = strlen(cmdline) + 1; // +1 for '\0'
+ if(len > LastArgv - Argv[0] - 2) // 2 ??
+ len = LastArgv - Argv[0] - 2;
+ memset(Argv[0], 0, LastArgv-Argv[0]);
+ strlcpy(Argv[0], cmdline, len);
+ Argv[1] = NULL;
+}
+
+void
+setproctitle(const char *format,...)
+{
+ char buf[256];
+ va_list args;
+ va_start(args, format);
+ vsnprintf(buf, sizeof(buf), format, args);
+ do_setproctitle(buf);
+ va_end(args);
+}
+#endif
diff --git a/pttbbs/mbbsd/assess.c b/pttbbs/mbbsd/assess.c
new file mode 100644
index 00000000..abaa77fb
--- /dev/null
+++ b/pttbbs/mbbsd/assess.c
@@ -0,0 +1,58 @@
+/* $Id$ */
+#include "bbs.h"
+
+#ifdef ASSESS
+
+/* do (*num) + n, n is integer. */
+inline static void inc(unsigned char *num, int n)
+{
+ if (n >= 0 && SALE_MAXVALUE - *num <= n)
+ (*num) = SALE_MAXVALUE;
+ else if (n < 0 && *num < -n)
+ (*num) = 0;
+ else
+ (*num) += n;
+}
+
+#define modify_column(_attr) \
+int inc_##_attr(const char *userid, int num) \
+{ \
+ userec_t xuser; \
+ int uid = getuser(userid, &xuser);\
+ if( uid > 0 ){ \
+ inc(&xuser._attr, num); \
+ passwd_update(uid, &xuser); \
+ return xuser._attr; }\
+ return 0;\
+}
+
+modify_column(goodpost); /* inc_goodpost */
+modify_column(badpost); /* inc_badpost */
+modify_column(goodsale); /* inc_goodsale */
+modify_column(badsale); /* inc_badsale */
+
+#if 0 //unused function
+void set_assess(const char *userid, unsigned char num, int type)
+{
+ userec_t xuser;
+ int uid = getuser(userid, &xuser);
+ if(uid<=0) return;
+ switch (type){
+ case GOODPOST:
+ xuser.goodpost = num;
+ break;
+ case BADPOST:
+ xuser.badpost = num;
+ break;
+ case GOODSALE:
+ xuser.goodsale = num;
+ break;
+ case BADSALE:
+ xuser.badsale = num;
+ break;
+ }
+ passwd_update(uid, &xuser);
+}
+#endif
+
+#endif
diff --git a/pttbbs/mbbsd/bbs.c b/pttbbs/mbbsd/bbs.c
new file mode 100644
index 00000000..ba75286c
--- /dev/null
+++ b/pttbbs/mbbsd/bbs.c
@@ -0,0 +1,3789 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define WHEREAMI_LEVEL 16
+
+static int recommend(int ent, fileheader_t * fhdr, const char *direct);
+static int do_add_recommend(const char *direct, fileheader_t *fhdr,
+ int ent, const char *buf, int type);
+
+#ifdef ASSESS
+static char * const badpost_reason[] = {
+ "廣告", "不當用辭", "人身攻擊"
+};
+#endif
+
+/* TODO multi.money is a mess.
+ * please help verify and finish these.
+ */
+/* modes to invalid multi.money */
+#define INVALIDMONEY_MODES (FILE_ANONYMOUS | FILE_BOTTOM | FILE_DIGEST | FILE_BID)
+
+/* query money by fileheader pointer.
+ * return <0 if money is not valid.
+ */
+int
+query_file_money(const fileheader_t *pfh)
+{
+ fileheader_t hdr;
+
+ if( (currmode & MODE_SELECT) &&
+ (pfh->multi.refer.flag) &&
+ (pfh->multi.refer.ref > 0)) // really? not sure, copied from other's code
+ {
+ char genbuf[MAXPATHLEN];
+
+ /* it is assumed that in MODE_SELECT, currboard is selected. */
+ setbfile(genbuf, currboard, ".DIR");
+ get_record(genbuf, &hdr, sizeof(hdr), pfh->multi.refer.ref);
+ pfh = &hdr;
+ }
+
+ if(pfh->filemode & INVALIDMONEY_MODES || pfh->multi.money > MAX_POST_MONEY)
+ return -1;
+
+ return pfh->multi.money;
+}
+
+/* hack for listing modes */
+enum LISTMODES {
+ LISTMODE_DATE = 0,
+ LISTMODE_MONEY,
+};
+static char *listmode_desc[] = {
+ "日 期",
+ "價 格",
+};
+static int currlistmode = LISTMODE_DATE;
+
+#define IS_LISTING_MONEY \
+ (currlistmode == LISTMODE_MONEY || \
+ ((currmode & MODE_SELECT) && (currsrmode & RS_MONEY)))
+
+void
+anticrosspost(void)
+{
+ log_file("etc/illegal_money", LOG_CREAT | LOG_VF,
+ ANSI_COLOR(1;33;46) "%s "
+ ANSI_COLOR(37;45) "cross post 文章 "
+ ANSI_COLOR(37) " %s" ANSI_RESET "\n",
+ cuser.userid, ctime4(&now));
+ post_violatelaw(cuser.userid, "Ptt系統警察", "Cross-post", "罰單處份");
+ cuser.userlevel |= PERM_VIOLATELAW;
+ cuser.timeviolatelaw = now;
+ cuser.vl_count++;
+ mail_id(cuser.userid, "Cross-Post罰單",
+ "etc/crosspost.txt", "Ptt警察部隊");
+ if ((now - cuser.firstlogin) / 86400 < 14)
+ delete_allpost(cuser.userid);
+ kick_all(cuser.userid); // XXX: in2: wait for testing
+ u_exit("Cross Post");
+ exit(0);
+}
+
+/* Heat CharlieL */
+int
+save_violatelaw(void)
+{
+ char buf[128], ok[3];
+ int day;
+
+ setutmpmode(VIOLATELAW);
+ clear();
+ stand_title("繳罰單中心");
+
+ if (!(cuser.userlevel & PERM_VIOLATELAW)) {
+ vmsg("你沒有被開罰單~~");
+ return 0;
+ }
+ day = cuser.vl_count*3 - (now - cuser.timeviolatelaw)/86400;
+ if (day > 0) {
+ vmsgf("依照違規次數, 你還需要反省 %d 天才能繳罰單", day);
+ return 0;
+ }
+ reload_money();
+ if (cuser.money < (int)cuser.vl_count * 1000) {
+ snprintf(buf, sizeof(buf),
+ ANSI_COLOR(1;31) "這是你第 %d 次違反本站法規"
+ "必須繳出 %d $Ptt ,你只有 %d 元, 錢不夠啦!!" ANSI_RESET,
+ (int)cuser.vl_count, (int)cuser.vl_count * 1000, cuser.money);
+ mouts(22, 0, buf);
+ pressanykey();
+ return 0;
+ }
+ move(5, 0);
+ outs(ANSI_COLOR(1;37) "你知道嗎? 因為你的違法 "
+ "已經造成很多人的不便" ANSI_RESET "\n");
+ outs(ANSI_COLOR(1;37) "你是否確定以後不會再犯了?" ANSI_RESET "\n");
+
+ if (!getdata(10, 0, "確定嗎?[Y/n]:", ok, sizeof(ok), LCECHO) ||
+ ok[0] == 'n' || ok[0] == 'N') {
+ mouts(22, 0, ANSI_COLOR(1;31) "等你想通了再來吧!! "
+ "我相信你不會知錯不改的~~~" ANSI_RESET);
+ pressanykey();
+ return 0;
+ }
+ snprintf(buf, sizeof(buf), "這是你第 %d 次違法 必須繳出 %d $Ptt",
+ cuser.vl_count, cuser.vl_count * 1000);
+ mouts(11, 0, buf);
+
+ if (!getdata(10, 0, "要付錢[Y/n]:", ok, sizeof(ok), LCECHO) ||
+ ok[0] == 'N' || ok[0] == 'n') {
+
+ mouts(22, 0, ANSI_COLOR(1;31) " 嗯 存夠錢 再來吧!!!" ANSI_RESET);
+ pressanykey();
+ return 0;
+ }
+
+ reload_money();
+ if (cuser.money < (int)cuser.vl_count * 1000) return 0; //Ptt:check one more time
+
+ demoney(-1000 * cuser.vl_count);
+ cuser.userlevel &= (~PERM_VIOLATELAW);
+ passwd_update(usernum, &cuser);
+ return 0;
+}
+
+/*
+ * void make_blist() { CreateNameList(); apply_boards(g_board_names); }
+ */
+
+static time4_t *board_note_time = NULL;
+
+void
+set_board(void)
+{
+ boardheader_t *bp;
+
+ bp = getbcache(currbid);
+ if( !HasBoardPerm(bp) ){
+ vmsg("access control violation, exit");
+ u_exit("access control violation!");
+ exit(-1);
+ }
+
+ if( HasUserPerm(PERM_SYSOP) &&
+ (bp->brdattr & BRD_HIDE) &&
+ !is_BM_cache(bp - bcache + 1) &&
+ hbflcheck((int)(bp - bcache) + 1, currutmp->uid) )
+ vmsg("進入未經授權看板");
+
+ board_note_time = &bp->bupdate;
+
+ if(bp->BM[0] <= ' ')
+ strcpy(currBM, "徵求中");
+ else
+ {
+ /* calculate with other title information */
+ int l = 0;
+
+ snprintf(currBM, sizeof(currBM), "板主:%s", bp->BM);
+ /* title has +7 leading symbols */
+ l += strlen(bp->title);
+ if(l >= 7)
+ l -= 7;
+ else
+ l = 0;
+ l += 8 + strlen(currboard); /* trailing stuff */
+ l += strlen(bp->brdname);
+ l = t_columns - l -strlen(currBM);
+
+#ifdef _DEBUG
+ {
+ char buf[MAXPATHLEN];
+ sprintf(buf, "title=%d, brdname=%d, currBM=%d, t_c=%d, l=%d",
+ strlen(bp->title), strlen(bp->brdname),
+ strlen(currBM), t_columns, l);
+ vmsg(buf);
+ }
+#endif
+
+ if(l < 0 && ((l += strlen(currBM)) > 7))
+ {
+ currBM[l] = 0;
+ currBM[l-1] = currBM[l-2] = '.';
+ }
+ }
+
+ /* init basic perm, but post perm is checked on demand */
+ currmode = (currmode & (MODE_DIRTY | MODE_GROUPOP)) | MODE_STARTED;
+ if (!HasUserPerm(PERM_NOCITIZEN) &&
+ (HasUserPerm(PERM_ALLBOARD) || is_BM_cache(currbid))) {
+ currmode = currmode | MODE_BOARD | MODE_POST | MODE_POSTCHECKED;
+ }
+}
+
+/* check post perm on demand, no double checks in current board
+ * currboard MUST be defined!
+ * XXX can we replace currboard with currbid ? */
+int
+CheckPostPerm(void)
+{
+ static time4_t last_chk_time = 0x0BAD0BB5; /* any magic number */
+ static int last_board_index = 0; /* for speed up */
+ int valid_index = 0;
+ boardheader_t *bp = NULL;
+
+ if (currmode & MODE_POSTCHECKED)
+ {
+ /* checked? let's check if perm reloaded */
+ if (last_board_index < 1 || last_board_index > SHM->Bnumber)
+ {
+ /* invalid board index, refetch. */
+ last_board_index = getbnum(currboard);
+ valid_index = 1;
+ }
+ assert(0<=last_board_index-1 && last_board_index-1<MAX_BOARD);
+ bp = getbcache(last_board_index);
+
+ if(bp->perm_reload != last_chk_time)
+ currmode &= ~MODE_POSTCHECKED;
+ }
+
+ if (!(currmode & MODE_POSTCHECKED))
+ {
+ if(!valid_index)
+ {
+ last_board_index = getbnum(currboard);
+ bp = getbcache(last_board_index);
+ }
+ last_chk_time = bp->perm_reload;
+ currmode |= MODE_POSTCHECKED;
+
+ // vmsg("reload board postperm");
+
+ if (haspostperm(currboard)) {
+ currmode |= MODE_POST;
+ return 1;
+ }
+ currmode &= ~MODE_POST;
+ return 0;
+ }
+ return (currmode & MODE_POST);
+}
+
+static void
+readtitle(void)
+{
+ boardheader_t *bp;
+ char *brd_title;
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ bp = getbcache(currbid);
+ if(bp->bvote != 2 && bp->bvote)
+ brd_title = "本看板進行投票中";
+ else
+ brd_title = bp->title + 7;
+
+ showtitle(currBM, brd_title);
+ outs("[←]離開 [→]閱\讀 [^P]發表文章 [b]備忘錄 [d]刪除 [z]精華區 [TAB]文摘 [h]說明\n");
+ prints(ANSI_COLOR(7) " 編號 %s 作 者 文 章 標 題",
+ IS_LISTING_MONEY ? listmode_desc[LISTMODE_MONEY] : listmode_desc[currlistmode]);
+
+#ifdef USE_COOLDOWN
+ if ( bp->brdattr & BRD_COOLDOWN &&
+ !((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)))
+ outslr("", 44, ANSI_RESET, 0);
+ else
+#endif
+ {
+ char buf[32];
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ sprintf(buf, "人氣:%d ",
+ SHM->bcache[currbid - 1].nuser);
+ outslr("", 44, buf, -1);
+ outs(ANSI_RESET);
+ }
+}
+
+static void
+readdoent(int num, fileheader_t * ent)
+{
+ int type;
+ char *mark, *title,
+ color, special = 0, isonline = 0, recom[8];
+ userinfo_t *uentp;
+ type = brc_unread(currbid, ent->filename) ? '+' : ' ';
+ if ((currmode & MODE_BOARD) && (ent->filemode & FILE_DIGEST))
+ type = (type == ' ') ? '*' : '#';
+ else if (currmode & MODE_BOARD || HasUserPerm(PERM_LOGINOK)) {
+ if (ent->filemode & FILE_MARKED)
+ {
+ if(ent->filemode & FILE_SOLVED)
+ type = '!';
+ else
+ type = (type == ' ') ? 'm' : 'M';
+ }
+ else if (TagNum && !Tagger(atoi(ent->filename + 2), 0, TAG_NIN))
+ type = 'D';
+ else if (ent->filemode & FILE_SOLVED)
+ type = (type == ' ') ? 's': 'S';
+ }
+ title = ent->filename[0]!='L' ? subject(ent->title) : "<本文鎖定>";
+ if (ent->filemode & FILE_VOTE)
+ color = '2', mark = "ˇ";
+ else if (ent->filemode & FILE_BID)
+ color = '6', mark = "$";
+ else if (title == ent->title)
+ color = '1', mark = "□";
+ else
+ color = '3', mark = "R:";
+
+ /* 把過長的 title 砍掉。 前面約有 33 個字元。 */
+ {
+ int l = t_columns - 34; /* 33+1, for trailing one more space */
+ unsigned char *p = (unsigned char*)title;
+
+ /* strlen 順便做 safe print checking */
+ while (*p && l > 0)
+ {
+ /* 本來應該做 DBCS checking, 懶得寫了 */
+ if(*p < ' ')
+ *p = ' ';
+ p++, l--;
+ }
+
+ if (*p && l <= 0)
+ strcpy((char*)p-3, " …");
+ }
+
+ if (!strncmp(title, "[公告]", 6))
+ special = 1;
+#if 1
+ if (!strchr(ent->owner, '.') && !SHM->GV2.e.noonlineuser &&
+ (uentp = search_ulist_userid(ent->owner)) && isvisible(currutmp, uentp))
+ isonline = 1;
+#else
+ if (!strchr(ent->owner, '.') && (uid = searchuser(ent->owner, NULL)) &&
+ !SHM->GV2.e.noonlineuser &&
+ (uentp = search_ulist(uid)) && isvisible(currutmp, uentp))
+ isonline = 1;
+#endif
+ if(ent->recommend>99)
+ strcpy(recom,"1m爆");
+ else if(ent->recommend>9)
+ sprintf(recom,"3m%2d",ent->recommend);
+ else if(ent->recommend>0)
+ sprintf(recom,"2m%2d",ent->recommend);
+ else if(ent->recommend<-99)
+ sprintf(recom,"0mXX");
+ else if(ent->recommend<-10)
+ sprintf(recom,"0mX%d",-ent->recommend);
+ else strcpy(recom,"0m ");
+
+ /* start printing */
+ if (ent->filemode & FILE_BOTTOM)
+ outs(" " ANSI_COLOR(1;33) " ★ " ANSI_RESET);
+ else
+ /* recently we found that many boards have >10k articles,
+ * so it's better to use 5+2 (2 for cursor marker) here.
+ */
+ prints("%7d", num);
+
+ prints(" %c\033[1;3%4.4s" ANSI_RESET, type, recom);
+
+ if(IS_LISTING_MONEY)
+ {
+ int m = query_file_money(ent);
+ if(m < 0)
+ outs(" ---- ");
+ else
+ prints("%5d ", m);
+ }
+ else // LISTMODE_DATE
+ {
+#ifdef COLORDATE
+ prints(ANSI_COLOR(%d) "%-6s" ANSI_RESET,
+ (ent->date[3] + ent->date[4]) % 7 + 31, ent->date);
+#else
+ prints("%-6s", ent->date);
+#endif
+ }
+
+ // print author
+ if(isonline) outs(ANSI_COLOR(1));
+ prints("%-13.12s", ent->owner);
+ if(isonline) outs(ANSI_RESET);
+
+ if (strncmp(currtitle, title, TTLEN))
+ prints("%s " ANSI_COLOR(1) "%.*s" ANSI_RESET "%s\n",
+ mark, special ? 6 : 0, title, special ? title + 6 : title);
+ else
+ prints("\033[1;3%cm%s %s" ANSI_RESET "\n",
+ color, mark, title);
+}
+
+int
+whereami(void)
+{
+ boardheader_t *bh, *p[WHEREAMI_LEVEL];
+ int i, j;
+
+ if (!currutmp->brc_id)
+ return 0;
+
+ move(1, 0);
+ clrtobot();
+ bh = getbcache(currutmp->brc_id);
+ p[0] = bh;
+ for (i = 0; i+1 < WHEREAMI_LEVEL && p[i]->parent>1 && p[i]->parent < numboards; i++)
+ p[i + 1] = getbcache(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(void)
+{
+ char bname[20];
+ char bpath[60];
+ boardheader_t *bh;
+ struct stat st;
+ int i;
+
+ setutmpmode(SELECT);
+ move(0, 0);
+ clrtoeol();
+ CompleteBoard(MSG_SELECT_BOARD, bname);
+ if (bname[0] == '\0' || !(i = getbnum(bname)))
+ return FULLUPDATE;
+ assert(0<=i-1 && i-1<MAX_BOARD);
+ bh = getbcache(i);
+ if (!HasBoardPerm(bh))
+ return FULLUPDATE;
+ strlcpy(bname, bh->brdname, sizeof(bname));
+ brc_update();
+ 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_board(bname);
+ set_board();
+ setbdir(currdirect, currboard);
+
+ move(1, 0);
+ clrtoeol();
+ return NEWDIRECT;
+}
+
+/* ----------------------------------------------------- */
+/* 改良 innbbsd 轉出信件、連線砍信之處理程序 */
+/* ----------------------------------------------------- */
+void
+outgo_post(const fileheader_t *fh, const char *board, const char *userid, const char *nickname)
+{
+ FILE *foo;
+
+ if ((foo = fopen("innd/out.bntp", "a"))) {
+ fprintf(foo, "%s\t%s\t%s\t%s\t%s\n",
+ board, fh->filename, userid, nickname, fh->title);
+ fclose(foo);
+ }
+}
+
+static void
+cancelpost(const fileheader_t *fh, int by_BM, char *newpath)
+{
+ FILE *fin, *fout;
+ char *ptr, *brd;
+ fileheader_t postfile;
+ char genbuf[200];
+ char nick[STRLEN], fn1[MAXPATHLEN];
+ int len = 42-strlen(currboard);
+ struct tm *ptime = localtime4(&now);
+
+ if(!fh->filename[0]) return;
+ setbfile(fn1, currboard, fh->filename);
+ if ((fin = fopen(fn1, "r"))) {
+ brd = by_BM ? "deleted" : "junk";
+
+ memcpy(&postfile, fh, sizeof(fileheader_t));
+ setbpath(newpath, brd);
+ stampfile_u(newpath, &postfile);
+
+ 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, '(')))
+ strlcpy(nick, ptr + 1, sizeof(nick));
+ break;
+ }
+ }
+ if(!strncasecmp(postfile.title, str_reply, 3))
+ len=len+4;
+ sprintf(postfile.title, "%-*.*s.%s板", len, len, fh->title, currboard);
+
+ 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);
+ log_file(fn1, LOG_CREAT | LOG_VF, "\n※ Deleted by: %s (%s) %d/%d",
+ cuser.userid, fromhost, ptime->tm_mon + 1, ptime->tm_mday);
+ Rename(fn1, newpath);
+ setbdir(genbuf, brd);
+ setbtotal(getbnum(brd));
+ append_record(genbuf, &postfile, sizeof(postfile));
+ }
+}
+
+static void
+do_deleteCrossPost(const fileheader_t *fh, char bname[])
+{
+ char bdir[MAXPATHLEN]="", file[MAXPATHLEN]="";
+ fileheader_t newfh;
+ if(!bname || !fh) return;
+
+ int i, bid = getbnum(bname);
+ if(bid <=0 || !fh->filename[0]) return;
+
+ boardheader_t *bp = getbcache(bid);
+ if(!bp) return;
+
+ setbdir(bdir, bname);
+ setbfile(file, bname, fh->filename);
+ memcpy(&newfh, fh, sizeof(fileheader_t));
+ // Ptt: protect original fh
+ // because getindex safe_article_delete will change fh in some case
+ if( (i=getindex(bdir, &newfh, 0))>0)
+ {
+#ifdef SAFE_ARTICLE_DELETE
+ if(bp && !(currmode & MODE_DIGEST) && bp->nuser > 30 )
+ safe_article_delete(i, &newfh, bdir);
+ else
+#endif
+ delete_record(bdir, sizeof(fileheader_t), i);
+ unlink(file);
+ setbtotal(bid);
+ }
+}
+
+static void
+deleteCrossPost(const fileheader_t *fh, char *bname)
+{
+ if(!fh || !fh->filename[0]) return;
+
+ if(!strcmp(bname, ALLPOST) || !strcmp(bname, "NEWIDPOST") ||
+ !strcmp(bname, ALLHIDPOST) || !strcmp(bname, "UnAnonymous"))
+ {
+ int len=0;
+ char xbname[TTLEN + 1], *po = strrchr(fh->title, '.');
+ if(!po) return;
+ po++;
+ len = (int) strlen(po)-2;
+
+ if(len > TTLEN) return;
+ sprintf(xbname, "%.*s", len, po);
+ do_deleteCrossPost(fh, xbname);
+ }
+ else
+ {
+ do_deleteCrossPost(fh, ALLPOST);
+ }
+}
+
+void
+delete_allpost(char *userid)
+{
+ fileheader_t fhdr;
+ int fd, i;
+ char bdir[MAXPATHLEN]="", file[MAXPATHLEN]="";
+
+ if(!userid) return;
+
+ setbdir(bdir, ALLPOST);
+ if( (fd = open(bdir, O_RDWR)) != -1)
+ {
+ for(i=0; read(fd, &fhdr, sizeof(fileheader_t)) >0; i++){
+ if(strcmp(fhdr.owner, userid))
+ continue;
+ deleteCrossPost(&fhdr, ALLPOST);
+ setbfile(file, ALLPOST, fhdr.filename);
+ unlink(file);
+
+ sprintf(fhdr.title, "(本文已被刪除)");
+ strcpy(fhdr.filename, ".deleted");
+ strcpy(fhdr.owner, "-");
+ lseek(fd, sizeof(fileheader_t) * i, SEEK_SET);
+ write(fd, &fhdr, sizeof(fileheader_t));
+ }
+ close(fd);
+ }
+}
+
+/* ----------------------------------------------------- */
+/* 發表、回應、編輯、轉錄文章 */
+/* ----------------------------------------------------- */
+void
+do_reply_title(int row, const char *title)
+{
+ char genbuf[200];
+ char genbuf2[4];
+
+ if (strncasecmp(title, str_reply, 4))
+ snprintf(save_title, sizeof(save_title), "Re: %s", title);
+ else
+ strlcpy(save_title, title, sizeof(save_title));
+ save_title[TTLEN - 1] = '\0';
+ snprintf(genbuf, sizeof(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(const char *fpath)
+{
+ fileheader_t mhdr;
+ char title[128];
+ char genbuf[200];
+
+ setbpath(genbuf, "UnAnonymous");
+ if (dashd(genbuf)) {
+ stampfile(genbuf, &mhdr);
+ unlink(genbuf);
+ // XXX: Link should use BBSHOME/blah
+ Link(fpath, genbuf);
+ strlcpy(mhdr.owner, cuser.userid, sizeof(mhdr.owner));
+ strlcpy(mhdr.title, save_title, sizeof(mhdr.title));
+ mhdr.filemode = 0;
+ setbdir(title, "UnAnonymous");
+ append_record(title, &mhdr, sizeof(mhdr));
+ }
+}
+*/
+
+void
+do_crosspost(const char *brd, fileheader_t *postfile, const char *fpath,
+ int isstamp)
+{
+ char genbuf[200];
+ int len = 42-strlen(currboard);
+ fileheader_t fh;
+ int bid = getbnum(brd);
+
+ if(bid <= 0 || bid > MAX_BOARD) return;
+
+ if(!strncasecmp(postfile->title, str_reply, 3))
+ len=len+4;
+
+ memcpy(&fh, postfile, sizeof(fileheader_t));
+ if(isstamp)
+ {
+ setbpath(genbuf, brd);
+ stampfile(genbuf, &fh);
+ }
+ else
+ setbfile(genbuf, brd, postfile->filename);
+
+ if(!strcmp(brd, "UnAnonymous"))
+ strcpy(fh.owner, cuser.userid);
+
+ sprintf(fh.title,"%-*.*s.%s板", len, len, postfile->title, currboard);
+ unlink(genbuf);
+ Copy((char *)fpath, genbuf);
+ postfile->filemode = FILE_LOCAL;
+ setbdir(genbuf, brd);
+ if (append_record(genbuf, &fh, sizeof(fileheader_t)) != -1) {
+ SHM->lastposttime[bid - 1] = now;
+ touchbpostnum(bid, 1);
+ }
+}
+static void
+setupbidinfo(bid_t *bidinfo)
+{
+ char buf[256];
+ bidinfo->enddate = gettime(20, now+86400,"結束標案於");
+ do{
+ getdata_str(21, 0, "底價:", buf, 8, LCECHO, "1");
+ } while( (bidinfo->high = atoi(buf)) <= 0 );
+ do{
+ getdata_str(21, 20, "每標至少增加多少:", buf, 5, LCECHO, "1");
+ } while( (bidinfo->increment = atoi(buf)) <= 0 );
+ getdata(21,44, "直接購買價(可不設):",buf, 10, LCECHO);
+ bidinfo->buyitnow = atoi(buf);
+
+ getdata_str(22,0,
+ "付款方式: 1.Ptt幣 2.郵局或銀行轉帳 3.支票或電匯 4.郵局貨到付款 [1]:",
+ buf, 3, LCECHO,"1");
+ bidinfo->payby = (buf[0] - '1');
+ if( bidinfo->payby < 0 || bidinfo->payby > 3)
+ bidinfo->payby = 0;
+ getdata_str(23, 0, "運費(0:免運費或文中說明)[0]:", buf, 6, LCECHO, "0");
+ bidinfo->shipping = atoi(buf);
+ if( bidinfo->shipping < 0 )
+ bidinfo->shipping = 0;
+}
+static void
+print_bidinfo(FILE *io, bid_t bidinfo)
+{
+ char *payby[4]={"Ptt幣", "郵局或銀行轉帳", "支票或電匯", "郵局貨到付款"};
+ if(io){
+ if( !bidinfo.userid[0] )
+ fprintf(io, "起標價: %-20d\n", bidinfo.high);
+ else
+ fprintf(io, "目前最高價:%-20d出價者:%-16s\n",
+ bidinfo.high, bidinfo.userid);
+ fprintf(io, "付款方式: %-20s結束於:%-16s\n",
+ payby[bidinfo.payby % 4], Cdate(& bidinfo.enddate));
+ if(bidinfo.buyitnow)
+ fprintf(io, "直接購買價:%-20d", bidinfo.buyitnow);
+ if(bidinfo.shipping)
+ fprintf(io, "運費:%d", bidinfo.shipping);
+ fprintf(io, "\n");
+ }
+ else{
+ if(!bidinfo.userid[0])
+ prints("起標價: %-20d\n", bidinfo.high);
+ else
+ prints("目前最高價:%-20d出價者:%-16s\n",
+ bidinfo.high, bidinfo.userid);
+ prints("付款方式: %-20s結束於:%-16s\n",
+ payby[bidinfo.payby % 4], Cdate(& bidinfo.enddate));
+ if(bidinfo.buyitnow)
+ prints("直接購買價:%-20d", bidinfo.buyitnow);
+ if(bidinfo.shipping)
+ prints("運費:%d", bidinfo.shipping);
+ outc('\n');
+ }
+}
+
+static int
+do_general(int isbid)
+{
+ bid_t bidinfo;
+ fileheader_t postfile;
+ char fpath[80], buf[80];
+ int aborted, defanony, ifuseanony, i;
+ char genbuf[200], *owner;
+ char ctype[8][5] = {"問題", "建議", "討論", "心得",
+ "閒聊", "請益", "公告", "情報"};
+ boardheader_t *bp;
+ int islocal, posttype=-1;
+
+ ifuseanony = 0;
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ bp = getbcache(currbid);
+
+ if( !CheckPostPerm()
+#ifdef FOREIGN_REG
+ // 不是外籍使用者在 PttForeign 板
+ && !((cuser.uflag2 & FOREIGN) && strcmp(bp->brdname, "PttForeign") == 0)
+#endif
+ ) {
+ vmsg("對不起,您目前無法在此發表文章!");
+ return READ_REDRAW;
+ }
+
+#ifndef DEBUG
+ if ( !((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)) &&
+ (cuser.firstlogin > (now - (time4_t)bcache[currbid - 1].post_limit_regtime * 2592000) ||
+ cuser.badpost > (255 - (unsigned int)(bcache[currbid - 1].post_limit_badpost)) ||
+ cuser.numlogins < ((unsigned int)(bcache[currbid - 1].post_limit_logins) * 10) ||
+ cuser.numposts < ((unsigned int)(bcache[currbid - 1].post_limit_posts) * 10)) ) {
+ move(5, 10);
+ vmsg("你不夠資深喔! (可按大寫 I 查看限制)");
+ return FULLUPDATE;
+ }
+#ifdef USE_COOLDOWN
+ if(check_cooldown(bp))
+ return READ_REDRAW;
+#endif
+#endif
+ clear();
+
+ if(likely(!isbid))
+ setbfile(genbuf, currboard, FN_POST_NOTE);
+ else
+ setbfile(genbuf, currboard, FN_POST_BID);
+
+ if (more(genbuf, NA) == -1) {
+ if(!isbid)
+ more("etc/" FN_POST_NOTE, NA);
+ else
+ more("etc/" FN_POST_BID, NA);
+ }
+ move(19, 0);
+ prints("%s於【" ANSI_COLOR(33) " %s" ANSI_RESET " 】 "
+ ANSI_COLOR(32) "%s" ANSI_RESET " 看板\n",
+ isbid?"公開招標":"發表文章",
+ currboard, bp->title + 7);
+
+ if (unlikely(isbid)) {
+ memset(&bidinfo,0,sizeof(bidinfo));
+ setupbidinfo(&bidinfo);
+ postfile.multi.money=bidinfo.high;
+ move(20,0);
+ clrtobot();
+ }
+ if (quote_file[0])
+ do_reply_title(20, currtitle);
+ else {
+ if (!isbid) {
+ move(21,0);
+ outs("種類:");
+ for(i=0; i<8 && bp->posttype[i*4]; i++)
+ strlcpy(ctype[i],bp->posttype+4*i,5);
+ if(i==0) i=8;
+ for(aborted=0; aborted<i; aborted++)
+ prints("%d.%4.4s ", aborted+1, ctype[aborted]);
+ sprintf(buf,"(1-%d或不選)",i);
+ getdata(21, 6+7*i, buf, save_title, 3, LCECHO);
+ posttype = save_title[0] - '1';
+ if (posttype >= 0 && posttype < i)
+ snprintf(save_title, sizeof(save_title),
+ "[%s] ", ctype[posttype]);
+ else
+ {
+ save_title[0] = '\0';
+ posttype=-1;
+ }
+ }
+ getdata_buf(22, 0, "標題:", save_title, TTLEN, DOECHO);
+ strip_ansi(save_title, save_title, STRIP_ALL);
+ if( strcmp(save_title, "[711iB] 增加上站次數程式") == 0 ){
+ cuser.userlevel |= PERM_VIOLATELAW;
+ sleep(60);
+ u_exit("bad program");
+ }
+ }
+ if (save_title[0] == '\0')
+ return FULLUPDATE;
+
+ curredit &= ~EDIT_MAIL;
+ curredit &= ~EDIT_ITEM;
+ setutmpmode(POSTING);
+ /* 未具備 Internet 權限者,只能在站內發表文章 */
+ /* 板主預設站內存檔 */
+ if (HasUserPerm(PERM_INTERNET) && !(bp->brdattr & BRD_LOCALSAVE))
+ local_article = 0;
+ else
+ local_article = 1;
+
+ /* build filename */
+ setbpath(fpath, currboard);
+ stampfile(fpath, &postfile);
+ if(isbid) {
+ FILE *fp;
+ if( (fp = fopen(fpath, "w")) != NULL ){
+ print_bidinfo(fp, bidinfo);
+ fclose(fp);
+ }
+ }
+ else if(posttype!=-1 && ((1<<posttype) & bp->posttype_f)) {
+ setbnfile(genbuf, bp->brdname, "postsample", posttype);
+ Copy(genbuf, fpath);
+ }
+
+ aborted = vedit(fpath, YEA, &islocal);
+ if (aborted == -1) {
+ unlink(fpath);
+ pressanykey();
+ return FULLUPDATE;
+ }
+ /* 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
+
+ /* 錢 */
+ if (aborted > MAX_POST_MONEY * 2)
+ aborted = MAX_POST_MONEY;
+ else
+ aborted /= 2;
+
+ if(ifuseanony) {
+ postfile.filemode |= FILE_ANONYMOUS;
+ postfile.multi.anon_uid = currutmp->uid;
+ }
+ else if(!isbid)
+ {
+ /* general article */
+#ifdef USE_TEXTLEN
+ struct stat st;
+
+ if (stat(fpath, &st) != -1)
+ {
+ /* put original file (text) length. */
+ postfile.textlen = st.st_size;
+ }
+#endif
+ postfile.multi.money = aborted;
+ }
+
+ strlcpy(postfile.owner, owner, sizeof(postfile.owner));
+ strlcpy(postfile.title, save_title, sizeof(postfile.title));
+ if (islocal) /* local save */
+ postfile.filemode |= FILE_LOCAL;
+
+ setbdir(buf, currboard);
+ if(isbid) {
+ sprintf(genbuf, "%s.bid", fpath);
+ append_record(genbuf,(void*) &bidinfo, sizeof(bidinfo));
+ postfile.filemode |= FILE_BID ;
+ }
+ strcpy(genbuf, fpath);
+ setbpath(fpath, currboard);
+ stampfile_u(fpath, &postfile);
+ // Ptt: stamp file again to make it order
+ // fix the bug that search failure in getindex
+ // stampfile_u is used when you don't want to clear other fields
+ if (append_record(buf, &postfile, sizeof(postfile)) == -1)
+ {
+ unlink(genbuf);
+ }
+ else
+ {
+ rename(genbuf, fpath);
+#ifdef LOGPOST
+ {
+ FILE *fp = fopen("log/post", "a");
+ fprintf(fp, "%d %s boards/%c/%s/%s\n",
+ now, cuser.userid, currboard[0], currboard,
+ postfile.filename);
+ fclose(fp);
+ }
+#endif
+ setbtotal(currbid);
+
+ if( currmode & MODE_SELECT )
+ append_record(currdirect, &postfile, sizeof(postfile));
+ if( !islocal && !(bp->brdattr & BRD_NOTRAN) ){
+#ifdef HAVE_ANONYMOUS
+ if( ifuseanony )
+ outgo_post(&postfile, currboard, owner, "Anonymous.");
+ else
+#endif
+ outgo_post(&postfile, currboard, cuser.userid, cuser.nickname);
+ }
+ brc_addlist(postfile.filename);
+
+ if( !bp->level || (currbrdattr & BRD_POSTMASK))
+ {
+ if ((now - cuser.firstlogin) / 86400 < 14)
+ do_crosspost("NEWIDPOST", &postfile, fpath, 0);
+
+ if (!(currbrdattr & BRD_HIDE) )
+ do_crosspost(ALLPOST, &postfile, fpath, 0);
+ else
+ do_crosspost(ALLHIDPOST, &postfile, fpath, 0);
+ }
+ outs("順利貼出佈告,");
+
+#ifdef MAX_POST_MONEY
+ if (aborted > MAX_POST_MONEY)
+ aborted = MAX_POST_MONEY;
+#endif
+ if (strcmp(currboard, "Test") && !ifuseanony &&
+ !(currbrdattr&BRD_BAD)) {
+ prints("這是您的第 %d 篇文章。",++cuser.numposts);
+ if(postfile.filemode&FILE_BID)
+ outs("招標文章沒有稿酬。");
+ else
+ {
+ prints(" 稿酬 %d 銀。",aborted);
+ demoney(aborted);
+ }
+ } 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)
+#endif
+ < 0)
+ msg = "作者無法收信";
+ } else {
+ sethomepath(genbuf, quote_user);
+ stampfile(genbuf, &postfile);
+ unlink(genbuf);
+ Copy(fpath, genbuf);
+
+ strlcpy(postfile.owner, cuser.userid, sizeof(postfile.owner));
+ strlcpy(postfile.title, save_title, sizeof(postfile.title));
+ sethomedir(genbuf, quote_user);
+ if (append_record(genbuf, &postfile, sizeof(postfile)) == -1)
+ msg = err_uid;
+ else
+ sendalert(quote_user, ALERT_NEW_MAIL);
+ }
+ outs(msg);
+ curredit ^= EDIT_BOTH;
+ }
+ if (currbrdattr & BRD_ANONYMOUS)
+ do_crosspost("UnAnonymous", &postfile, fpath, 0);
+#ifdef USE_COOLDOWN
+ if(bp->nuser>30)
+ {
+ if (cooldowntimeof(usernum)<now)
+ add_cooldowntime(usernum, 5);
+ }
+ add_posttimes(usernum, 1);
+#endif
+ }
+ pressanykey();
+ return FULLUPDATE;
+}
+
+int
+do_post(void)
+{
+ boardheader_t *bp;
+ STATINC(STAT_DOPOST);
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ bp = getbcache(currbid);
+ if (bp->brdattr & BRD_VOTEBOARD)
+ return do_voteboard(0);
+ else if (!(bp->brdattr & BRD_GROUPBOARD))
+ return do_general(0);
+ return 0;
+}
+
+int
+do_post_vote(void)
+{
+ return do_voteboard(1);
+}
+
+int
+do_post_openbid(void)
+{
+ char ans[4];
+ boardheader_t *bp;
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ bp = getbcache(currbid);
+ if (!(bp->brdattr & BRD_VOTEBOARD))
+ {
+ getdata(b_lines - 1, 0,
+ "確定要公開招標嗎? [y/N] ",
+ ans, sizeof(ans), LCECHO);
+ if(ans[0] != 'y')
+ return FULLUPDATE;
+
+ return do_general(1);
+ }
+ return 0;
+}
+
+static void
+do_generalboardreply(/*const*/ fileheader_t * fhdr)
+{
+ char genbuf[3];
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ if ( !((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)) &&
+ (cuser.firstlogin > (now - (time4_t)bcache[currbid - 1].post_limit_regtime * 2592000) ||
+ cuser.badpost > (255 - (unsigned int)(bcache[currbid - 1].post_limit_badpost)) ||
+ cuser.numlogins < ((unsigned int)(bcache[currbid - 1].post_limit_logins) * 10) ||
+ cuser.numposts < ((unsigned int)(bcache[currbid - 1].post_limit_posts) * 10)) ) {
+ getdata(b_lines - 1, 0, "▲ 回應至 (M)作者信箱 (Q)取消?[M] ",
+ genbuf, sizeof(genbuf), LCECHO);
+ switch (genbuf[0]) {
+ case 'q':
+ break;
+ default:
+ mail_reply(0, fhdr, 0);
+ }
+ }
+ else {
+ getdata(b_lines - 1, 0,
+ "▲ 回應至 (F)看板 (M)作者信箱 (B)二者皆是 (Q)取消?[F] ",
+ genbuf, sizeof(genbuf), LCECHO);
+ switch (genbuf[0]) {
+ case 'm':
+ mail_reply(0, fhdr, 0);
+ case 'q':
+ break;
+
+ case 'b':
+ curredit = EDIT_BOTH;
+ default:
+ strlcpy(currtitle, fhdr->title, sizeof(currtitle));
+ strlcpy(quote_user, fhdr->owner, sizeof(quote_user));
+ do_post();
+ curredit &= ~EDIT_BOTH;
+ }
+ }
+ *quote_file = 0;
+}
+
+
+int
+invalid_brdname(const char *brd)
+{
+ register char ch, rv=0;
+
+ ch = *brd++;
+ if (!isalpha((int)ch))
+ rv = 2;
+ while ((ch = *brd++)) {
+ if (not_alnum(ch) && ch != '_' && ch != '-' && ch != '.')
+ return (1|rv);
+ }
+ return rv;
+}
+
+static int
+b_call_in(int ent, const fileheader_t * fhdr, const char *direct)
+{
+ userinfo_t *u = search_ulist(searchuser(fhdr->owner, NULL));
+ if (u) {
+ int fri_stat;
+ fri_stat = friend_stat(currutmp, u);
+ if (isvisible_stat(currutmp, u, fri_stat) && call_in(u, fri_stat))
+ return FULLUPDATE;
+ }
+ return DONOTHING;
+}
+
+
+static int
+b_posttype(int ent, const fileheader_t * fhdr, const char *direct)
+{
+ boardheader_t *bp;
+ int i, aborted;
+ char filepath[PATHLEN], genbuf[60], title[5], posttype_f, posttype[33]="";
+
+ if(!(currmode & MODE_BOARD)) return DONOTHING;
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ bp = getbcache(currbid);
+
+ move(2,0);
+ clrtobot();
+ posttype_f = bp->posttype_f;
+ for( i = 0 ; i < 8 ; ++i ){
+ move(2,0);
+ outs("文章種類: ");
+ strlcpy(genbuf, bp->posttype + i * 4, 5);
+ sprintf(title, "%d.", i + 1);
+ if( !getdata_buf(2, 11, title, genbuf, 5, DOECHO) )
+ break;
+ sprintf(posttype + i * 4, "%-4.4s", genbuf);
+ if( posttype_f & (1<<i) ){
+ if( getdata(2, 20, "設定範本格式?(Y/n)", genbuf, 3, LCECHO) &&
+ genbuf[0]=='n' ){
+ posttype_f &= ~(1<<i);
+ continue;
+ }
+ }
+ else if ( !getdata(2, 20, "設定範本格式?(y/N)", genbuf, 3, LCECHO) ||
+ genbuf[0] != 'y' )
+ continue;
+
+ setbnfile(filepath, bp->brdname, "postsample", i);
+ aborted = vedit(filepath, NA, NULL);
+ if (aborted == -1) {
+ clear();
+ posttype_f &= ~(1<<i);
+ continue;
+ }
+ posttype_f |= (1<<i);
+ }
+ bp->posttype_f = posttype_f;
+ strlcpy(bp->posttype, posttype, sizeof(bp->posttype)); /* 這邊應該要防race condition */
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
+ return FULLUPDATE;
+}
+
+static int
+do_reply(/*const*/ fileheader_t * fhdr)
+{
+ boardheader_t *bp;
+ if (!CheckPostPerm() ) return DONOTHING;
+ if (fhdr->filemode &FILE_SOLVED)
+ {
+ if(fhdr->filemode & FILE_MARKED)
+ {
+ vmsg("很抱歉, 此文章已結案並標記, 不得回應.");
+ return FULLUPDATE;
+ }
+ if(getkey("此篇文章已結案, 是否真的要回應?(y/N)")!='y')
+ return FULLUPDATE;
+ }
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ bp = getbcache(currbid);
+ if (bp->brdattr & BRD_NOREPLY) {
+ vmsg("很抱歉, 本板不開放回覆文章.");
+ return FULLUPDATE;
+ }
+
+ setbfile(quote_file, bp->brdname, fhdr->filename);
+ if (bp->brdattr & BRD_VOTEBOARD || (fhdr->filemode & FILE_VOTE))
+ do_voteboardreply(fhdr);
+ else
+ do_generalboardreply(fhdr);
+ *quote_file = 0;
+ return FULLUPDATE;
+}
+
+static int
+reply_post(int ent, /*const*/ fileheader_t * fhdr, const char *direct)
+{
+ return do_reply(fhdr);
+}
+
+static int
+edit_post(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char fpath[80];
+ char genbuf[200];
+ fileheader_t postfile;
+ boardheader_t *bp = getbcache(currbid);
+ struct stat oldstat, newstat;
+ int isSysop = 0, recordTouched = 0;
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ if (strcmp(bp->brdname, "Security") == 0)
+ return DONOTHING;
+
+ if (HasUserPerm(PERM_SYSOP))
+ isSysop = 1;
+ else if ((bp->brdattr & BRD_VOTEBOARD) ||
+ (fhdr->filemode & FILE_VOTE) ||
+ !CheckPostPerm() ||
+ strcmp(fhdr->owner, cuser.userid) != EQUSTR ||
+ strcmp(cuser.userid, STR_GUEST) == EQUSTR)
+ return DONOTHING;
+
+ if( currmode & MODE_SELECT )
+ return DONOTHING;
+
+#ifdef SAFE_ARTICLE_DELETE
+ if( fhdr->filename[0] == '.' )
+ return DONOTHING;
+#endif
+
+ setutmpmode(REEDIT);
+ setbpath(fpath, currboard);
+ stampfile(fpath, &postfile);
+ setdirpath(genbuf, direct, fhdr->filename);
+ local_article = fhdr->filemode & FILE_LOCAL;
+
+#ifdef USE_TEXTLEN
+ if(fhdr->textlen > 0)
+ {
+ /* TODO SYSOP may need some function to edit entire file. */
+ CopyN(genbuf, fpath, fhdr->textlen);
+ }
+ else
+#endif
+ {
+ Copy(genbuf, fpath);
+ }
+
+ strlcpy(save_title, fhdr->title, sizeof(save_title));
+
+ do {
+ stat(genbuf, &oldstat);
+
+ if (vedit(fpath, 0, NULL) == -1)
+ break;
+
+ stat(genbuf, &newstat);
+
+ /* check textlen */
+ if(fhdr->textlen > 0)
+ {
+ int gotnewstat = -1;
+
+#ifdef USE_TEXTLEN
+ /* TODO should we reload textlen info?
+ * multiple editing will make textlen invalid. */
+ if(fhdr->textlen != newstat.st_size)
+ {
+#ifdef DEBUG
+ vmsg("textlen != st_size, append tail.");
+#endif
+ gotnewstat = stat(fpath, &newstat);
+
+ /* copy from old content. */
+ AppendTail(genbuf, fpath, fhdr->textlen);
+ } else {
+ gotnewstat = stat(fpath, &newstat);
+ }
+#endif
+
+ /* now update the record. */
+ if(gotnewstat != -1)
+ fhdr->textlen = newstat.st_size;
+ else
+ fhdr->textlen = 0;
+
+ recordTouched = 1;
+
+ } else /* old flavor, no textlen info */
+ if (oldstat.st_mtime != newstat.st_mtime)
+ {
+ if (tolower(getans(
+ "檔案已被別人修改過,要覆蓋\掉它嗎 [Y/n]?")) == 'n')
+ {
+ FILE *fp, *src;
+
+ if(tolower(getans(
+ "要把被修改過的文章附加在結尾並重新編輯嗎 [Y/n]?")) == 'n')
+ break;
+
+ /* merge new and old stuff */
+ fp = fopen(fpath, "at");
+ src = fopen(genbuf, "rt");
+
+ if(!fp)
+ {
+ vmsg("抱歉,檔案已損毀。");
+ if(src) fclose(src);
+ return FULLUPDATE;
+ }
+
+ if(src)
+ {
+ int c = 0;
+ struct tm *ptime;
+ time4_t xt = (time4_t)newstat.st_mtime;
+
+ fprintf(fp, MSG_SEPERATOR "\n");
+ fprintf(fp, "以下為被別人修改過的最新內容: ");
+ ptime = localtime4(&xt);
+ fprintf(fp,
+ " (%02d/%02d %02d:%02d)\n",
+ ptime->tm_mon + 1, ptime->tm_mday,
+ ptime->tm_hour, ptime->tm_min);
+ fprintf(fp, MSG_SEPERATOR "\n");
+ while ((c = fgetc(src)) >= 0)
+ fputc(c, fp);
+ fclose(src);
+ }
+ fclose(fp);
+ continue;
+ }
+ }
+
+ Rename(fpath, genbuf);
+
+ if(strcmp(save_title, fhdr->title)){
+ // Ptt: here is the black hole problem
+ // (try getindex)
+ strcpy(fhdr->title, save_title);
+ recordTouched = 1;
+ }
+
+ if(recordTouched)
+ substitute_ref_record(direct, fhdr, ent);
+
+ break;
+ } while (1);
+
+ /* should we do this when editing was aborted? */
+ unlink(fpath);
+
+ return FULLUPDATE;
+}
+
+#define UPDATE_USEREC (currmode |= MODE_DIRTY)
+
+static int
+cross_post(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char xboard[20], fname[80], xfpath[80], xtitle[80];
+ char inputbuf[10], genbuf[200], genbuf2[4];
+ fileheader_t xfile;
+ FILE *xptr;
+ int author;
+ boardheader_t *bp;
+
+ move(2, 0);
+ clrtoeol();
+ move(1, 0);
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ bp = getbcache(currbid);
+ if (bp && (bp->brdattr & BRD_VOTEBOARD) )
+ return FULLUPDATE;
+
+ CompleteBoard("轉錄本文章於看板:", xboard);
+ if (*xboard == '\0')
+ return FULLUPDATE;
+
+ if (!haspostperm(xboard))
+ {
+ vmsg("看板不存在或該看板禁止您發表文章!");
+ return FULLUPDATE;
+ }
+
+ /* 借用變數 */
+ ent = StringHash(fhdr->title);
+ author = getbnum(xboard);
+ assert(0<=author-1 && author-1<MAX_BOARD);
+
+ if ((ent != 0 && ent == postrecord.checksum[0]) &&
+ (author != 0 && author != postrecord.last_bid)) {
+ /* 檢查 cross post 次數 */
+ if (postrecord.times++ > MAX_CROSSNUM)
+ anticrosspost();
+ } else {
+ postrecord.times = 0;
+ postrecord.last_bid = author;
+ postrecord.checksum[0] = ent;
+ }
+
+ if ( !((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)) &&
+ (cuser.firstlogin > (now - (time4_t)bcache[author - 1].post_limit_regtime * 2592000) ||
+ cuser.badpost > (255 - (unsigned int)(bcache[author - 1].post_limit_badpost)) ||
+ cuser.numlogins < ((unsigned int)(bcache[author - 1].post_limit_logins) * 10) ||
+ cuser.numposts < ((unsigned int)(bcache[author - 1].post_limit_posts) * 10)) ) {
+ vmsg("你不夠資深喔! (可在目的看板內按大寫 I 查看限制)");
+ return FULLUPDATE;
+ }
+
+#ifdef USE_COOLDOWN
+ if(check_cooldown(getbcache(author)))
+ return FULLUPDATE;
+#endif
+
+ ent = 1;
+ author = 0;
+ if (HasUserPerm(PERM_SYSOP) || !strcmp(fhdr->owner, cuser.userid)) {
+ getdata(2, 0, "(1)原文轉載 (2)舊轉錄格式?[1] ",
+ genbuf, 3, DOECHO);
+ if (genbuf[0] != '2') {
+ ent = 0;
+ getdata(2, 0, "保留原作者名稱嗎?[Y] ", inputbuf, 3, DOECHO);
+ if (inputbuf[0] != 'n' && inputbuf[0] != 'N')
+ author = '1';
+ }
+ }
+ if (ent)
+ snprintf(xtitle, sizeof(xtitle), "[轉錄]%.66s", fhdr->title);
+ else
+ strlcpy(xtitle, fhdr->title, sizeof(xtitle));
+
+ snprintf(genbuf, sizeof(genbuf), "採用原標題《%.60s》嗎?[Y] ", xtitle);
+ getdata(2, 0, genbuf, genbuf2, 4, LCECHO);
+ if (genbuf2[0] == 'n' || genbuf2[0] == 'N') {
+ if (getdata_str(2, 0, "標題:", genbuf, TTLEN, DOECHO, xtitle))
+ strlcpy(xtitle, genbuf, sizeof(xtitle));
+ }
+ getdata(2, 0, "(S)存檔 (L)站內 (Q)取消?[Q] ", genbuf, 3, LCECHO);
+ if (genbuf[0] == 'l' || genbuf[0] == 's') {
+ int currmode0 = currmode;
+ const char *save_currboard;
+
+ currmode = 0;
+ setbpath(xfpath, xboard);
+ stampfile(xfpath, &xfile);
+ if (author)
+ strlcpy(xfile.owner, fhdr->owner, sizeof(xfile.owner));
+ else
+ strlcpy(xfile.owner, cuser.userid, sizeof(xfile.owner));
+ strlcpy(xfile.title, xtitle, sizeof(xfile.title));
+ if (genbuf[0] == 'l') {
+ xfile.filemode = FILE_LOCAL;
+ }
+ setbfile(fname, currboard, fhdr->filename);
+ xptr = fopen(xfpath, "w");
+
+ strlcpy(save_title, xfile.title, sizeof(save_title));
+ save_currboard = currboard;
+ currboard = xboard;
+ write_header(xptr, save_title);
+ currboard = save_currboard;
+
+ if ((bp->brdattr & BRD_HIDE) && (bp->brdattr & BRD_POSTMASK))
+ {
+ /* invisible board */
+ fprintf(xptr, "※ [本文轉錄自某隱形看板]\n\n");
+ b_suckinfile_invis(xptr, fname, currboard);
+ } else {
+ /* public board */
+ fprintf(xptr, "※ [本文轉錄自 %s 看板]\n\n", currboard);
+ b_suckinfile(xptr, fname);
+ }
+
+ addsignature(xptr, 0);
+ fclose(xptr);
+
+#ifdef USE_AUTOCPLOG
+ /* add cp log. bp is currboard now. */
+ if(bp->brdattr & BRD_CPLOG)
+ {
+ char buf[MAXPATHLEN], tail[STRLEN];
+ char bname[STRLEN] = "";
+ struct tm *ptime = localtime4(&now);
+ int maxlength = 51 +2 - 6;
+ int bid = getbnum(xboard);
+
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ bp = getbcache(bid);
+ if ((bp->brdattr & BRD_HIDE) && (bp->brdattr & BRD_POSTMASK))
+ {
+ /* mosaic it */
+ /*
+ // mosaic method 1
+ char *pbname = bname;
+ while(*pbname)
+ *pbname++ = '?';
+ */
+ // mosaic method 2
+ strcpy(bname, "某隱形看板");
+ } else {
+ sprintf(bname, "看板 %s", xboard);
+ }
+
+ maxlength -= (strlen(cuser.userid) + strlen(bname));
+
+#ifdef GUESTRECOMMEND
+ snprintf(tail, sizeof(tail),
+ "%15s %02d/%02d",
+ fromhost,
+ ptime->tm_mon + 1, ptime->tm_mday);
+#else
+ maxlength += (15 - 6);
+ snprintf(tail, sizeof(tail),
+ " %02d/%02d %02d:%02d",
+ ptime->tm_mon + 1, ptime->tm_mday,
+ ptime->tm_hour, ptime->tm_min);
+#endif
+ snprintf(buf, sizeof(buf),
+ // ANSI_COLOR(32) <- system will add green
+ "※ " ANSI_COLOR(1;32) "%s"
+ ANSI_COLOR(0;32) ":轉錄至"
+ "%s" ANSI_RESET "%*s%s\n" ,
+ cuser.userid, bname, maxlength, "",
+ tail);
+
+ do_add_recommend(direct, fhdr, ent, buf, 2);
+ } else
+#endif
+ {
+ int bid = getbnum(xboard);
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ /* now point bp to new bord */
+ bp = getbcache(bid);
+ }
+
+ /*
+ * Cross fs有問題 } else { unlink(xfpath); link(fname, xfpath); }
+ */
+ setbdir(fname, xboard);
+ append_record(fname, &xfile, sizeof(xfile));
+ if (!xfile.filemode && !(bp->brdattr & BRD_NOTRAN))
+ outgo_post(&xfile, xboard, cuser.userid, cuser.nickname);
+#ifdef USE_COOLDOWN
+ if(bp->nuser>30)
+ {
+ if (cooldowntimeof(usernum)<now)
+ add_cooldowntime(usernum, 5);
+ }
+ add_posttimes(usernum, 1);
+#endif
+ setbtotal(getbnum(xboard));
+
+ if (strcmp(xboard, "Test") == 0)
+ outs("測試信件不列入紀錄,敬請包涵。");
+ else
+ cuser.numposts++;
+
+ UPDATE_USEREC;
+ outs("文章轉錄完成");
+ pressanykey();
+ currmode = currmode0;
+ }
+ return FULLUPDATE;
+}
+
+static int
+read_post(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char genbuf[100];
+ int more_result;
+
+ if (fhdr->owner[0] == '-' || fhdr->filename[0] == 'L')
+ return READ_SKIP;
+
+ STATINC(STAT_READPOST);
+ setdirpath(genbuf, direct, fhdr->filename);
+
+ more_result = more(genbuf, YEA);
+
+ {
+ int posttime=atoi(fhdr->filename+2);
+ if(posttime>now-12*3600)
+ STATINC(STAT_READPOST_12HR);
+ else if(posttime>now-1*86400)
+ STATINC(STAT_READPOST_1DAY);
+ else if(posttime>now-3*86400)
+ STATINC(STAT_READPOST_3DAY);
+ else if(posttime>now-7*86400)
+ STATINC(STAT_READPOST_7DAY);
+ else
+ STATINC(STAT_READPOST_OLD);
+ }
+ brc_addlist(fhdr->filename);
+ strlcpy(currtitle, subject(fhdr->title), sizeof(currtitle));
+
+ switch(more_result)
+ {
+ case -1:
+ clear();
+ vmsg("此文章無內容");
+ return FULLUPDATE;
+ case 999:
+ return do_reply(fhdr);
+ case 998:
+ recommend(ent, fhdr, direct);
+ return FULLUPDATE;
+ }
+ if(more_result)
+ return more_result;
+ return FULLUPDATE;
+}
+
+int
+do_limitedit(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char genbuf[256], buf[256];
+ int temp;
+ boardheader_t *bp = getbcache(currbid);
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ if (!((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP) ||
+ (HasUserPerm(PERM_SYSSUPERSUBOP) && GROUPOP())))
+ return DONOTHING;
+
+ strcpy(buf, "更改 ");
+ if (HasUserPerm(PERM_SYSOP) || (HasUserPerm(PERM_SYSSUPERSUBOP) && GROUPOP()))
+ strcat(buf, "(A)本板發表限制 ");
+ strcat(buf, "(B)本板預設");
+ if (fhdr->filemode & FILE_VOTE)
+ strcat(buf, " (C)本篇");
+ strcat(buf, "連署限制 (Q)取消?[Q]");
+ genbuf[0] = getans(buf);
+
+ if ((HasUserPerm(PERM_SYSOP) || (HasUserPerm(PERM_SYSSUPERSUBOP) && GROUPOP())) && genbuf[0] == 'a') {
+ sprintf(genbuf, "%u", bp->post_limit_regtime);
+ do {
+ getdata_buf(b_lines - 1, 0, "註冊時間限制 (以'月'為單位,0~255):", genbuf, 4, LCECHO);
+ temp = atoi(genbuf);
+ } while (temp < 0 || temp > 255);
+ bp->post_limit_regtime = (unsigned char)temp;
+
+ sprintf(genbuf, "%u", bp->post_limit_logins * 10);
+ do {
+ getdata_buf(b_lines - 1, 0, "上站次數下限 (0~2550):", genbuf, 5, LCECHO);
+ temp = atoi(genbuf);
+ } while (temp < 0 || temp > 2550);
+ bp->post_limit_logins = (unsigned char)(temp / 10);
+
+ sprintf(genbuf, "%u", bp->post_limit_posts * 10);
+ do {
+ getdata_buf(b_lines - 1, 0, "文章篇數下限 (0~2550):", genbuf, 5, LCECHO);
+ temp = atoi(genbuf);
+ } while (temp < 0 || temp > 2550);
+ bp->post_limit_posts = (unsigned char)(temp / 10);
+
+ sprintf(genbuf, "%u", 255 - bp->post_limit_badpost);
+ do {
+ getdata_buf(b_lines - 1, 0, "劣文篇數上限 (0~255):", genbuf, 5, LCECHO);
+ temp = atoi(genbuf);
+ } while (temp < 0 || temp > 255);
+ bp->post_limit_badpost = (unsigned char)(255 - temp);
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
+ log_usies("SetBoard", bp->brdname);
+ vmsg("修改完成!");
+ return FULLUPDATE;
+ }
+ else if (genbuf[0] == 'b') {
+ sprintf(genbuf, "%u", bp->vote_limit_regtime);
+ do {
+ getdata_buf(b_lines - 1, 0, "註冊時間限制 (以'月'為單位,0~255):", genbuf, 4, LCECHO);
+ temp = atoi(genbuf);
+ } while (temp < 0 || temp > 255);
+ bp->vote_limit_regtime = (unsigned char)temp;
+
+ sprintf(genbuf, "%u", bp->vote_limit_logins * 10);
+ do {
+ getdata_buf(b_lines - 1, 0, "上站次數下限 (0~2550):", genbuf, 5, LCECHO);
+ temp = atoi(genbuf);
+ } while (temp < 0 || temp > 2550);
+ bp->vote_limit_logins = (unsigned char)(temp / 10);
+
+ sprintf(genbuf, "%u", bp->vote_limit_posts * 10);
+ do {
+ getdata_buf(b_lines - 1, 0, "文章篇數下限 (0~2550):", genbuf, 5, LCECHO);
+ temp = atoi(genbuf);
+ } while (temp < 0 || temp > 2550);
+ bp->vote_limit_posts = (unsigned char)(temp / 10);
+
+ sprintf(genbuf, "%u", 255 - bp->vote_limit_badpost);
+ do {
+ getdata_buf(b_lines - 1, 0, "劣文篇數上限 (0~255):", genbuf, 5, LCECHO);
+ temp = atoi(genbuf);
+ } while (temp < 0 || temp > 255);
+ bp->vote_limit_badpost = (unsigned char)(255 - temp);
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
+ log_usies("SetBoard", bp->brdname);
+ vmsg("修改完成!");
+ return FULLUPDATE;
+ }
+ else if ((fhdr->filemode & FILE_VOTE) && genbuf[0] == 'c') {
+ sprintf(genbuf, "%u", fhdr->multi.vote_limits.regtime);
+ do {
+ getdata_buf(b_lines - 1, 0, "註冊時間限制 (以'月'為單位,0~255):", genbuf, 4, LCECHO);
+ temp = atoi(genbuf);
+ } while (temp < 0 || temp > 255);
+ fhdr->multi.vote_limits.regtime = temp;
+
+ sprintf(genbuf, "%u", (unsigned int)(fhdr->multi.vote_limits.logins) * 10);
+ do {
+ getdata_buf(b_lines - 1, 0, "上站次數下限 (0~2550):", genbuf, 5, LCECHO);
+ temp = atoi(genbuf);
+ } while (temp < 0 || temp > 2550);
+ temp /= 10;
+ fhdr->multi.vote_limits.logins = (unsigned char)temp;
+
+ sprintf(genbuf, "%u", (unsigned int)(fhdr->multi.vote_limits.posts) * 10);
+ do {
+ getdata_buf(b_lines - 1, 0, "文章篇數下限 (0~2550):", genbuf, 5, LCECHO);
+ temp = atoi(genbuf);
+ } while (temp < 0 || temp > 2550);
+ temp /= 10;
+ fhdr->multi.vote_limits.posts = (unsigned char)temp;
+
+ sprintf(genbuf, "%u", (unsigned int)(fhdr->multi.vote_limits.badpost));
+ do {
+ getdata_buf(b_lines - 1, 0, "劣文篇數上限 (0~255):", genbuf, 5, LCECHO);
+ temp = atoi(genbuf);
+ } while (temp < 0 || temp > 255);
+ fhdr->multi.vote_limits.badpost = (unsigned char)temp;
+
+ substitute_ref_record(direct, fhdr, ent);
+ vmsg("修改完成!");
+ return FULLUPDATE;
+ }
+ vmsg("取消修改");
+ return FULLUPDATE;
+}
+
+/* ----------------------------------------------------- */
+/* 採集精華區 */
+/* ----------------------------------------------------- */
+static int
+b_man(void)
+{
+ char buf[PATHLEN];
+
+ setapath(buf, currboard);
+ if ((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)) {
+ char genbuf[128];
+ int fd;
+ snprintf(genbuf, sizeof(genbuf), "%s/.rebuild", buf);
+ if ((fd = open(genbuf, O_CREAT, 0640)) > 0)
+ close(fd);
+ }
+ return a_menu(currboard, buf, HasUserPerm(PERM_ALLBOARD) ? 2 :
+ (currmode & MODE_BOARD ? 1 : 0),
+ NULL);
+}
+
+#ifndef NO_GAMBLE
+static int
+stop_gamble(void)
+{
+ boardheader_t *bp = getbcache(currbid);
+ char fn_ticket[128], fn_ticket_end[128];
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ if (!bp->endgamble || bp->endgamble > now)
+ return 0;
+
+ setbfile(fn_ticket, currboard, FN_TICKET);
+ setbfile(fn_ticket_end, currboard, FN_TICKET_END);
+
+ rename(fn_ticket, fn_ticket_end);
+ if (bp->endgamble) {
+ bp->endgamble = 0;
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
+ }
+ return 1;
+}
+static int
+join_gamble(int ent, const fileheader_t * fhdr, const char *direct)
+{
+ if (!HasUserPerm(PERM_LOGINOK))
+ return DONOTHING;
+ if (stop_gamble()) {
+ vmsg("目前未舉辦賭盤或賭盤已開獎");
+ return DONOTHING;
+ }
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ ticket(currbid);
+ return FULLUPDATE;
+}
+static int
+hold_gamble(void)
+{
+ char fn_ticket[128], fn_ticket_end[128], genbuf[128], msg[256] = "",
+ yn[10] = "";
+ char tmp[128];
+ boardheader_t *bp = getbcache(currbid);
+ int i;
+ FILE *fp = NULL;
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ if (!(currmode & MODE_BOARD))
+ return 0;
+ if (bp->brdattr & BRD_BAD )
+ {
+ vmsg("違法看板禁止使用賭盤");
+ return 0;
+ }
+
+ setbfile(fn_ticket, currboard, FN_TICKET);
+ setbfile(fn_ticket_end, currboard, FN_TICKET_END);
+ setbfile(genbuf, currboard, FN_TICKET_LOCK);
+
+ 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);
+ if (bp->endgamble) {
+ bp->endgamble = 0;
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
+
+ }
+ return FULLUPDATE;
+ }
+ if (dashf(fn_ticket_end)) {
+ getdata(b_lines - 1, 0, "已經有舉辦賭盤, "
+ "是否要開獎 [否/是]?(N/y):", yn, 3, LCECHO);
+ if (yn[0] != 'y')
+ return FULLUPDATE;
+ if(cpuload(NULL) > MAX_CPULOAD/4)
+ {
+ vmsg("負荷過高 請於系統負荷低時開獎..");
+ return FULLUPDATE;
+ }
+ openticket(currbid);
+ return FULLUPDATE;
+ } else if (dashf(genbuf)) {
+ vmsg(" 目前系統正在處理開獎事宜, 請結果出爐後再舉辦.......");
+ 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(tmp, currboard, FN_TICKET_ITEMS ".tmp");
+
+ //sprintf(genbuf, "%s/" FN_TICKET_ITEMS, direct);
+
+ if (!(fp = fopen(tmp, "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);
+ if (!getdata(3, 0, "設定自動封盤時間?(Y/n)", yn, 3, LCECHO) || yn[0] != 'n') {
+ bp->endgamble = gettime(4, now, "封盤於");
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
+ }
+ move(6, 0);
+ snprintf(genbuf, sizeof(genbuf),
+ "\n請到 %s 板 按'f'參與賭博!\n\n"
+ "一張 %d Ptt幣, 這是%s的賭博\n%s%s\n",
+ currboard,
+ i, i < 100 ? "小賭式" : i < 500 ? "平民級" :
+ i < 1000 ? "貴族級" : i < 5000 ? "富豪級" : "傾家蕩產",
+ bp->endgamble ? "賭盤結束時間: " : "",
+ bp->endgamble ? Cdate(&bp->endgamble) : ""
+ );
+ strcat(msg, genbuf);
+ outs("請依次輸入彩票名稱, 需提供2~8項. (未滿八項, 輸入直接按Enter)\n");
+ //outs(ANSI_COLOR(1;33) "注意輸入後無法修改!\n");
+ for( i = 0 ; i < 8 ; ++i ){
+ snprintf(yn, sizeof(yn), " %d)", i + 1);
+ getdata(7 + i, 0, yn, genbuf, 9, DOECHO);
+ if (!genbuf[0] && i > 1)
+ break;
+ fprintf(fp, "%s\n", genbuf);
+ }
+ fclose(fp);
+
+ setbfile(genbuf, currboard, FN_TICKET_RECORD);
+ unlink(genbuf); // Ptt: 防堵利用不同id同時舉辦賭場
+ setbfile(genbuf, currboard, FN_TICKET_USER);
+ unlink(genbuf); // Ptt: 防堵利用不同id同時舉辦賭場
+
+ setbfile(genbuf, currboard, FN_TICKET_ITEMS);
+ setbfile(tmp, currboard, FN_TICKET_ITEMS ".tmp");
+ if(!dashf(fn_ticket))
+ Rename(tmp, genbuf);
+
+ snprintf(genbuf, sizeof(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);
+ /* 設定完才把檔名改過來 */
+
+ vmsg("賭盤設定完成");
+ return FULLUPDATE;
+}
+#endif
+
+static int
+cite_post(int ent, const fileheader_t * fhdr, const char *direct)
+{
+ char fpath[PATHLEN];
+ char title[TTLEN + 1];
+
+ setbfile(fpath, currboard, fhdr->filename);
+ strlcpy(title, "◇ ", sizeof(title));
+ strlcpy(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, const 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)) {
+ strlcpy(tmpfhdr.title, genbuf, sizeof(tmpfhdr.title));
+ dirty++;
+ }
+ }
+ if (HasUserPerm(PERM_SYSOP)) {
+ if (getdata(b_lines - 1, 0, "作者:", genbuf, IDLEN + 2, DOECHO)) {
+ strlcpy(tmpfhdr.owner, genbuf, sizeof(tmpfhdr.owner));
+ dirty++;
+ }
+ if (getdata(b_lines - 1, 0, "日期:", genbuf, 6, DOECHO)) {
+ snprintf(tmpfhdr.date, sizeof(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_ref_record(direct, fhdr, ent);
+ }
+ return FULLUPDATE;
+ }
+ return DONOTHING;
+}
+
+static int
+solve_post(int ent, fileheader_t * fhdr, const char *direct)
+{
+ if ((currmode & MODE_BOARD)) {
+ fhdr->filemode ^= FILE_SOLVED;
+ substitute_ref_record(direct, fhdr, ent);
+ return PART_REDRAW;
+ }
+ return DONOTHING;
+}
+
+
+static int
+recommend_cancel(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char yn[5];
+ if (!(currmode & MODE_BOARD))
+ return DONOTHING;
+ getdata(b_lines - 1, 0, "確定要推薦歸零[y/N]? ", yn, 5, LCECHO);
+ if (yn[0] != 'y')
+ return FULLUPDATE;
+#ifdef ASSESS
+ // to save resource
+ if (fhdr->recommend > 9)
+ inc_goodpost(fhdr->owner, -1 * (fhdr->recommend / 10));
+#endif
+ fhdr->recommend = 0;
+
+ substitute_ref_record(direct, fhdr, ent);
+ return FULLUPDATE;
+}
+
+static int
+do_add_recommend(const char *direct, fileheader_t *fhdr,
+ int ent, const char *buf, int type)
+{
+ char path[PATHLEN];
+ int update = 0;
+ /*
+ race here:
+ 為了減少 system calls , 現在直接用當前的推文數 +1 寫入 .DIR 中.
+ 造成
+ 1.若該文檔名被換掉的話, 推文將寫至舊檔名中 (造成幽靈檔)
+ 2.沒有重新讀一次, 所以推文數可能被少算
+ 3.若推的時候前文被刪, 將加到後文的推文數
+
+ */
+ setdirpath(path, direct, fhdr->filename);
+ if( log_file(path, 0, buf) == -1 ){ // 不 CREATE
+ vmsg("推薦/競標失敗");
+ return -1;
+ }
+
+ /* This is a solution to avoid most racing (still some), but cost four
+ * system calls. */
+ if(type == 0 && fhdr->recommend < 100 )
+ update = 1;
+ else if(type == 1 && fhdr->recommend > -100)
+ update = -1;
+
+ if( update ){
+ int fd;
+ //Ptt: update only necessary
+ if( (fd = open(direct, O_RDWR)) < 0 )
+ return -1;
+ if( lseek(fd, (sizeof(fileheader_t) * (ent - 1) +
+ (char *)&fhdr->recommend - (char *)fhdr),
+ SEEK_SET) >= 0 ){
+ // 如果 lseek 失敗就不會 write
+ read(fd, &fhdr->recommend, sizeof(char));
+ fhdr->recommend += update;
+ lseek(fd, -1, SEEK_CUR);
+ write(fd, &fhdr->recommend, sizeof(char));
+ }
+ close(fd);
+ }
+ return 0;
+}
+
+static int
+do_bid(int ent, fileheader_t * fhdr, const boardheader_t *bp,
+ const char *direct, const struct tm *ptime)
+{
+ char genbuf[200], fpath[PATHLEN],say[30],*money;
+ bid_t bidinfo;
+ int mymax, next;
+
+ setdirpath(fpath, direct, fhdr->filename);
+ strcat(fpath, ".bid");
+ get_record(fpath, &bidinfo, sizeof(bidinfo), 1);
+
+ move(18,0);
+ clrtobot();
+ prints("競標主題: %s\n", fhdr->title);
+ print_bidinfo(0, bidinfo);
+ money = bidinfo.payby ? " NT$ " : "Ptt$ ";
+ if( now > bidinfo.enddate || bidinfo.high == bidinfo.buyitnow ){
+ outs("此競標已經結束,");
+ if( bidinfo.userid[0] ) {
+ /*if(!payby && bidinfo.usermax!=-1)
+ {以Ptt幣自動扣款
+ }*/
+ prints("恭喜 %s 以 %d 得標!", bidinfo.userid, bidinfo.high);
+#ifdef ASSESS
+ if (!(bidinfo.flag & SALE_COMMENTED) && strcmp(bidinfo.userid, currutmp->userid) == 0){
+ char tmp = getans("您對於這次交易的評價如何? 1:佳 2:欠佳 3:普通[Q]");
+ if ('1' <= tmp && tmp <= '3'){
+ switch(tmp){
+ case 1:
+ inc_goodsale(bidinfo.userid, 1);
+ break;
+ case 2:
+ inc_badsale(bidinfo.userid, 1);
+ break;
+ }
+ bidinfo.flag |= SALE_COMMENTED;
+
+ substitute_record(fpath, &bidinfo, sizeof(bidinfo), 1);
+ }
+ }
+#endif
+ }
+ else outs("無人得標!");
+ pressanykey();
+ return FULLUPDATE;
+ }
+
+ if( bidinfo.userid[0] ){
+ prints("下次出價至少要:%s%d", money,bidinfo.high + bidinfo.increment);
+ if( bidinfo.buyitnow )
+ prints(" (輸入 %d 等於以直接購買結束)",bidinfo.buyitnow);
+ next = bidinfo.high + bidinfo.increment;
+ }
+ else{
+ prints("起標價: %d", bidinfo.high);
+ next=bidinfo.high;
+ }
+ if( !strcmp(cuser.userid,bidinfo.userid) ){
+ outs("你是最高得標者!");
+ pressanykey();
+ return FULLUPDATE;
+ }
+ if( strcmp(cuser.userid, fhdr->owner) == 0 ){
+ vmsg("警告! 本人不能出價!");
+ getdata_str(23, 0, "是否要提早結標? (y/N)", genbuf, 3, LCECHO,"n");
+ if( genbuf[0] != 'y' )
+ return FULLUPDATE;
+ snprintf(genbuf, sizeof(genbuf),
+ ANSI_COLOR(1;31) "→ "
+ ANSI_COLOR(33) "賣方%s提早結標"
+ ANSI_RESET "%*s"
+ "標%15s %02d/%02d\n",
+ cuser.userid, (int)(45 - strlen(cuser.userid) - strlen(money)),
+ " ", fromhost, ptime->tm_mon + 1, ptime->tm_mday);
+ do_add_recommend(direct, fhdr, ent, genbuf, 0);
+ bidinfo.enddate = now;
+ substitute_record(fpath, &bidinfo, sizeof(bidinfo), 1);
+ vmsg("提早結標完成");
+ return FULLUPDATE;
+ }
+ getdata_str(23, 0, "是否要下標? (y/N)", genbuf, 3, LCECHO,"n");
+ if( genbuf[0] != 'y' )
+ return FULLUPDATE;
+
+ getdata(23, 0, "您的最高下標金額(0:取消):", genbuf, 10, LCECHO);
+ mymax = atoi(genbuf);
+ if( mymax <= 0 ){
+ vmsg("取消下標");
+ return FULLUPDATE;
+ }
+
+ getdata(23,0,"下標感言:",say,12,DOECHO);
+ get_record(fpath, &bidinfo, sizeof(bidinfo), 1);
+
+ if( bidinfo.buyitnow && mymax > bidinfo.buyitnow )
+ mymax = bidinfo.buyitnow;
+ else if( !bidinfo.userid[0] )
+ next = bidinfo.high;
+ else
+ next = bidinfo.high + bidinfo.increment;
+
+ if( mymax< next || (bidinfo.payby == 0 && cuser.money < mymax) ){
+ vmsg("標金不足搶標");
+ return FULLUPDATE;
+ }
+
+ snprintf(genbuf, sizeof(genbuf),
+ ANSI_COLOR(1;31) "→ " ANSI_COLOR(33) "%s" ANSI_RESET ANSI_COLOR(33) ":%s" ANSI_RESET "%*s"
+ "%s%-15d標%15s %02d/%02d\n",
+ cuser.userid, say,
+ (int)(31 - strlen(cuser.userid) - strlen(say)), " ",
+ money,
+ next, fromhost,
+ ptime->tm_mon + 1, ptime->tm_mday);
+ do_add_recommend(direct, fhdr, ent, genbuf, 0);
+ if( next > bidinfo.usermax ){
+ bidinfo.usermax = mymax;
+ bidinfo.high = next;
+ strcpy(bidinfo.userid, cuser.userid);
+ }
+ else if( mymax > bidinfo.usermax ) {
+ bidinfo.high = bidinfo.usermax + bidinfo.increment;
+ if( bidinfo.high > mymax )
+ bidinfo.high = mymax;
+ bidinfo.usermax = mymax;
+ strcpy(bidinfo.userid, cuser.userid);
+
+ snprintf(genbuf, sizeof(genbuf),
+ ANSI_COLOR(1;31) "→ " ANSI_COLOR(33) "自動競標%s勝出" ANSI_RESET
+ ANSI_COLOR(33) ANSI_RESET "%*s%s%-15d標 %02d/%02d\n",
+ cuser.userid,
+ (int)(20 - strlen(cuser.userid)), " ", money,
+ bidinfo.high,
+ ptime->tm_mon + 1, ptime->tm_mday);
+ do_add_recommend(direct, fhdr, ent, genbuf, 0);
+ }
+ else {
+ if( mymax + bidinfo.increment < bidinfo.usermax )
+ bidinfo.high = mymax + bidinfo.increment;
+ else
+ bidinfo.high=bidinfo.usermax; /*這邊怪怪的*/
+ snprintf(genbuf, sizeof(genbuf),
+ ANSI_COLOR(1;31) "→ " ANSI_COLOR(33) "自動競標%s勝出"
+ ANSI_RESET ANSI_COLOR(33) ANSI_RESET "%*s%s%-15d標 %02d/%02d\n",
+ bidinfo.userid,
+ (int)(20 - strlen(bidinfo.userid)), " ", money,
+ bidinfo.high,
+ ptime->tm_mon + 1, ptime->tm_mday);
+ do_add_recommend(direct, fhdr, ent, genbuf, 0);
+ }
+ substitute_record(fpath, &bidinfo, sizeof(bidinfo), 1);
+ vmsg("恭喜您! 以最高價搶標完成!");
+ return FULLUPDATE;
+}
+
+static int
+recommend(int ent, fileheader_t * fhdr, const char *direct)
+{
+ struct tm *ptime = localtime4(&now);
+ char buf[PATHLEN], msg[STRLEN];
+#ifndef OLDRECOMMEND
+ static const char *ctype[3] = {
+ "推", "噓", "→"
+ };
+ static const char *ctype_attr[3] = {
+ ANSI_COLOR(1;33),
+ ANSI_COLOR(1;31),
+ ANSI_COLOR(1;37),
+ }, *ctype_attr2[3] = {
+ ANSI_COLOR(1;37),
+ ANSI_COLOR(1;31),
+ ANSI_COLOR(1;31),
+ }, *ctype_long[3] = {
+ "值得推薦",
+ "給它噓聲",
+ "只加→註解"
+ };
+#endif
+ int type, maxlength;
+ boardheader_t *bp;
+ static time4_t lastrecommend = 0;
+ static int lastrecommend_bid = -1;
+ static char lastrecommend_fname[FNLEN] = "";
+ int isGuest = (strcmp(cuser.userid, STR_GUEST) == EQUSTR);
+ int logIP = 0;
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ bp = getbcache(currbid);
+ if (bp->brdattr & BRD_NORECOMMEND || fhdr->filename[0] == 'L' ||
+ ((fhdr->filemode & FILE_MARKED) && (fhdr->filemode & FILE_SOLVED))) {
+ vmsg("抱歉, 禁止推薦或競標");
+ return FULLUPDATE;
+ }
+
+ if ( !CheckPostPerm() ||
+ bp->brdattr & BRD_VOTEBOARD ||
+#ifndef GUESTRECOMMEND
+ isGuest ||
+#endif
+ fhdr->filemode & FILE_VOTE) {
+ vmsg("您權限不足, 無法推薦!");
+ return FULLUPDATE;
+ }
+
+#ifdef SAFE_ARTICLE_DELETE
+ if (fhdr->filename[0] == '.') {
+ vmsg("本文已刪除");
+ return FULLUPDATE;
+ }
+#endif
+
+ if( fhdr->filemode & FILE_BID){
+ return do_bid(ent, fhdr, bp, direct, ptime);
+ }
+
+#ifndef DEBUG
+ if ( !((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)) &&
+ (cuser.firstlogin > (now - (time4_t)bcache[currbid - 1].post_limit_regtime * 2592000) ||
+ cuser.badpost > (255 - (unsigned int)(bcache[currbid - 1].post_limit_badpost)) ||
+ cuser.numlogins < ((unsigned int)(bcache[currbid - 1].post_limit_logins) * 10) ||
+ cuser.numposts < ((unsigned int)(bcache[currbid - 1].post_limit_posts) * 10)) ) {
+ move(5, 10);
+ vmsg("你不夠資深喔! (可按大寫 I 查看限制)");
+ return FULLUPDATE;
+ }
+#endif
+
+ if((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP))
+ {
+ /* I'm BM or SYSOP. */
+ }
+ else if (bp->brdattr & BRD_NOFASTRECMD)
+ {
+ int d = (int)bp->fastrecommend_pause - (now - lastrecommend);
+ if (d > 0)
+ {
+ vmsgf("本板禁止快速連續推文,請再等 %d 秒", d);
+ return FULLUPDATE;
+ }
+ }
+ {
+ // kcwu
+ static unsigned char lastrecommend_minute = 0;
+ static unsigned short recommend_in_minute = 0;
+ unsigned char now_in_minute = (unsigned char)(now / 60);
+ if(now_in_minute != lastrecommend_minute) {
+ recommend_in_minute = 0;
+ lastrecommend_minute = now_in_minute;
+ }
+ recommend_in_minute++;
+ if(recommend_in_minute>60) {
+ vmsg("系統禁止短時間內大量推文");
+ return FULLUPDATE;
+ }
+ }
+
+#ifdef USE_COOLDOWN
+ if(check_cooldown(bp))
+ return FULLUPDATE;
+#endif
+
+ type = 0;
+
+ move(b_lines, 0); clrtoeol();
+
+ if (fhdr->recommend == 0 && strcmp(cuser.userid, fhdr->owner) == 0)
+ {
+ // owner recomment first time
+ type = 2;
+ move(b_lines-1, 0); clrtoeol();
+ outs("本人推薦第一次, 使用 → 加註方式\n");
+ }
+#ifndef DEBUG
+ else if (!(currmode & MODE_BOARD) &&
+ (now - lastrecommend) < (
+#if 0
+ /* i'm not sure whether this is better or not */
+ (bp->brdattr & BRD_NOFASTRECMD) ?
+ bp->fastrecommend_pause :
+#endif
+ 90))
+ {
+ // too close
+ type = 2;
+ move(b_lines-1, 0); clrtoeol();
+ outs("推薦時間太近, 使用 → 加註方式\n");
+ }
+#endif
+
+#ifndef OLDRECOMMEND
+ else if (!(bp->brdattr & BRD_NOBOO))
+ {
+ /* most people use recommendation just for one-line reply.
+ * so we change default to (2)= comment only now.
+#define RECOMMEND_DEFAULT_VALUE (2)
+ */
+#define RECOMMEND_DEFAULT_VALUE (0) /* current user behavior */
+
+ outs(ANSI_COLOR(1) "您覺得這篇文章 ");
+ prints("%s1.%s %s2.%s %s3.%s " ANSI_RESET "[%d]? ",
+ ctype_attr[0], ctype_long[0],
+ ctype_attr[1], ctype_long[1],
+ ctype_attr[2], ctype_long[2],
+ RECOMMEND_DEFAULT_VALUE+1);
+
+ // poor BBS term has problem positioning with ANSI.
+ move(b_lines, 55);
+ type = igetch() - '1';
+ if(type < 0 || type > 2)
+ type = RECOMMEND_DEFAULT_VALUE;
+ move(b_lines, 0); clrtoeol();
+ }
+#endif
+
+ if(type > 2 || type < 0)
+ type = 0;
+
+ maxlength = 78 -
+ 3 /* lead */ -
+ 6 /* date */ -
+ 1 /* space */ -
+ 6 /* time */;
+
+ if (bp->brdattr & BRD_IPLOGRECMD || isGuest)
+ {
+ maxlength -= 15 /* IP */;
+ logIP = 1;
+ }
+
+#ifdef OLDRECOMMEND
+ maxlength -= 2; /* '推' */
+ maxlength -= strlen(cuser.userid);
+ sprintf(buf, "%s %s:", "→" , cuser.userid);
+
+#else
+ maxlength -= strlen(cuser.userid);
+ sprintf(buf, "%s %s:", ctype[type], cuser.userid);
+#endif
+
+ if (!getdata(b_lines, 0, buf, msg, maxlength, DOECHO))
+ return FULLUPDATE;
+
+#if 0
+ scroll();
+ if(getans("確定要%s嗎? 請仔細考慮[y/N]: ", ctype[type]) != 'y')
+ return FULLUPDATE;
+#else
+
+ {
+ char ans[3];
+ sprintf(buf+strlen(buf), ANSI_COLOR(7) "%-*s"
+ ANSI_RESET " 確定[y/N]:", maxlength, msg);
+ if(!getdata(b_lines, 0, buf, ans, sizeof(ans), LCECHO) ||
+ ans[0] != 'y')
+ return FULLUPDATE;
+ }
+#endif
+
+ STATINC(STAT_RECOMMEND);
+
+ {
+ /* build tail first. */
+ char tail[STRLEN];
+
+ if(logIP)
+ {
+ snprintf(tail, sizeof(tail),
+ "%15s %02d/%02d %02d:%02d",
+ fromhost,
+ ptime->tm_mon+1, ptime->tm_mday,
+ ptime->tm_hour, ptime->tm_min);
+ } else {
+ snprintf(tail, sizeof(tail),
+ " %02d/%02d %02d:%02d",
+ ptime->tm_mon+1, ptime->tm_mday,
+ ptime->tm_hour, ptime->tm_min);
+ }
+
+#ifdef OLDRECOMMEND
+ snprintf(buf, sizeof(buf),
+ ANSI_COLOR(1;31) "→ " ANSI_COLOR(33) "%s"
+ ANSI_RESET ANSI_COLOR(33) ":%-*s" ANSI_RESET
+ "推%s\n",
+ cuser.userid, maxlength, msg, tail);
+#else
+ snprintf(buf, sizeof(buf),
+ "%s%s " ANSI_COLOR(33) "%s" ANSI_RESET ANSI_COLOR(33)
+ ":%-*s" ANSI_RESET "%s\n",
+ ctype_attr2[type], ctype[type], cuser.userid,
+ maxlength, msg, tail);
+#endif
+ }
+
+ do_add_recommend(direct, fhdr, ent, buf, type);
+
+#ifdef ASSESS
+ /* 每 10 次推文 加一次 goodpost */
+ if (type ==0 && (fhdr->filemode & FILE_MARKED) && fhdr->recommend % 10 == 0)
+ inc_goodpost(fhdr->owner, 1);
+#endif
+ lastrecommend = now;
+ lastrecommend_bid = currbid;
+ strlcpy(lastrecommend_fname, fhdr->filename, sizeof(lastrecommend_fname));
+ return FULLUPDATE;
+}
+
+static int
+mark_post(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char buf[STRLEN], fpath[STRLEN];
+
+ if (!(currmode & MODE_BOARD))
+ return DONOTHING;
+
+ setbpath(fpath, currboard);
+ sprintf(buf, "%s/%s", fpath, fhdr->filename);
+
+ if( !(fhdr->filemode & FILE_MARKED) && /* 若目前還沒有 mark 才要 check */
+ access(buf, F_OK) < 0 )
+ return DONOTHING;
+
+ fhdr->filemode ^= FILE_MARKED;
+
+#ifdef ASSESS
+ if (!(fhdr->filemode & FILE_BID)){
+ if (fhdr->filemode & FILE_MARKED) {
+ if (!(currbrdattr & BRD_BAD) && fhdr->recommend >= 10)
+ inc_goodpost(fhdr->owner, fhdr->recommend / 10);
+ }
+ else if (fhdr->recommend > 9)
+ inc_goodpost(fhdr->owner, -1 * (fhdr->recommend / 10));
+ }
+#endif
+
+ substitute_ref_record(direct, fhdr, ent);
+ return PART_REDRAW;
+}
+
+int
+del_range(int ent, const fileheader_t *fhdr, const char *direct)
+{
+ char num1[8], num2[8];
+ int inum1, inum2;
+ boardheader_t *bp = NULL;
+
+ /* 有三種情況會進這裡, 信件, 看板, 精華區 */
+ if( !(direct[0] == 'h') ){ /* 信件不用 check */
+ bp = getbcache(currbid);
+ if (strcmp(bp->brdname, "Security") == 0)
+ return DONOTHING;
+ }
+
+ /* rocker.011018: 串接模式下還是不允許刪除比較好 */
+ if (currmode & MODE_SELECT) {
+ vmsg("請先回到正常模式後再進行刪除...");
+ return FULLUPDATE;
+ }
+
+ if ((currstat != READING) || (currmode & MODE_BOARD)) {
+ getdata(1, 0, "[設定刪除範圍] 起點:", num1, 6, DOECHO);
+ inum1 = atoi(num1);
+ if (inum1 <= 0) {
+ vmsg("起點有誤");
+ return FULLUPDATE;
+ }
+ getdata(1, 28, "終點:", num2, 6, DOECHO);
+ inum2 = atoi(num2);
+ if (inum2 < inum1) {
+ vmsg("終點有誤");
+ return FULLUPDATE;
+ }
+ getdata(1, 48, msg_sure_ny, num1, 3, LCECHO);
+ if (*num1 == 'y') {
+ outmsg("處理中,請稍後...");
+ refresh();
+#ifdef SAFE_ARTICLE_DELETE
+ if(bp && !(currmode & MODE_DIGEST) && bp->nuser > 30 )
+ safe_article_delete_range(direct, inum1, inum2);
+ else
+ delete_range(direct, inum1, inum2);
+#else
+ delete_range(direct, inum1, inum2);
+#endif
+ fixkeep(direct, inum1);
+
+ if (currmode & MODE_BOARD) // Ptt:update cache
+ setbtotal(currbid);
+ else if(currstat == RMAIL)
+ setupmailusage();
+
+ return DIRCHANGED;
+ }
+ return FULLUPDATE;
+ }
+ return DONOTHING;
+}
+
+static int
+del_post(int ent, fileheader_t * fhdr, char *direct)
+{
+ char genbuf[100], newpath[PATHLEN];
+ int not_owned, tusernum;
+ boardheader_t *bp;
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ bp = getbcache(currbid);
+
+ /* TODO recursive lookup */
+ if (currmode & MODE_SELECT) {
+ vmsg("請回到一般模式再刪除文章");
+ return DONOTHING;
+ }
+
+ if(fhdr->filemode & FILE_ANONYMOUS)
+ /* When the file is anonymous posted, fhdr->multi.anon_uid is author.
+ * see do_general() */
+ tusernum = fhdr->multi.anon_uid;
+ else
+ tusernum = searchuser(fhdr->owner, NULL);
+
+ if (strcmp(bp->brdname, "Security") == 0)
+ return DONOTHING;
+ if ((fhdr->filemode & FILE_BOTTOM) ||
+ (fhdr->filemode & FILE_MARKED) || (fhdr->filemode & FILE_DIGEST) ||
+ (fhdr->owner[0] == '-'))
+ return DONOTHING;
+
+ not_owned = (tusernum == usernum ? 0: 1);
+ if ((!(currmode & MODE_BOARD) && not_owned) ||
+ ((bp->brdattr & BRD_VOTEBOARD) && !HasUserPerm(PERM_SYSOP)) ||
+ !strcmp(cuser.userid, STR_GUEST))
+ return DONOTHING;
+
+ if (fhdr->filename[0]=='L') fhdr->filename[0]='M';
+
+ getdata(1, 0, msg_del_ny, genbuf, 3, LCECHO);
+ if (genbuf[0] == 'y') {
+ if(
+#ifdef SAFE_ARTICLE_DELETE
+ (bp->nuser > 30 && !(currmode & MODE_DIGEST) &&
+ !safe_article_delete(ent, fhdr, direct)) ||
+#endif
+ !delete_record(direct, sizeof(fileheader_t), ent)
+ ) {
+
+ cancelpost(fhdr, not_owned, newpath);
+ deleteCrossPost(fhdr, bp->brdname);
+#ifdef ASSESS
+#define SIZE sizeof(badpost_reason) / sizeof(char *)
+
+ if (not_owned && tusernum > 0 && !(currmode & MODE_DIGEST)) {
+ if (now - atoi(fhdr->filename + 2) > 7 * 24 * 60 * 60)
+ /* post older than a week */
+ genbuf[0] = 'n';
+ else
+ getdata(1, 40, "惡劣文章?(y/N)", genbuf, 3, LCECHO);
+
+ if (genbuf[0]=='y') {
+ int i;
+ char *userid=getuserid(tusernum);
+ int rpt_bid;
+
+ move(b_lines - 2, 0);
+ for (i = 0; i < SIZE; i++)
+ prints("%d.%s ", i + 1, badpost_reason[i]);
+ prints("%d.%s", i + 1, "其他");
+ getdata(b_lines - 1, 0, "請選擇[0:取消劣文]:", genbuf, 3, LCECHO);
+ i = genbuf[0] - '1';
+ if (i >= 0 && i < SIZE)
+ sprintf(genbuf,"劣文退回(%s)", badpost_reason[i]);
+ else if(i==SIZE)
+ {
+ strcpy(genbuf,"劣文退回(");
+ getdata_buf(b_lines, 0, "請輸入原因", genbuf+9,
+ 50, DOECHO);
+ strcat(genbuf,")");
+ }
+ if(i>=0 && i <= SIZE)
+ {
+ strncat(genbuf, fhdr->title, 64-strlen(genbuf));
+
+#ifdef USE_COOLDOWN
+ add_cooldowntime(tusernum, 60);
+ add_posttimes(tusernum, 15); //Ptt: 凍結 post for 1 hour
+#endif
+
+ if (!(inc_badpost(userid, 1) % 5)){
+ userec_t xuser;
+ post_violatelaw(userid, "Ptt 系統警察", "劣文累計 5 篇", "罰單一張");
+ mail_violatelaw(userid, "Ptt 系統警察", "劣文累計 5 篇", "罰單一張");
+ kick_all(userid);
+ passwd_query(tusernum, &xuser);
+ xuser.money = moneyof(tusernum);
+ xuser.vl_count++;
+ xuser.userlevel |= PERM_VIOLATELAW;
+ xuser.timeviolatelaw = now;
+ passwd_update(tusernum, &xuser);
+ }
+ sendalert(userid, ALERT_PWD_BADPOST);
+ mail_id(userid, genbuf, newpath, cuser.userid);
+
+#ifdef BAD_POST_RECORD
+ rpt_bid = getbnum(BAD_POST_RECORD);
+ if (rpt_bid > 0) {
+ fileheader_t report_fh;
+ char report_path[PATHLEN];
+
+ setbpath(report_path, BAD_POST_RECORD);
+ stampfile(report_path, &report_fh);
+
+ strcpy(report_fh.owner, "[Ptt警察局]");
+ snprintf(report_fh.title, sizeof(report_fh.title),
+ "%s 板 %s 板主給予 %s 一篇劣文",
+ currboard, cuser.userid, userid);
+ Copy(newpath, report_path);
+
+ setbdir(report_path, BAD_POST_RECORD);
+ append_record(report_path, &report_fh, sizeof(report_fh));
+
+ touchbtotal(rpt_bid);
+ }
+#endif /* defined(BAD_POST_RECORD) */
+ }
+ }
+ }
+#undef SIZE
+#endif
+
+ setbtotal(currbid);
+ if (fhdr->multi.money < 0 || fhdr->filemode & FILE_ANONYMOUS)
+ fhdr->multi.money = 0;
+ if (not_owned && tusernum && fhdr->multi.money > 0 &&
+ strcmp(currboard, "Test") && strcmp(currboard, ALLPOST)) {
+ deumoney(tusernum, -fhdr->multi.money);
+#ifdef USE_COOLDOWN
+ if (bp->brdattr & BRD_COOLDOWN)
+ add_cooldowntime(tusernum, 15);
+#endif
+ }
+ if (!not_owned && strcmp(currboard, "Test") &&
+ strcmp(currboard, ALLPOST)) {
+ if (cuser.numposts)
+ cuser.numposts--;
+ if (!(currmode & MODE_DIGEST && currmode & MODE_BOARD)){
+ demoney(-fhdr->multi.money);
+ vmsgf("您的文章減為 %d 篇,支付清潔費 %d 銀",
+ cuser.numposts, fhdr->multi.money);
+ }
+ }
+ return DIRCHANGED;
+ }
+ }
+ return FULLUPDATE;
+}
+
+static int // Ptt: 修石頭文
+show_filename(int ent, const fileheader_t * fhdr, const char *direct)
+{
+ if(!HasUserPerm(PERM_SYSOP)) return DONOTHING;
+ vmsgf("檔案名稱: %s ", fhdr->filename);
+ return PART_REDRAW;
+}
+
+static int
+lock_post(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char fn1[MAXPATHLEN];
+ char genbuf[256] = {'\0'};
+ int i;
+
+ if (!(currmode & MODE_BOARD) && !HasUserPerm(PERM_SYSOP | PERM_POLICE))
+ return DONOTHING;
+
+ if (fhdr->filename[0]=='M') {
+ if (!HasUserPerm(PERM_SYSOP | PERM_POLICE))
+ return DONOTHING;
+
+ getdata(b_lines - 1, 0, "請輸入鎖定理由:", genbuf, 50, DOECHO);
+
+ if (getans("要將文章鎖定嗎(y/N)?") != 'y')
+ return FULLUPDATE;
+ setbfile(fn1, currboard, fhdr->filename);
+ fhdr->filename[0] = 'L';
+ }
+ else if (fhdr->filename[0]=='L') {
+ if (getans("要將文章鎖定解除嗎(y/N)?") != 'y')
+ return FULLUPDATE;
+ fhdr->filename[0] = 'M';
+ setbfile(fn1, currboard, fhdr->filename);
+ }
+ substitute_ref_record(direct, fhdr, ent);
+ post_policelog(currboard, fhdr->title, "鎖文", genbuf, fhdr->filename[0] == 'L' ? 1 : 0);
+ if (fhdr->filename[0] == 'L') {
+ fhdr->filename[0] = 'M';
+ do_crosspost("PoliceLog", fhdr, fn1, 0);
+ fhdr->filename[0] = 'L';
+ snprintf(genbuf, sizeof(genbuf), "%s 板遭鎖定文章 - %s", currboard, fhdr->title);
+ for (i = 0; i < MAX_BMs && SHM->BMcache[currbid-1][i] != -1; i++)
+ mail_id(SHM->userid[SHM->BMcache[currbid-1][i] - 1], genbuf, fn1, "[系統]");
+ }
+ return FULLUPDATE;
+}
+
+static int
+view_postmoney(int ent, const fileheader_t * fhdr, const char *direct)
+{
+ if(fhdr->filemode & FILE_ANONYMOUS)
+ /* When the file is anonymous posted, fhdr->multi.anon_uid is author.
+ * see do_general() */
+ vmsgf("匿名管理編號: %d (同一人號碼會一樣)",
+ fhdr->multi.anon_uid + (int)currutmp->pid);
+ else {
+ int m = query_file_money(fhdr);
+
+ if(m < 0)
+ m = vmsgf("特殊文章,無價格記錄。");
+ else
+ m = vmsgf("這一篇文章值 %d 銀", m);
+
+ /* QQ: enable money listing mode */
+ if (m == 'Q')
+ {
+ currlistmode = (currlistmode == LISTMODE_MONEY) ?
+ LISTMODE_DATE : LISTMODE_MONEY;
+ vmsg((currlistmode == LISTMODE_MONEY) ?
+ "開啟文章價格列表模式" : "停止列出文章價格");
+ }
+ }
+
+ return FULLUPDATE;
+}
+
+#ifdef OUTJOBSPOOL
+/* 看板備份 */
+static int
+tar_addqueue(void)
+{
+ char email[60], qfn[80], ans[2];
+ FILE *fp;
+ char bakboard, bakman;
+ clear();
+ showtitle("看板備份", BBSNAME);
+ move(2, 0);
+ if (!((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP))) {
+ move(5, 10);
+ outs("妳要是板主或是站長才能醬醬啊 -.-\"\"");
+ pressanykey();
+ return FULLUPDATE;
+ }
+ snprintf(qfn, sizeof(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
+
+/* ----------------------------------------------------- */
+/* 看板備忘錄、文摘、精華區 */
+/* ----------------------------------------------------- */
+int
+b_note_edit_bname(int bid)
+{
+ char buf[PATHLEN];
+ int aborted;
+ boardheader_t *fh = getbcache(bid);
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ setbfile(buf, fh->brdname, fn_notes);
+ aborted = vedit(buf, NA, NULL);
+ if (aborted == -1) {
+ clear();
+ outs(msg_cancel);
+ pressanykey();
+ } else {
+ if (!getdata(2, 0, "設定有效期限天?(n/Y)", buf, 3, LCECHO)
+ || buf[0] != 'n')
+ fh->bupdate = gettime(3, fh->bupdate ? fh->bupdate : now,
+ "有效日期至");
+ else
+ fh->bupdate = 0;
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ substitute_record(fn_board, fh, sizeof(boardheader_t), bid);
+ }
+ return 0;
+}
+
+static int
+b_notes_edit(void)
+{
+ if (currmode & MODE_BOARD) {
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ b_note_edit_bname(currbid);
+ return FULLUPDATE;
+ }
+ return 0;
+}
+
+static int
+b_water_edit(void)
+{
+ if (currmode & MODE_BOARD) {
+ friend_edit(BOARD_WATER);
+ return FULLUPDATE;
+ }
+ return 0;
+}
+
+static int
+visable_list_edit(void)
+{
+ if (currmode & MODE_BOARD) {
+ friend_edit(BOARD_VISABLE);
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ hbflreload(currbid);
+ return FULLUPDATE;
+ }
+ return 0;
+}
+
+static int
+b_post_note(void)
+{
+ 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);
+
+
+ setbfile(buf, currboard, FN_POST_BID);
+ if (more(buf, NA) == -1)
+ more("etc/" FN_POST_BID, NA);
+ getdata(b_lines - 2, 0, "是否要用自訂競標文章注意事項?",
+ yn, sizeof(yn), LCECHO);
+ if (yn[0] == 'y')
+ vedit(buf, NA, NULL);
+ else
+ unlink(buf);
+
+ return FULLUPDATE;
+ }
+ return 0;
+}
+
+
+static int
+can_vote_edit(void)
+{
+ if (currmode & MODE_BOARD) {
+ friend_edit(FRIEND_CANVOTE);
+ return FULLUPDATE;
+ }
+ return 0;
+}
+
+static int
+bh_title_edit(void)
+{
+ boardheader_t *bp;
+
+ if (currmode & MODE_BOARD) {
+ char genbuf[BTLEN];
+
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ 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, STRIP_ALL);
+ strlcpy(bp->title + 7, genbuf, sizeof(bp->title) - 7);
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
+ log_usies("SetBoard", currboard);
+ return FULLUPDATE;
+ }
+ return 0;
+}
+
+static int
+b_notes(void)
+{
+ char buf[PATHLEN];
+ int mr = 0;
+
+ setbfile(buf, currboard, fn_notes);
+ mr = more(buf, NA);
+
+ if (mr == -1)
+ {
+ clear();
+ move(4, 20);
+ outs("本看板尚無「備忘錄」。");
+ }
+ if(mr != READ_NEXT)
+ pressanykey();
+ return FULLUPDATE;
+}
+
+int
+board_select(void)
+{
+ currmode &= ~MODE_SELECT;
+ currsrmode = 0;
+ if (currstat == RMAIL)
+ sethomedir(currdirect, cuser.userid);
+ else
+ setbdir(currdirect, currboard);
+ return NEWDIRECT;
+}
+
+int
+board_digest(void)
+{
+ 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;
+}
+
+
+static int
+push_bottom(int ent, fileheader_t *fhdr, const char *direct)
+{
+ int num;
+ char buf[256];
+ if ((currmode & MODE_DIGEST) || !(currmode & MODE_BOARD)
+ || fhdr->filename[0]=='L')
+ return DONOTHING;
+ setbottomtotal(currbid); // <- Ptt : will be remove when stable
+ num = getbottomtotal(currbid);
+ if( getans(fhdr->filemode & FILE_BOTTOM ?
+ "取消置底公告?(y/N)":
+ "加入置底公告?(y/N)") != 'y' )
+ return READ_REDRAW;
+ if(!(fhdr->filemode & FILE_BOTTOM) ){
+ sprintf(buf, "%s.bottom", direct);
+ if(num >= 5){
+ vmsg("不得超過 5 篇重要公告 請精簡!");
+ return READ_REDRAW;
+ }
+ fhdr->filemode ^= FILE_BOTTOM;
+ fhdr->multi.refer.flag = 1;
+ fhdr->multi.refer.ref = ent;
+ append_record(buf, fhdr, sizeof(fileheader_t));
+ }
+ else{
+ fhdr->filemode ^= FILE_BOTTOM;
+ num = delete_record(direct, sizeof(fileheader_t), ent);
+ }
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ setbottomtotal(currbid);
+ return DIRCHANGED;
+}
+
+static int
+good_post(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char genbuf[200];
+ char genbuf2[200];
+ int delta = 0;
+
+ if ((currmode & MODE_DIGEST) || !(currmode & MODE_BOARD))
+ return DONOTHING;
+
+ if(getans(fhdr->filemode & FILE_DIGEST ?
+ "取消看板文摘?(Y/n)" : "收入看板文摘?(Y/n)") == 'n')
+ return READ_REDRAW;
+
+ 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, NULL), -1000); // TODO if searchuser() return 0
+ if (!(currmode & MODE_SELECT))
+ fhdr->multi.money -= 1000;
+ else
+ delta = -1000;
+ }
+ } else {
+ fileheader_t digest;
+ char *ptr, buf[PATHLEN];
+
+ memcpy(&digest, fhdr, sizeof(digest));
+ digest.filename[0] = 'G';
+ strlcpy(buf, direct, sizeof(buf));
+ ptr = strrchr(buf, '/');
+ assert(ptr);
+ ptr++;
+ ptr[0] = '\0';
+ snprintf(genbuf, sizeof(genbuf), "%s%s", buf, digest.filename);
+
+ if (dashf(genbuf))
+ unlink(genbuf);
+
+ digest.filemode = 0;
+ snprintf(genbuf2, sizeof(genbuf2), "%s%s", buf, fhdr->filename);
+ Copy(genbuf2, genbuf);
+ strcpy(ptr, fn_mandex);
+ append_record(buf, &digest, sizeof(digest));
+
+#ifdef GLOBAL_DIGEST
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ if(!(getbcache(currbid)->brdattr & BRD_HIDE)) {
+ getdata(1, 0, "好文值得出版到全站文摘?(N/y)", genbuf2, 3, LCECHO);
+ if(genbuf2[0] == 'y')
+ do_crosspost(GLOBAL_DIGEST, &digest, genbuf, 1);
+ }
+#endif
+
+ 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, NULL), 1000); // TODO if searchuser() return 0
+ if (!(currmode & MODE_SELECT))
+ fhdr->multi.money += 1000;
+ else
+ delta = 1000;
+ }
+ }
+ substitute_ref_record(direct, fhdr, ent);
+ return FULLUPDATE;
+}
+
+static int
+b_help(void)
+{
+ show_helpfile(fn_boardhelp);
+ return FULLUPDATE;
+}
+
+static int
+b_config(void)
+{
+ boardheader_t *bp=NULL;
+ int touched = 0, finished = 0;
+ bp = getbcache(currbid);
+
+ while(!finished) {
+ move(b_lines - 14, 0); clrtobot();
+
+ outs(MSG_SEPERATOR);
+ prints("\n目前 %s 看板設定:\n", bp->brdname);
+ prints(" 中文敘述: %s\n", bp->title);
+ prints(" 板主名單: %s\n", (bp->BM[0] > ' ')? bp->BM : "(無)");
+
+ prints( " " ANSI_COLOR(1;36) "h" ANSI_RESET
+ " - 公開狀態(是否隱形): %s " ANSI_RESET "\n",
+ (bp->brdattr & BRD_HIDE) ?
+ ANSI_COLOR(1)"隱形":"公開");
+
+ prints( " " ANSI_COLOR(1;36) "r" ANSI_RESET
+ " - %s " ANSI_RESET "推薦文章\n",
+ (bp->brdattr & BRD_NORECOMMEND) ?
+ ANSI_COLOR(1)"不可":"可以");
+
+#ifndef OLDRECOMMEND
+ prints( " " ANSI_COLOR(1;36) "b" ANSI_RESET
+ " - %s " ANSI_RESET "噓文\n",
+ ((bp->brdattr & BRD_NORECOMMEND) || (bp->brdattr & BRD_NOBOO))
+ ? ANSI_COLOR(1)"不可":"可以");
+#endif
+ {
+ int d = 0;
+
+ if(bp->brdattr & BRD_NORECOMMEND)
+ {
+ d = -1;
+ } else {
+ if ((bp->brdattr & BRD_NOFASTRECMD) &&
+ (bp->fastrecommend_pause > 0))
+ d = bp->fastrecommend_pause;
+ }
+
+ prints( " " ANSI_COLOR(1;36) "f" ANSI_RESET
+ " - %s " ANSI_RESET "快速連推文章",
+ d != 0 ?
+ ANSI_COLOR(1)"限制": "可以");
+ if(d > 0)
+ prints(", 最低間隔時間: %d 秒", d);
+ outs("\n");
+ }
+
+ prints( " " ANSI_COLOR(1;36) "i" ANSI_RESET
+ " - 推文時 %s" ANSI_RESET " 記錄來源 IP\n",
+ (bp->brdattr & BRD_IPLOGRECMD) ?
+ ANSI_COLOR(1)"要":"不用");
+
+#ifdef USE_AUTOCPLOG
+ prints( " " ANSI_COLOR(1;36) "x" ANSI_RESET
+ " - 轉錄文章時 %s " ANSI_RESET "自動記錄\n",
+ (bp->brdattr & BRD_CPLOG) ?
+ ANSI_COLOR(1)"會" : "不會" );
+#endif
+
+ prints( " " ANSI_COLOR(1;36) "o" ANSI_RESET
+ " - 若有轉信則發文時預設 %s " ANSI_RESET "\n",
+ (bp->brdattr & BRD_LOCALSAVE) ?
+ "站內存檔(不轉出)" : ANSI_COLOR(1)"站際存檔(轉出)" );
+
+ prints( " " ANSI_COLOR(1;36) "e" ANSI_RESET
+ " - 發文權限: %s" ANSI_RESET " (站長才可設定此項)\n",
+ (bp->brdattr & BRD_RESTRICTEDPOST) ?
+ ANSI_COLOR(1)"只有板友才可發文" : "無特別設定" );
+
+ prints( " " ANSI_COLOR(1;36) "1" ANSI_RESET
+ " - 未滿十八歲 " ANSI_COLOR(1) "%s" ANSI_RESET
+ " 進入\n",
+ (bp->brdattr & BRD_OVER18) ? "不可以" : "可以" );
+
+ prints( " " ANSI_COLOR(1;36) "y" ANSI_RESET
+ " - " ANSI_COLOR(1) "%s" ANSI_RESET
+ " 回文 (群組長以上才可設定此項)",
+ (bp->brdattr & BRD_NOREPLY) ? "不可以" : "可以" );
+
+ move(b_lines - 10, 62);
+ prints("發文限制");
+ move(b_lines - 9, 64);
+ prints("上站次數 %d 次以上", (int)bp->post_limit_logins * 10);
+ move(b_lines - 8, 64);
+ prints("文章篇數 %d 篇以上", (int)bp->post_limit_posts * 10);
+ move(b_lines - 7, 64);
+ prints("註冊時間 %d 個月以上", (int)bp->post_limit_regtime);
+ move(b_lines - 6, 64);
+ prints("劣文篇數 %d 篇以下", 255 - (int)bp->post_limit_badpost);
+ move(b_lines, 0);
+
+ if (!((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)))
+ {
+ vmsg("您對此板無管理權限");
+ return FULLUPDATE;
+ }
+
+ switch(tolower(getans("請輸入要改變的設定, 其它鍵結束: ")))
+ {
+#ifdef USE_AUTOCPLOG
+ case 'x':
+ bp->brdattr ^= BRD_CPLOG;
+ touched = 1;
+ break;
+#endif
+ case 'o':
+ bp->brdattr ^= BRD_LOCALSAVE;
+ touched = 1;
+ break;
+
+ case 'e':
+ if(HasUserPerm(PERM_SYSOP))
+ {
+ bp->brdattr ^= BRD_RESTRICTEDPOST;
+ touched = 1;
+ } else {
+ vmsg("此項設定需要站長權限");
+ }
+ break;
+
+ case 'h':
+#ifndef BMCHS
+ if (!HasUserPerm(PERM_SYSOP))
+ {
+ vmsg("此項設定需要站長權限");
+ break;
+ }
+#endif
+ if(bp->brdattr & BRD_HIDE)
+ {
+ bp->brdattr &= ~BRD_HIDE;
+ bp->brdattr &= ~BRD_POSTMASK;
+ } else {
+ bp->brdattr |= BRD_HIDE;
+ bp->brdattr |= BRD_POSTMASK;
+ }
+ touched = 1;
+ break;
+
+ case 'r':
+ bp->brdattr ^= BRD_NORECOMMEND;
+ touched = 1;
+ break;
+
+ case 'i':
+ bp->brdattr ^= BRD_IPLOGRECMD;
+ touched = 1;
+ break;
+
+ case 'f':
+ bp->brdattr &= ~BRD_NORECOMMEND;
+ bp->brdattr ^= BRD_NOFASTRECMD;
+ touched = 1;
+
+ if(bp->brdattr & BRD_NOFASTRECMD)
+ {
+ char buf[8] = "";
+
+ if(bp->fastrecommend_pause > 0)
+ sprintf(buf, "%d", bp->fastrecommend_pause);
+ getdata_str(b_lines-1, 0,
+ "請輸入連推時間限制(單位: 秒) [5~240]: ",
+ buf, 4, ECHO, buf);
+ if(buf[0] >= '0' && buf[0] <= '9')
+ bp->fastrecommend_pause = atoi(buf);
+
+ if( bp->fastrecommend_pause < 5 ||
+ bp->fastrecommend_pause > 240)
+ {
+ if(buf[0])
+ {
+ vmsg("輸入時間無效,請使用 5~240 之間的數字。");
+ }
+ bp->fastrecommend_pause = 0;
+ bp->brdattr &= ~BRD_NOFASTRECMD;
+ }
+ }
+ break;
+#ifndef OLDRECOMMEND
+ case 'b':
+ if(bp->brdattr & BRD_NORECOMMEND)
+ bp->brdattr |= BRD_NOBOO;
+ bp->brdattr ^= BRD_NOBOO;
+ touched = 1;
+ if (!(bp->brdattr & BRD_NOBOO))
+ bp->brdattr &= ~BRD_NORECOMMEND;
+ break;
+#endif
+ case '1':
+ bp->brdattr ^= BRD_OVER18;
+ touched = 1;
+ break;
+
+ case 'y':
+ if (!(HasUserPerm(PERM_SYSOP) || (HasUserPerm(PERM_SYSSUPERSUBOP) && GROUPOP()) ) ) {
+ vmsg("此項設定需要群組長或站長權限");
+ break;
+ }
+ bp->brdattr ^= BRD_NOREPLY;
+ touched = 1;
+ break;
+
+ default:
+ finished = 1;
+ break;
+ }
+ }
+ if(touched)
+ {
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
+ vmsg("已儲存新設定");
+ }
+ else
+ vmsg("未改變任何設定");
+
+ return FULLUPDATE;
+}
+
+/* ----------------------------------------------------- */
+/* 板主設定隱形/ 解隱形 */
+/* ----------------------------------------------------- */
+char board_hidden_status;
+#ifdef BMCHS
+static int
+change_hidden(void)
+{
+ boardheader_t *bp;
+ if (!((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)))
+ return DONOTHING;
+
+ bp = getbcache(currbid);
+ if (((bp->brdattr & BRD_HIDE) && (bp->brdattr & BRD_POSTMASK))) {
+ if (getans("目前看板隱形中, 要解除嗎(y/N)?") != 'y')
+ return FULLUPDATE;
+ bp->brdattr &= ~BRD_HIDE;
+ bp->brdattr &= ~BRD_POSTMASK;
+ outs("君心今傳眾人,無處不聞弦歌。\n");
+ board_hidden_status = 0;
+ hbflreload(currbid);
+ } else {
+ if (getans("要設定看板為隱形嗎(y/N)?") != 'y')
+ return FULLUPDATE;
+ bp->brdattr |= BRD_HIDE;
+ bp->brdattr |= BRD_POSTMASK;
+ outs("君心今已掩抑,惟盼善自珍重。\n");
+ board_hidden_status = 1;
+ }
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
+ log_usies("SetBoard", bp->brdname);
+ pressanykey();
+ return FULLUPDATE;
+}
+
+static int
+change_counting(void)
+{
+
+ boardheader_t *bp;
+ if (!((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)))
+ return DONOTHING;
+
+ bp = getbcache(currbid);
+ if (!(bp->brdattr & BRD_HIDE && bp->brdattr & BRD_POSTMASK))
+ return FULLUPDATE;
+
+ if (bp->brdattr & BRD_BMCOUNT) {
+ if (getans("目前板列入十大排行, 要取消列入十大排行嘛(Y/N)?[N]") != 'y')
+ return FULLUPDATE;
+ bp->brdattr &= ~BRD_BMCOUNT;
+ outs("你再灌水也不會有十大的呀。\n");
+ } else {
+ if (getans("目前看板不列入十大排行, 要列入十大嘛(Y/N)?[N]") != 'y')
+ return FULLUPDATE;
+ bp->brdattr |= BRD_BMCOUNT;
+ outs("快灌水衝十大第一吧。\n");
+ }
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
+ pressanykey();
+ return FULLUPDATE;
+}
+
+#endif
+
+/**
+ * 改變目前所在板文章的預設儲存方式
+ */
+static int
+change_localsave(void)
+{
+ vmsg("此功\能已整合進大寫 I 看板設定,請按 I 設定。");
+ return FULLUPDATE;
+#if 0
+ boardheader_t *bp;
+ if (!((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)))
+ return DONOTHING;
+
+ bp = getbcache(currbid);
+ if (bp->brdattr & BRD_LOCALSAVE) {
+ if (getans("目前板預設站內存檔, 要改變嘛(y/N)?") != 'y')
+ return FULLUPDATE;
+ bp->brdattr &= ~BRD_LOCALSAVE;
+ outs("文章預設轉出,請有所節制。\n");
+ } else {
+ if (getans("目前板預設站際存檔, 要改變嗎(y/N)?") != 'y')
+ return FULLUPDATE;
+ bp->brdattr |= BRD_LOCALSAVE;
+ outs("文章預設不轉出,轉信要自行選擇喔。\n");
+ }
+ substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
+ pressanykey();
+ return FULLUPDATE;
+#endif
+}
+
+#ifdef USE_COOLDOWN
+
+int check_cooldown(boardheader_t *bp)
+{
+ int diff = cooldowntimeof(usernum) - now;
+ int i, limit[8] = {4000,1,2000,2,1000,3,-1,10};
+
+ if(diff<0)
+ SHM->cooldowntime[usernum - 1] &= 0xFFFFFFF0;
+ else if( !((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)))
+ {
+ if( bp->brdattr & BRD_COOLDOWN )
+ {
+ vmsgf("冷靜一下吧! (限制 %d 分 %d 秒)", diff/60, diff%60);
+ return 1;
+ }
+ else if(posttimesof(usernum)==0xf)
+ {
+ vmsgf("對不起,您被設劣文! (限制 %d 分 %d 秒)", diff/60, diff%60);
+ return 1;
+ }
+#ifdef NO_WATER_POST
+ else
+ {
+ for(i=0; i<4; i++)
+ if(bp->nuser>limit[i*2] && posttimesof(usernum)>=limit[i*2+1])
+ {
+ vmsgf("對不起,您的文章或推文太水囉! (限制 %d 分 %d 秒)",
+ diff/60, diff%60);
+ return 1;
+ }
+ }
+#endif // NO_WATER_POST
+ }
+ return 0;
+}
+/**
+ * 設定看板冷靜功能, 限制使用者發文時間
+ */
+static int
+change_cooldown(void)
+{
+ char genbuf[256] = {'\0'};
+ boardheader_t *bp = getbcache(currbid);
+
+ if (!(HasUserPerm(PERM_SYSOP | PERM_POLICE) ||
+ (HasUserPerm(PERM_SYSSUPERSUBOP) && GROUPOP())))
+ return DONOTHING;
+
+ if (bp->brdattr & BRD_COOLDOWN) {
+ if (getans("目前降溫中, 要開放嗎(y/N)?") != 'y')
+ return FULLUPDATE;
+ bp->brdattr &= ~BRD_COOLDOWN;
+ outs("大家都可以 post 文章了。\n");
+ } else {
+ getdata(b_lines - 1, 0, "請輸入冷靜理由:", genbuf, 50, DOECHO);
+ if (getans("要限制 post 頻率, 降溫嗎(y/N)?") != 'y')
+ return FULLUPDATE;
+ bp->brdattr |= BRD_COOLDOWN;
+ outs("開始冷靜。\n");
+ }
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
+ post_policelog(bp->brdname, NULL, "冷靜", genbuf, bp->brdattr & BRD_COOLDOWN);
+ pressanykey();
+ return FULLUPDATE;
+}
+#endif
+
+/* ----------------------------------------------------- */
+/* 看板功能表 */
+/* ----------------------------------------------------- */
+/* onekey_size was defined in ../include/pttstruct.h, as ((int)'z') */
+const onekey_t read_comms[] = {
+ { 1, show_filename }, // Ctrl('A')
+ { 0, NULL }, // Ctrl('B')
+ { 0, NULL }, // Ctrl('C')
+ { 0, NULL }, // Ctrl('D')
+ { 1, lock_post }, // Ctrl('E')
+ { 0, NULL }, // Ctrl('F')
+#ifdef NO_GAMBLE
+ { 0, NULL }, // Ctrl('G')
+#else
+ { 0, hold_gamble }, // Ctrl('G')
+#endif
+ { 0, NULL }, // Ctrl('H')
+ { 0, board_digest }, // Ctrl('I') KEY_TAB 9
+ { 0, NULL }, // Ctrl('J')
+ { 0, NULL }, // Ctrl('K')
+ { 0, NULL }, // Ctrl('L')
+ { 0, NULL }, // Ctrl('M')
+#ifdef BMCHS
+ { 0, change_counting }, // Ctrl('N')
+#else
+ { 0, NULL }, // Ctrl('N')
+#endif
+ { 0, do_post_openbid }, // Ctrl('O')
+ { 0, do_post }, // Ctrl('P')
+ { 0, NULL }, // Ctrl('Q')
+ { 0, NULL }, // Ctrl('R')
+ { 0, NULL }, // Ctrl('S')
+ { 0, NULL }, // Ctrl('T')
+ { 0, NULL }, // Ctrl('U')
+ { 0, do_post_vote }, // Ctrl('V')
+ { 0, whereami }, // Ctrl('W')
+ { 0, change_localsave }, // Ctrl('X')
+ { 0, NULL }, // Ctrl('Y')
+ { 1, push_bottom }, // Ctrl('Z') 26
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, // 'A' 65
+ { 0, bh_title_edit }, // 'B'
+ { 1, do_limitedit }, // 'C'
+ { 1, del_range }, // 'D'
+ { 1, edit_post }, // 'E'
+ { 0, NULL }, // 'F'
+ { 0, NULL }, // 'G'
+#ifdef BMCHS
+ { 0, change_hidden }, // 'H'
+#else
+ { 0, NULL }, // 'H'
+#endif
+ { 0, b_config }, // 'I'
+#ifdef USE_COOLDOWN
+ { 0, change_cooldown }, // 'J'
+#else
+ { 0, NULL }, // 'J'
+#endif
+ { 0, b_water_edit }, // 'K'
+ { 1, solve_post }, // 'L'
+ { 0, b_vote_maintain }, // 'M'
+ { 0, NULL }, // 'N'
+ { 0, b_post_note }, // 'O'
+ { 0, NULL }, // 'P'
+ { 1, view_postmoney }, // 'Q'
+ { 0, b_results }, // 'R'
+ { 0, NULL }, // 'S'
+ { 1, edit_title }, // 'T'
+ { 0, NULL }, // 'U'
+ { 0, b_vote }, // 'V'
+ { 0, b_notes_edit }, // 'W'
+ { 1, recommend }, // 'X'
+ { 1, recommend_cancel }, // 'Y'
+ { 0, NULL }, // 'Z' 90
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, // 'a' 97
+ { 0, b_notes }, // 'b'
+ { 1, cite_post }, // 'c'
+ { 1, del_post }, // 'd'
+ { 0, NULL }, // 'e'
+#ifdef NO_GAMBLE
+ { 0, NULL }, // 'f'
+#else
+ { 0, join_gamble }, // 'f'
+#endif
+ { 1, good_post }, // 'g'
+ { 0, b_help }, // 'h'
+ { 0, b_posttype }, // 'i'
+ { 0, NULL }, // 'j'
+ { 0, NULL }, // 'k'
+ { 0, NULL }, // 'l'
+ { 1, mark_post }, // 'm'
+ { 0, NULL }, // 'n'
+ { 0, can_vote_edit }, // 'o'
+ { 0, NULL }, // 'p'
+ { 0, NULL }, // 'q'
+ { 1, read_post }, // 'r'
+ { 0, do_select }, // 's'
+ { 0, NULL }, // 't'
+#ifdef OUTJOBSPOOL
+ { 0, tar_addqueue }, // 'u'
+#else
+ { 0, NULL }, // 'u'
+#endif
+ { 0, visable_list_edit }, // 'v'
+ { 1, b_call_in }, // 'w'
+ { 1, cross_post }, // 'x'
+ { 1, reply_post }, // 'y'
+ { 0, b_man }, // 'z' 122
+};
+
+int
+Read(void)
+{
+ int mode0 = currutmp->mode;
+ int stat0 = currstat, tmpbid = currutmp->brc_id;
+ char buf[PATHLEN];
+#ifdef LOG_BOARD
+ time4_t usetime = now;
+#endif
+
+ if ( !currboard[0] )
+ brc_initial_board(DEFAULT_BOARD);
+
+ setutmpmode(READING);
+ set_board();
+
+ if (board_note_time && board_visit_time < *board_note_time) {
+ int mr;
+
+ setbfile(buf, currboard, fn_notes);
+ mr = more(buf, NA);
+ if(mr == -1)
+ *board_note_time=0;
+ else if (mr != READ_NEXT)
+ pressanykey();
+ }
+ setutmpbid(currbid);
+ setbdir(buf, currboard);
+ curredit &= ~EDIT_MAIL;
+ i_read(READING, buf, readtitle, readdoent, read_comms,
+ currbid);
+ currmode &= ~MODE_POSTCHECKED;
+#ifdef LOG_BOARD
+ log_board(currboard, now - usetime);
+#endif
+ brc_update();
+ setutmpbid(tmpbid);
+ currutmp->mode = mode0;
+ currstat = stat0;
+ return 0;
+}
+
+void
+ReadSelect(void)
+{
+ int mode0 = currutmp->mode;
+ int stat0 = currstat;
+
+ currstat = SELECT;
+ if (do_select() == NEWDIRECT)
+ Read();
+ setutmpbid(0);
+ currutmp->mode = mode0;
+ currstat = stat0;
+}
+
+#ifdef LOG_BOARD
+static void
+log_board(iconst char *mode, time4_t usetime)
+{
+ if (usetime > 30) {
+ log_file(FN_USEBOARD, LOG_CREAT | LOG_VF,
+ "USE %-20.20s Stay: %5ld (%s) %s\n",
+ mode, usetime, cuser.userid, ctime4(&now));
+ }
+}
+#endif
+
+int
+Select(void)
+{
+ do_select();
+ return 0;
+}
+
+#ifdef HAVEMOBILE
+void
+mobile_message(const char *mobile, char *message)
+{
+ bsmtp(fpath, title, rcpt);
+}
+#endif
diff --git a/pttbbs/mbbsd/board.c b/pttbbs/mbbsd/board.c
new file mode 100644
index 00000000..e9267703
--- /dev/null
+++ b/pttbbs/mbbsd/board.c
@@ -0,0 +1,1356 @@
+/* $Id$ */
+#include "bbs.h"
+
+/* personal board state
+ * 相對於看板的 attr (BRD_* in ../include/pttstruct.h),
+ * 這些是用在 user interface 的 flag */
+#define NBRD_FAV 1
+#define NBRD_BOARD 2
+#define NBRD_LINE 4
+#define NBRD_FOLDER 8
+#define NBRD_TAG 16
+#define NBRD_UNREAD 32
+#define NBRD_SYMBOLIC 64
+
+#define TITLE_MATCH(bptr, key) ((key)[0] && !strcasestr((bptr)->title, (key)))
+
+
+#define B_TOTAL(bptr) (SHM->total[(bptr)->bid - 1])
+#define B_LASTPOSTTIME(bptr) (SHM->lastposttime[(bptr)->bid - 1])
+#define B_BH(bptr) (&bcache[(bptr)->bid - 1])
+typedef struct {
+ int bid;
+ unsigned char myattr;
+} __attribute__ ((packed)) boardstat_t;
+
+/**
+ * class_bid 的意義
+ * class_bid < 0 熱門看板
+ * class_bid = 0 我的最愛
+ * class_bid = 1 分類看板
+ * class_bid > 1 其他目錄
+ */
+#define IN_HOTBOARD() (class_bid < 0)
+#define IN_FAVORITE() (class_bid == 0)
+#define IN_CLASSROOT() (class_bid == 1)
+#define IN_SUBCLASS() (class_bid > 1)
+#define IN_CLASS() (class_bid > 0)
+static int class_bid = 0;
+
+static int nbrdsize = 0;
+static boardstat_t *nbrd = NULL;
+static char choose_board_depth = 0;
+static int brdnum;
+static char yank_flag = 1;
+
+static time4_t last_save_fav_and_brc;
+
+/* These are all the states yank_flag may be. */
+#define LIST_FAV() (yank_flag = 0)
+#define LIST_BRD() (yank_flag = 1)
+#define IS_LISTING_FAV() (yank_flag == 0)
+#define IS_LISTING_BRD() (yank_flag == 1)
+
+inline int getbid(const boardheader_t *fh)
+{
+ return (fh - bcache);
+}
+inline boardheader_t *getparent(const boardheader_t *fh)
+{
+ if(fh->parent>0)
+ return getbcache(fh->parent);
+ else
+ return NULL;
+}
+
+void imovefav(int old)
+{
+ char buf[5];
+ int new;
+
+ getdata(b_lines - 1, 0, "請輸入新次序:", buf, sizeof(buf), DOECHO);
+ new = atoi(buf) - 1;
+ if (new < 0 || brdnum <= new){
+ vmsg("輸入範圍有誤!");
+ return;
+ }
+ move_in_current_folder(old, new);
+}
+
+void
+init_brdbuf(void)
+{
+ if (brc_initialize())
+ return;
+ brc_initial_board(DEFAULT_BOARD);
+ set_board();
+}
+
+void
+save_brdbuf(void)
+{
+ fav_save();
+ fav_free();
+}
+
+int
+HasBoardPerm(boardheader_t *bptr)
+{
+ register int level, brdattr;
+
+ level = bptr->level;
+ brdattr = bptr->brdattr;
+
+ if (HasUserPerm(PERM_SYSOP))
+ return 1;
+
+ /* 十八禁看板 */
+ if( (brdattr & BRD_OVER18) && !over18 )
+ return 0;
+
+ /* 板主 */
+ if( is_BM_cache(bptr - bcache + 1) ) /* XXXbid */
+ return 1;
+
+ /* 祕密看板:核對首席板主的好友名單 */
+ if (brdattr & BRD_HIDE) { /* 隱藏 */
+ if (hbflcheck((int)(bptr - bcache) + 1, currutmp->uid)) {
+ if (brdattr & BRD_POSTMASK)
+ return 0;
+ else
+ return 2;
+ } else
+ return 1;
+ }
+
+ /* 限制閱讀權限 */
+ if (level && !(brdattr & BRD_POSTMASK) && !HasUserPerm(level))
+ return 0;
+
+ return 1;
+}
+
+static int
+check_newpost(boardstat_t * ptr)
+{ /* Ptt 改 */
+ time4_t ftime;
+
+ ptr->myattr &= ~NBRD_UNREAD;
+ if (B_BH(ptr)->brdattr & (BRD_GROUPBOARD | BRD_SYMBOLIC))
+ return 0;
+
+ if (B_TOTAL(ptr) == 0)
+ {
+ setbtotal(ptr->bid);
+ setbottomtotal(ptr->bid);
+ }
+ if (B_TOTAL(ptr) == 0)
+ return 0;
+ ftime = B_LASTPOSTTIME(ptr);
+
+ /* 有些 util, 尤其是 innbbsd, 會用到比較新的 time stamp,
+ * 只要不太誇張就 ok */
+ if (ftime > now + 10)
+ ftime = B_LASTPOSTTIME(ptr) = now - 1;
+
+ if ( brc_unread_time(ptr->bid, ftime) )
+ ptr->myattr |= NBRD_UNREAD;
+
+ return 1;
+}
+
+static void
+load_uidofgid(const int gid, const int type)
+{
+ boardheader_t *bptr, *currbptr, *parent;
+ int bid, n, childcount = 0;
+ assert(0<=type && type<2);
+ assert(0<= gid-1 && gid-1<MAX_BOARD);
+ currbptr = parent = &bcache[gid - 1];
+ assert(0<=numboards && numboards<=MAX_BOARD);
+ for (n = 0; n < numboards; ++n) {
+ bid = SHM->bsorted[type][n]+1;
+ if( bid<=0 || !(bptr = getbcache(bid))
+ || bptr->brdname[0] == '\0' )
+ continue;
+ if (bptr->gid == gid) {
+ if (currbptr == parent)
+ currbptr->firstchild[type] = bid;
+ else {
+ currbptr->next[type] = bid;
+ currbptr->parent = gid;
+ }
+ childcount++;
+ currbptr = bptr;
+ }
+ }
+ parent->childcount = childcount;
+ if (currbptr == parent) // no child
+ currbptr->firstchild[type] = -1;
+ else // the last child
+ currbptr->next[type] = -1;
+}
+
+static boardstat_t *
+addnewbrdstat(int n, int state)
+{
+ boardstat_t *ptr;
+
+ assert(0<=n && n<MAX_BOARD);
+ assert(0<=brdnum && brdnum<nbrdsize);
+ ptr = &nbrd[brdnum++];
+ //boardheader_t *bptr = &bcache[n];
+ //ptr->total = &(SHM->total[n]);
+ //ptr->lastposttime = &(SHM->lastposttime[n]);
+
+ ptr->bid = n + 1;
+ ptr->myattr = state;
+ if ((B_BH(ptr)->brdattr & BRD_HIDE) && state == NBRD_BOARD)
+ B_BH(ptr)->brdattr |= BRD_POSTMASK;
+ if (!IS_LISTING_FAV())
+ ptr->myattr &= ~NBRD_FAV;
+ check_newpost(ptr);
+ return ptr;
+}
+
+#if !HOTBOARDCACHE
+static int
+cmpboardfriends(const void *brd, const void *tmp)
+{
+#ifdef USE_COOLDOWN
+ if ((B_BH((boardstat_t*)tmp)->brdattr & BRD_COOLDOWN) &&
+ (B_BH((boardstat_t*)brd)->brdattr & BRD_COOLDOWN))
+ return 0;
+ else if ( B_BH((boardstat_t*)tmp)->brdattr & BRD_COOLDOWN ) {
+ if (B_BH((boardstat_t*)brd)->nuser == 0)
+ return 0;
+ else
+ return 1;
+ }
+ else if ( B_BH((boardstat_t*)brd)->brdattr & BRD_COOLDOWN ) {
+ if (B_BH((boardstat_t*)tmp)->nuser == 0)
+ return 0;
+ else
+ return -1;
+ }
+#endif
+ return ((B_BH((boardstat_t*)tmp)->nuser) -
+ (B_BH((boardstat_t*)brd)->nuser));
+}
+#endif
+
+static void
+load_boards(char *key)
+{
+ int type = cuser.uflag & BRDSORT_FLAG ? 1 : 0;
+ int i;
+ int state;
+
+ brdnum = 0;
+ if (nbrd) {
+ free(nbrd);
+ nbrdsize = 0;
+ nbrd = NULL;
+ }
+ if (!IN_CLASS()) {
+ if(IS_LISTING_FAV()){
+ fav_t *fav = get_current_fav();
+ int nfav = get_data_number(fav);
+ if( nfav == 0 ) {
+ nbrdsize = 1;
+ nbrd = (boardstat_t *)malloc(sizeof(boardstat_t) * 1);
+ addnewbrdstat(0, 0); // dummy
+ return;
+ }
+ nbrdsize = nfav;
+ nbrd = (boardstat_t *)malloc(sizeof(boardstat_t) * nfav);
+ for( i = 0 ; i < fav->DataTail; ++i ){
+ int state;
+ if (!(fav->favh[i].attr & FAVH_FAV))
+ continue;
+
+ if ( !key[0] ){
+ if (get_item_type(&fav->favh[i]) == FAVT_LINE )
+ state = NBRD_LINE;
+ else if (get_item_type(&fav->favh[i]) == FAVT_FOLDER )
+ state = NBRD_FOLDER;
+ else {
+ state = NBRD_BOARD;
+ if (is_set_attr(&fav->favh[i], FAVH_UNREAD))
+ state |= NBRD_UNREAD;
+ }
+ } else {
+ if (get_item_type(&fav->favh[i]) == FAVT_LINE )
+ continue;
+ else if (get_item_type(&fav->favh[i]) == FAVT_FOLDER ){
+ if( strcasestr(
+ get_folder_title(fav_getid(&fav->favh[i])),
+ key)
+ )
+ state = NBRD_FOLDER;
+ else
+ continue;
+ }else{
+ boardheader_t *bptr = getbcache(fav_getid(&fav->favh[i]));
+ assert(0<=fav_getid(&fav->favh[i])-1 && fav_getid(&fav->favh[i])-1<MAX_BOARD);
+ if (strcasestr(bptr->title, key))
+ state = NBRD_BOARD;
+ else
+ continue;
+ if (is_set_attr(&fav->favh[i], FAVH_UNREAD))
+ state |= NBRD_UNREAD;
+ }
+ }
+
+ if (is_set_attr(&fav->favh[i], FAVH_TAG))
+ state |= NBRD_TAG;
+ if (is_set_attr(&fav->favh[i], FAVH_ADM_TAG))
+ state |= NBRD_TAG;
+ addnewbrdstat(fav_getid(&fav->favh[i]) - 1, NBRD_FAV | state);
+ }
+ }
+#if HOTBOARDCACHE
+ else if(IN_HOTBOARD()){
+ nbrdsize = SHM->nHOTs;
+ if(nbrdsize == 0) {
+ nbrdsize = 1;
+ nbrd = (boardstat_t *)malloc(sizeof(boardstat_t) * 1);
+ addnewbrdstat(0, 0); // dummy
+ return;
+ }
+ assert(0<nbrdsize);
+ nbrd = (boardstat_t *)malloc(sizeof(boardstat_t) * nbrdsize);
+ for( i = 0 ; i < nbrdsize; ++i ) {
+ if(SHM->HBcache[i] == -1)
+ continue;
+ addnewbrdstat(SHM->HBcache[i], HasBoardPerm(&bcache[SHM->HBcache[i]]));
+ }
+ }
+#endif
+ else { // general case
+ nbrdsize = numboards;
+ assert(0<nbrdsize && nbrdsize<=MAX_BOARD);
+ nbrd = (boardstat_t *) malloc(sizeof(boardstat_t) * nbrdsize);
+ for (i = 0; i < nbrdsize; i++) {
+ int n = SHM->bsorted[type][i];
+ boardheader_t *bptr;
+ if (n < 0)
+ continue;
+ bptr = &bcache[n];
+ if (bptr == NULL)
+ continue;
+ if (!bptr->brdname[0] ||
+ (bptr->brdattr & (BRD_GROUPBOARD | BRD_SYMBOLIC)) ||
+ !((state = HasBoardPerm(bptr)) || GROUPOP()) ||
+ TITLE_MATCH(bptr, key)
+#if ! HOTBOARDCACHE
+ || (IN_HOTBOARD() && bptr->nuser < 5)
+#endif
+ )
+ continue;
+ addnewbrdstat(n, state);
+ }
+ }
+#if ! HOTBOARDCACHE
+ if (IN_HOTBOARD())
+ qsort(nbrd, brdnum, sizeof(boardstat_t), cmpboardfriends);
+#endif
+ } else { /* load boards of a subclass */
+ boardheader_t *bptr = getbcache(class_bid);
+ int childcount;
+ int bid;
+
+ assert(0<=class_bid-1 && class_bid-1<MAX_BOARD);
+ if (bptr->firstchild[type] == 0 || bptr->childcount==0)
+ load_uidofgid(class_bid, type);
+
+ childcount = bptr->childcount; // Ptt: child count after load_uidofgid
+
+ nbrdsize = childcount + 5;
+ nbrd = (boardstat_t *) malloc((childcount+5) * sizeof(boardstat_t));
+ // 預留兩個以免大量開板時掛調
+ for (bid = bptr->firstchild[type]; bid > 0 &&
+ brdnum < childcount+5; bid = bptr->next[type]) {
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ bptr = getbcache(bid);
+ state = HasBoardPerm(bptr);
+ if ( !(state || GROUPOP()) || TITLE_MATCH(bptr, key) )
+ continue;
+
+ if (bptr->brdattr & BRD_SYMBOLIC) {
+ /* Only SYSOP knows a board is symbolic */
+ if (HasUserPerm(PERM_SYSOP) || HasUserPerm(PERM_SYSSUPERSUBOP))
+ state |= NBRD_SYMBOLIC;
+ else {
+ bid = BRD_LINK_TARGET(bptr);
+ if (bcache[bid - 1].brdname[0] == 0) {
+ vmsg("連結已損毀,請至 SYSOP 回報此問題。");
+ continue;
+ }
+ }
+ }
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ addnewbrdstat(bid-1, state);
+ }
+ if(childcount < brdnum) {
+ //Ptt: dirty fix fix soon
+ fprintf(stderr, "childcount < brdnum, %d<%d, class_bid=%d\n",childcount,brdnum,class_bid);
+ getbcache(class_bid)->childcount = 0;
+ }
+
+
+ }
+}
+
+static int
+search_board(void)
+{
+ int num;
+ char genbuf[IDLEN + 2];
+ struct NameList namelist;
+
+ move(0, 0);
+ clrtoeol();
+ NameList_init(&namelist);
+ assert(brdnum<=nbrdsize);
+ for (num = 0; num < brdnum; num++)
+ if (!IS_LISTING_FAV() ||
+ (nbrd[num].myattr & NBRD_BOARD && HasBoardPerm(B_BH(&nbrd[num]))) )
+ NameList_add(&namelist, B_BH(&nbrd[num])->brdname);
+ namecomplete2(&namelist, MSG_SELECT_BOARD, genbuf);
+ NameList_delete(&namelist);
+
+#ifdef DEBUG
+ vmsg(genbuf);
+#endif
+
+ for (num = 0; num < brdnum; num++)
+ if (!strcasecmp(B_BH(&nbrd[num])->brdname, genbuf))
+ return num;
+ return -1;
+}
+
+static int
+unread_position(char *dirfile, boardstat_t * ptr)
+{
+ fileheader_t fh;
+ char fname[FNLEN];
+ register int num, fd, step, total;
+
+ total = B_TOTAL(ptr);
+ num = total + 1;
+ if ((ptr->myattr & NBRD_UNREAD) && (fd = open(dirfile, O_RDWR)) > 0) {
+ if (!brc_initial_board(B_BH(ptr)->brdname)) {
+ num = 1;
+ } else {
+ num = total - 1;
+ step = 4;
+ while (num > 0) {
+ lseek(fd, (off_t) (num * sizeof(fh)), SEEK_SET);
+ if (read(fd, fname, FNLEN) <= 0 ||
+ !brc_unread(ptr->bid, fname))
+ break;
+ num -= step;
+ if (step < 32)
+ step += step >> 1;
+ }
+ if (num < 0)
+ num = 0;
+ while (num < total) {
+ lseek(fd, (off_t) (num * sizeof(fh)), SEEK_SET);
+ if (read(fd, fname, FNLEN) <= 0 ||
+ brc_unread(ptr->bid, fname))
+ break;
+ num++;
+ }
+ }
+ close(fd);
+ }
+ if (num < 0)
+ num = 0;
+ return num;
+}
+
+static char
+get_fav_type(boardstat_t *ptr)
+{
+ if (ptr->myattr & NBRD_FOLDER)
+ return FAVT_FOLDER;
+ else if (ptr->myattr & NBRD_BOARD)
+ return FAVT_BOARD;
+ else if (ptr->myattr & NBRD_LINE)
+ return FAVT_LINE;
+ return 0;
+}
+
+static void
+brdlist_foot(void)
+{
+ outs( ANSI_COLOR(34;46) " 選擇看板 "
+ ANSI_COLOR(31;47) " (c)" ANSI_COLOR(30) "新文章模式 "
+ ANSI_COLOR(31) "(v/V)" ANSI_COLOR(30) "標記已讀/未讀 "
+ ANSI_COLOR(31) "(y)" ANSI_COLOR(30) "篩選");
+ if(IS_LISTING_FAV())
+ outs("最愛");
+ else if (IS_LISTING_BRD())
+ outs("部份");
+ else outs("全部");
+
+ outslr(" " ANSI_COLOR(31) "(m)" ANSI_COLOR(30) "切換最愛",
+ 73, ANSI_RESET, 0);
+}
+
+
+static inline char *
+make_class_color(char *name)
+{
+ /* 34 is too dark */
+ char *colorset[8] = {"", ANSI_COLOR(32),
+ ANSI_COLOR(33), ANSI_COLOR(36), ANSI_COLOR(1;34),
+ ANSI_COLOR(1), ANSI_COLOR(1;32), ANSI_COLOR(1;33)};
+
+ return colorset[(unsigned int)
+ (name[0] + name[1] +
+ name[2] + name[3]) & 0x7];
+}
+
+#define HILIGHT_COLOR ANSI_COLOR(1;36)
+
+static void
+show_brdlist(int head, int clsflag, int newflag)
+{
+ int myrow = 2;
+ if (unlikely(IN_CLASSROOT())) {
+ currstat = CLASS;
+ myrow = 6;
+ showtitle("分類看板", BBSName);
+ movie(0);
+ move(1, 0);
+ outs(
+ " "
+ "◣ ╭—" ANSI_COLOR(33) "●\n"
+ " 寣X " ANSI_RESET " "
+ "◢█" ANSI_COLOR(47) "☉" ANSI_COLOR(40) "██◣蔌n"
+ " " ANSI_COLOR(44) " ︿︿︿︿︿︿︿︿ "
+ ANSI_COLOR(33) "" ANSI_RESET ANSI_COLOR(44) " ◣◢███▼▼▼ " ANSI_RESET "\n"
+ " " ANSI_COLOR(44) " "
+ ANSI_COLOR(33) " " ANSI_RESET ANSI_COLOR(44) " ◤◥███▲▲▲ " ANSI_RESET "\n"
+ " ︿︿︿︿︿︿︿︿ " ANSI_COLOR(33)
+ "│" ANSI_RESET " ◥████◤ 鱋n"
+ " " ANSI_COLOR(33) ""
+ "——" ANSI_RESET " ◤ —+" ANSI_RESET);
+ } else if (clsflag) {
+ showtitle("看板列表", BBSName);
+ // [m]加入或移出我的最愛
+ outs("[←][q]主選單 [→][r]閱\讀 [↑↓]選擇 [PgUp][PgDn]翻頁 [S]排序 [/]搜尋 [h]求助\n");
+ outs(ANSI_COLOR(7));
+ outs( newflag ?
+ "總數 未讀 看 板 " :
+ " 編號 看 板 ");
+ outs( " 類別 轉信 中 文 敘 述 人氣 板 主");
+ outslr("", 72, ANSI_RESET, 0);
+ move(b_lines, 0);
+ brdlist_foot();
+ }
+ if (brdnum > 0) {
+ boardstat_t *ptr;
+ char *unread[2] = {ANSI_COLOR(37) " " ANSI_RESET, ANSI_COLOR(1;31) "ˇ" ANSI_RESET};
+
+ if (IS_LISTING_FAV() && get_data_number(get_current_fav()) == 0){
+ mouts(3, 0, " --- 空目錄 - 請按 a (add) 或 i (insert) 加入看板 ---");
+ return;
+ }
+
+ while (++myrow < b_lines) {
+ move(myrow, 0);
+ clrtoeol();
+ if (head < brdnum) {
+ assert(0<=head && head<nbrdsize);
+ ptr = &nbrd[head++];
+ if (ptr->myattr & NBRD_LINE){
+ if( !newflag )
+ prints("%5d %c %s------------ ------------------------------------------" ANSI_RESET,
+ head,
+ ptr->myattr & NBRD_TAG ? 'D' : ' ',
+ ptr->myattr & NBRD_FAV ? "" : ANSI_COLOR(1;30));
+ else
+ prints(" %s------------ ------------------------------------------" ANSI_RESET, ptr->myattr & NBRD_FAV ? "" : ANSI_COLOR(1;30));
+ continue;
+ }
+ else if (ptr->myattr & NBRD_FOLDER){
+ char *title = get_folder_title(ptr->bid);
+ if( !newflag )
+ prints("%5d %c %sMyFavFolder" ANSI_RESET " 目錄 □%-34s" ANSI_RESET,
+ head,
+ ptr->myattr & NBRD_TAG ? 'D' : ' ',
+ !(cuser.uflag2 & FAVNOHILIGHT) ? HILIGHT_COLOR : "",
+ title);
+ else
+ prints("%6d %sMyFavFolder" ANSI_RESET " 目錄 □%-34s" ANSI_RESET,
+ get_data_number(get_fav_folder(getfolder(ptr->bid))),
+ !(cuser.uflag2 & FAVNOHILIGHT) ? HILIGHT_COLOR : "",
+ title);
+ continue;
+ }
+
+ if (IN_CLASSROOT())
+ outs(" ");
+ else {
+ if (!GROUPOP() && !HasBoardPerm(B_BH(ptr))) {
+ prints("%5d %c Unknown?? 隱板 ?這個板是隱板",
+ head, ptr->myattr & NBRD_TAG ? 'D' : ' ');
+ continue;
+ }
+ }
+
+ if (!newflag) {
+ prints("%5d%c%s", head,
+ !(B_BH(ptr)->brdattr & BRD_HIDE) ? ' ' :
+ (B_BH(ptr)->brdattr & BRD_POSTMASK) ? ')' : '-',
+ (ptr->myattr & NBRD_TAG) ? "D " :
+ (B_BH(ptr)->brdattr & BRD_GROUPBOARD) ? " " :
+ unread[ptr->myattr & NBRD_UNREAD ? 1 : 0]);
+ } else {
+ if (B_BH(ptr)->brdattr & BRD_GROUPBOARD)
+ outs(" ");
+ else
+ prints("%6d%s", (int)(B_TOTAL(ptr)),
+ unread[ptr->myattr & NBRD_UNREAD ? 1 : 0]);
+ }
+ if (!IN_CLASSROOT()) {
+ prints("%s%-13s" ANSI_RESET "%s%5.5s" ANSI_COLOR(0;37) "%2.2s" ANSI_RESET
+ "%-34.34s",
+ ((!(cuser.uflag2 & FAVNOHILIGHT) &&
+ getboard(ptr->bid) != NULL))? HILIGHT_COLOR : "",
+ B_BH(ptr)->brdname,
+ make_class_color(B_BH(ptr)->title),
+ B_BH(ptr)->title, B_BH(ptr)->title + 5, B_BH(ptr)->title + 7);
+
+#ifdef USE_COOLDOWN
+ if (B_BH(ptr)->brdattr & BRD_COOLDOWN)
+ outs("靜 ");
+ else if (B_BH(ptr)->brdattr & BRD_BAD)
+#else
+ if (B_BH(ptr)->brdattr & BRD_BAD)
+#endif
+ outs(" X ");
+
+ else if (B_BH(ptr)->nuser <= 0)
+ prints(" %c ", B_BH(ptr)->bvote ? 'V' : ' ');
+ else if (B_BH(ptr)->nuser <= 10)
+ prints("%2d ", B_BH(ptr)->nuser);
+ else if (B_BH(ptr)->nuser <= 50)
+ prints(ANSI_COLOR(1;33) "%2d" ANSI_RESET " ", B_BH(ptr)->nuser);
+
+ else if (B_BH(ptr)->nuser >= 5000)
+ outs(ANSI_COLOR(1;34) "爆!" ANSI_RESET);
+ else if (B_BH(ptr)->nuser >= 2000)
+ outs(ANSI_COLOR(1;31) "爆!" ANSI_RESET);
+ else if (B_BH(ptr)->nuser >= 1000)
+ outs(ANSI_COLOR(1) "爆!" ANSI_RESET);
+ else if (B_BH(ptr)->nuser >= 100)
+ outs(ANSI_COLOR(1) "HOT" ANSI_RESET);
+ else //if (B_BH(ptr)->nuser > 50)
+ prints(ANSI_COLOR(1;31) "%2d" ANSI_RESET " ", B_BH(ptr)->nuser);
+ prints("%.*s" ANSI_CLRTOEND, t_columns - 66, B_BH(ptr)->BM);
+ } else {
+ prints("%-40.40s %.*s", B_BH(ptr)->title + 7,
+ t_columns - 66, B_BH(ptr)->BM);
+ }
+ }
+ clrtoeol();
+ }
+ }
+}
+
+static void
+set_menu_BM(char *BM)
+{
+ if (!HasUserPerm(PERM_NOCITIZEN) && (HasUserPerm(PERM_ALLBOARD) || is_BM(BM))) {
+ currmode |= MODE_GROUPOP;
+ cuser.userlevel |= PERM_SYSSUBOP;
+ }
+}
+
+static void replace_link_by_target(boardstat_t *board)
+{
+ assert(0<=board->bid-1 && board->bid-1<MAX_BOARD);
+ board->bid = BRD_LINK_TARGET(getbcache(board->bid));
+ board->myattr &= ~NBRD_SYMBOLIC;
+}
+static int
+paste_taged_brds(int gid)
+{
+ fav_t *fav;
+ int bid, tmp;
+
+ if (gid == 0 || ! (HasUserPerm(PERM_SYSOP) || GROUPOP()) ||
+ getans("貼上標記的看板?(y/N)")!='y') return 0;
+ fav = get_fav_root();
+ for (tmp = 0; tmp < fav->DataTail; tmp++) {
+ boardheader_t *bh;
+ bid = fav_getid(&fav->favh[tmp]);
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ bh = getbcache(bid);
+ if( !is_set_attr(&fav->favh[tmp], FAVH_ADM_TAG))
+ continue;
+ set_attr(&fav->favh[tmp], FAVH_ADM_TAG, FALSE);
+ if (bh->gid != gid) {
+ bh->gid = gid;
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ substitute_record(FN_BOARD, bh,
+ sizeof(boardheader_t), bid);
+ reset_board(bid);
+ log_usies("SetBoardGID", bh->brdname);
+ }
+ }
+ sort_bcache();
+ return 1;
+}
+
+static void
+choose_board(int newflag)
+{
+ static int num = 0;
+ boardstat_t *ptr;
+ int head = -1, ch = 0, currmodetmp, tmp, tmp1, bidtmp;
+ char keyword[13] = "", buf[64];
+
+ setutmpmode(newflag ? READNEW : READBRD);
+ if( get_fav_root() == NULL )
+ fav_load();
+ ++choose_board_depth;
+ brdnum = 0;
+ if (!cuser.userlevel) /* guest yank all boards */
+ LIST_BRD();
+
+ do {
+ if (brdnum <= 0) {
+ load_boards(keyword);
+ if (brdnum <= 0) {
+ if (keyword[0] != 0) {
+ vmsg("沒有任何看板標題有此關鍵字");
+ keyword[0] = 0;
+ brdnum = -1;
+ continue;
+ }
+ if (IS_LISTING_BRD()) {
+ if (HasUserPerm(PERM_SYSOP) || GROUPOP()) {
+ if (paste_taged_brds(class_bid) ||
+ m_newbrd(class_bid, 0) == -1)
+ break;
+ brdnum = -1;
+ continue;
+ } else
+ break;
+ }
+ }
+ head = -1;
+ }
+
+ /* reset the cursor when out of range */
+ if (num < 0)
+ num = 0;
+ else if (num >= brdnum)
+ num = brdnum - 1;
+
+ if (head < 0) {
+ if (newflag) {
+ tmp = num;
+ assert(brdnum<=nbrdsize);
+ while (num < brdnum) {
+ ptr = &nbrd[num];
+ if (ptr->myattr & NBRD_UNREAD)
+ break;
+ num++;
+ }
+ if (num >= brdnum)
+ num = tmp;
+ }
+ head = (num / p_lines) * p_lines;
+ show_brdlist(head, 1, newflag);
+ } else if (num < head || num >= head + p_lines) {
+ head = (num / p_lines) * p_lines;
+ show_brdlist(head, 0, newflag);
+ }
+ if (IN_CLASSROOT())
+ ch = cursor_key(7 + num - head, 10);
+ else
+ ch = cursor_key(3 + num - head, 0);
+
+ switch (ch) {
+ case Ctrl('W'):
+ whereami();
+ head = -1;
+ break;
+ case 'e':
+ case KEY_LEFT:
+ case EOF:
+ ch = 'q';
+ case 'q':
+ if (keyword[0]) {
+ keyword[0] = 0;
+ brdnum = -1;
+ ch = ' ';
+ }
+ break;
+ case 'c':
+ show_brdlist(head, 1, newflag ^= 1);
+ break;
+ case KEY_PGUP:
+ case 'P':
+ case 'b':
+ case Ctrl('B'):
+ if (num) {
+ num -= p_lines;
+ break;
+ }
+ case KEY_END:
+ case '$':
+ num = brdnum - 1;
+ break;
+ case ' ':
+ case KEY_PGDN:
+ case 'N':
+ case Ctrl('F'):
+ if (num == brdnum - 1)
+ num = 0;
+ else
+ num += p_lines;
+ break;
+ case Ctrl('I'):
+ t_idle();
+ show_brdlist(head, 1, newflag);
+ break;
+ case KEY_UP:
+ case 'p':
+ case 'k':
+ if (num-- <= 0)
+ num = brdnum - 1;
+ break;
+ case '*':
+ if (IS_LISTING_FAV()) {
+ int i = 0;
+ assert(brdnum<=nbrdsize);
+ for (i = 0; i < brdnum; i++)
+ {
+ ptr = &nbrd[i];
+ if (IS_LISTING_FAV()){
+ assert(nbrdsize>0);
+ if(get_fav_type(&nbrd[0]) != 0)
+ fav_tag(ptr->bid, get_fav_type(ptr), 2);
+ }
+ ptr->myattr ^= NBRD_TAG;
+ }
+ head = 9999;
+ }
+ break;
+ case 't':
+ assert(0<=num && num<nbrdsize);
+ ptr = &nbrd[num];
+ if (IS_LISTING_FAV()){
+ assert(nbrdsize>0);
+ if(get_fav_type(&nbrd[0]) != 0)
+ fav_tag(ptr->bid, get_fav_type(ptr), 2);
+ }
+ else{
+ /* 站長管理用的 tag */
+ if (ptr->myattr & NBRD_TAG)
+ set_attr(getadmtag(ptr->bid), FAVH_ADM_TAG, FALSE);
+ else
+ fav_add_admtag(ptr->bid);
+ }
+ ptr->myattr ^= NBRD_TAG;
+ head = 9999;
+ case KEY_DOWN:
+ case 'n':
+ case 'j':
+ if (++num < brdnum)
+ break;
+ case '0':
+ case KEY_HOME:
+ num = 0;
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if ((tmp = search_num(ch, brdnum)) >= 0)
+ num = tmp;
+ brdlist_foot();
+ break;
+ case 'F':
+ case 'f':
+ if (HasUserPerm(PERM_SYSOP)) {
+ getbcache(class_bid)->firstchild[cuser.uflag & BRDSORT_FLAG ? 1 : 0] = 0;
+ brdnum = -1;
+ }
+ break;
+ case 'h':
+ show_helpfile(fn_boardlisthelp);
+ show_brdlist(head, 1, newflag);
+ break;
+ case '/':
+ getdata_buf(b_lines - 1, 0, "請輸入看板中文關鍵字:",
+ keyword, sizeof(keyword), DOECHO);
+ brdnum = -1;
+ break;
+ case 'S':
+ if(IS_LISTING_FAV()){
+ move(b_lines - 2, 0);
+ outs("重新排序看板 "
+ ANSI_COLOR(1;33) "(注意, 這個動作會覆寫原來設定)" ANSI_RESET " \n");
+ tmp = getans("排序方式 (1)按照板名排序 (2)按照類別排序 ==> [0]取消 ");
+ if( tmp == '1' )
+ fav_sort_by_name();
+ else if( tmp == '2' )
+ fav_sort_by_class();
+ }
+ else
+ cuser.uflag ^= BRDSORT_FLAG;
+ brdnum = -1;
+ break;
+ case 'y':
+ if (HasUserPerm(PERM_LOGINOK)) {
+ if (get_current_fav() != NULL || !IS_LISTING_FAV()){
+ if (cuser.userlevel)
+ yank_flag ^= 1; /* FAV <=> BRD */
+ }
+ brdnum = -1;
+ }
+ break;
+ case 'D':
+ if (HasUserPerm(PERM_SYSOP) ||
+ (HasUserPerm(PERM_SYSSUPERSUBOP) && GROUPOP())) {
+ assert(0<=num && num<nbrdsize);
+ ptr = &nbrd[num];
+ if (ptr->myattr & NBRD_SYMBOLIC) {
+ if (getans("確定刪除連結?[N/y]") == 'y')
+ delete_symbolic_link(getbcache(ptr->bid), ptr->bid);
+ }
+ brdnum = -1;
+ }
+ break;
+ case Ctrl('D'):
+ if (HasUserPerm(PERM_LOGINOK)) {
+ if (getans("刪除所有標記[N]?") == 'y'){
+ fav_remove_all_tagged_item();
+ brdnum = -1;
+ }
+ }
+ break;
+ case Ctrl('A'):
+ if (HasUserPerm(PERM_LOGINOK)) {
+ fav_add_all_tagged_item();
+ brdnum = -1;
+ }
+ break;
+ case Ctrl('T'):
+ if (HasUserPerm(PERM_LOGINOK)) {
+ fav_remove_all_tag();
+ brdnum = -1;
+ }
+ break;
+ case Ctrl('P'):
+ if (paste_taged_brds(class_bid))
+ brdnum = -1;
+ break;
+ case 'L':
+ if ((HasUserPerm(PERM_SYSOP) ||
+ (HasUserPerm(PERM_SYSSUPERSUBOP) && GROUPOP())) && IN_CLASS()) {
+ if (make_symbolic_link_interactively(class_bid) < 0)
+ break;
+ brdnum = -1;
+ head = 9999;
+ }
+ else if (HasUserPerm(PERM_LOGINOK) && IS_LISTING_FAV()) {
+ if (fav_add_line() == NULL) {
+ vmsg("新增失敗,分隔線/總最愛 數量達最大值。");
+ break;
+ }
+ /* done move if it's the first item. */
+ assert(nbrdsize>0);
+ if (get_fav_type(&nbrd[0]) != 0)
+ move_in_current_folder(brdnum, num);
+ brdnum = -1;
+ head = 9999;
+ }
+ break;
+/*
+ case 'l':
+ if (HasUserPerm(PERM_SYSOP) && (nbrd[num].myattr & NBRD_SYMBOLIC)) {
+ replace_link_by_target(&nbrd[num]);
+ head = 9999;
+ }
+ break;
+*/
+ case 'z':
+ case 'm':
+ if (HasUserPerm(PERM_LOGINOK)) {
+ assert(0<=num && num<nbrdsize);
+ ptr = &nbrd[num];
+ if (IS_LISTING_FAV()) {
+ if (ptr->myattr & NBRD_FAV) {
+ if (getans("你確定刪除嗎? [N/y]") != 'y')
+ break;
+ fav_remove_item(ptr->bid, get_fav_type(ptr));
+ ptr->myattr &= ~NBRD_FAV;
+ }
+ }
+ else {
+ if (getboard(ptr->bid) != NULL) {
+ fav_remove_item(ptr->bid, FAVT_BOARD);
+ ptr->myattr &= ~NBRD_FAV;
+ }
+ else {
+ if (fav_add_board(ptr->bid) == NULL)
+ vmsg("你的最愛太多了啦 真花心");
+ else
+ ptr->myattr |= NBRD_FAV;
+ }
+ }
+ brdnum = -1;
+ head = 9999;
+ }
+ break;
+ case 'M':
+ if (HasUserPerm(PERM_LOGINOK)){
+ if (IN_FAVORITE() && IS_LISTING_FAV()){
+ imovefav(num);
+ brdnum = -1;
+ head = 9999;
+ }
+ }
+ break;
+ case 'g':
+ if (HasUserPerm(PERM_LOGINOK) && IS_LISTING_FAV()) {
+ fav_type_t *ft;
+ if (fav_stack_full()){
+ vmsg("目錄已達最大層數!!");
+ break;
+ }
+ if ((ft = fav_add_folder()) == NULL) {
+ vmsg("新增失敗,目錄/總最愛 數量達最大值。");
+ break;
+ }
+ fav_set_folder_title(ft, "新的目錄");
+ /* don't move if it's the first item */
+ assert(nbrdsize>0);
+ if (get_fav_type(&nbrd[0]) != 0)
+ move_in_current_folder(brdnum, num);
+ brdnum = -1;
+ head = 9999;
+ }
+ break;
+ case 'T':
+ assert(0<=num && num<nbrdsize);
+ if (HasUserPerm(PERM_LOGINOK) && nbrd[num].myattr & NBRD_FOLDER) {
+ fav_type_t *ft = getfolder(nbrd[num].bid);
+ strlcpy(buf, get_item_title(ft), sizeof(buf));
+ getdata_buf(b_lines - 1, 0, "請輸入板名:", buf, 65, DOECHO);
+ fav_set_folder_title(ft, buf);
+ brdnum = -1;
+ }
+ break;
+ case 'K':
+ if (HasUserPerm(PERM_LOGINOK)) {
+ char c, fname[80];
+ if (get_current_fav() != get_fav_root()) {
+ vmsg("請到我的最愛最上層執行本功\能");
+ break;
+ }
+
+ c = getans("請選擇 2)備份我的最愛 3)取回最愛備份 [Q]");
+ if(!c)
+ break;
+ if(getans("確定嗎 [y/N] ") != 'y')
+ break;
+ switch(c){
+ case '2':
+ fav_save();
+ setuserfile(fname, FAV);
+ sprintf(buf, "%s.bak", fname);
+ Copy(fname, buf);
+ break;
+ case '3':
+ setuserfile(fname, FAV);
+ sprintf(buf, "%s.bak", fname);
+ if (!dashf(buf)){
+ vmsg("你沒有備份你的最愛喔");
+ break;
+ }
+ Copy(buf, fname);
+ fav_free();
+ fav_load();
+ break;
+ }
+ brdnum = -1;
+ }
+ break;
+#if 0
+ case 'z':
+ if (HasUserPerm(PERM_LOGINOK))
+ vmsg("這個功\能已經被我的最愛取代掉了喔!");
+ break;
+
+ case 'Z':
+ if (HasUserPerm(PERM_LOGINOK))
+ vmsg("為避免誤按此功\能已取消,請改至個人設定區修改設定");
+ break;
+
+ if (HasUserPerm(PERM_LOGINOK)) {
+ char genbuf[64];
+ sprintf(genbuf, "確定要 %s訂閱\ 新看板? [N/y] ", cuser.uflag2 & FAVNEW_FLAG ? "取消" : "");
+ if (getans(genbuf) != 'y')
+ break;
+
+ cuser.uflag2 ^= FAVNEW_FLAG;
+ if (cuser.uflag2 & FAVNEW_FLAG)
+ vmsg("切換為訂閱\新看板模式");
+ else
+ vmsg("取消訂閱\新看板");
+ }
+ break;
+#endif
+
+ case 'v':
+ case 'V':
+ assert(0<=num && num<nbrdsize);
+ ptr = &nbrd[num];
+ if(nbrd[num].bid < 0 || !HasBoardPerm(B_BH(ptr)))
+ break;
+ if (ch == 'v') {
+ ptr->myattr &= ~NBRD_UNREAD;
+ brc_toggle_all_read(ptr->bid, 1);
+ } else {
+ brc_toggle_all_read(ptr->bid, 0);
+ ptr->myattr |= NBRD_UNREAD;
+ }
+ show_brdlist(head, 0, newflag);
+ break;
+ case 's':
+ if ((tmp = search_board()) == -1) {
+ show_brdlist(head, 1, newflag);
+ break;
+ }
+ head = -1;
+ num = tmp;
+ break;
+ case 'E':
+ if (HasUserPerm(PERM_SYSOP | PERM_BOARD) || GROUPOP()) {
+ assert(0<=num && num<nbrdsize);
+ ptr = &nbrd[num];
+ move(1, 1);
+ clrtobot();
+ m_mod_board(B_BH(ptr)->brdname);
+ brdnum = -1;
+ }
+ break;
+ case 'R':
+ if (HasUserPerm(PERM_SYSOP) || GROUPOP()) {
+ m_newbrd(class_bid, 1);
+ brdnum = -1;
+ }
+ break;
+ case 'B':
+ if (HasUserPerm(PERM_SYSOP) || GROUPOP()) {
+ m_newbrd(class_bid, 0);
+ brdnum = -1;
+ }
+ break;
+ case 'W':
+ if (IN_SUBCLASS() &&
+ (HasUserPerm(PERM_SYSOP) || GROUPOP())) {
+ setbpath(buf, getbcache(class_bid)->brdname);
+ mkdir(buf, 0755); /* Ptt:開群組目錄 */
+ b_note_edit_bname(class_bid);
+ brdnum = -1;
+ }
+ break;
+
+ case 'a':
+ case 'i':
+ if(IS_LISTING_FAV() && HasUserPerm(PERM_LOGINOK)){
+ char bname[IDLEN + 1];
+ int bid;
+ move(0, 0);
+ clrtoeol();
+ /* use CompleteBoard or CompleteBoardAndGroup ? */
+ CompleteBoard(ANSI_COLOR(7) "【 增加我的最愛 】" ANSI_RESET "\n"
+ "請輸入欲加入的看板名稱(按空白鍵自動搜尋):",
+ bname);
+
+ if (bname[0] && (bid = getbnum(bname)) &&
+ HasBoardPerm(getbcache(bid))) {
+ fav_type_t * ptr = getboard(bid);
+ if (ptr != NULL) { // already in fav list
+ // move curser to item
+ for (num = 0; num<nbrdsize && bid != nbrd[num].bid; ++num);
+ assert(bid==nbrd[num].bid);
+ } else {
+ ptr = fav_add_board(bid);
+
+ if (ptr == NULL)
+ vmsg("你的最愛太多了啦 真花心");
+ else {
+ ptr->attr |= NBRD_FAV;
+
+ if (ch == 'i' && get_data_number(get_current_fav()) > 1)
+ move_in_current_folder(brdnum, num);
+ else
+ num = brdnum;
+ }
+ }
+ }
+ }
+ brdnum = -1;
+ head = 9999;
+ break;
+
+ case 'w':
+ /* allowing save once per 10 minutes */
+ if (now - last_save_fav_and_brc > 10 * 60) {
+ fav_save();
+ brc_finalize();
+
+ last_save_fav_and_brc = now;
+ }
+ break;
+
+ case KEY_RIGHT:
+ case '\n':
+ case '\r':
+ case 'r':
+ {
+ if (IS_LISTING_FAV()) {
+ assert(nbrdsize>0);
+ if (get_fav_type(&nbrd[0]) == 0)
+ break;
+ assert(0<=num && num<nbrdsize);
+ ptr = &nbrd[num];
+ if (ptr->myattr & NBRD_LINE)
+ break;
+ if (ptr->myattr & NBRD_FOLDER){
+ int t = num;
+ num = 0;
+ fav_folder_in(ptr->bid);
+ choose_board(0);
+ fav_folder_out();
+ num = t;
+ LIST_FAV(); // XXX press 'y' in fav makes yank_flag = LIST_BRD
+ brdnum = -1;
+ head = 9999;
+ break;
+ }
+ } else {
+ assert(0<=num && num<nbrdsize);
+ ptr = &nbrd[num];
+ if (ptr->myattr & NBRD_SYMBOLIC) {
+ replace_link_by_target(ptr);
+ }
+ }
+
+ assert(0<=ptr->bid-1 && ptr->bid-1<MAX_BOARD);
+ if (!(B_BH(ptr)->brdattr & BRD_GROUPBOARD)) { /* 非sub class */
+ if (HasBoardPerm(B_BH(ptr))) {
+ brc_initial_board(B_BH(ptr)->brdname);
+
+ if (newflag) {
+ setbdir(buf, currboard);
+ tmp = unread_position(buf, ptr);
+ head = tmp - t_lines / 2;
+ getkeep(buf, head > 1 ? head : 1, tmp + 1);
+ }
+ Read();
+ check_newpost(ptr);
+ head = -1;
+ setutmpmode(newflag ? READNEW : READBRD);
+ }
+ } else { /* sub class */
+ move(12, 1);
+ bidtmp = class_bid;
+ currmodetmp = currmode;
+ tmp1 = num;
+ num = 0;
+ if (!(B_BH(ptr)->brdattr & BRD_TOP))
+ class_bid = ptr->bid;
+ else
+ class_bid = -1; /* 熱門群組用 */
+
+ if (!GROUPOP()) /* 如果還沒有小組長權限 */
+ set_menu_BM(B_BH(ptr)->BM);
+
+ if (now < B_BH(ptr)->bupdate) {
+ int mr = 0;
+
+ setbfile(buf, B_BH(ptr)->brdname, fn_notes);
+ mr = more(buf, NA);
+ if (mr != -1 && mr != READ_NEXT)
+ pressanykey();
+ }
+ tmp = currutmp->brc_id;
+ setutmpbid(ptr->bid);
+ free(nbrd);
+ nbrd = NULL;
+ nbrdsize = 0;
+ if (IS_LISTING_FAV()) {
+ LIST_BRD();
+ choose_board(0);
+ LIST_FAV();
+ }
+ else
+ choose_board(0);
+ currmode = currmodetmp; /* 離開板板後就把權限拿掉喔 */
+ num = tmp1;
+ class_bid = bidtmp;
+ setutmpbid(tmp);
+ brdnum = -1;
+ }
+ }
+ }
+ } while (ch != 'q');
+ free(nbrd);
+ nbrd = NULL;
+ nbrdsize = 0;
+ --choose_board_depth;
+}
+
+int
+Class(void)
+{
+ init_brdbuf();
+ class_bid = 1;
+ LIST_BRD();
+ choose_board(0);
+ return 0;
+}
+
+int
+Favorite(void)
+{
+ init_brdbuf();
+ class_bid = 0;
+ LIST_FAV();
+ choose_board(0);
+ return 0;
+}
+
+
+int
+New(void)
+{
+ int mode0 = currutmp->mode;
+ int stat0 = currstat;
+
+ class_bid = 0;
+ init_brdbuf();
+ choose_board(1);
+ currutmp->mode = mode0;
+ currstat = stat0;
+ return 0;
+}
diff --git a/pttbbs/mbbsd/brc.c b/pttbbs/mbbsd/brc.c
new file mode 100644
index 00000000..3982d3da
--- /dev/null
+++ b/pttbbs/mbbsd/brc.c
@@ -0,0 +1,518 @@
+/* $Id$ */
+#include "bbs.h"
+
+/**
+ * 關於本檔案的細節,請見 docs/brc.txt。
+ */
+
+#ifndef BRC_MAXNUM
+#define BRC_STRLEN 15 /* Length of board name, for old brc */
+#define BRC_MAXSIZE 24576 /* Effective size of brc rc file, 8192 * 3 */
+#define BRC_MAXNUM 80 /* Upper bound of brc_num, size of brc_list */
+#endif
+
+#define BRC_BLOCKSIZE 1024
+
+#if MAX_BOARD > 65535 || BRC_MAXSIZE > 65535
+#error Max number of boards or BRC_MAXSIZE cannot fit in unsighed short, \
+please rewrite brc.c
+#endif
+
+typedef unsigned short brcbid_t;
+typedef unsigned short brcnbrd_t;
+
+/* old brc rc file form:
+ * board_name 15 bytes
+ * brc_num 1 byte, binary integer
+ * brc_list brc_num * sizeof(int) bytes, brc_num binary integer(s) */
+
+static char brc_initialized = 0;
+static time4_t brc_expire_time;
+ /* Will be set to the time one year before login. All the files created
+ * before then will be recognized read. */
+
+static int brc_changed = 0; /**< brc_list/brc_num changed */
+/* The below two will be filled by read_brc_buf() and brc_update() */
+static char *brc_buf = NULL;
+static int brc_size;
+static int brc_alloc;
+
+// read records for currbid
+static int brc_currbid;
+static int brc_num;
+static time4_t brc_list[BRC_MAXNUM];
+
+static char * const fn_oldboardrc = ".boardrc";
+static char * const fn_brc = ".brc2";
+
+/**
+ * find read records of bid in given buffer region
+ *
+ * @param[in] begin
+ * @param[in] endp
+ * @param bid
+ * @param[out] num number of records, which could be written for \a bid
+ * = brc_num if found
+ * = 0 or dangling size if not found
+ *
+ * @return address of record. \a begin <= addr < \a endp.
+ * 0 if not found
+ */
+/* Returns the address of the record strictly between begin and endp with
+ * bid equal to the parameter. Returns 0 if not found.
+ * brcnbrd_t *num is an output parameter which will filled with brc_num
+ * if the record is found. If not found the record, *num will be the number
+ * of dangling bytes. */
+static char *
+brc_findrecord_in(char *begin, char *endp, brcbid_t bid, brcnbrd_t *num)
+{
+ char *tmpp, *ptr = begin;
+ brcbid_t tbid;
+ while (ptr + sizeof(brcbid_t) + sizeof(brcnbrd_t) < endp) {
+ /* for each available records */
+ tmpp = ptr;
+ tbid = *(brcbid_t*)tmpp;
+ tmpp += sizeof(brcbid_t);
+ *num = *(brcnbrd_t*)tmpp;
+ tmpp += sizeof(brcnbrd_t) + *num * sizeof(time4_t); /* end of record */
+
+ if ( tmpp > endp ){
+ /* dangling, ignore the trailing data */
+ *num = (brcnbrd_t)(endp - ptr); /* for brc_insert_record() */
+ return 0;
+ }
+ if ( tbid == bid )
+ return ptr;
+ ptr = tmpp;
+ }
+
+ *num = 0;
+ return 0;
+}
+
+static time4_t *
+brc_find_record(int bid, int *num)
+{
+ char *p;
+ brcnbrd_t tnum;
+ p = brc_findrecord_in(brc_buf, brc_buf + brc_size, bid, &tnum);
+ *num = tnum;
+ if (p)
+ return (time4_t*)(p + sizeof(brcbid_t) + sizeof(brcnbrd_t));
+ *num = 0;
+ return 0;
+}
+
+static char *
+brc_putrecord(char *ptr, char *endp, brcbid_t bid,
+ brcnbrd_t num, const time4_t *list)
+{
+ char * tmp;
+ if (num > 0 && list[0] > brc_expire_time &&
+ ptr + sizeof(brcbid_t) + sizeof(brcnbrd_t) < endp) {
+ if (num > BRC_MAXNUM)
+ num = BRC_MAXNUM;
+
+ if (num == 0) return ptr;
+
+ *(brcbid_t*)ptr = bid; /* write in bid */
+ ptr += sizeof(brcbid_t);
+ *(brcnbrd_t*)ptr = num; /* write in brc_num */
+ ptr += sizeof(brcnbrd_t);
+ tmp = ptr + num * sizeof(time4_t);
+ if (tmp <= endp)
+ memcpy(ptr, list, num * sizeof(time4_t)); /* write in brc_list */
+ ptr = tmp;
+ }
+ return ptr;
+}
+
+static inline int
+brc_enlarge_buf(void)
+{
+ char *buffer;
+ if (brc_alloc >= BRC_MAXSIZE)
+ return 0;
+
+#ifdef CRITICAL_MEMORY
+#define THE_MALLOC(X) MALLOC(X)
+#define THE_FREE(X) FREE(X)
+#else
+#define THE_MALLOC(X) alloca(X)
+#define THE_FREE(X) (void)(X)
+ /* alloca get memory from stack and automatically freed when
+ * function returns. */
+#endif
+
+ buffer = (char*)THE_MALLOC(brc_alloc);
+ assert(buffer);
+
+ memcpy(buffer, brc_buf, brc_alloc);
+ free(brc_buf);
+ brc_alloc += BRC_BLOCKSIZE;
+ brc_buf = (char*)malloc(brc_alloc);
+ assert(brc_buf);
+ memcpy(brc_buf, buffer, brc_alloc - BRC_BLOCKSIZE);
+
+#ifdef DEBUG
+ vmsgf("brc enlarged to %d bytes", brc_alloc);
+#endif
+
+ THE_FREE(buffer);
+ return 1;
+
+#undef THE_MALLOC
+#undef THE_FREE
+}
+
+static inline void
+brc_get_buf(int size){
+ if (!size)
+ brc_alloc = BRC_BLOCKSIZE;
+ else
+ brc_alloc = (size + BRC_BLOCKSIZE - 1) / BRC_BLOCKSIZE * BRC_BLOCKSIZE;
+ if (brc_alloc > BRC_MAXSIZE)
+ brc_alloc = BRC_MAXSIZE;
+ brc_buf = (char*)malloc(brc_alloc);
+ assert(brc_buf);
+}
+
+static inline void
+brc_insert_record(brcbid_t bid, brcnbrd_t num, const time4_t* list)
+{
+ char *ptr;
+ int new_size, end_size;
+ brcnbrd_t tnum;
+
+ ptr = brc_findrecord_in(brc_buf, brc_buf + brc_size, bid, &tnum);
+
+ while (num > 0 && list[num - 1] < brc_expire_time)
+ num--; /* don't write the times before brc_expire_time */
+
+ if (!ptr) {
+ brc_size -= (int)tnum;
+
+ /* put on the beginning */
+ if (num){
+ new_size = sizeof(brcbid_t) + sizeof(brcnbrd_t)
+ + num * sizeof(time4_t);
+ brc_size += new_size;
+ if (brc_size > brc_alloc && !brc_enlarge_buf())
+ brc_size = BRC_MAXSIZE;
+ if (brc_size > new_size)
+ memmove(brc_buf + new_size, brc_buf, brc_size - new_size);
+ brc_putrecord(brc_buf, brc_buf + new_size, bid, num, list);
+ }
+ } else {
+ /* ptr points to the old current brc list.
+ * tmpp is the end of it (exclusive). */
+ int len = sizeof(brcbid_t) + sizeof(brcnbrd_t) + tnum * sizeof(time4_t);
+ char *tmpp = ptr + len;
+ end_size = brc_buf + brc_size - tmpp;
+ if (num) {
+ int sindex = ptr - brc_buf;
+ new_size = (sizeof(brcbid_t) + sizeof(brcnbrd_t)
+ + num * sizeof(time4_t));
+ brc_size += new_size - len;
+ if (brc_size > brc_alloc) {
+ if (brc_enlarge_buf()) {
+ ptr = brc_buf + sindex;
+ tmpp = ptr + len;
+ } else {
+ end_size -= brc_size - BRC_MAXSIZE;
+ brc_size = BRC_MAXSIZE;
+ }
+ }
+ if (end_size > 0 && ptr + new_size != tmpp)
+ memmove(ptr + new_size, tmpp, end_size);
+ brc_putrecord(ptr, brc_buf + brc_alloc, bid, num, list);
+ } else { /* deleting record */
+ memmove(ptr, tmpp, end_size);
+ brc_size -= len;
+ }
+ }
+
+ brc_changed = 0;
+}
+
+/**
+ * write \a brc_num and \a brc_list back to \a brc_buf.
+ */
+void
+brc_update(){
+ if (brc_currbid && brc_changed && cuser.userlevel && brc_num > 0) {
+ brc_initialize();
+ brc_insert_record(brc_currbid, brc_num, brc_list);
+ }
+}
+
+/* return 1 if successfully read from old .boardrc file.
+ * otherwise, return 0. */
+inline static void
+read_old_brc(int fd)
+{
+ char brdname[BRC_STRLEN + 1];
+ char *ptr;
+ brcnbrd_t num;
+ brcbid_t bid;
+ brcbid_t read_brd[512];
+ int nRead = 0, i;
+
+ ptr = brc_buf;
+ brc_size = 0;
+ while (read(fd, brdname, BRC_STRLEN + 1) == BRC_STRLEN + 1) {
+ num = brdname[BRC_STRLEN];
+ brdname[BRC_STRLEN] = 0;
+ bid = getbnum(brdname);
+
+ for (i = 0; i < nRead; ++i)
+ if (read_brd[i] == bid)
+ break;
+ if (i != nRead){
+ lseek(fd, num * sizeof(int), SEEK_CUR);
+ continue;
+ }
+ read_brd[nRead >= 512 ? nRead = 0 : nRead++] = bid;
+
+ *(brcbid_t*)ptr = bid;
+ ptr += sizeof(brcbid_t);
+ *(brcnbrd_t*)ptr = num;
+ ptr += sizeof(brcnbrd_t);
+ if (read(fd, ptr, sizeof(int) * num) != sizeof(int) * num)
+ break;
+
+ brc_size += sizeof(brcbid_t) + sizeof(brcnbrd_t)
+ + sizeof(time4_t) * num;
+ ptr += sizeof(time4_t) * num;
+ }
+}
+
+inline static void
+read_brc_buf(void)
+{
+ if (brc_buf == NULL) {
+ char brcfile[STRLEN];
+ int fd;
+ struct stat brcstat;
+
+ setuserfile(brcfile, fn_brc);
+ if ((fd = open(brcfile, O_RDONLY)) != -1) {
+ fstat(fd, &brcstat);
+ brc_get_buf(brcstat.st_size);
+ brc_size = read(fd, brc_buf, brc_alloc);
+ close(fd);
+ } else {
+ setuserfile(brcfile, fn_oldboardrc);
+ if ((fd = open(brcfile, O_RDONLY)) != -1) {
+ fstat(fd, &brcstat);
+ brc_get_buf(brcstat.st_size);
+ read_old_brc(fd);
+ close(fd);
+ } else
+ brc_size = 0;
+ }
+ }
+}
+
+/* release allocated memory
+ *
+ * Do not destory brc_currbid, brc_num, brc_list.
+ */
+void
+brc_release()
+{
+ if (brc_buf) {
+ free(brc_buf);
+ brc_buf = NULL;
+ }
+ brc_changed = 0;
+ brc_size = brc_alloc = 0;
+}
+
+void
+brc_finalize(){
+ char brcfile[STRLEN];
+ char tmpfile[STRLEN];
+
+ if(!brc_initialized)
+ return;
+
+ brc_update();
+ setuserfile(brcfile, fn_brc);
+ snprintf(tmpfile, sizeof(tmpfile), "%s.tmp.%x", brcfile, getpid());
+ if (brc_buf != NULL) {
+ int fd = open(tmpfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd != -1) {
+ int ok=0;
+ if(write(fd, brc_buf, brc_size)==brc_size)
+ ok=1;
+ close(fd);
+ if(ok)
+ Rename(tmpfile, brcfile);
+ else
+ unlink(tmpfile);
+ }
+ }
+
+ brc_release();
+ brc_initialized = 0;
+}
+
+int
+brc_initialize(){
+ if (brc_initialized)
+ return 1;
+ brc_initialized = 1;
+ brc_expire_time = login_start_time - 365 * 86400;
+ read_brc_buf();
+ return 0;
+}
+
+/**
+ * get the read records for bid
+ *
+ * @param bid
+ * @param[out] num number of record for \a bid. 1 <= \a num <= \a BRC_MAXNUM
+ * \a num = 1 if no records.
+ * @param[out] list the list of records, length \a num
+ *
+ * @return number of read record, 0 if no records
+ */
+static int
+brc_read_record(int bid, int *num, time4_t *list){
+ char *ptr;
+ brcnbrd_t tnum;
+ ptr = brc_findrecord_in(brc_buf, brc_buf + brc_size, bid, &tnum);
+ *num = tnum;
+ if ( ptr ){
+ assert(0 <= *num && *num <= BRC_MAXNUM);
+ memcpy(list, ptr + sizeof(brcbid_t) + sizeof(brcnbrd_t),
+ *num * sizeof(time4_t));
+ return *num;
+ }
+ list[0] = *num = 1;
+ return 0;
+}
+
+/**
+ * @return number of records in \a boardname
+ */
+int
+brc_initial_board(const char *boardname)
+{
+ brc_initialize();
+
+ if (strcmp(currboard, boardname) == 0) {
+ assert(currbid == brc_currbid);
+ return brc_num;
+ }
+
+ brc_update(); /* write back first */
+ currbid = getbnum(boardname);
+ if( currbid == 0 )
+ currbid = getbnum(DEFAULT_BOARD);
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ brc_currbid = currbid;
+ currboard = bcache[currbid - 1].brdname;
+ currbrdattr = bcache[currbid - 1].brdattr;
+
+ return brc_read_record(brc_currbid, &brc_num, brc_list);
+}
+
+static void
+brc_trunc(int bid, time4_t ftime){
+ brc_insert_record(bid, 1, &ftime);
+ if ( bid == brc_currbid ){
+ brc_num = 1;
+ brc_list[0] = ftime;
+ brc_changed = 0;
+ }
+}
+
+void
+brc_toggle_all_read(int bid, int is_all_read)
+{
+ brc_initialize();
+ if (is_all_read)
+ brc_trunc(bid, now);
+ else
+ brc_trunc(bid, 1);
+}
+
+void
+brc_addlist(const char *fname)
+{
+ int n, i;
+ time4_t ftime;
+
+ assert(currbid == brc_currbid);
+ if (!cuser.userlevel)
+ return;
+ brc_initialize();
+
+ ftime = atoi(&fname[2]);
+ if (ftime <= brc_expire_time /* too old, don't do any thing */
+ /* || fname[0] != 'M' || fname[1] != '.' */ ) {
+ return;
+ }
+ if (brc_num <= 0) { /* uninitialized */
+ brc_list[0] = ftime;
+ brc_num = 1;
+ brc_changed = 1;
+ return;
+ }
+ if ((brc_num == 1) && (ftime < brc_list[0])) /* most when after 'v' */
+ return;
+ for (n = 0; n < brc_num; n++) { /* using linear search */
+ if (ftime == brc_list[n]) {
+ return;
+ } else if (ftime > brc_list[n]) {
+ if (brc_num < BRC_MAXNUM)
+ brc_num++;
+ /* insert ftime into brc_list */
+ for (i = brc_num - 1; --i >= n; brc_list[i + 1] = brc_list[i]);
+ brc_list[n] = ftime;
+ brc_changed = 1;
+ return;
+ }
+ }
+}
+
+int
+brc_unread_time(int bid, time4_t ftime)
+{
+ int i;
+ int bnum;
+ const time4_t *blist;
+
+ brc_initialize();
+ if (ftime <= brc_expire_time) /* too old */
+ return 0;
+
+ if (brc_currbid && bid == brc_currbid) {
+ blist = brc_list;
+ bnum = brc_num;
+ } else {
+ blist = brc_find_record(bid, &bnum);
+ }
+
+ if (bnum <= 0)
+ return 1;
+ for (i = 0; i < bnum; i++) { /* using linear search */
+ if (ftime > blist[i])
+ return 1;
+ else if (ftime == blist[i])
+ return 0;
+ }
+ return 0;
+}
+
+int
+brc_unread(int bid, const char *fname)
+{
+ int ftime;
+
+ ftime = atoi(&fname[2]); /* this will get the time of the file created */
+
+ return brc_unread_time(bid, ftime);
+}
diff --git a/pttbbs/mbbsd/cache.c b/pttbbs/mbbsd/cache.c
new file mode 100644
index 00000000..f38263e8
--- /dev/null
+++ b/pttbbs/mbbsd/cache.c
@@ -0,0 +1,1071 @@
+/* $Id$ */
+#include "bbs.h"
+
+#ifdef _BBS_UTIL_C_
+# define log_usies(a, b) ;
+# define abort_bbs(a) exit(1)
+#endif
+/*
+ * the reason for "safe_sleep" is that we may call sleep during SIGALRM
+ * handler routine, while SIGALRM is blocked. if we use the original sleep,
+ * we'll never wake up.
+ */
+unsigned int
+safe_sleep(unsigned int seconds)
+{
+ /* jochang sleep有問題時用 */
+ sigset_t set, oldset;
+
+ sigemptyset(&set);
+ sigprocmask(SIG_BLOCK, &set, &oldset);
+ if (sigismember(&oldset, SIGALRM)) {
+ unsigned int retv;
+ log_usies("SAFE_SLEEP ", "avoid hang");
+ sigemptyset(&set);
+ sigaddset(&set, SIGALRM);
+ sigprocmask(SIG_UNBLOCK, &set, NULL);
+ retv = sleep(seconds);
+ sigprocmask(SIG_BLOCK, &set, NULL);
+ return retv;
+ }
+ return sleep(seconds);
+}
+
+/*
+ * section - SHM
+ */
+static void
+attach_err(int shmkey, const char *name)
+{
+ fprintf(stderr, "[%s error] key = %x\n", name, shmkey);
+ fprintf(stderr, "errno = %d: %s\n", errno, strerror(errno));
+ exit(1);
+}
+
+void *
+attach_shm(int shmkey, int shmsize)
+{
+ void *shmptr = (void *)NULL;
+ int shmid;
+
+ shmid = shmget(shmkey, shmsize,
+#ifdef USE_HUGETLB
+ SHM_HUGETLB |
+#endif
+ 0);
+ if (shmid < 0) {
+ // SHM should be created by uhash_loader, NOT mbbsd or other utils
+ attach_err(shmkey, "shmget");
+ } else {
+ shmptr = (void *)shmat(shmid, NULL, 0);
+ if (shmptr == (void *)-1)
+ attach_err(shmkey, "shmat");
+ }
+
+ return shmptr;
+}
+
+void
+attach_SHM(void)
+{
+ SHM = attach_shm(SHM_KEY, SHMSIZE);
+ if(SHM->version != SHM_VERSION) {
+ fprintf(stderr, "Error: SHM->version(%d) != SHM_VERSION(%d)\n", SHM->version, SHM_VERSION);
+ fprintf(stderr, "Please use the source code version corresponding to SHM,\n"
+ "or use ipcrm(1) command to clean share memory.\n");
+ exit(1);
+ }
+ if (!SHM->loaded) /* (uhash) assume fresh shared memory is
+ * zeroed */
+ exit(1);
+ if (SHM->Btouchtime == 0)
+ SHM->Btouchtime = 1;
+ bcache = SHM->bcache;
+ numboards = SHM->Bnumber;
+
+ if (SHM->Ptouchtime == 0)
+ SHM->Ptouchtime = 1;
+
+ if (SHM->Ftouchtime == 0)
+ SHM->Ftouchtime = 1;
+}
+
+/* ----------------------------------------------------- */
+/* semaphore : for critical section */
+/* ----------------------------------------------------- */
+#define SEM_FLG 0600 /* semaphore mode */
+
+#ifndef __FreeBSD__
+/* according to X/OPEN, we have to define it ourselves */
+union semun {
+ int val; /* value for SETVAL */
+ struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */
+ unsigned short int *array; /* array for GETALL, SETALL */
+ struct seminfo *__buf; /* buffer for IPC_INFO */
+};
+#endif
+
+void
+sem_init(int semkey, int *semid)
+{
+ union semun s;
+
+ s.val = 1;
+ *semid = semget(semkey, 1, 0);
+ if (*semid == -1) {
+ *semid = semget(semkey, 1, IPC_CREAT | SEM_FLG);
+ if (*semid == -1)
+ attach_err(semkey, "semget");
+ semctl(*semid, 0, SETVAL, s);
+ }
+}
+
+void
+sem_lock(int op, int semid)
+{
+ struct sembuf sops;
+
+ sops.sem_num = 0;
+ sops.sem_flg = SEM_UNDO;
+ sops.sem_op = op;
+ if (semop(semid, &sops, 1)) {
+ perror("semop");
+ exit(1);
+ }
+}
+
+/*
+ * section - user cache(including uhash)
+ */
+/* uhash ****************************************** */
+/*
+ * the design is this: we use another stand-alone program to create and load
+ * data into the hash. (that program could be run in rc-scripts or something
+ * like that) after loading completes, the stand-alone program sets loaded to
+ * 1 and exits.
+ *
+ * the bbs exits if it can't attach to the shared memory or the hash is not
+ * loaded yet.
+ */
+
+void
+add_to_uhash(int n, const char *id)
+{
+ int *p, h = StringHash(id)%(1<<HASH_BITS);
+ int times;
+ strlcpy(SHM->userid[n], id, sizeof(SHM->userid[n]));
+
+ p = &(SHM->hash_head[h]);
+
+ for (times = 0; times < MAX_USERS && *p != -1; ++times)
+ p = &(SHM->next_in_hash[*p]);
+
+ if (times == MAX_USERS)
+ abort_bbs(0);
+
+ SHM->next_in_hash[*p = n] = -1;
+}
+
+void
+remove_from_uhash(int n)
+{
+/*
+ * note: after remove_from_uhash(), you should add_to_uhash() (likely with a
+ * different name)
+ */
+ int h = StringHash(SHM->userid[n])%(1<<HASH_BITS);
+ int *p = &(SHM->hash_head[h]);
+ int times;
+
+ for (times = 0; times < MAX_USERS && (*p != -1 && *p != n); ++times)
+ p = &(SHM->next_in_hash[*p]);
+
+ if (times == MAX_USERS)
+ abort_bbs(0);
+
+ if (*p == n)
+ *p = SHM->next_in_hash[n];
+}
+
+#if (1<<HASH_BITS)*10 < MAX_USERS
+#warning "Suggest to use bigger HASH_BITS for better searchuser() performance,"
+#warning "searchuser() average chaining MAX_USERS/(1<<HASH_BITS) times."
+#endif
+int
+dosearchuser(const char *userid, char *rightid)
+{
+ int h, p, times;
+ STATINC(STAT_SEARCHUSER);
+ h = StringHash(userid)%(1<<HASH_BITS);
+ p = SHM->hash_head[h];
+
+ for (times = 0; times < MAX_USERS && p != -1 && p < MAX_USERS ; ++times) {
+ if (strcasecmp(SHM->userid[p], userid) == 0) {
+ if(userid[0] && rightid) strcpy(rightid, SHM->userid[p]);
+ return p + 1;
+ }
+ p = SHM->next_in_hash[p];
+ }
+
+ return 0;
+}
+
+int
+searchuser(const char *userid, char *rightid)
+{
+ if(userid[0]=='\0')
+ return 0;
+ return dosearchuser(userid, rightid);
+}
+
+int
+getuser(const char *userid, userec_t *xuser)
+{
+ int uid;
+
+ if ((uid = searchuser(userid, NULL))) {
+ passwd_query(uid, xuser);
+ xuser->money = moneyof(uid);
+ }
+ return uid;
+}
+
+char *
+getuserid(int num)
+{
+ if (--num >= 0 && num < MAX_USERS)
+ return ((char *)SHM->userid[num]);
+ return NULL;
+}
+
+void
+setuserid(int num, const char *userid)
+{
+ if (num > 0 && num <= MAX_USERS) {
+/* Ptt: it may cause problems
+ if (num > SHM->number)
+ SHM->number = num;
+ else
+*/
+ remove_from_uhash(num - 1);
+ add_to_uhash(num - 1, userid);
+ }
+}
+
+#ifndef _BBS_UTIL_C_
+char *
+u_namearray(char buf[][IDLEN + 1], int *pnum, char *tag)
+{
+ register char *ptr, tmp;
+ register int n, total;
+ char tagbuf[STRLEN];
+ int ch, ch2, num;
+
+ if (*tag == '\0') {
+ *pnum = SHM->number;
+ return SHM->userid[0];
+ }
+ for (n = 0; tag[n]; n++)
+ tagbuf[n] = chartoupper(tag[n]);
+ tagbuf[n] = '\0';
+ ch = tagbuf[0];
+ ch2 = ch - 'A' + 'a';
+ total = SHM->number;
+ for (n = num = 0; n < total; n++) {
+ ptr = SHM->userid[n];
+ tmp = *ptr;
+ if (tmp == ch || tmp == ch2) {
+ if (chkstr(tag, tagbuf, ptr))
+ strcpy(buf[num++], ptr);
+ }
+ }
+ *pnum = num;
+ return buf[0];
+}
+#endif
+
+void
+getnewutmpent(const userinfo_t * up)
+{
+/* Ptt:這裡加上 hash 觀念找空的 utmp */
+ register int i;
+ register userinfo_t *uentp;
+ unsigned int p = StringHash(up->userid) % USHM_SIZE;
+ for (i = 0; i < USHM_SIZE; i++, p++) {
+ if (p == USHM_SIZE)
+ p = 0;
+ uentp = &(SHM->uinfo[p]);
+ if (!(uentp->pid)) {
+ memcpy(uentp, up, sizeof(userinfo_t));
+ currutmp = uentp;
+ return;
+ }
+ }
+ exit(1);
+}
+
+int
+apply_ulist(int (*fptr) (const userinfo_t *))
+{
+ register userinfo_t *uentp;
+ register int i, state;
+
+ for (i = 0; i < USHM_SIZE; i++) {
+ uentp = &(SHM->uinfo[i]);
+ if (uentp->pid && (PERM_HIDE(currutmp) || !PERM_HIDE(uentp)))
+ if ((state = (*fptr) (uentp)))
+ return state;
+ }
+ return 0;
+}
+
+userinfo_t *
+search_ulist_pid(int pid)
+{
+ register int i = 0, j, start = 0, end = SHM->UTMPnumber - 1;
+ int *ulist;
+ register userinfo_t *u;
+ if (end == -1)
+ return NULL;
+ ulist = SHM->sorted[SHM->currsorted][8];
+ for (i = ((start + end) / 2);; i = (start + end) / 2) {
+ u = &SHM->uinfo[ulist[i]];
+ j = pid - u->pid;
+ if (!j) {
+ return u;
+ }
+ if (end == start) {
+ break;
+ } else if (i == start) {
+ i = end;
+ start = end;
+ } else if (j > 0)
+ start = i;
+ else
+ end = i;
+ }
+ return 0;
+}
+
+userinfo_t *
+search_ulistn(int uid, int unum)
+{
+ register int i = 0, j, start = 0, end = SHM->UTMPnumber - 1;
+ int *ulist;
+ register userinfo_t *u;
+ if (end == -1)
+ return NULL;
+ ulist = SHM->sorted[SHM->currsorted][7];
+ for (i = ((start + end) / 2);; i = (start + end) / 2) {
+ u = &SHM->uinfo[ulist[i]];
+ j = uid - u->uid;
+ if (j == 0) {
+ for (; i > 0 && uid == SHM->uinfo[ulist[i - 1]].uid; --i)
+ ;/* 指到第一筆 */
+ if ( i + unum - 1 >= 0 &&
+ (ulist[i + unum - 1] >= 0 &&
+ uid == SHM->uinfo[ulist[i + unum - 1]].uid ) )
+ return &SHM->uinfo[ulist[i + unum - 1]];
+ break; /* 超過範圍 */
+ }
+ if (end == start) {
+ break;
+ } else if (i == start) {
+ i = end;
+ start = end;
+ } else if (j > 0)
+ start = i;
+ else
+ end = i;
+ }
+ return 0;
+}
+
+userinfo_t *
+search_ulist_userid(const char *userid)
+{
+ register int i = 0, j, start = 0, end = SHM->UTMPnumber - 1;
+ int *ulist;
+ register userinfo_t * u;
+ if (end == -1)
+ return NULL;
+ ulist = SHM->sorted[SHM->currsorted][0];
+ for (i = ((start + end) / 2);; i = (start + end) / 2) {
+ u = &SHM->uinfo[ulist[i]];
+ j = strcasecmp(userid, u->userid);
+ if (!j) {
+ return u;
+ }
+ if (end == start) {
+ break;
+ } else if (i == start) {
+ i = end;
+ start = end;
+ } else if (j > 0)
+ start = i;
+ else
+ end = i;
+ }
+ return 0;
+}
+
+#ifndef _BBS_UTIL_C_
+int
+count_logins(int uid, int show)
+{
+ register int i = 0, j, start = 0, end = SHM->UTMPnumber - 1, count;
+ int *ulist;
+ userinfo_t *u;
+ if (end == -1)
+ return 0;
+ ulist = SHM->sorted[SHM->currsorted][7];
+ for (i = ((start + end) / 2);; i = (start + end) / 2) {
+ u = &SHM->uinfo[ulist[i]];
+ j = uid - u->uid;
+ if (!j) {
+ for (; i > 0 && uid == SHM->uinfo[ulist[i - 1]].uid; i--);
+ /* 指到第一筆 */
+ for (count = 0; (ulist[i + count] &&
+ (u = &SHM->uinfo[ulist[i + count]]) &&
+ uid == u->uid); count++) {
+ if (show)
+ prints("(%d) 目前狀態為: %-17.16s(來自 %s)\n",
+ count + 1, modestring(u, 0),
+ u->from);
+ }
+ return count;
+ }
+ if (end == start) {
+ break;
+ } else if (i == start) {
+ i = end;
+ start = end;
+ } else if (j > 0)
+ start = i;
+ else
+ end = i;
+ }
+ return 0;
+}
+
+void
+purge_utmp(userinfo_t * uentp)
+{
+ logout_friend_online(uentp);
+ memset(uentp, 0, sizeof(userinfo_t));
+ SHM->UTMPneedsort = 1;
+}
+#endif
+
+/*
+ * section - money cache
+ */
+int
+setumoney(int uid, int money)
+{
+ SHM->money[uid - 1] = money;
+ passwd_update_money(uid);
+ return SHM->money[uid - 1];
+}
+
+int
+deumoney(int uid, int money)
+{
+ if (uid <= 0 || uid > MAX_USERS){
+#if defined(_BBS_UTIL_C_)
+ printf("internal error: deumoney(%d, %d)\n", uid, money);
+#else
+ vmsg("internal error");
+#endif
+ return -1;
+ }
+
+ if (money < 0 && moneyof(uid) < -money)
+ return setumoney(uid, 0);
+ else
+ return setumoney(uid, SHM->money[uid - 1] + money);
+}
+
+/*
+ * section - utmp
+ */
+#if !defined(_BBS_UTIL_C_) /* _BBS_UTIL_C_ 不會有 utmp */
+void
+setutmpmode(unsigned int mode)
+{
+ if (currstat != mode)
+ currutmp->mode = currstat = mode;
+ /* 追蹤使用者 */
+ if (HasUserPerm(PERM_LOGUSER)) {
+ log_user("setutmpmode to %s(%d)\n", modestring(currutmp, 0), mode);
+ }
+}
+#endif
+
+/*
+ * section - board cache
+ */
+void touchbtotal(int bid) {
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ SHM->total[bid - 1] = 0;
+ SHM->lastposttime[bid - 1] = 0;
+}
+
+
+/**
+ * qsort comparison function - 照板名排序
+ */
+static int
+cmpboardname(const void * i, const void * j)
+{
+ return strcasecmp(bcache[*(int*)i].brdname, bcache[*(int*)j].brdname);
+}
+
+/**
+ * qsort comparison function - 先照群組排序、同一個群組內依板名排
+ */
+static int
+cmpboardclass(const void * i, const void * j)
+{
+ boardheader_t *brd1 = &bcache[*(int*)i], *brd2 = &bcache[*(int*)j];
+ int cmp;
+
+ cmp=strncmp(brd1->title, brd2->title, 4);
+ if(cmp!=0) return cmp;
+ return strcasecmp(brd1->brdname, brd2->brdname);
+}
+
+
+void
+sort_bcache(void)
+{
+ int i;
+ /* critical section 盡量不要呼叫 */
+ /* 只有新增 或移除看板 需要呼叫到 */
+ if(SHM->Bbusystate) {
+ sleep(1);
+ return;
+ }
+ SHM->Bbusystate = 1;
+ for (i = 0; i < SHM->Bnumber; i++) {
+ SHM->bsorted[0][i] = SHM->bsorted[1][i] = i;
+ }
+ qsort(SHM->bsorted[0], SHM->Bnumber, sizeof(int), cmpboardname);
+ qsort(SHM->bsorted[1], SHM->Bnumber, sizeof(int), cmpboardclass);
+
+ for (i = 0; i < SHM->Bnumber; i++) {
+ bcache[i].firstchild[0] = 0;
+ bcache[i].firstchild[1] = 0;
+ }
+ SHM->Bbusystate = 0;
+}
+
+#ifdef _BBS_UTIL_C_
+void
+reload_bcache(void)
+{
+ int i, fd;
+ pid_t pid;
+ for( i = 0 ; i < 10 && SHM->Bbusystate ; ++i ){
+ printf("SHM->Bbusystate is currently locked (value: %d). "
+ "please wait... ", SHM->Bbusystate);
+ sleep(1);
+ }
+
+ SHM->Bbusystate = 1;
+ if ((fd = open(fn_board, O_RDONLY)) > 0) {
+ SHM->Bnumber =
+ read(fd, bcache, MAX_BOARD * sizeof(boardheader_t)) /
+ sizeof(boardheader_t);
+ close(fd);
+ }
+ memset(SHM->lastposttime, 0, MAX_BOARD * sizeof(time4_t));
+ memset(SHM->total, 0, MAX_BOARD * sizeof(int));
+
+ /* 等所有 boards 資料更新後再設定 uptime */
+ SHM->Buptime = SHM->Btouchtime;
+ log_usies("CACHE", "reload bcache");
+ SHM->Bbusystate = 0;
+ sort_bcache();
+
+ printf("load bottom in background");
+ if( (pid = fork()) > 0 )
+ return;
+ setproctitle("loading bottom");
+ for( i = 0 ; i < MAX_BOARD ; ++i )
+ if( SHM->bcache[i].brdname[0] ){
+ char fn[128];
+ int n;
+ sprintf(fn, "boards/%c/%s/.DIR.bottom",
+ SHM->bcache[i].brdname[0],
+ SHM->bcache[i].brdname);
+ n = get_num_records(fn, sizeof(fileheader_t));
+ if( n > 5 )
+ n = 5;
+ SHM->n_bottom[i] = n;
+ }
+ printf("load bottom done");
+ if( pid == 0 )
+ exit(0);
+ // if pid == -1 should be returned
+}
+
+void resolve_boards(void)
+{
+ while (SHM->Buptime < SHM->Btouchtime) {
+ reload_bcache();
+ }
+ numboards = SHM->Bnumber;
+}
+#endif /* defined(_BBS_UTIL_C_)*/
+
+#if 0
+/* Unused */
+void touch_boards(void)
+{
+ SHM->Btouchtime = COMMON_TIME;
+ numboards = -1;
+ resolve_boards();
+}
+#endif
+
+void addbrd_touchcache(void)
+{
+ SHM->Bnumber++;
+ numboards = SHM->Bnumber;
+ reset_board(numboards);
+ sort_bcache();
+}
+
+void
+reset_board(int bid) /* XXXbid: from 1 */
+{ /* Ptt: 這樣就不用老是touch board了 */
+ int fd;
+ boardheader_t *bhdr;
+
+ if (--bid < 0)
+ return;
+ assert(0<=bid && bid<MAX_BOARD);
+ if (SHM->Bbusystate || COMMON_TIME - SHM->busystate_b[bid] < 10) {
+ safe_sleep(1);
+ } else {
+ SHM->busystate_b[bid] = COMMON_TIME;
+
+ bhdr = bcache;
+ bhdr += bid;
+ if ((fd = open(fn_board, O_RDONLY)) > 0) {
+ lseek(fd, (off_t) (bid * sizeof(boardheader_t)), SEEK_SET);
+ read(fd, bhdr, sizeof(boardheader_t));
+ close(fd);
+ }
+ SHM->busystate_b[bid] = 0;
+
+ buildBMcache(bid + 1); /* XXXbid */
+ }
+}
+
+#ifndef _BBS_UTIL_C_ /* because of HasBoardPerm() in board.c */
+int
+apply_boards(int (*func) (boardheader_t *))
+{
+ register int i;
+ register boardheader_t *bhdr;
+
+ for (i = 0, bhdr = bcache; i < numboards; i++, bhdr++) {
+ if (!(bhdr->brdattr & BRD_GROUPBOARD) && HasBoardPerm(bhdr) &&
+ (*func) (bhdr) == QUIT)
+ return QUIT;
+ }
+ return 0;
+}
+#endif
+
+void
+setbottomtotal(int bid)
+{
+ boardheader_t *bh = getbcache(bid);
+ char fname[PATHLEN];
+ int n;
+
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ if(!bh->brdname[0]) return;
+ setbfile(fname, bh->brdname, ".DIR.bottom");
+ n = get_num_records(fname, sizeof(fileheader_t));
+ if(n>5)
+ {
+#ifdef DEBUG_BOTTOM
+ log_file("fix_bottom", LOG_CREAT | LOG_VF, "%s n:%d\n", fname, n);
+#endif
+ unlink(fname);
+ SHM->n_bottom[bid-1]=0;
+ }
+ else
+ SHM->n_bottom[bid-1]=n;
+}
+void
+setbtotal(int bid)
+{
+ boardheader_t *bh = getbcache(bid);
+ struct stat st;
+ char genbuf[256];
+ int num, fd;
+
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ setbfile(genbuf, bh->brdname, ".DIR");
+ if ((fd = open(genbuf, O_RDWR)) < 0)
+ return; /* .DIR掛了 */
+ fstat(fd, &st);
+ num = st.st_size / sizeof(fileheader_t);
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ SHM->total[bid - 1] = num;
+
+ if (num > 0) {
+ lseek(fd, (off_t) (num - 1) * sizeof(fileheader_t), SEEK_SET);
+ if (read(fd, genbuf, FNLEN) >= 0) {
+ SHM->lastposttime[bid - 1] = (time4_t) atoi(&genbuf[2]);
+ }
+ } else
+ SHM->lastposttime[bid - 1] = 0;
+ close(fd);
+}
+
+void
+touchbpostnum(int bid, int delta)
+{
+ int *total = &SHM->total[bid - 1];
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ if (*total)
+ *total += delta;
+}
+
+int
+getbnum(const char *bname)
+{
+ register int i = 0, j, start = 0, end = SHM->Bnumber - 1;
+ int *blist = SHM->bsorted[0];
+ if(SHM->Bbusystate)
+ sleep(1);
+ for (i = ((start + end) / 2);; i = (start + end) / 2) {
+ if (!(j = strcasecmp(bname, bcache[blist[i]].brdname)))
+ return (int)(blist[i] + 1);
+ if (end == start) {
+ break;
+ } else if (i == start) {
+ i = end;
+ start = end;
+ } else if (j > 0)
+ start = i;
+ else
+ end = i;
+ }
+ return 0;
+}
+
+int
+haspostperm(const char *bname)
+{
+ register int i;
+ char buf[200];
+
+ setbfile(buf, bname, fn_water);
+ if (belong(buf, cuser.userid))
+ return 0;
+
+ if (!strcasecmp(bname, DEFAULT_BOARD))
+ return 1;
+
+ if (!(i = getbnum(bname)))
+ return 0;
+ assert(0<=i-1 && i-1<MAX_BOARD);
+
+ if (bcache[i - 1].brdattr & BRD_GUESTPOST)
+ return 1;
+
+ if (!HasUserPerm(PERM_POST))
+ return 0;
+
+ /* 秘密看板特別處理 */
+ if (bcache[i - 1].brdattr & BRD_HIDE)
+ return 1;
+ else if (bcache[i - 1].brdattr & BRD_RESTRICTEDPOST &&
+ hbflcheck(i, usernum))
+ return 0;
+
+ i = bcache[i - 1].level;
+
+ if (HasUserPerm(PERM_VIOLATELAW) && (i & PERM_VIOLATELAW))
+ return 1;
+ else if (HasUserPerm(PERM_VIOLATELAW))
+ return 0;
+
+ return (i & ~PERM_POST) ? HasUserPerm(i & ~PERM_POST) : 1;
+}
+
+void buildBMcache(int bid) /* bid starts from 1 */
+{
+ char s[IDLEN * 3 + 3], *ptr;
+ int i, uid;
+ char *strtok_pos;
+
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ strlcpy(s, getbcache(bid)->BM, sizeof(s));
+ for( i = 0 ; s[i] != 0 ; ++i )
+ if( !isalpha((int)s[i]) && !isdigit((int)s[i]) )
+ s[i] = ' ';
+
+ for( ptr = strtok_r(s, " ", &strtok_pos), i = 0 ;
+ i < MAX_BMs && ptr != NULL ;
+ ptr = strtok_r(NULL, " ", &strtok_pos), ++i )
+ if( (uid = searchuser(ptr, NULL)) != 0 )
+ SHM->BMcache[bid-1][i] = uid;
+ for( ; i < MAX_BMs ; ++i )
+ SHM->BMcache[bid-1][i] = -1;
+}
+
+int is_BM_cache(int bid) /* bid starts from 1 */
+{
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ --bid;
+ // XXX hard coded MAX_BMs=4
+ if( currutmp->uid == SHM->BMcache[bid][0] ||
+ currutmp->uid == SHM->BMcache[bid][1] ||
+ currutmp->uid == SHM->BMcache[bid][2] ||
+ currutmp->uid == SHM->BMcache[bid][3] ){
+ cuser.userlevel |= PERM_BM;
+ return 1;
+ }
+ return 0;
+}
+
+/*-------------------------------------------------------*/
+/* PTT cache */
+/*-------------------------------------------------------*/
+/* cache for 動態看板 */
+void
+reload_pttcache(void)
+{
+ if (SHM->Pbusystate)
+ safe_sleep(1);
+ else { /* jochang: temporary workaround */
+ fileheader_t item, subitem;
+ char pbuf[256], buf[256], *chr;
+ FILE *fp, *fp1, *fp2;
+ int id;
+
+ SHM->Pbusystate = 1;
+ SHM->last_film = 0;
+ bzero(SHM->notes, sizeof(SHM->notes));
+ setapath(pbuf, "Note");
+ setadir(buf, pbuf);
+ id = 0;
+ if ((fp = fopen(buf, "r"))) {
+ while (fread(&item, sizeof(item), 1, fp)) {
+ if (item.title[3] == '<' && item.title[8] == '>') {
+ snprintf(buf, sizeof(buf), "%s/%s/.DIR",
+ pbuf, item.filename);
+ if (!(fp1 = fopen(buf, "r")))
+ continue;
+ while (fread(&subitem, sizeof(subitem), 1, fp1)) {
+ snprintf(buf, sizeof(buf),
+ "%s/%s/%s", pbuf, item.filename,
+ subitem.filename);
+ if (!(fp2 = fopen(buf, "r")))
+ continue;
+ fread(SHM->notes[id], sizeof(char), sizeof(SHM->notes[0]), fp2);
+ SHM->notes[id][sizeof(SHM->notes[0]) - 1] = 0;
+ id++;
+ fclose(fp2);
+ if (id >= MAX_MOVIE)
+ break;
+ }
+ fclose(fp1);
+ if (id >= MAX_MOVIE)
+ break;
+ }
+ }
+ fclose(fp);
+ }
+ SHM->last_film = id - 1;
+
+ fp = fopen("etc/today_is", "r");
+ if (fp) {
+ fgets(SHM->today_is, 15, fp);
+ if ((chr = strchr(SHM->today_is, '\n')))
+ *chr = 0;
+ SHM->today_is[15] = 0;
+ fclose(fp);
+ }
+ /* 等所有資料更新後再設定 uptime */
+
+ SHM->Puptime = SHM->Ptouchtime;
+ log_usies("CACHE", "reload pttcache");
+ SHM->Pbusystate = 0;
+ }
+}
+
+void
+resolve_garbage(void)
+{
+ int count = 0;
+
+ while (SHM->Puptime < SHM->Ptouchtime) { /* 不用while等 */
+ reload_pttcache();
+ if (count++ > 10 && SHM->Pbusystate) {
+ /*
+ * Ptt: 這邊會有問題 load超過10 秒會所有進loop的process tate = 0
+ * 這樣會所有prcosee都會在load 動態看板 會造成load大增
+ * 但沒有用這個function的話 萬一load passwd檔的process死了
+ * 又沒有人把他 解開 同樣的問題發生在reload passwd
+ */
+ SHM->Pbusystate = 0;
+#ifndef _BBS_UTIL_C_
+ log_usies("CACHE", "refork Ptt dead lock");
+#endif
+ }
+ }
+}
+
+/*-------------------------------------------------------*/
+/* PTT's cache */
+/*-------------------------------------------------------*/
+/* cache for from host 與最多上線人數 */
+void
+reload_fcache(void)
+{
+ if (SHM->Fbusystate)
+ safe_sleep(1);
+ else {
+ FILE *fp;
+
+ SHM->Fbusystate = 1;
+ bzero(SHM->home_ip, sizeof(SHM->home_ip));
+ if ((fp = fopen("etc/domain_name_query.cidr", "r"))) {
+ char buf[256], *ip, *mask;
+ char *strtok_pos;
+
+ SHM->home_num = 0;
+ while (fgets(buf, sizeof(buf), fp)) {
+ if (!buf[0] || buf[0] == '#' || buf[0] == ' ' || buf[0] == '\n')
+ continue;
+
+ if (buf[0] == '@') {
+ SHM->home_ip[0] = 0;
+ SHM->home_mask[0] = 0xFFFFFFFF;
+ SHM->home_num++;
+ continue;
+ }
+
+ ip = strtok_r(buf, " \t", &strtok_pos);
+ if ((mask = strchr(ip, '/')) != NULL) {
+ int shift = 32 - atoi(mask + 1);
+ SHM->home_ip[SHM->home_num] = ipstr2int(ip);
+ SHM->home_mask[SHM->home_num] = (0xFFFFFFFF >> shift ) << shift;
+ }
+ else {
+ SHM->home_ip[SHM->home_num] = ipstr2int(ip);
+ SHM->home_mask[SHM->home_num] = 0xFFFFFFFF;
+ }
+ ip = strtok_r(NULL, " \t", &strtok_pos);
+ if (ip == NULL) {
+ strncpy(SHM->home_desc[SHM->home_num], "雲深不知處",
+ sizeof(SHM->home_desc[SHM->home_num]));
+ }
+ else {
+ strncpy(SHM->home_desc[SHM->home_num], ip,
+ sizeof(SHM->home_desc[SHM->home_num]));
+ chomp(SHM->home_desc[SHM->home_num]);
+ }
+ (SHM->home_num)++;
+ if (SHM->home_num == MAX_FROM)
+ break;
+ }
+ fclose(fp);
+ }
+ SHM->max_user = 0;
+
+ /* 等所有資料更新後再設定 uptime */
+ SHM->Fuptime = SHM->Ftouchtime;
+#if !defined(_BBS_UTIL_C_)
+ log_usies("CACHE", "reload fcache");
+#endif
+ SHM->Fbusystate = 0;
+ }
+}
+
+void
+resolve_fcache(void)
+{
+ while (SHM->Fuptime < SHM->Ftouchtime)
+ reload_fcache();
+}
+
+/*
+ * section - hbfl (hidden board friend list)
+ */
+void
+hbflreload(int bid)
+{
+ int hbfl[MAX_FRIEND + 1], i, num, uid;
+ char buf[128];
+ FILE *fp;
+
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ memset(hbfl, 0, sizeof(hbfl));
+ setbfile(buf, bcache[bid - 1].brdname, fn_visable);
+ if ((fp = fopen(buf, "r")) != NULL) {
+ for (num = 1; num <= MAX_FRIEND; ++num) {
+ if (fgets(buf, sizeof(buf), fp) == NULL)
+ break;
+ for (i = 0; buf[i] != 0; ++i)
+ if (buf[i] == ' ') {
+ buf[i] = 0;
+ break;
+ }
+ if (strcasecmp(STR_GUEST, buf) == 0 ||
+ (uid = searchuser(buf, NULL)) == 0) {
+ --num;
+ continue;
+ }
+ hbfl[num] = uid;
+ }
+ fclose(fp);
+ }
+ hbfl[0] = COMMON_TIME;
+ memcpy(SHM->hbfl[bid-1], hbfl, sizeof(hbfl));
+}
+
+/* 是否"不"通過板友測試. 如果在板友名單中的話傳回 0, 否則為 1 */
+int
+hbflcheck(int bid, int uid)
+{
+ int i;
+
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ if (SHM->hbfl[bid-1][0] < login_start_time - HBFLexpire)
+ hbflreload(bid);
+ for (i = 1; SHM->hbfl[bid-1][i] != 0 && i <= MAX_FRIEND; ++i) {
+ if (SHM->hbfl[bid-1][i] == uid)
+ return 0;
+ }
+ return 1;
+}
+
+#ifdef USE_COOLDOWN
+void add_cooldowntime(int uid, int min)
+{
+ // Ptt: I will use the number below 15 seconds.
+ time4_t base= now > SHM->cooldowntime[uid - 1]?
+ now : SHM->cooldowntime[uid - 1];
+ base += min*60;
+ base &= 0xFFFFFFF0;
+
+ SHM->cooldowntime[uid - 1] = base;
+}
+void add_posttimes(int uid, int times)
+{
+ if((SHM->cooldowntime[uid - 1] & 0xF) + times <0xF)
+ SHM->cooldowntime[uid - 1] += times;
+ else
+ SHM->cooldowntime[uid - 1] |= 0xF;
+}
+#endif
diff --git a/pttbbs/mbbsd/cal.c b/pttbbs/mbbsd/cal.c
new file mode 100644
index 00000000..4fad13ff
--- /dev/null
+++ b/pttbbs/mbbsd/cal.c
@@ -0,0 +1,521 @@
+/* $Id$ */
+#include "bbs.h"
+
+/* 防堵 Multi play */
+static int
+is_playing(int unmode)
+{
+ register int i;
+ register userinfo_t *uentp;
+ unsigned int p = StringHash(cuser.userid) % USHM_SIZE;
+
+ for (i = 0; i < USHM_SIZE; i++, p++) { // XXX linear search
+ if (p == USHM_SIZE)
+ p = 0;
+ uentp = &(SHM->uinfo[p]);
+ if (uentp->uid == usernum)
+ if (uentp->lockmode == unmode)
+ return 1;
+ }
+ return 0;
+}
+
+int
+lockutmpmode(int unmode, int state)
+{
+ int errorno = 0;
+
+ if (currutmp->lockmode)
+ errorno = LOCK_THIS;
+ else if (state == LOCK_MULTI && is_playing(unmode))
+ errorno = LOCK_MULTI;
+
+ if (errorno) {
+ clear();
+ move(10, 20);
+ if (errorno == LOCK_THIS)
+ prints("請先離開 %s 才能再 %s ",
+ ModeTypeTable[currutmp->lockmode],
+ ModeTypeTable[unmode]);
+ else
+ prints("抱歉! 您已有其他線相同的ID正在%s",
+ ModeTypeTable[unmode]);
+ pressanykey();
+ return errorno;
+ }
+ setutmpmode(unmode);
+ currutmp->lockmode = unmode;
+ return 0;
+}
+
+int
+unlockutmpmode(void)
+{
+ currutmp->lockmode = 0;
+ return 0;
+}
+
+/* 使用錢的函數 */
+#define VICE_NEW "vice.new"
+
+const char*
+money_level(int money)
+{
+ int i = 0;
+
+ static const char *money_msg[] =
+ {
+ "債台高築", "赤貧", "清寒", "普通", "小康",
+ "小富", "中富", "大富翁", "富可敵國", "比爾蓋\天", NULL
+ };
+ while (money_msg[i] && money > 10)
+ i++, money /= 10;
+
+ if(!money_msg[i])
+ i--;
+ return money_msg[i];
+}
+
+/* Heat:發票 */
+int
+vice(int money, const char *item)
+{
+ char buf[128];
+ unsigned int viceserial = (currutmp->lastact % 10000) * 10000 + random() % 10000;
+
+ demoney(-money);
+ if(money>=100)
+ {
+ setuserfile(buf, VICE_NEW);
+ log_file(buf, LOG_CREAT | LOG_VF, "%8.8d\n", viceserial);
+ }
+ snprintf(buf, sizeof(buf),
+ "%s 花了$%d 編號[%08d]", item, money, viceserial);
+ mail_id(cuser.userid, buf, "etc/vice.txt", "Ptt經濟部");
+ return 0;
+}
+
+#define lockreturn(unmode, state) if(lockutmpmode(unmode, state)) return
+#define lockreturn0(unmode, state) if(lockutmpmode(unmode, state)) return 0
+#define lockbreak(unmode, state) if(lockutmpmode(unmode, state)) break
+#define SONGBOOK "etc/SONGBOOK"
+#define OSONGPATH "etc/SONGO"
+
+static int
+osong(void)
+{
+ char sender[IDLEN + 1], receiver[IDLEN + 1], buf[200],
+ genbuf[200], filename[256], say[51];
+ char trans_buffer[PATHLEN];
+ char address[45];
+ FILE *fp, *fp1;
+ //*fp2;
+ fileheader_t mail;
+ int nsongs;
+
+ strlcpy(buf, Cdatedate(&now), sizeof(buf));
+
+ lockreturn0(OSONG, LOCK_MULTI);
+
+ /* Jaky 一人一天點一首 */
+ if (!strcmp(buf, Cdatedate(&cuser.lastsong)) && !HasUserPerm(PERM_SYSOP)) {
+ move(22, 0);
+ vmsg("你今天已經點過囉,明天再點吧....");
+ unlockutmpmode();
+ return 0;
+ }
+
+ while (1) {
+ char ans[4];
+ move(12, 0);
+ clrtobot();
+ prints("親愛的 %s 歡迎來到歐桑自動點歌系統\n", cuser.userid);
+ getdata(13, 0, "請選擇 " ANSI_COLOR(1) "1)" ANSI_RESET " 開始點歌、"
+ ANSI_COLOR(1) "2)" ANSI_RESET " 看歌本、"
+ "或是 " ANSI_COLOR(1) "3)" ANSI_RESET " 離開: ",
+ ans, sizeof(ans), DOECHO);
+
+ if (ans[0] == '1')
+ break;
+ else if (ans[0] == '2') {
+ a_menu("點歌歌本", SONGBOOK, 0, NULL);
+ clear();
+ }
+ else if (ans[0] == '3') {
+ vmsg("謝謝光臨 :)");
+ unlockutmpmode();
+ return 0;
+ }
+ }
+
+ if (cuser.money < 200) {
+ move(22, 0);
+ vmsg("點歌要200銀唷!....");
+ unlockutmpmode();
+ return 0;
+ }
+
+ getdata_str(14, 0, "點歌者(可匿名): ", sender, sizeof(sender), DOECHO, cuser.userid);
+ getdata(15, 0, "點給(可匿名): ", receiver, sizeof(receiver), DOECHO);
+
+ getdata_str(16, 0, "想要要對他(她)說..:", say,
+ sizeof(say), DOECHO, "我愛妳..");
+ snprintf(save_title, sizeof(save_title),
+ "%s:%s", sender, say);
+ getdata_str(17, 0, "寄到誰的信箱(真實 ID 或 E-mail)?",
+ address, sizeof(address), LCECHO, receiver);
+ outs("\n接著要選歌囉..進入歌本好好的選一首歌吧..^o^");
+ pressanykey();
+ a_menu("點歌歌本", SONGBOOK, 0, trans_buffer);
+ if (!trans_buffer[0] || strstr(trans_buffer, "home") ||
+ strstr(trans_buffer, "boards") || !(fp = fopen(trans_buffer, "r"))) {
+ unlockutmpmode();
+ return 0;
+ }
+ strlcpy(filename, OSONGPATH, sizeof(filename));
+
+ stampfile(filename, &mail);
+
+ unlink(filename);
+
+ if (!(fp1 = fopen(filename, "w"))) {
+ fclose(fp);
+ unlockutmpmode();
+ return 0;
+ }
+ strlcpy(mail.owner, "點歌機", sizeof(mail.owner));
+ snprintf(mail.title, sizeof(mail.title), "◇ %s 點給 %s ", sender, receiver);
+
+ while (fgets(buf, sizeof(buf), fp)) {
+ char *po;
+ if (!strncmp(buf, "標題: ", 6)) {
+ clear();
+ move(10, 10);
+ outs(buf);
+ pressanykey();
+ fclose(fp);
+ fclose(fp1);
+ unlockutmpmode();
+ return 0;
+ }
+ while ((po = strstr(buf, "<~Src~>"))) {
+ const char *dot = "";
+ if (is_validuserid(sender) && strcmp(sender, cuser.userid) != 0)
+ dot = ".";
+ po[0] = 0;
+ snprintf(genbuf, sizeof(genbuf), "%s%s%s%s", buf, sender, dot, po + 7);
+ strlcpy(buf, genbuf, sizeof(buf));
+ }
+ while ((po = strstr(buf, "<~Des~>"))) {
+ po[0] = 0;
+ snprintf(genbuf, sizeof(genbuf), "%s%s%s", buf, receiver, po + 7);
+ strlcpy(buf, genbuf, sizeof(buf));
+ }
+ while ((po = strstr(buf, "<~Say~>"))) {
+ po[0] = 0;
+ snprintf(genbuf, sizeof(genbuf), "%s%s%s", buf, say, po + 7);
+ strlcpy(buf, genbuf, sizeof(buf));
+ }
+ fputs(buf, fp1);
+ }
+ fclose(fp1);
+ fclose(fp);
+
+ log_file("etc/osong.log", LOG_CREAT | LOG_VF, "id: %-12s ◇ %s 點給 %s : \"%s\", 轉寄至 %s\n", cuser.userid, sender, receiver, say, address, ctime4(&now));
+
+ if (append_record(OSONGPATH "/.DIR", &mail, sizeof(mail)) != -1) {
+ cuser.lastsong = now;
+ /* Jaky 超過 MAX_MOVIE 首歌就開始砍 */
+ nsongs = get_num_records(OSONGPATH "/.DIR", sizeof(mail));
+ if (nsongs > MAX_MOVIE) {
+ // XXX race condition
+ delete_range(OSONGPATH "/.DIR", 1, nsongs - MAX_MOVIE);
+ }
+ snprintf(genbuf, sizeof(genbuf), "%s says \"%s\" to %s.", sender, say, receiver);
+ log_usies("OSONG", genbuf);
+ /* 把第一首拿掉 */
+ vice(200, "點歌");
+ }
+ snprintf(save_title, sizeof(save_title), "%s:%s", sender, say);
+ hold_mail(filename, receiver);
+
+ if (address[0]) {
+#ifndef USE_BSMTP
+ bbs_sendmail(filename, save_title, address);
+#else
+ bsmtp(filename, save_title, address);
+#endif
+ }
+ clear();
+ outs(
+ "\n\n 恭喜您點歌完成囉..\n"
+ " 一小時內動態看板會自動重新更新\n"
+ " 大家就可以看到您點的歌囉\n\n"
+ " 點歌有任何問題可以到Note板的精華區找答案\n"
+ " 也可在Note板精華區看到自己的點歌記錄\n"
+ " 有任何保貴的意見也歡迎到Note板留話\n"
+ " 讓親切的板主為您服務\n");
+ pressanykey();
+ sortsong();
+ topsong();
+
+ unlockutmpmode();
+ return 1;
+}
+
+int
+ordersong(void)
+{
+ osong();
+ return 0;
+}
+
+static int
+inmailbox(int m)
+{
+ userec_t xuser;
+ passwd_query(usernum, &xuser);
+ cuser.exmailbox = xuser.exmailbox + m;
+ passwd_update(usernum, &cuser);
+ return cuser.exmailbox;
+}
+
+
+#if !HAVE_FREECLOAK
+/* 花錢選單 */
+int
+p_cloak(void)
+{
+ if (getans(currutmp->invisible ? "確定要現身?[y/N]" : "確定要隱身?[y/N]") != 'y')
+ return 0;
+ if (cuser.money >= 19) {
+ vice(19, "付費隱身");
+ currutmp->invisible %= 2;
+ vmsg((currutmp->invisible ^= 1) ? MSG_CLOAKED : MSG_UNCLOAK);
+ }
+ return 0;
+}
+#endif
+
+int
+p_from(void)
+{
+ if (getans("確定要改故鄉?[y/N]") != 'y')
+ return 0;
+ reload_money();
+ if (cuser.money < 49)
+ return 0;
+ if (getdata_buf(b_lines - 1, 0, "請輸入新故鄉:",
+ currutmp->from, sizeof(currutmp->from), DOECHO)) {
+ vice(49, "更改故鄉");
+ currutmp->from_alias = 0;
+ }
+ return 0;
+}
+
+int
+p_exmail(void)
+{
+ char ans[4], buf[200];
+ int n;
+
+ if (cuser.exmailbox >= MAX_EXKEEPMAIL) {
+ vmsgf("容量最多增加 %d 封,不能再買了。", MAX_EXKEEPMAIL);
+ return 0;
+ }
+ snprintf(buf, sizeof(buf),
+ "您曾增購 %d 封容量,還要再買多少?", cuser.exmailbox);
+
+ getdata_str(b_lines - 2, 0, buf, ans, sizeof(ans), LCECHO, "1");
+
+ n = atoi(ans);
+ if (!ans[0] || n<=0)
+ return 0;
+ if (n + cuser.exmailbox > MAX_EXKEEPMAIL)
+ n = MAX_EXKEEPMAIL - cuser.exmailbox;
+ reload_money();
+ if (cuser.money < n * 1000)
+ return 0;
+ vice(n * 1000, "購買信箱");
+ inmailbox(n);
+ return 0;
+}
+
+void
+mail_redenvelop(const char *from, const char *to, int money, char mode)
+{
+ char genbuf[200];
+ fileheader_t fhdr;
+ FILE *fp;
+
+ sethomepath(genbuf, to);
+ stampfile(genbuf, &fhdr);
+ if (!(fp = fopen(genbuf, "w")))
+ return;
+ fprintf(fp, "作者: %s\n"
+ "標題: 招財進寶\n"
+ "時間: %s\n"
+ ANSI_COLOR(1;33) "親愛的 %s :\n\n" ANSI_RESET
+ ANSI_COLOR(1;31) " 我包給你一個 %d 元的大紅包喔 ^_^\n\n"
+ " 禮輕情意重,請笑納...... ^_^" ANSI_RESET "\n",
+ from, ctime4(&now), to, money);
+ fclose(fp);
+ snprintf(fhdr.title, sizeof(fhdr.title), "招財進寶");
+ strlcpy(fhdr.owner, from, sizeof(fhdr.owner));
+
+ if (mode == 'y')
+ vedit(genbuf, NA, NULL);
+ sethomedir(genbuf, to);
+ append_record(genbuf, &fhdr, sizeof(fhdr));
+}
+
+/* 計算贈與稅 */
+int
+give_tax(int money)
+{
+ int i, tax = 0;
+ int tax_bound[] = {1000000, 100000, 10000, 1000, 0};
+ double tax_rate[] = {0.4, 0.3, 0.2, 0.1, 0.08};
+ for (i = 0; i <= 4; i++)
+ if (money > tax_bound[i]) {
+ tax += (money - tax_bound[i]) * tax_rate[i];
+ money -= (money - tax_bound[i]);
+ }
+ return (tax <= 0) ? 1 : tax;
+}
+
+int do_give_money(char *id, int uid, int money)
+{
+ int tax;
+#ifdef PLAY_ANGEL
+ userec_t xuser;
+#endif
+
+ reload_money();
+ if (money > 0 && cuser.money >= money) {
+ tax = give_tax(money);
+ if (money - tax <= 0)
+ return -1; /* 繳完稅就沒錢給了 */
+ deumoney(uid, money - tax);
+ demoney(-money);
+ log_file(FN_MONEY, LOG_CREAT | LOG_VF, "%-12s 給 %-12s %d\t(稅後 %d)\t%s",
+ cuser.userid, id, money, money - tax, ctime4(&now));
+#ifdef PLAY_ANGEL
+ getuser(id, &xuser);
+ if (!strcmp(xuser.myangel, cuser.userid)){
+ mail_redenvelop(
+ getkey("他是你的小主人,是否匿名?[Y/n]") == 'n' ?
+ cuser.userid : "小天使", id, money - tax,
+ getans("要自行書寫紅包袋嗎?[y/N]"));
+ } else
+#endif
+ mail_redenvelop(cuser.userid, id, money - tax,
+ getans("要自行書寫紅包袋嗎?[y/N]"));
+ return 0;
+ }
+ return -1;
+}
+
+int
+p_give(void)
+{
+ int uid;
+ char id[IDLEN + 1], money_buf[20];
+
+ move(1, 0);
+ usercomplete("這位幸運兒的id:", id);
+ if (!id[0] || !strcmp(cuser.userid, id) ||
+ !getdata(2, 0, "要給多少錢:", money_buf, 7, LCECHO)) {
+ vmsg("交易取消!");
+ return -1;
+ }
+ if ((uid = searchuser(id, id)) == 0) {
+ vmsg("查無此人!");
+ return -1;
+ }
+ return do_give_money(id, uid, atoi(money_buf));
+}
+
+int
+p_sysinfo(void)
+{
+ char *cpuloadstr;
+ int load;
+ extern char *compile_time;
+#ifdef DETECT_CLIENT
+ extern Fnv32_t client_code;
+#endif
+
+ load = cpuload(NULL);
+ cpuloadstr = (load < 5 ? "良好" : (load < 20 ? "尚可" : "過重"));
+
+ clear();
+ showtitle("系統資訊", BBSNAME);
+ move(2, 0);
+ prints("您現在位於 " TITLE_COLOR BBSNAME ANSI_RESET " (" MYIP ")\n"
+ "系統負載情況: %s\n"
+ "線上服務人數: %d/%d\n"
+#ifdef DETECT_CLIENT
+ "client code: %8.8X\n"
+#endif
+ "編譯時間: %s\n"
+ "起始時間: %s\n",
+ cpuloadstr, SHM->UTMPnumber,
+#ifdef DYMAX_ACTIVE
+ SHM->GV2.e.dymaxactive > 2000 ? SHM->GV2.e.dymaxactive : MAX_ACTIVE,
+#else
+ MAX_ACTIVE,
+#endif
+#ifdef DETECT_CLIENT
+ client_code,
+#endif
+ compile_time, ctime4(&start_time));
+ if (HasUserPerm(PERM_SYSOP)) {
+ struct rusage ru;
+#ifdef __linux__
+ int vmdata=0, vmstk=0;
+ FILE * fp;
+ char buf[128];
+ if ((fp = fopen("/proc/self/status", "r"))) {
+ while (fgets(buf, 128, fp)) {
+ sscanf(buf, "VmData: %d", &vmdata);
+ sscanf(buf, "VmStk: %d", &vmstk);
+ }
+ fclose(fp);
+ }
+#endif
+ getrusage(RUSAGE_SELF, &ru);
+ prints("記憶體用量: "
+#ifdef IA32
+ "sbrk: %d KB, "
+#endif
+#ifdef __linux__
+ "VmData: %d KB, VmStk: %d KB, "
+#endif
+ "idrss: %d KB, isrss: %d KB\n",
+#ifdef IA32
+ ((int)sbrk(0) - 0x8048000) / 1024,
+#endif
+#ifdef __linux__
+ vmdata, vmstk,
+#endif
+ (int)ru.ru_idrss, (int)ru.ru_isrss);
+ prints("CPU 用量: %ld.%06ldu %ld.%06lds",
+ ru.ru_utime.tv_sec, ru.ru_utime.tv_usec,
+ ru.ru_stime.tv_sec, ru.ru_stime.tv_usec);
+#ifdef CPULIMIT
+ prints(" (limit %d secs)", (int)(CPULIMIT * 60));
+#endif
+ outs("\n特別參數:"
+#ifdef CRITICAL_MEMORY
+ " CRITICAL_MEMORY"
+#endif
+#ifdef OUTTACACHE
+ " OUTTACACHE"
+#endif
+ );
+ }
+ pressanykey();
+ return 0;
+}
+
diff --git a/pttbbs/mbbsd/calendar.c b/pttbbs/mbbsd/calendar.c
new file mode 100644
index 00000000..52a0c111
--- /dev/null
+++ b/pttbbs/mbbsd/calendar.c
@@ -0,0 +1,351 @@
+/* $Id$ */
+#include "bbs.h"
+
+#if !defined(PTTBBS_UTIL)
+
+typedef struct event_t {
+ int year, month, day, days;
+ int color;
+ char *content;
+ struct event_t *next;
+} event_t;
+
+static int
+MonthDay(int m, int leap)
+{
+ int day[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+ return leap && m == 2 ? 29 : day[m - 1];
+}
+
+static int
+IsLeap(int y)
+{
+ if (y % 400 == 0 || (y % 4 == 0 && y % 100 != 0))
+ return 1;
+ else
+ return 0;
+}
+
+static int
+Days(int y, int m, int d)
+{
+ int i, w;
+
+ w = 1 + 365 * (y - 1)
+ + ((y - 1) / 4) - ((y - 1) / 100) + ((y - 1) / 400)
+ + d - 1;
+ for (i = 1; i < m; i++)
+ w += MonthDay(i, IsLeap(y));
+ return w;
+}
+
+int ParseDate(const char *date, int *year, int *month, int *day)
+{
+ char *y, *m, *d;
+ char buf[128];
+ char *strtok_pos;
+
+ strlcpy(buf, date, sizeof(buf));
+ y = strtok_r(buf, "/", &strtok_pos);
+ m = strtok_r(NULL, "/", &strtok_pos);
+ d = strtok_r(NULL, "", &strtok_pos);
+ if (!y || !m || !d)
+ return 1;
+
+ *year = atoi(y);
+ *month = atoi(m);
+ *day = atoi(d);
+ if (*year < 1 || *month < 1 || *month > 12 ||
+ *day < 1 || *day > MonthDay(*month, IsLeap(*year)))
+ return 1;
+ return 0;
+}
+
+static int
+ParseEventDate(const char *date, event_t * t)
+{
+ int retval = ParseDate(date, &t->year, &t->month, &t->day);
+ t->days = Days(t->year, t->month, t->day);
+ return retval;
+}
+
+static int
+ParseColor(const char *color)
+{
+ struct {
+ char *str;
+ int val;
+ } c[] = {
+ {
+ "black", 0
+ },
+ {
+ "red", 1
+ },
+ {
+ "green", 2
+ },
+ {
+ "yellow", 3
+ },
+ {
+ "blue", 4
+ },
+ {
+ "magenta", 5
+ },
+ {
+ "cyan", 6
+ },
+ {
+ "white", 7
+ }
+ };
+ int i;
+
+ for (i = 0; (unsigned)i < sizeof(c) / sizeof(c[0]); i++)
+ if (strcasecmp(color, c[i].str) == 0)
+ return c[i].val;
+ return 7;
+}
+
+static void
+InsertEvent(event_t * head, event_t * t)
+{
+ event_t *p;
+
+ for (p = head; p->next && p->next->days < t->days; p = p->next);
+ t->next = p->next;
+ p->next = t;
+}
+
+static void
+FreeEvent(event_t * e)
+{
+ event_t *n;
+
+ while (e) {
+ n = e->next;
+ free(e->content); /* from strdup() */
+ free(e);
+ e = n;
+ }
+}
+
+static event_t *
+ReadEvent(int today)
+{
+ FILE *fp;
+ char buf[256];
+ static event_t head;
+
+ head.next = NULL;
+ sethomefile(buf, cuser.userid, "calendar");
+ fp = fopen(buf, "r");
+ if (fp) {
+ while (fgets(buf, sizeof(buf), fp)) {
+ char *date, *color, *content;
+ event_t *t;
+ char *strtok_pos;
+
+ if (buf[0] == '#')
+ continue;
+
+ date = strtok_r(buf, " \t\n", &strtok_pos);
+ color = strtok_r(NULL, " \t\n", &strtok_pos);
+ content = strtok_r(NULL, "\n", &strtok_pos);
+ if (!date || !color || !content)
+ continue;
+
+ t = malloc(sizeof(event_t));
+ if (ParseEventDate(date, t) || t->days < today) {
+ free(t);
+ continue;
+ }
+ t->color = ParseColor(color) + 30;
+ for (; *content == ' ' || *content == '\t'; content++);
+ t->content = strdup(content);
+ InsertEvent(&head, t);
+ }
+ fclose(fp);
+ }
+ return head.next;
+}
+
+static char **
+AllocCalBuffer(int line, int len)
+{
+ int i;
+ char **p;
+
+ p = malloc(sizeof(char *) * line);
+ p[0] = malloc(sizeof(char) * line * len);
+ for (i = 1; i < line; i++)
+ p[i] = p[i - 1] + len;
+ return p;
+}
+
+static void
+FreeCalBuffer(char **buf)
+{
+ free(buf[0]);
+ free(buf);
+}
+
+#define CALENDAR_COLOR ANSI_COLOR(0;30;47)
+#define HEADER_COLOR ANSI_COLOR(1;44)
+#define HEADER_SUNDAY_COLOR ANSI_COLOR(31)
+#define HEADER_DAY_COLOR ANSI_COLOR(33)
+
+static int
+GenerateCalendar(char **buf, int y, int m, int today, event_t * e)
+{
+ char *week_str[7] = {"日", "一", "二", "三", "四", "五", "六"};
+ char *month_color[12] = {
+ ANSI_COLOR(1;32), ANSI_COLOR(1;33), ANSI_COLOR(1;35), ANSI_COLOR(1;36),
+ ANSI_COLOR(1;32), ANSI_COLOR(1;33), ANSI_COLOR(1;35), ANSI_COLOR(1;36),
+ ANSI_COLOR(1;32), ANSI_COLOR(1;33), ANSI_COLOR(1;35), ANSI_COLOR(1;36)
+ };
+ char *month_str[12] = {
+ "一月 ", "二月 ", "三月 ", "四月 ", "五月 ", "六月 ",
+ "七月 ", "八月 ", "九月 ", "十月 ", "十一月", "十二月"
+ };
+
+ char *p, attr1[16], *attr2;
+ int i, d, w, line = 0, first_day = Days(y, m, 1);
+
+
+ /* week day banner */
+ p = buf[line];
+ p += sprintf(p, " %s%s%s%s", HEADER_COLOR, HEADER_SUNDAY_COLOR,
+ week_str[0], HEADER_DAY_COLOR);
+ for (i = 1; i < 7; i++)
+ p += sprintf(p, " %s", week_str[i]);
+ p += sprintf(p, ANSI_RESET);
+
+ /* indent for first line */
+ p = buf[++line];
+ p += sprintf(p, " %s", CALENDAR_COLOR);
+ for (i = 0, w = first_day % 7; i < w; i++)
+ p += sprintf(p, " ");
+
+ /* initial event */
+ for (; e && e->days < first_day; e = e->next);
+
+ d = MonthDay(m, IsLeap(y));
+ for (i = 1; i <= d; i++, w = (w + 1) % 7) {
+ attr1[0] = 0;
+ attr2 = "";
+ while (e && e->days == first_day + i - 1) {
+ sprintf(attr1, ANSI_COLOR(1;%d), e->color);
+ attr2 = CALENDAR_COLOR;
+ e = e->next;
+ }
+ if (today == first_day + i - 1) {
+ strlcpy(attr1, ANSI_COLOR(1;37;42), sizeof(attr1));
+ attr2 = CALENDAR_COLOR;
+ }
+ p += sprintf(p, "%s%2d%s", attr1, i, attr2);
+
+ if (w == 6) {
+ p += sprintf(p, ANSI_RESET);
+ p = buf[++line];
+ /* show month */
+ if (line >= 2 && line <= 4)
+ p += sprintf(p, "%s%2.2s\33[m %s", month_color[m - 1],
+ month_str[m - 1] + (line - 2) * 2,
+ CALENDAR_COLOR);
+ else if (i < d)
+ p += sprintf(p, " %s", CALENDAR_COLOR);
+ } else
+ *p++ = ' ';
+ }
+
+ /* fill up the last line */
+ if (w) {
+ for (w = 7 - w; w; w--)
+ p += sprintf(p, w == 1 ? " " : " ");
+ p += sprintf(p, ANSI_RESET);
+ }
+ return line + 1;
+}
+
+int
+calendar(void)
+{
+ char **buf;
+ struct tm snow;
+ int i, y, m, today, lines = 0;
+ event_t *head = NULL, *e = NULL;
+
+ /* initialize date */
+ memcpy(&snow, localtime4(&now), sizeof(struct tm));
+ today = Days(snow.tm_year + 1900, snow.tm_mon + 1, snow.tm_mday);
+ y = snow.tm_year + 1900, m = snow.tm_mon + 1;
+
+ /* read event */
+ head = e = ReadEvent(today);
+
+ /* generate calendar */
+ buf = AllocCalBuffer(22, 256);
+ for (i = 0; i < 22; i++)
+ sprintf(buf[i], "%24s", "");
+ for (i = 0; i < 3; i++) {
+ lines += GenerateCalendar(buf + lines, y, m, today, e) + 1;
+ if (m == 12)
+ y++, m = 1;
+ else
+ m++;
+ }
+
+ /* output */
+ clear();
+ outc('\n');
+ for (i = 0; i < 22; i++) {
+ outs(buf[i]);
+ if (i == 0) {
+ prints("\t" ANSI_COLOR(1;37)
+ "現在是 %d.%02d.%02d %2d:%02d:%02d%cm" ANSI_RESET,
+ snow.tm_year + 1900, snow.tm_mon + 1, snow.tm_mday,
+ (snow.tm_hour == 0 || snow.tm_hour == 12) ?
+ 12 : snow.tm_hour % 12, snow.tm_min, snow.tm_sec,
+ snow.tm_hour >= 12 ? 'p' : 'a');
+ } else if (i >= 2 && e) {
+ prints("\t" ANSI_COLOR(1;37)
+ "(尚有 " ANSI_COLOR(%d) "%3d"
+ ANSI_COLOR(37) " 天)"
+ ANSI_RESET " %02d/%02d %s",
+ e->color, e->days - today,
+ e->month, e->day, e->content);
+ e = e->next;
+ }
+ outc('\n');
+ }
+ FreeEvent(head);
+ FreeCalBuffer(buf);
+ pressanykey();
+ return 0;
+}
+
+#endif
+
+int getHoroscope(int m, int d)
+{
+ if (m > 12 || m < 1)
+ return 1;
+
+ // Return: 1 .. 12
+ // 摩羯 水瓶 雙魚 牡羊 金牛 雙子 巨蟹 獅子 處女 天秤 天蠍 射手
+ const int firstday[12] = {
+ /* Jan. */ 20, 19, 21, 20, 21, 21, 23, 23, 23, 23, 22, 22
+ };
+ if (d >= firstday[m - 1]) {
+ if (m == 12)
+ return 1;
+ else
+ return m + 1;
+ }
+ else
+ return m;
+}
diff --git a/pttbbs/mbbsd/card.c b/pttbbs/mbbsd/card.c
new file mode 100644
index 00000000..ab9ce89a
--- /dev/null
+++ b/pttbbs/mbbsd/card.c
@@ -0,0 +1,663 @@
+/* $Id$ */
+#include "bbs.h"
+
+enum CardSuit {
+ Spade, Heart, Diamond, Club
+};
+static int
+card_remain(int cards[])
+{
+ int i, temp = 0;
+
+ for (i = 0; i < 52; i++)
+ temp += cards[i];
+ if (temp == 52)
+ return 1;
+ return 0;
+}
+
+static enum CardSuit
+card_flower(int card)
+{
+ return (card / 13);
+}
+
+/* 1...13 */
+static int
+card_number(int card)
+{
+ return (card % 13 + 1);
+}
+
+static int
+card_isblackjack(int card1, int card2)
+{
+ return
+ (card_number(card1)==1 && (10<=card_number(card2) && card_number(card2)<=13)) ||
+ (card_number(card2)==1 && (10<=card_number(card1) && card_number(card1)<=13));
+}
+
+static int
+card_select(int *now)
+{
+ char *cc[2] = {ANSI_COLOR(44) " " ANSI_RESET,
+ ANSI_COLOR(1;33;41) " △ " ANSI_RESET};
+
+ while (1) {
+ move(20, 0);
+ clrtoeol();
+ prints("%s%s%s%s%s", (*now == 0) ? cc[1] : cc[0],
+ (*now == 1) ? cc[1] : cc[0],
+ (*now == 2) ? cc[1] : cc[0],
+ (*now == 3) ? cc[1] : cc[0],
+ (*now == 4) ? cc[1] : cc[0]);
+ switch (igetch()) {
+ case 'Q':
+ case 'q':
+ return 0;
+ case '+':
+ case ',':
+ return 1;
+ case '\r':
+ return -1;
+ case KEY_LEFT:
+ *now = (*now + 4) % 5;
+ break;
+ case KEY_RIGHT:
+ *now = (*now + 1) % 5;
+ break;
+ case '1':
+ *now = 0;
+ break;
+ case '2':
+ *now = 1;
+ break;
+ case '3':
+ *now = 2;
+ break;
+ case '4':
+ *now = 3;
+ break;
+ case '5':
+ *now = 4;
+ break;
+ }
+ }
+}
+
+static void
+card_display(int cline, int number, enum CardSuit flower, int show)
+{
+ int color = 31;
+ char *cn[13] = {"A", "2", "3", "4", "5", "6",
+ "7", "8", "9", "10", "J", "Q", "K"};
+ if (flower == 0 || flower == 3)
+ color = 36;
+ if ((show < 0) && (cline > 1 && cline < 8))
+ outs("│" ANSI_COLOR(1;33;42) "※※※※" ANSI_RESET "│");
+ else
+ switch (cline) {
+ case 1:
+ outs("╭────╮");
+ break;
+ case 2:
+ prints("│" ANSI_COLOR(1;%d) "%s" ANSI_RESET " │", color, cn[number - 1]);
+ break;
+ case 3:
+ if (flower == 1)
+ prints("│" ANSI_COLOR(1;%d) "◢◣◢◣" ANSI_RESET "│", color);
+ else
+ prints("│" ANSI_COLOR(1;%d) " ◢◣ " ANSI_RESET "│", color);
+ break;
+ case 4:
+ if (flower == 1)
+ prints("│" ANSI_COLOR(1;%d) "████" ANSI_RESET "│", color);
+ else if (flower == 3)
+ prints("│" ANSI_COLOR(1;%d) "◣██◢" ANSI_RESET "│", color);
+ else
+ prints("│" ANSI_COLOR(1;%d) "◢██◣" ANSI_RESET "│", color);
+ break;
+ case 5:
+ if (flower == 0)
+ prints("│" ANSI_COLOR(1;%d) "████" ANSI_RESET "│", color);
+ else if (flower == 3)
+ prints("│" ANSI_COLOR(1;%d) "█◥◤█" ANSI_RESET "│", color);
+ else
+ prints("│" ANSI_COLOR(1;%d) "◥██◤" ANSI_RESET "│", color);
+ break;
+ case 6:
+ if (flower == 0)
+ prints("│" ANSI_COLOR(1;%d) " ◢◣ " ANSI_RESET "│", color);
+ else if (flower == 3)
+ prints("│" ANSI_COLOR(1;%d) "◥◢◣◤" ANSI_RESET "│", color);
+ else
+ prints("│" ANSI_COLOR(1;%d) " ◥◤ " ANSI_RESET "│", color);
+ break;
+ case 7:
+ prints("│ " ANSI_COLOR(1;%d) "%s" ANSI_RESET "│", color, cn[number - 1]);
+ break;
+ case 8:
+ outs("╰────╯");
+ break;
+ }
+}
+
+static void
+card_show(int maxncard, int cpu[], int c[], int me[], int m[])
+{
+ int i, j;
+
+ for (j = 0; j < 8; j++) {
+ move(2 + j, 0);
+ clrtoeol();
+ for (i = 0; i < maxncard && cpu[i] >= 0; i++)
+ card_display(j + 1, card_number(cpu[i]),
+ card_flower(cpu[i]), c[i]);
+ }
+
+ for (j = 0; j < 8; j++) {
+ move(11 + j, 0);
+ clrtoeol();
+ for (i = 0; i < maxncard && me[i] >= 0; i++)
+ card_display(j + 1, card_number(me[i]), card_flower(me[i]), m[i]);
+ }
+}
+static void
+card_new(int cards[])
+{
+ memset(cards, 0, sizeof(int) * 52);
+}
+
+static int
+card_give(int cards[])
+{
+ int i;
+ int freecard[52];
+ int nfreecard = 0;
+
+ for(i=0; i<52; i++)
+ if(cards[i]==0)
+ freecard[nfreecard++]=i;
+
+ assert(nfreecard>0); /* caller 要負責確保還有剩牌 */
+
+ i=freecard[random()%nfreecard];
+ cards[i] = 1;
+ return i;
+}
+
+static void
+card_start(char name[])
+{
+ clear();
+ stand_title(name);
+ move(1, 0);
+ outs(" " ANSI_COLOR(1;33;41) " 電 腦 " ANSI_RESET);
+ move(10, 0);
+ outs(ANSI_COLOR(1;34;44) "◆∼◆∼◆∼◆∼◆∼◆∼◆∼◆∼◆∼◆∼◆∼◆∼"
+ "◆∼◆∼◆∼◆∼◆∼◆∼◆∼◆" ANSI_RESET);
+ move(19, 0);
+ outs(" " ANSI_COLOR(1;37;42) " 自 己 " ANSI_RESET);
+}
+
+static int
+card_99_add(int i, int aom, int count)
+{
+ if (i == 4 || i == 5 || i == 11)
+ return count;
+ else if (i == 12)
+ return count + 20 * aom;
+ else if (i == 10)
+ return count + 10 * aom;
+ else if (i == 13)
+ return 99;
+ else
+ return count + i;
+}
+
+static int
+card_99_cpu(int cpu[], int *count)
+{
+ int stop = -1;
+ int twenty = -1;
+ int ten = -1;
+ int kill = -1;
+ int temp, num[10];
+ int other = -1;
+ int think = 99 - (*count);
+ int i, j;
+
+ for (i = 0; i < 10; i++)
+ num[i] = -1;
+ for (i = 0; i < 5; i++) {
+ temp = card_number(cpu[i]);
+ if (temp == 4 || temp == 5 || temp == 11)
+ stop = i;
+ else if (temp == 12)
+ twenty = i;
+ else if (temp == 10)
+ ten = i;
+ else if (temp == 13)
+ kill = i;
+ else {
+ other = i;
+ num[temp] = i;
+ }
+ }
+ for (j = 9; j > 0; j--)
+ if (num[j] >= 0 && j != 4 && j != 5 && think >= j) {
+ (*count) += j;
+ return num[j];
+ }
+ if ((think >= 20) && (twenty >= 0)) {
+ (*count) += 20;
+ return twenty;
+ } else if ((think >= 10) && (ten >= 0)) {
+ (*count) += 10;
+ return ten;
+ } else if (stop >= 0)
+ return stop;
+ else if (kill >= 0) {
+ (*count) = 99;
+ return kill;
+ } else if (ten >= 0) {
+ (*count) -= 10;
+ return ten;
+ } else if (twenty >= 0) {
+ (*count) -= 20;
+ return twenty;
+ } else {
+ (*count) += card_number(cpu[0]);
+ return 0;
+ }
+}
+
+int
+card_99(void)
+{
+ int i, j, turn;
+ int cpu[5], c[5], me[5], m[5];
+ int cards[52];
+ int count = 0;
+ char *ff[4] = {ANSI_COLOR(1;36) "黑桃", ANSI_COLOR(1;31) "紅心",
+ ANSI_COLOR(1;31) "方塊", ANSI_COLOR(1;36) "黑花"};
+ char *cn[13] = {"A", "2", "3", "4", "5", "6",
+ "7", "8", "9", "10", "J", "Q", "K"};
+ for (i = 0; i < 5; i++)
+ cpu[i] = c[i] = me[i] = m[i] = -1;
+ setutmpmode(CARD_99);
+ card_start("天長地久");
+ card_new(cards);
+ for (i = 0; i < 5; i++) {
+ cpu[i] = card_give(cards);
+ me[i] = card_give(cards);
+ m[i] = 1;
+ }
+ card_show(5, cpu, c, me, m);
+ j = 0;
+ turn = 1;
+ move(21, 0);
+ clrtoeol();
+ prints("[0]目前 %d , 殘 %d 點\n", count, 99 - count);
+ outs("左右鍵移動游標, [Enter]確定, [ + ]表加二十(加十), [Q/q]放棄遊戲");
+ while (1) {
+ i = card_select(&j);
+ if (i == 0) /* 放棄遊戲 */
+ return 0;
+ count = card_99_add(card_number(me[j]), i, count);
+ move(21 + (turn / 2) % 2, 0);
+ clrtoeol();
+ prints("[%d]您出 %s%s" ANSI_RESET " 目前 " ANSI_COLOR(1;31) "%d/" ANSI_COLOR(34) "%d" ANSI_RESET " 點",
+ turn, ff[card_flower(me[j])],
+ cn[card_number(me[j]) - 1], count, 99 - count);
+ me[j] = card_give(cards);
+ turn++;
+ if (count < 0)
+ count = 0;
+ card_show(5, cpu, c, me, m);
+ pressanykey();
+ if (count > 99) {
+ move(22, 0);
+ clrtoeol();
+ prints("[%d]結果..YOU LOSS..目前 " ANSI_COLOR(1;31) "%d/" ANSI_COLOR(34) "%d" ANSI_RESET " 點",
+ turn, count, 99 - count);
+ pressanykey();
+ return 0;
+ }
+ i = card_99_cpu(cpu, &count);
+ move(21 + (turn / 2 + 1) % 2, 40);
+ prints("[%d]電腦出 %s%s" ANSI_RESET " 目前 " ANSI_COLOR(1;31) "%d/" ANSI_COLOR(34) "%d" ANSI_RESET " 點",
+ turn, ff[card_flower(cpu[i])],
+ cn[card_number(cpu[i]) - 1], count, 99 - count);
+ cpu[i] = card_give(cards);
+ turn++;
+ if (count < 0)
+ count = 0;
+ if (count > 99) {
+ move(22, 0);
+ clrtoeol();
+ prints("[%d]結果..YOU WIN!..目前 " ANSI_COLOR(1;31) "%d/" ANSI_COLOR(34) "%d" ANSI_RESET " 點",
+ turn, count, 99 - count);
+ pressanykey();
+ return 0;
+ }
+ if (!card_remain(cards)) {
+ card_new(cards);
+ for (i = 0; i < 5; i++) {
+ cards[me[i]] = 1;
+ cards[cpu[i]] = 1;
+ }
+ }
+ }
+}
+
+#define PMONEY (10)
+#define TEN_HALF (5) /* 十點半的Ticket */
+#define JACK (10) /* 黑傑克的Ticket */
+#define NINE99 (99) /* 99 的Ticket */
+
+static int
+game_log(int type, int money)
+{
+ FILE *fp;
+
+ if (money > 0)
+ demoney(money);
+
+ switch (type) {
+ case JACK:
+ fp = fopen(BBSHOME "/etc/card/jack.log", "a");
+ if (!fp)
+ return 0;
+ fprintf(fp, "%s win:%d\n", cuser.userid, money);
+ fclose(fp);
+ break;
+ case TEN_HALF:
+ fp = fopen(BBSHOME "/etc/card/tenhalf.log", "a");
+ if (!fp)
+ return 0;
+ fprintf(fp, "%s win:%d\n", cuser.userid, money);
+ fclose(fp);
+ break;
+ }
+ usleep(100000); // sleep 0.1s
+ return 0;
+}
+
+static int
+card_double_ask(void)
+{
+ char buf[100], buf2[3];
+
+ snprintf(buf, sizeof(buf),
+ "[ %s ]您現在共有 %d P幣, 現在要分組(加收 %d 元)嗎? [y/N]",
+ cuser.userid, cuser.money, JACK);
+ reload_money();
+ if (cuser.money < JACK)
+ return 0;
+ getdata(20, 0, buf, buf2, sizeof(buf2), LCECHO);
+ if (buf2[0] == 'y' || buf2[0] == 'Y')
+ return 1;
+ return 0;
+}
+
+static int
+card_ask(void)
+{
+ char buf[100], buf2[3];
+
+ snprintf(buf, sizeof(buf), "[ %s ]您現在共有 %d P幣, 還要加牌嗎? [y/N]",
+ cuser.userid, cuser.money);
+ getdata(20, 0, buf, buf2, sizeof(buf2), LCECHO);
+ if (buf2[0] == 'y' || buf2[0] == 'Y')
+ return 1;
+ return 0;
+}
+
+static int
+card_alls_lower(int all[])
+{
+ int i, count = 0;
+ for (i = 0; i < 6 && all[i] >= 0; i++)
+ if (card_number(all[i]) <= 10)
+ count += card_number(all[i]);
+ else
+ count += 10;
+ return count;
+}
+
+static int
+card_alls_upper(int all[])
+{
+ int i, count;
+
+ count = card_alls_lower(all);
+ for (i = 0; i < 6 && all[i] >= 0 && count <= 11; i++)
+ if (card_number(all[i]) == 1)
+ count += 10;
+ return count;
+}
+
+static int
+card_jack(int *db)
+{
+ int i, j;
+ int cpu[6], c[6], me[6], m[6];
+ int cards[52];
+
+ for (i = 0; i < 6; i++)
+ cpu[i] = c[i] = me[i] = m[i] = -1;
+
+ if ((*db) < 0) {
+ card_new(cards);
+ card_start("黑傑克");
+ for (i = 0; i < 2; i++) {
+ cpu[i] = card_give(cards);
+ me[i] = card_give(cards);
+ }
+ } else {
+ card_new(cards);
+ cards[*db]=1;
+ card_start("黑傑克DOUBLE追加局");
+ cpu[0] = card_give(cards);
+ cpu[1] = card_give(cards);
+ me[0] = *db;
+ me[1] = card_give(cards);
+ *db = -1;
+ }
+ c[1] = m[0] = m[1] = 1;
+ card_show(6, cpu, c, me, m);
+
+ /* black jack */
+ if (card_isblackjack(me[0],me[1])) {
+ if(card_isblackjack(cpu[0],cpu[1])) {
+ c[0]=1;
+ card_show(6, cpu, c, me, m);
+ game_log(JACK, JACK);
+ vmsgf("你跟電腦都拿到黑傑克, 退還 %d 元", JACK);
+ return 0;
+ }
+ game_log(JACK, JACK * 5/2);
+ vmsgf("很不錯唷! (黑傑克!! 加 %d 元)", JACK * 5/2);
+ return 0;
+ } else if(card_isblackjack(cpu[0],cpu[1])) {
+ c[0] = 1;
+ card_show(6, cpu, c, me, m);
+ game_log(JACK, 0);
+ vmsg("嘿嘿...不好意思....黑傑克!!");
+ return 0;
+ }
+
+ /* double 拆牌 */
+ if ((card_number(me[0]) == card_number(me[1])) &&
+ (card_double_ask())) {
+ *db = me[1];
+ me[1] = card_give(cards);
+ card_show(6, cpu, c, me, m);
+ }
+
+ i = 2;
+ while (i < 6 && card_ask()) {
+ me[i] = card_give(cards);
+ m[i] = 1;
+ card_show(6, cpu, c, me, m);
+ if (card_alls_lower(me) > 21) {
+ game_log(JACK, 0);
+ vmsg("嗚嗚...爆掉了!");
+ return 0;
+ }
+ i++;
+ }
+ if (i == 6) { /* 畫面只能擺六張牌, 因此直接算玩家贏. 黑傑克實際上沒這規則 */
+ game_log(JACK, JACK * 10);
+ vmsgf("好厲害唷! 六張牌還沒爆! 加P幣 %d 元!", 5 * JACK);
+ return 0;
+ }
+
+ j = 2;
+ c[0] = 1;
+ while (j<6 && card_alls_upper(cpu)<=16) {
+ cpu[j] = card_give(cards);
+ c[j] = 1;
+ if (card_alls_lower(cpu) > 21) {
+ card_show(6, cpu, c, me, m);
+ game_log(JACK, JACK * 2);
+ vmsgf("呵呵...電腦爆掉了! 你贏了! 可得P幣 %d 元", JACK * 2);
+ return 0;
+ }
+ j++;
+ }
+ card_show(6, cpu, c, me, m);
+ if(card_alls_upper(cpu)==card_alls_upper(me)) {
+ game_log(JACK, JACK);
+ vmsgf("平局,退回P幣 %d 元!", JACK);
+ return 0;
+ }
+ if(card_alls_upper(cpu)<card_alls_upper(me)) {
+ game_log(JACK, JACK * 2);
+ vmsgf("呵呵...電腦比較小! 你贏了! 可得P幣 %d 元", JACK * 2);
+ return 0;
+ }
+ game_log(JACK, 0);
+ vmsg("哇哇...電腦贏了!");
+ return 0;
+}
+
+int
+g_card_jack(void)
+{
+ int db;
+ char buf[3];
+
+ setutmpmode(JACK_CARD);
+ while (1) {
+ reload_money();
+ if (cuser.money < JACK) {
+ outs("您的錢不夠唷!去多發表些有意義的文章再來~~~");
+ return 0;
+ }
+ getdata(b_lines - 1, 0, "確定要玩黑傑克嗎 一次十元唷?(Y/N)?[N]",
+ buf, 3, LCECHO);
+ if ((*buf != 'y') && (*buf != 'Y'))
+ break;
+ else {
+ db = -1;
+ vice(PMONEY, "黑傑克");
+ do {
+ card_jack(&db);
+ } while(db>=0);
+ }
+ }
+ return 0;
+}
+
+static int
+card_all(int all[])
+{
+ int i, count = 0;
+
+ for (i = 0; i < 5 && all[i] >= 0; i++)
+ if (card_number(all[i]) <= 10)
+ count += 2 * card_number(all[i]);
+ else
+ count += 1;
+ return count;
+}
+
+static int
+ten_helf(void)
+{
+ int i, j;
+ int cpu[5], c[5], me[5], m[5];
+ int cards[52];
+
+ card_start("十點半");
+ card_new(cards);
+ for (i = 0; i < 5; i++)
+ cpu[i] = c[i] = me[i] = m[i] = -1;
+
+ cpu[0] = card_give(cards);
+ me[0] = card_give(cards);
+ m[0] = 1;
+ card_show(5, cpu, c, me, m);
+ i = 1;
+ while (i < 5 && card_ask()) {
+ me[i] = card_give(cards);
+ m[i] = 1;
+ card_show(5, cpu, c, me, m);
+ if (card_all(me) > 21) {
+ game_log(TEN_HALF, 0);
+ vmsg("嗚嗚...爆掉了!");
+ return 0;
+ }
+ i++;
+ }
+ if (i == 5) { /* 過五關 */
+ game_log(TEN_HALF, PMONEY * 5);
+ vmsgf("好厲害唷! 過五關嘍! 加P幣 %d 元!", 5 * PMONEY);
+ return 0;
+ }
+ j = 1;
+ c[0] = 1;
+ while (j < 5 && ((card_all(cpu) < card_all(me)) ||
+ (card_all(cpu) == card_all(me) && j < i))) {
+ cpu[j] = card_give(cards);
+ c[j] = 1;
+ if (card_all(cpu) > 21) {
+ card_show(5, cpu, c, me, m);
+ game_log(TEN_HALF, PMONEY * 2);
+ vmsgf("呵呵...電腦爆掉了! 你贏了! 可得P幣 %d 元", PMONEY * 2);
+ return 0;
+ }
+ j++;
+ }
+ card_show(5, cpu, c, me, m);
+ game_log(TEN_HALF, 0);
+ vmsg("哇哇...電腦贏了!");
+ return 0;
+}
+
+int
+g_ten_helf(void)
+{
+ char buf[3];
+
+ setutmpmode(TENHALF);
+ while (1) {
+ reload_money();
+ if (cuser.money < TEN_HALF) {
+ outs("您的錢不夠唷!去多發表些有意義的文章再來~~~");
+ return 0;
+ }
+ getdata(b_lines - 1, 0,
+ ANSI_COLOR(1;37) "確定要玩十點半嗎 一次十元唷?(Y/N)?[N]" ANSI_RESET,
+ buf, 3, LCECHO);
+ if (buf[0] != 'y' && buf[0] != 'Y')
+ return 0;
+ else {
+ vice(PMONEY, "十點半");
+ ten_helf();
+ }
+ }
+ return 0;
+}
diff --git a/pttbbs/mbbsd/chat.c b/pttbbs/mbbsd/chat.c
new file mode 100644
index 00000000..cb252e15
--- /dev/null
+++ b/pttbbs/mbbsd/chat.c
@@ -0,0 +1,577 @@
+/* $Id$ */
+#include "bbs.h"
+
+#ifndef DBCSAWARE
+#define dbcs_off (1)
+#endif
+
+#define STOP_LINE (t_lines-3)
+static int chatline;
+static FILE *flog;
+static void
+printchatline(const char *str)
+{
+ move(chatline, 0);
+ if (*str == '>' && !PERM_HIDE(currutmp))
+ return;
+ else if (chatline < STOP_LINE - 1)
+ chatline++;
+ else {
+ region_scroll_up(2, STOP_LINE - 2);
+ move(STOP_LINE - 2, 0);
+ }
+ outs(str);
+ outc('\n');
+ outs("→");
+
+ if (flog)
+ fprintf(flog, "%s\n", str);
+}
+
+static void
+chat_clear(char*unused)
+{
+ for (chatline = 2; chatline < STOP_LINE; chatline++) {
+ move(chatline, 0);
+ clrtoeol();
+ }
+ move(b_lines, 0);
+ clrtoeol();
+ move(chatline = 2, 0);
+ outs("→");
+}
+
+static void
+print_chatid(const char *chatid)
+{
+ move(b_lines - 1, 0);
+ clrtoeol();
+ outs(chatid);
+ outc(':');
+}
+
+static int
+chat_send(int fd, const char *buf)
+{
+ int len;
+ char genbuf[200];
+
+ len = snprintf(genbuf, sizeof(genbuf), "%s\n", buf);
+ return (send(fd, genbuf, len, 0) == len);
+}
+
+struct ChatBuf {
+ char buf[128];
+ int bufstart;
+};
+
+static int
+chat_recv(struct ChatBuf *cb, int fd, char *chatroom, char *chatid, size_t chatid_size)
+{
+ int c, len;
+ char *bptr;
+
+ len = sizeof(cb->buf) - cb->bufstart - 1;
+ if ((c = recv(fd, cb->buf + cb->bufstart, len, 0)) <= 0)
+ return -1;
+ c += cb->bufstart;
+
+ bptr = cb->buf;
+ while (c > 0) {
+ len = strlen(bptr) + 1;
+ if (len > c && (unsigned)len < (sizeof(cb->buf)/ 2) )
+ break;
+
+ if (*bptr == '/') {
+ switch (bptr[1]) {
+ case 'c':
+ chat_clear(NULL);
+ break;
+ case 'n':
+ strlcpy(chatid, bptr + 2, chatid_size);
+ print_chatid(chatid);
+ clrtoeol();
+ break;
+ case 'r':
+ strlcpy(chatroom, bptr + 2, IDLEN);
+ break;
+ case 't':
+ move(0, 0);
+ clrtoeol();
+ prints(ANSI_COLOR(1;37;46) " 談天室 [%-12s] " ANSI_COLOR(45) " 話題:%-48s" ANSI_RESET,
+ chatroom, bptr + 2);
+ }
+ } else
+ printchatline(bptr);
+
+ c -= len;
+ bptr += len;
+ }
+
+ if (c > 0) {
+ memmove(cb->buf, bptr, sizeof(cb->buf)-(bptr-cb->buf));
+ cb->bufstart = len - 1;
+ } else
+ cb->bufstart = 0;
+ return 0;
+}
+
+static int
+printuserent(const userinfo_t * uentp)
+{
+ static char uline[80];
+ static int cnt;
+ char pline[30];
+
+ if (!uentp) {
+ if (cnt)
+ printchatline(uline);
+ bzero(uline, sizeof(uline));
+ cnt = 0;
+ return 0;
+ }
+ if (!HasUserPerm(PERM_SYSOP) && !HasUserPerm(PERM_SEECLOAK) && uentp->invisible)
+ return 0;
+
+ snprintf(pline, sizeof(pline), "%-13s%c%-10s ", uentp->userid,
+ uentp->invisible ? '#' : ' ',
+ modestring(uentp, 1));
+ if (cnt < 2)
+ strlcat(pline, "│", sizeof(pline));
+ strlcat(uline, pline, sizeof(uline));
+ if (++cnt == 3) {
+ printchatline(uline);
+ memset(uline, 0, 80);
+ cnt = 0;
+ }
+ return 0;
+}
+
+static void
+chathelp(const char *cmd, const char *desc)
+{
+ char buf[STRLEN];
+
+ snprintf(buf, sizeof(buf), " %-20s- %s", cmd, desc);
+ printchatline(buf);
+}
+
+static void
+chat_help(char *arg)
+{
+ if (strstr(arg, " op")) {
+ printchatline("談天室管理員專用指令");
+ chathelp("[/f]lag [+-][ls]", "設定鎖定、秘密狀態");
+ chathelp("[/i]nvite <id>", "邀請 <id> 加入談天室");
+ chathelp("[/k]ick <id>", "將 <id> 踢出談天室");
+ chathelp("[/o]p <id>", "將 Op 的權力轉移給 <id>");
+ chathelp("[/t]opic <text>", "換個話題");
+ chathelp("[/w]all", "廣播 (站長專用)");
+ } else {
+ chathelp("[//]help", "MUD-like 社交動詞");
+ chathelp("[/.]help", "chicken 鬥雞用指令");
+ chathelp("[/h]elp op", "談天室管理員專用指令");
+ chathelp("[/a]ct <msg>", "做一個動作");
+ chathelp("[/b]ye [msg]", "道別");
+ chathelp("[/c]lear", "清除螢幕");
+ chathelp("[/j]oin <room>", "建立或加入談天室");
+ chathelp("[/l]ist [room]", "列出談天室使用者");
+ chathelp("[/m]sg <id> <msg>", "跟 <id> 說悄悄話");
+ chathelp("[/n]ick <id>", "將談天代號換成 <id>");
+ chathelp("[/p]ager", "切換呼叫器");
+ chathelp("[/q]uery", "查詢網友");
+ chathelp("[/r]oom", "列出一般談天室");
+ chathelp("[/w]ho", "列出本談天室使用者");
+ chathelp("[/w]hoin <room>", "列出談天室<room> 的使用者");
+ }
+}
+
+static void
+chat_date(char *unused)
+{
+ char genbuf[200];
+
+ snprintf(genbuf, sizeof(genbuf),
+ "◆ " BBSNAME "標準時間: %s", Cdate(&now));
+ printchatline(genbuf);
+}
+
+static void
+chat_pager(char *unused)
+{
+ char genbuf[200];
+
+ char *msgs[PAGER_MODES] = {
+ /* Ref: please match PAGER* in modes.h */
+ "關閉", "打開", "拔掉", "防水", "好友"
+ };
+
+ snprintf(genbuf, sizeof(genbuf), "◆ 您的呼叫器:[%s]",
+ msgs[currutmp->pager = (currutmp->pager + 1) % PAGER_MODES]);
+ printchatline(genbuf);
+}
+
+static void
+chat_query(char *arg)
+{
+ char *uid;
+ int tuid;
+ userec_t xuser;
+ char *strtok_pos;
+
+ printchatline("");
+ strtok_r(arg, str_space, &strtok_pos);
+ if ((uid = strtok_r(NULL, str_space, &strtok_pos)) && (tuid = getuser(uid, &xuser))) {
+ char buf[128], *ptr;
+ FILE *fp;
+
+ snprintf(buf, sizeof(buf), "%s(%s) 共上站 %d 次,發表過 %d 篇文章",
+ xuser.userid, xuser.nickname,
+ xuser.numlogins, xuser.numposts);
+ printchatline(buf);
+
+ snprintf(buf, sizeof(buf),
+ "最近(%s)從[%s]上站", Cdate(&xuser.lastlogin),
+ (xuser.lasthost[0] ? xuser.lasthost : "(不詳)"));
+ printchatline(buf);
+
+ sethomefile(buf, xuser.userid, fn_plans);
+ if ((fp = fopen(buf, "r"))) {
+ tuid = 0;
+ while (tuid++ < MAX_QUERYLINES && fgets(buf, 128, fp)) {
+ if ((ptr = strchr(buf, '\n')))
+ ptr[0] = '\0';
+ printchatline(buf);
+ }
+ fclose(fp);
+ }
+ } else
+ printchatline(err_uid);
+}
+
+static void
+chat_users(char* unused)
+{
+ printchatline("");
+ printchatline("【 " BBSNAME "的遊客列表 】");
+ printchatline(msg_shortulist);
+
+ if (apply_ulist(printuserent) == -1)
+ printchatline("空無一人");
+ printuserent(NULL);
+}
+
+typedef struct chat_command_t {
+ char *cmdname; /* Chatroom command length */
+ void (*cmdfunc) (char *); /* Pointer to function */
+} chat_command_t;
+
+static const chat_command_t chat_cmdtbl[] = {
+ {"help", chat_help},
+ {"clear", chat_clear},
+ {"date", chat_date},
+ {"pager", chat_pager},
+ {"query", chat_query},
+ {NULL, NULL}
+};
+
+static int
+chat_cmd_match(const char *buf, const char *str)
+{
+ while (*str && *buf && !isspace((int)*buf))
+ if (tolower(*buf++) != *str++)
+ return 0;
+ return 1;
+}
+
+static int
+chat_cmd(char *buf, int fd)
+{
+ int i;
+
+ if (*buf++ != '/')
+ return 0;
+
+ for (i = 0; chat_cmdtbl[i].cmdname; i++) {
+ if (chat_cmd_match(buf, chat_cmdtbl[i].cmdname)) {
+ chat_cmdtbl[i].cmdfunc(buf);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#define MAXLASTCMD 6
+static int chatid_len = 10;
+
+int
+t_chat(void)
+{
+ char chatroom[IDLEN];/* Chat-Room Name */
+ char inbuf[80], chatid[20], lastcmd[MAXLASTCMD][80], *ptr = "";
+ struct sockaddr_in sin;
+ int cfd, cmdpos, ch;
+ int currchar;
+ int chatting = YEA;
+ char fpath[80];
+ struct ChatBuf chatbuf;
+
+ if(HasUserPerm(PERM_VIOLATELAW))
+ {
+ vmsg("請先繳罰單才能使用聊天室!");
+ return -1;
+ }
+
+ memset(&chatbuf, 0, sizeof(chatbuf));
+
+ outs(" 驅車前往 請梢候........ ");
+ memset(&sin, 0, sizeof sin);
+#ifdef __FreeBSD__
+ sin.sin_len = sizeof(sin);
+#endif
+ sin.sin_family = PF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = htons(NEW_CHATPORT);
+ cfd = socket(sin.sin_family, SOCK_STREAM, 0);
+ if (connect(cfd, (struct sockaddr *) & sin, sizeof sin) != 0) {
+ outs("\n "
+ "哇! 沒人在那邊耶...要有那地方的人先去開門啦!...");
+ system("bin/xchatd");
+ pressanykey();
+ close(cfd);
+ return -1;
+ }
+
+ while (1) {
+ getdata(b_lines - 1, 0, "請輸入聊天代號:", chatid, 9, DOECHO);
+ if(!chatid[0])
+ strlcpy(chatid, cuser.userid, sizeof(chatid));
+ chatid[8] = '\0';
+ /*
+ * 新格式: /! UserID ChatID Password
+ */
+ snprintf(inbuf, sizeof(inbuf), "/! %s %s %s",
+ cuser.userid, chatid, cuser.passwd);
+ chat_send(cfd, inbuf);
+ if (recv(cfd, inbuf, 3, 0) != 3) {
+ close(cfd);
+ return 0;
+ }
+ if (!strcmp(inbuf, CHAT_LOGIN_OK))
+ break;
+ else if (!strcmp(inbuf, CHAT_LOGIN_EXISTS))
+ ptr = "這個代號已經有人用了";
+ else if (!strcmp(inbuf, CHAT_LOGIN_INVALID))
+ ptr = "這個代號是錯誤的";
+ else if (!strcmp(inbuf, CHAT_LOGIN_BOGUS))
+ ptr = "請勿派遣分身進入聊天室 !!";
+
+ move(b_lines - 2, 0);
+ outs(ptr);
+ clrtoeol();
+ bell();
+ }
+
+ add_io(cfd, 0);
+
+ currchar = 0;
+ cmdpos = -1;
+ memset(lastcmd, 0, sizeof(lastcmd));
+
+ setutmpmode(CHATING);
+ currutmp->in_chat = YEA;
+ strlcpy(currutmp->chatid, chatid, sizeof(currutmp->chatid));
+
+ clear();
+ chatline = 2;
+
+ move(STOP_LINE, 0);
+ outs(msg_seperator);
+ move(STOP_LINE, 60);
+ outs(" /help 查詢指令 ");
+ move(1, 0);
+ outs(msg_seperator);
+ print_chatid(chatid);
+ memset(inbuf, 0, sizeof(inbuf));
+
+ setuserfile(fpath, "chat_XXXXXX");
+ flog = fdopen(mkstemp(fpath), "w");
+
+ while (chatting) {
+ move(b_lines - 1, currchar + chatid_len);
+ ch = igetch();
+
+ switch (ch) {
+ case KEY_DOWN:
+ cmdpos += MAXLASTCMD - 2;
+ case KEY_UP:
+ cmdpos++;
+ cmdpos %= MAXLASTCMD;
+ strlcpy(inbuf, lastcmd[cmdpos], sizeof(inbuf));
+ move(b_lines - 1, chatid_len);
+ clrtoeol();
+ outs(inbuf);
+ currchar = strlen(inbuf);
+ continue;
+ case KEY_LEFT:
+ if (currchar)
+ {
+ --currchar;
+#ifdef DBCSAWARE
+ if(currchar > 0 &&
+ ISDBCSAWARE() &&
+ getDBCSstatus((unsigned char*)inbuf, currchar) == DBCS_TRAILING)
+ currchar --;
+#endif
+ }
+ continue;
+ case KEY_RIGHT:
+ if (inbuf[currchar])
+ {
+ ++currchar;
+#ifdef DBCSAWARE
+ if(inbuf[currchar] &&
+ ISDBCSAWARE() &&
+ getDBCSstatus((unsigned char*)inbuf, currchar) == DBCS_TRAILING)
+ currchar++;
+#endif
+ }
+ continue;
+ case KEY_UNKNOWN:
+ continue;
+ }
+
+ if (ISNEWMAIL(currutmp)) {
+ printchatline("◆ 噹!郵差又來了...");
+ }
+ if (ch == I_OTHERDATA) {/* incoming */
+ if (chat_recv(&chatbuf, cfd, chatroom, chatid, 9) == -1) {
+ chatting = chat_send(cfd, "/b");
+ break;
+ }
+ } else if (isprint2(ch)) {
+ if (currchar < 68) {
+ if (inbuf[currchar]) { /* insert */
+ int i;
+
+ for (i = currchar; inbuf[i] && i < 68; i++);
+ inbuf[i + 1] = '\0';
+ for (; i > currchar; i--)
+ inbuf[i] = inbuf[i - 1];
+ } else /* append */
+ inbuf[currchar + 1] = '\0';
+ inbuf[currchar] = ch;
+ move(b_lines - 1, currchar + chatid_len);
+ outs(&inbuf[currchar++]);
+ }
+ } else if (ch == '\n' || ch == '\r') {
+ if (*inbuf) {
+ chatting = chat_cmd(inbuf, cfd);
+ if (chatting == 0)
+ chatting = chat_send(cfd, inbuf);
+ if (!strncmp(inbuf, "/b", 2))
+ break;
+
+ for (cmdpos = MAXLASTCMD - 1; cmdpos; cmdpos--)
+ strlcpy(lastcmd[cmdpos],
+ lastcmd[cmdpos - 1], sizeof(lastcmd[cmdpos]));
+ strlcpy(lastcmd[0], inbuf, sizeof(lastcmd[0]));
+
+ inbuf[0] = '\0';
+ currchar = 0;
+ cmdpos = -1;
+ }
+ print_chatid(chatid);
+ move(b_lines - 1, chatid_len);
+ } else if (ch == Ctrl('H') || ch == '\177') {
+ if (currchar) {
+#ifdef DBCSAWARE
+ int dbcs_off = 1;
+ if (ISDBCSAWARE() &&
+ getDBCSstatus((unsigned char*)inbuf, currchar-1) == DBCS_TRAILING)
+ dbcs_off = 2;
+#endif
+ currchar -= dbcs_off;
+ inbuf[69] = '\0';
+ memcpy(&inbuf[currchar], &inbuf[currchar + dbcs_off],
+ 69 - currchar);
+ move(b_lines - 1, currchar + chatid_len);
+ clrtoeol();
+ outs(&inbuf[currchar]);
+ }
+ } else if (ch == Ctrl('Z') || ch == Ctrl('Y')) {
+ inbuf[0] = '\0';
+ currchar = 0;
+ print_chatid(chatid);
+ move(b_lines - 1, chatid_len);
+ } else if (ch == Ctrl('C')) {
+ chat_send(cfd, "/b");
+ break;
+ } else if (ch == Ctrl('D')) {
+ if ((size_t)currchar < strlen(inbuf)) {
+#ifdef DBCSAWARE
+ int dbcs_off = 1;
+ if (ISDBCSAWARE() && inbuf[currchar+1] &&
+ getDBCSstatus((unsigned char*)inbuf, currchar+1) == DBCS_TRAILING)
+ dbcs_off = 2;
+#endif
+ inbuf[69] = '\0';
+ memcpy(&inbuf[currchar], &inbuf[currchar + dbcs_off],
+ 69 - currchar);
+ move(b_lines - 1, currchar + chatid_len);
+ clrtoeol();
+ outs(&inbuf[currchar]);
+ }
+ } else if (ch == Ctrl('K')) {
+ inbuf[currchar] = 0;
+ move(b_lines - 1, currchar + chatid_len);
+ clrtoeol();
+ } else if (ch == Ctrl('A')) {
+ currchar = 0;
+ } else if (ch == Ctrl('E')) {
+ currchar = strlen(inbuf);
+ } else if (ch == Ctrl('I')) {
+ screen_backup_t old_screen;
+
+ screen_backup(&old_screen);
+ add_io(0, 0);
+ t_idle();
+ screen_restore(&old_screen);
+ add_io(cfd, 0);
+ } else if (ch == Ctrl('Q')) {
+ print_chatid(chatid);
+ move(b_lines - 1, chatid_len);
+ outs(inbuf);
+ continue;
+ }
+ }
+
+ close(cfd);
+ add_io(0, 0);
+ currutmp->in_chat = currutmp->chatid[0] = 0;
+
+ if (flog) {
+ char ans[4];
+
+ fclose(flog);
+ more(fpath, NA);
+ getdata(b_lines - 1, 0, "清除(C) 移至備忘錄(M) (C/M)?[C]",
+ ans, sizeof(ans), LCECHO);
+ if (*ans == 'm') {
+ fileheader_t mymail;
+ char title[128];
+ char genbuf[200];
+
+ sethomepath(genbuf, cuser.userid);
+ stampfile(genbuf, &mymail);
+ mymail.filemode = FILE_READ ;
+ strlcpy(mymail.owner, "[備.忘.錄]", sizeof(mymail.owner));
+ strlcpy(mymail.title, "會議" ANSI_COLOR(1;33) "記錄" ANSI_RESET, sizeof(mymail.title));
+ sethomedir(title, cuser.userid);
+ append_record(title, &mymail, sizeof(mymail));
+ Rename(fpath, genbuf);
+ } else
+ unlink(fpath);
+ }
+ return 0;
+}
diff --git a/pttbbs/mbbsd/chc.c b/pttbbs/mbbsd/chc.c
new file mode 100644
index 00000000..82087703
--- /dev/null
+++ b/pttbbs/mbbsd/chc.c
@@ -0,0 +1,1012 @@
+/* $Id$ */
+#include "bbs.h"
+#include "chc.h"
+
+#define assert_not_reached() assert(!"Should never be here!!!")
+
+extern const double elo_exp_tab[1000];
+
+enum Turn {
+ BLK = 0,
+ RED
+};
+
+enum Kind {
+ KIND_K=1,
+ KIND_A,
+ KIND_E,
+ KIND_R,
+ KIND_H,
+ KIND_C,
+ KIND_P,
+};
+#define CENTER(a, b) (((a) + (b)) >> 1)
+#define CHC_TIMEOUT 300
+
+#define PHOTO_LINE 15
+#define PHOTO_COLUMN (256 + 25)
+
+typedef struct drc_t {
+ ChessStepType type; /* necessary one */
+ rc_t from, to;
+} drc_t;
+
+typedef struct {
+ rc_t select;
+ char selected;
+} chc_tag_data_t;
+
+/* chess framework action functions */
+static void chc_init_user(const userinfo_t *uinfo, ChessUser *user);
+static void chc_init_user_userec(const userec_t *urec, ChessUser *user);
+static void chc_init_board(board_t board);
+static void chc_drawline(const ChessInfo* info, int line);
+static void chc_movecur(int r, int c);
+static int chc_prepare_play(ChessInfo* info);
+static int chc_select(ChessInfo* info, rc_t scrloc, ChessGameResult* result);
+static void chc_prepare_step(ChessInfo* info, const void* step);
+static ChessGameResult chc_movechess(board_t board, const drc_t* move);
+static void chc_drawstep(ChessInfo* info, const drc_t* move);
+static void chc_gameend(ChessInfo* info, ChessGameResult result);
+static void chc_genlog(ChessInfo* info, FILE* fp, ChessGameResult result);
+
+
+static const char * const turn_color[2]={BLACK_COLOR, RED_COLOR};
+
+/* some constant variable definition */
+
+static const char * const turn_str[2] = {"黑的", "紅的"};
+
+static const char * const num_str[2][10] = {
+ {"", "1", "2", "3", "4", "5", "6", "7", "8", "9"},
+ {"", "一", "二", "三", "四", "五", "六", "七", "八", "九"},
+};
+
+static const char * const chess_str[2][8] = {
+ /* 0 1 2 3 4 5 6 7 */
+ {" ", "將", "士", "象", "車", "馬", "包", "卒"},
+ {" ", "帥", "仕", "相", "車", "傌", "炮", "兵"}
+};
+
+static const char * const chess_brd[BRD_ROW * 2 - 1] = {
+ /* 0 1 2 3 4 5 6 7 8 */
+ "┌─┬─┬─┬─┬─┬─┬─┬─┐", /* 0 */
+ "│ │ │ │\│/│ │ │ │",
+ "├─┼─┼─┼─┼─┼─┼─┼─┤", /* 1 */
+ "│ │ │ │/│\│ │ │ │",
+ "├─┼─┼─┼─┼─┼─┼─┼─┤", /* 2 */
+ "│ │ │ │ │ │ │ │ │",
+ "├─┼─┼─┼─┼─┼─┼─┼─┤", /* 3 */
+ "│ │ │ │ │ │ │ │ │",
+ "├─┴─┴─┴─┴─┴─┴─┴─┤", /* 4 */
+ "│ 楚 河 漢 界 │",
+ "├─┬─┬─┬─┬─┬─┬─┬─┤", /* 5 */
+ "│ │ │ │ │ │ │ │ │",
+ "├─┼─┼─┼─┼─┼─┼─┼─┤", /* 6 */
+ "│ │ │ │ │ │ │ │ │",
+ "├─┼─┼─┼─┼─┼─┼─┼─┤", /* 7 */
+ "│ │ │ │\│/│ │ │ │",
+ "├─┼─┼─┼─┼─┼─┼─┼─┤", /* 8 */
+ "│ │ │ │/│\│ │ │ │",
+ "└─┴─┴─┴─┴─┴─┴─┴─┘" /* 9 */
+};
+
+static char * const hint_str[] = {
+ " q 認輸離開",
+ " p 要求和棋",
+ "方向鍵 移動遊標",
+ "Enter 選擇/移動"
+};
+
+static const ChessActions chc_actions = {
+ &chc_init_user,
+ &chc_init_user_userec,
+ (void (*) (void*)) &chc_init_board,
+ &chc_drawline,
+ &chc_movecur,
+ &chc_prepare_play,
+ NULL, /* process_key */
+ &chc_select,
+ &chc_prepare_step,
+ (ChessGameResult (*) (void*, const void*)) &chc_movechess,
+ (void (*)(ChessInfo*, const void*)) &chc_drawstep,
+ NULL, /* post_game */
+ &chc_gameend,
+ &chc_genlog
+};
+
+static const ChessConstants chc_constants = {
+ sizeof(drc_t),
+ CHC_TIMEOUT,
+ BRD_ROW,
+ BRD_COL,
+ 0,
+ "楚河漢界",
+ "photo_cchess",
+#ifdef GLOBAL_CCHESS_LOG
+ GLOBAL_CCHESS_LOG,
+#else
+ NULL,
+#endif
+ { BLACK_COLOR, RED_COLOR },
+ {"黑的", "紅的"}
+};
+
+/*
+ * Start of the drawing function.
+ */
+static void
+chc_movecur(int r, int c)
+{
+ move(r * 2 + 3, c * 4 + 4);
+}
+
+static char *
+getstep(board_t board, const rc_t *from, const rc_t *to, char buf[])
+{
+ int turn, fc, tc;
+ char *dir;
+ int twin = 0, twin_r = 0;
+ int len = 0;
+
+ turn = CHE_O(board[from->r][from->c]);
+ if(CHE_P(board[from->r][from->c] != KIND_P)) { // TODO 目前不管兵卒前後
+ int i;
+ for(i=0;i<10;i++)
+ if(board[i][from->c]==board[from->r][from->c]) {
+ if(i!=from->r) {
+ twin=1;
+ twin_r=i;
+ }
+ }
+ }
+ fc = (turn == BLK ? from->c + 1 : 9 - from->c);
+ tc = (turn == BLK ? to->c + 1 : 9 - to->c);
+ if (from->r == to->r)
+ dir = "平";
+ else {
+ if (from->c == to->c)
+ tc = from->r - to->r;
+ if (tc < 0)
+ tc = -tc;
+
+ if ((turn == BLK && to->r > from->r) ||
+ (turn == RED && to->r < from->r))
+ dir = "進";
+ else
+ dir = "退";
+ }
+
+
+ len=sprintf(buf, "%s", turn_color[turn]);
+ /* 傌二|前傌 */
+ if(twin) {
+ len+=sprintf(buf+len, "%s%s",
+ ((from->r>twin_r)==(turn==(BLK)))?"前":"後",
+ chess_str[turn][CHE_P(board[from->r][from->c])]);
+ } else {
+ len+=sprintf(buf+len, "%s%s",
+ chess_str[turn][CHE_P(board[from->r][from->c])],
+ num_str[turn][fc]);
+ }
+ /* 進三 */
+ len+=sprintf(buf+len, "%s%s" ANSI_RESET, dir, num_str[turn][tc]);
+ /* :象 */
+ if(board[to->r][to->c]) {
+ len+=sprintf(buf+len,":%s%s" ANSI_RESET,
+ turn_color[turn^1],
+ chess_str[turn^1][CHE_P(board[to->r][to->c])]);
+ }
+ return buf;
+}
+
+inline static const char*
+chc_timestr(int second)
+{
+ static char str[10];
+ snprintf(str, sizeof(str), "%d:%02d", second / 60, second % 60);
+ return str;
+}
+
+static void
+chc_drawline(const ChessInfo* info, int line)
+{
+ int i, j;
+ board_p board = (board_p) info->board;
+ chc_tag_data_t *tag = info->tag;
+
+ if (line == 0) {
+ prints(ANSI_COLOR(1;46) " 象棋對戰 " ANSI_COLOR(45)
+ "%30s VS %-20s%10s" ANSI_RESET,
+ info->user1.userid, info->user2.userid,
+ info->mode == CHESS_MODE_WATCH ? "[觀棋模式]" : "");
+ } else if (line >= 3 && line <= 21) {
+ outs(" ");
+ for (i = 0; i < 9; i++) {
+ j = board[RTL(info,line)][CTL(info,i)];
+ if ((line & 1) == 1 && j) {
+ if (tag->selected &&
+ tag->select.r == RTL(info,line) && tag->select.c == CTL(info,i)) {
+ prints("%s%s" ANSI_RESET,
+ CHE_O(j) == BLK ? BLACK_REVERSE : RED_REVERSE,
+ chess_str[CHE_O(j)][CHE_P(j)]);
+ }
+ else {
+ prints("%s%s" ANSI_RESET,
+ turn_color[CHE_O(j)],
+ chess_str[CHE_O(j)][CHE_P(j)]);
+ }
+ } else
+ prints("%c%c", chess_brd[line - 3][i * 4],
+ chess_brd[line - 3][i * 4 + 1]);
+ if (i != 8)
+ prints("%c%c", chess_brd[line - 3][i * 4 + 2],
+ chess_brd[line - 3][i * 4 + 3]);
+ }
+ } else if (line == 2 || line == 22) {
+ outs(" ");
+ if (line == 2)
+ for (i = 1; i <= 9; i++)
+ prints("%s ", num_str[REDDOWN(info)?0:1][i]);
+ else
+ for (i = 9; i >= 1; i--)
+ prints("%s ", num_str[REDDOWN(info)?1:0][i]);
+ }
+
+ ChessDrawExtraInfo(info, line, 8);
+}
+/*
+ * End of the drawing function.
+ */
+
+
+/*
+ * Start of the log function.
+ */
+
+static void
+chc_log_machine_step(FILE* fp, board_t board, const drc_t *step)
+{
+ const static char chess_char[8] = {
+ 0, 'K', 'A', 'B', 'R', 'N', 'C', 'P'
+ };
+ /* We have black at bottom in rc_t but the standard is
+ * the red side at bottom, so that a rotation is needed. */
+ fprintf(fp, "%c%c%d%c%c%d ",
+ chess_char[CHE_P(board[step->from.r][step->from.c])],
+ step->from.c + 'a', BRD_ROW - step->from.r - 1,
+ board[step->to.r][step->to.c] ? 'x' : '-',
+ step->to.c + 'a', BRD_ROW - step->to.r - 1
+ );
+}
+
+static int
+#if defined(__linux__)
+chc_filter(const struct dirent *dir)
+#else
+chc_filter(struct dirent *dir)
+#endif
+{
+ if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0 )
+ return 0;
+ return strstr(dir->d_name, ".poem") != NULL;
+}
+
+static int
+chc_log_poem(FILE* outfp)
+{
+ struct dirent **namelist;
+ int n;
+
+ // TODO use readdir(), don't use lots of memory
+ n = scandir(BBSHOME"/etc/chess", &namelist, chc_filter, alphasort);
+ if (n < 0)
+ perror("scandir");
+ else {
+ char buf[80];
+ FILE *fp;
+ sprintf(buf, BBSHOME"/etc/chess/%s", namelist[random() % n]->d_name);
+ if ((fp = fopen(buf, "r")) == NULL)
+ return -1;
+
+ while(fgets(buf, sizeof(buf), fp) != NULL)
+ fputs(buf, outfp);
+ while(n--)
+ free(namelist[n]);
+ free(namelist);
+ fclose(fp);
+ }
+ return 0;
+}
+
+static void
+chc_genlog(ChessInfo* info, FILE* fp, ChessGameResult result)
+{
+ const int nStep = info->history.used;
+ board_t board;
+ int i;
+
+ fprintf(fp, "按 z 可進入打譜模式\n");
+ fprintf(fp, "\n");
+
+ if (info->myturn == RED)
+ fprintf(fp, "%s(%d) V.S. %s(%d)\n",
+ info->user1.userid, info->user1.orig_rating,
+ info->user2.userid, info->user2.orig_rating);
+ else
+ fprintf(fp, "%s(%d) V.S. %s(%d)\n",
+ info->user2.userid, info->user2.orig_rating,
+ info->user1.userid, info->user1.orig_rating);
+
+ chc_init_board(board);
+ /* format: "%3d. %8.8s %8.8s %3d. %8.8s %8.8s\n" */
+ for (i = 0; i < nStep; i++) {
+ char buf[80];
+ const drc_t *move = (const drc_t*) ChessHistoryRetrieve(info, i);
+ buf[0]='\0';
+ if (move->type == CHESS_STEP_NORMAL) {
+ getstep(board, &move->from, &move->to, buf);
+ chc_movechess(board, move);
+ if(i%2==0) fprintf(fp, "%3d. ",i/2+1);
+ strip_ansi(buf, buf, STRIP_ALL);
+ fprintf(fp, "%8.8s ", buf);
+ if(i%4==3 || i==nStep-1) fputc('\n', fp);
+ }
+ }
+
+ if (result == CHESS_RESULT_TIE)
+ fprintf(fp, "=> 和局\n");
+ else if (result == CHESS_RESULT_WIN || result == CHESS_RESULT_LOST)
+ fprintf(fp, "=> %s 勝\n",
+ (info->myturn == RED) == (result== CHESS_RESULT_WIN) ?
+ "紅" : "黑");
+
+ /* generate machine readable log.
+ * http://www.elephantbase.net/protocol/cchess_pgn.htm */
+ {
+ /* machine readable header */
+ time_t temp = (time_t) now;
+ struct tm *mytm = localtime(&temp);
+
+ fprintf(fp,
+ "\n\n<chclog>\n"
+ "[Game \"Chinese Chess\"]\n"
+ "[Date \"%d.%d.%d\"]\n"
+ "[Red \"%s\"]\n"
+ "[Black \"%s\"]\n",
+ mytm->tm_year + 1900, mytm->tm_mon + 1, mytm->tm_mday,
+ info->myturn == RED ? info->user1.userid : info->user2.userid,
+ info->myturn == RED ? info->user2.userid : info->user1.userid
+ );
+
+ if (result == CHESS_RESULT_TIE || result == CHESS_RESULT_WIN ||
+ result == CHESS_RESULT_LOST)
+ fprintf(fp, "[Result \"%s\"]\n",
+ result == CHESS_RESULT_TIE ? "0.5-0.5" :
+ (info->myturn == RED) == (result== CHESS_RESULT_WIN) ?
+ "1-0" : "0-1");
+ else
+ fprintf(fp, "[Result \"*\"]\n");
+
+ fprintf(fp,
+ "[Notation \"Coord\"]\n"
+ "[FEN \"rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR"
+ " r - - 0 1\"]\n");
+ }
+ chc_init_board(board);
+ for (i = 0; i < nStep - 1; i += 2) {
+ const drc_t *move = (const drc_t*) ChessHistoryRetrieve(info, i);
+ fprintf(fp, "%2d. ", i / 2 + 1);
+ if (move->type == CHESS_STEP_NORMAL) {
+ chc_log_machine_step(fp, board, move);
+ chc_movechess(board, move);
+ }
+
+ fputs(" ", fp);
+
+ move = (const drc_t*) ChessHistoryRetrieve(info, i + 1);
+ if (move->type == CHESS_STEP_NORMAL) {
+ chc_log_machine_step(fp, board, move);
+ chc_movechess(board, move);
+ }
+
+ fputc('\n', fp);
+ }
+ if (i < nStep) {
+ const drc_t *move = (const drc_t*) ChessHistoryRetrieve(info, i);
+ if (move->type == CHESS_STEP_NORMAL) {
+ fprintf(fp, "%2d. ", i / 2 + 1);
+ chc_log_machine_step(fp, board, move);
+ fputc('\n', fp);
+ }
+ }
+
+ fputs("</chclog>\n\n--\n\n", fp);
+ chc_log_poem(fp);
+}
+/*
+ * End of the log function.
+ */
+
+
+/*
+ * Start of the rule function.
+ */
+static void
+chc_init_board(board_t board)
+{
+ memset(board, 0, sizeof(board_t));
+ board[0][4] = CHE(KIND_K, BLK); /* 將 */
+ board[0][3] = board[0][5] = CHE(KIND_A, BLK); /* 士 */
+ board[0][2] = board[0][6] = CHE(KIND_E, BLK); /* 象 */
+ board[0][0] = board[0][8] = CHE(KIND_R, BLK); /* 車 */
+ board[0][1] = board[0][7] = CHE(KIND_H, BLK); /* 馬 */
+ board[2][1] = board[2][7] = CHE(KIND_C, BLK); /* 包 */
+ board[3][0] = board[3][2] = board[3][4] =
+ board[3][6] = board[3][8] = CHE(KIND_P, BLK); /* 卒 */
+
+ board[9][4] = CHE(KIND_K, RED); /* 帥 */
+ board[9][3] = board[9][5] = CHE(KIND_A, RED); /* 仕 */
+ board[9][2] = board[9][6] = CHE(KIND_E, RED); /* 相 */
+ board[9][0] = board[9][8] = CHE(KIND_R, RED); /* 車 */
+ board[9][1] = board[9][7] = CHE(KIND_H, RED); /* 傌 */
+ board[7][1] = board[7][7] = CHE(KIND_C, RED); /* 炮 */
+ board[6][0] = board[6][2] = board[6][4] =
+ board[6][6] = board[6][8] = CHE(KIND_P, RED); /* 兵 */
+}
+
+static void
+chc_prepare_step(ChessInfo* info, const void* step)
+{
+ const drc_t* move = (const drc_t*) step;
+ getstep((board_p) info->board,
+ &move->from, &move->to, info->last_movestr);
+}
+
+static ChessGameResult
+chc_movechess(board_t board, const drc_t* move)
+{
+ int end = (CHE_P(board[move->to.r][move->to.c]) == KIND_K);
+
+ board[move->to.r][move->to.c] = board[move->from.r][move->from.c];
+ board[move->from.r][move->from.c] = 0;
+
+ return end ? CHESS_RESULT_WIN : CHESS_RESULT_CONTINUE;
+}
+
+static void
+chc_drawstep(ChessInfo* info, const drc_t* move)
+{
+ ChessDrawLine(info, LTR(info, move->from.r));
+ ChessDrawLine(info, LTR(info, move->to.r));
+}
+
+/* 求兩座標行或列(rowcol)的距離 */
+static int
+dist(rc_t from, rc_t to, int rowcol)
+{
+ int d;
+
+ d = rowcol ? from.c - to.c : from.r - to.r;
+ return d > 0 ? d : -d;
+}
+
+/* 兩座標(行或列rowcol)中間有幾顆棋子 */
+static int
+between(board_t board, rc_t from, rc_t to, int rowcol)
+{
+ int i, rtv = 0;
+
+ if (rowcol) {
+ if (from.c > to.c)
+ i = from.c, from.c = to.c, to.c = i;
+ for (i = from.c + 1; i < to.c; i++)
+ if (board[to.r][i])
+ rtv++;
+ } else {
+ if (from.r > to.r)
+ i = from.r, from.r = to.r, to.r = i;
+ for (i = from.r + 1; i < to.r; i++)
+ if (board[i][to.c])
+ rtv++;
+ }
+ return rtv;
+}
+
+static int
+chc_canmove(board_t board, rc_t from, rc_t to)
+{
+ int i;
+ int rd, cd, turn;
+
+ if(0 ||
+ !(0<=from.r && from.r<BRD_ROW) ||
+ !(0<=from.c && from.c<BRD_COL) ||
+ !(0<=to.r && to.r<BRD_ROW) ||
+ !(0<=to.c && to.c<BRD_COL))
+ return 0;
+
+ rd = dist(from, to, 0);
+ cd = dist(from, to, 1);
+ turn = CHE_O(board[from.r][from.c]);
+
+ /* general check */
+ if (board[to.r][to.c] && CHE_O(board[to.r][to.c]) == turn)
+ return 0;
+
+ /* individual check */
+ switch (CHE_P(board[from.r][from.c])) {
+ case KIND_K: /* 將 帥 */
+ if (!(rd == 1 && cd == 0) &&
+ !(rd == 0 && cd == 1))
+ return 0;
+ if ((turn == BLK && to.r > 2) ||
+ (turn == RED && to.r < 7) ||
+ to.c < 3 || to.c > 5)
+ return 0;
+ break;
+ case KIND_A: /* 士 仕 */
+ if (!(rd == 1 && cd == 1))
+ return 0;
+ if ((turn == BLK && to.r > 2) ||
+ (turn == RED && to.r < 7) ||
+ to.c < 3 || to.c > 5)
+ return 0;
+ break;
+ case KIND_E: /* 象 相 */
+ if (!(rd == 2 && cd == 2))
+ return 0;
+ if ((turn == BLK && to.r > 4) ||
+ (turn == RED && to.r < 5))
+ return 0;
+ /* 拐象腿 */
+ if (board[CENTER(from.r, to.r)][CENTER(from.c, to.c)])
+ return 0;
+ break;
+ case KIND_R: /* 車 */
+ if (!(rd > 0 && cd == 0) &&
+ !(rd == 0 && cd > 0))
+ return 0;
+ if (between(board, from, to, rd == 0))
+ return 0;
+ break;
+ case KIND_H: /* 馬 傌 */
+ if (!(rd == 2 && cd == 1) &&
+ !(rd == 1 && cd == 2))
+ return 0;
+ /* 拐馬腳 */
+ if (rd == 2) {
+ if (board[CENTER(from.r, to.r)][from.c])
+ return 0;
+ } else {
+ if (board[from.r][CENTER(from.c, to.c)])
+ return 0;
+ }
+ break;
+ case KIND_C: /* 包 炮 */
+ if (!(rd > 0 && cd == 0) &&
+ !(rd == 0 && cd > 0))
+ return 0;
+ i = between(board, from, to, rd == 0);
+ if ((i > 1) ||
+ (i == 1 && !board[to.r][to.c]) ||
+ (i == 0 && board[to.r][to.c]))
+ return 0;
+ break;
+ case KIND_P: /* 卒 兵 */
+ if (!(rd == 1 && cd == 0) &&
+ !(rd == 0 && cd == 1))
+ return 0;
+ if (((turn == BLK && to.r < 5) ||
+ (turn == RED && to.r > 4)) &&
+ cd != 0)
+ return 0;
+ if ((turn == BLK && to.r < from.r) ||
+ (turn == RED && to.r > from.r))
+ return 0;
+ break;
+ }
+ return 1;
+}
+
+/* 找 turn's king 的座標 */
+static int
+findking(board_t board, int turn, rc_t * buf)
+{
+ int i, r, c;
+
+ r = (turn == BLK ? 0 : 7);
+ for (i = 0; i < 3; r++, i++)
+ for (c = 3; c < 6; c++)
+ if (CHE_P(board[r][c]) == KIND_K &&
+ CHE_O(board[r][c]) == turn) {
+ buf->r = r, buf->c = c;
+ return 1;
+ }
+ /* one's king may be eaten */
+ return 0;
+}
+
+static int
+chc_iskfk(board_t board)
+{
+ rc_t from, to;
+
+ if (!findking(board, BLK, &to)) return 0;
+ if (!findking(board, RED, &from)) return 0;
+ if (from.c == to.c && between(board, from, to, 0) == 0)
+ return 1;
+ return 0;
+}
+
+static int
+chc_ischeck(board_t board, int turn)
+{
+ rc_t from, to;
+
+ if (!findking(board, turn, &to)) return 0;
+ for (from.r = 0; from.r < BRD_ROW; from.r++)
+ for (from.c = 0; from.c < BRD_COL; from.c++)
+ if (board[from.r][from.c] &&
+ CHE_O(board[from.r][from.c]) != turn)
+ if (chc_canmove(board, from, to))
+ return 1;
+ return 0;
+}
+/*
+ * End of the rule function.
+ */
+
+static void
+chcusr_put(userec_t* userec, const ChessUser* user)
+{
+ userec->chc_win = user->win;
+ userec->chc_lose = user->lose;
+ userec->chc_tie = user->tie;
+ userec->chess_elo_rating = user->rating;
+}
+
+static void
+chc_init_user(const userinfo_t *uinfo, ChessUser *user)
+{
+ strlcpy(user->userid, uinfo->userid, sizeof(user->userid));
+ user->win = uinfo->chc_win;
+ user->lose = uinfo->chc_lose;
+ user->tie = uinfo->chc_tie;
+ user->rating = uinfo->chess_elo_rating;
+ if(user->rating == 0)
+ user->rating = 1500; /* ELO initial value */
+ user->orig_rating = user->rating;
+}
+
+static void
+chc_init_user_userec(const userec_t *urec, ChessUser *user)
+{
+ strlcpy(user->userid, urec->userid, sizeof(user->userid));
+ user->win = urec->chc_win;
+ user->lose = urec->chc_lose;
+ user->tie = urec->chc_tie;
+ user->rating = urec->chess_elo_rating;
+ if(user->rating == 0)
+ user->rating = 1500; /* ELO initial value */
+ user->orig_rating = user->rating;
+}
+
+static int
+chc_prepare_play(ChessInfo* info)
+{
+ if (chc_ischeck((board_p) info->board, info->turn)) {
+ strlcpy(info->warnmsg, ANSI_COLOR(1;31) "將軍!" ANSI_RESET,
+ sizeof(info->warnmsg));
+ bell();
+ } else
+ info->warnmsg[0] = 0;
+
+ return 0;
+}
+
+static int
+chc_select(ChessInfo* info, rc_t scrloc, ChessGameResult* result)
+{
+ chc_tag_data_t* tag = (chc_tag_data_t*) info->tag;
+ board_p board = (board_p) info->board;
+ rc_t loc;
+
+ assert(tag);
+
+ /* transform from screen to internal coordinate */
+ if(REDDOWN(info)) {
+ loc = scrloc;
+ } else {
+ loc.r = BRD_ROW-scrloc.r-1;
+ loc.c = BRD_COL-scrloc.c-1;
+ }
+
+ if (!tag->selected) {
+ /* trying to pick something */
+ if (board[loc.r][loc.c] &&
+ CHE_O(board[loc.r][loc.c]) == info->turn) {
+ /* they can pick up this */
+ tag->selected = 1;
+ tag->select = loc;
+ ChessDrawLine(info, LTR(info, loc.r));
+ }
+ return 0;
+ } else if (tag->select.r == loc.r && tag->select.c == loc.c) {
+ /* cancel selection */
+ tag->selected = 0;
+ ChessDrawLine(info, LTR(info, loc.r));
+ return 0;
+ } else if (chc_canmove(board, tag->select, loc)) {
+ /* moving the chess */
+ drc_t moving = { CHESS_STEP_NORMAL, tag->select, loc };
+ board_t tmpbrd;
+ int valid_step = 1;
+
+ if (CHE_P(board[loc.r][loc.c]) == KIND_K)
+ /* 移到對方將帥 */
+ *result = CHESS_RESULT_WIN;
+ else {
+ memcpy(tmpbrd, board, sizeof(board_t));
+ chc_movechess(tmpbrd, &moving);
+ valid_step = !chc_iskfk(tmpbrd);
+ }
+
+ if (valid_step) {
+ getstep(board, &moving.from, &moving.to, info->last_movestr);
+
+ chc_movechess(board, &moving);
+ ChessDrawLine(info, LTR(info, moving.from.r));
+ ChessDrawLine(info, LTR(info, moving.to.r));
+
+ ChessHistoryAppend(info, &moving);
+ ChessStepSend(info, &moving);
+
+ tag->selected = 0;
+ return 1;
+ } else {
+ /* 王見王 */
+ strlcpy(info->warnmsg,
+ ANSI_COLOR(1;33) "不可以王見王" ANSI_RESET,
+ sizeof(info->warnmsg));
+ bell();
+ ChessDrawLine(info, CHESS_DRAWING_WARN_ROW);
+ return 0;
+ }
+ } else
+ /* nothing happened */
+ return 0;
+}
+
+int round_to_int(double x)
+{
+ /* assume that double cast to int will drop fraction parts */
+ if(x>=0)
+ return (int)(x+0.5);
+ return (int)(x-0.5);
+}
+
+/*
+ * ELO rating system
+ * see http://www.wordiq.com/definition/ELO_rating_system
+ */
+static void
+count_chess_elo_rating(ChessUser* user1, const ChessUser* user2, double myres)
+{
+ double k;
+ double exp_res;
+ int diff;
+ int newrating;
+
+ if(user1->rating < 1800)
+ k = 30;
+ else if(user1->rating < 2000)
+ k = 25;
+ else if(user1->rating < 2200)
+ k = 20;
+ else if(user1->rating < 2400)
+ k = 15;
+ else
+ k = 10;
+
+ //exp_res = 1.0/(1.0 + pow(10.0, (user2->rating-user1->rating)/400.0));
+ //user1->rating += (int)floor(k*(myres-exp_res)+0.5);
+ diff=(int)user2->rating-(int)user1->rating;
+ if(diff<=-1000 || diff>=1000)
+ exp_res=diff>0?0.0:1.0;
+ else if(diff>=0)
+ exp_res=elo_exp_tab[diff];
+ else
+ exp_res=1.0-elo_exp_tab[-diff];
+ newrating = (int)user1->rating + round_to_int(k*(myres-exp_res));
+ if(newrating > 3000) newrating = 3000;
+ if(newrating < 1) newrating = 1;
+ user1->rating = newrating;
+}
+
+
+/* 象棋功能進入點:
+ * chc_main: 對奕
+ * chc_personal: 打譜
+ * chc_watch: 觀棋
+ * talk.c: 對奕
+ */
+void
+chc(int s, ChessGameMode mode)
+{
+ ChessInfo* info = NewChessInfo(&chc_actions, &chc_constants, s, mode);
+ board_t board;
+ chc_tag_data_t tag;
+
+ chc_init_board(board);
+ tag.selected = 0;
+
+ info->board = board;
+ info->tag = &tag;
+
+ if (info->mode == CHESS_MODE_VERSUS) {
+ /* Assume that info->user1 is me. */
+ info->user1.lose++;
+ count_chess_elo_rating(&info->user1, &info->user2, 0.0);
+ passwd_query(usernum, &cuser);
+ chcusr_put(&cuser, &info->user1);
+ passwd_update(usernum, &cuser);
+ }
+
+ if (mode == CHESS_MODE_WATCH)
+ setutmpmode(CHESSWATCHING);
+ else
+ setutmpmode(CHC);
+ currutmp->sig = SIG_CHC;
+
+ ChessPlay(info);
+
+ DeleteChessInfo(info);
+}
+
+static void
+chc_gameend(ChessInfo* info, ChessGameResult result)
+{
+ ChessUser* const user1 = &info->user1;
+ ChessUser* const user2 = &info->user2;
+
+ if (info->mode == CHESS_MODE_VERSUS) {
+ if (info->myturn == RED) {
+ /* 由紅方作 log. 記的是下棋前的原始分數 */
+ /* NOTE, 若紅方斷線則無 log */
+ time_t t = time(NULL);
+ char buf[100];
+ sprintf(buf, "%s %s(%d,W%d/D%d/L%d) %s %s(%d,W%d/D%d/L%d)\n",
+ ctime(&t),
+ user1->userid, user1->rating, user1->win,
+ user1->tie, user1->lose - 1,
+ (result == CHESS_RESULT_TIE ? "和" :
+ result == CHESS_RESULT_WIN ? "勝" : "負"),
+ user2->userid, user2->rating, user2->win,
+ user2->tie, user2->lose - 1);
+ buf[24] = ' '; // replace '\n'
+ log_file(BBSHOME "/log/chc.log", LOG_CREAT, buf);
+ }
+
+ user1->rating = user1->orig_rating;
+ user1->lose--;
+ if (result == CHESS_RESULT_WIN) {
+ count_chess_elo_rating(user1, user2, 1.0);
+ user1->win++;
+ currutmp->chc_win++;
+ } else if (result == CHESS_RESULT_LOST) {
+ count_chess_elo_rating(user1, user2, 0.0);
+ user1->lose++;
+ currutmp->chc_lose++;
+ } else {
+ count_chess_elo_rating(user1, user2, 0.5);
+ user1->tie++;
+ currutmp->chc_tie++;
+ }
+ currutmp->chess_elo_rating = user1->rating;
+ chcusr_put(&cuser, user1);
+ passwd_update(usernum, &cuser);
+ } else if (info->mode == CHESS_MODE_REPLAY) {
+ free(info->board);
+ free(info->tag);
+ }
+}
+
+int
+chc_main(void)
+{
+ return ChessStartGame('c', SIG_CHC, "楚河漢界之爭");
+}
+
+int
+chc_personal(void)
+{
+ chc(0, CHESS_MODE_PERSONAL);
+ return 0;
+}
+
+int
+chc_watch(void)
+{
+ return ChessWatchGame(&chc, CHC, "楚河漢界之爭");
+}
+
+ChessInfo*
+chc_replay(FILE* fp)
+{
+ ChessInfo *info;
+ char buf[256];
+
+ info = NewChessInfo(&chc_actions, &chc_constants,
+ 0, CHESS_MODE_REPLAY);
+
+ while (fgets(buf, sizeof(buf), fp)) {
+ if (strcmp("</chclog>\n", buf) == 0)
+ break;
+ if (buf[0] == '[') {
+ if (strncmp(buf + 1, "Red", 3) == 0 ||
+ strncmp(buf + 1, "Black", 5) == 0) {
+ /* /\[(Red|Black) "([a-zA-Z0-9]+)"\]/; $2 */
+ userec_t rec;
+ char *userid;
+ char *strtok_pos;
+ ChessUser *user =
+ (buf[1] == 'R' ? &info->user1 : &info->user2);
+
+ strtok_r(buf, "\"", &strtok_pos);
+ userid = strtok_r(NULL, "\"", &strtok_pos);
+ if (userid != NULL && getuser(userid, &rec))
+ chc_init_user_userec(&rec, user);
+ }
+ } else {
+ /* " 1. Ch2-e2 Nb9-c7" */
+ drc_t step = { CHESS_STEP_NORMAL };
+ const char *p = strchr(buf, '.');
+
+ if (p == NULL) continue;
+
+ ++p; /* skip '.' */
+ while (*p && isspace(*p)) ++p;
+ if (!*p) continue;
+
+ /* p -> "Ch2-e2 ...." */
+ step.from.c = p[1] - 'a';
+ step.from.r = BRD_ROW - 1 - (p[2] - '0');
+ step.to.c = p[4] - 'a';
+ step.to.r = BRD_ROW - 1 - (p[5] - '0');
+
+#define INVALID_ROW(R) ((R) < 0 || (R) >= BRD_ROW)
+#define INVALID_COL(C) ((C) < 0 || (C) >= BRD_COL)
+#define INVALID_LOC(S) (INVALID_ROW(S.r) || INVALID_COL(S.c))
+ if (INVALID_LOC(step.from) || INVALID_LOC(step.to))
+ continue;
+ ChessHistoryAppend(info, &step);
+
+ p += 6;
+ while (*p && isspace(*p)) ++p;
+ if (!*p) continue;
+
+ /* p -> "Nb9-c7\n" */
+ step.from.c = p[1] - 'a';
+ step.from.r = BRD_ROW - 1 - (p[2] - '0');
+ step.to.c = p[4] - 'a';
+ step.to.r = BRD_ROW - 1 - (p[5] - '0');
+
+ if (INVALID_LOC(step.from) || INVALID_LOC(step.to))
+ continue;
+ ChessHistoryAppend(info, &step);
+
+#undef INVALID_ROW
+#undef INVALID_COL
+#undef INVALID_LOC
+ }
+ }
+
+ info->board = malloc(sizeof(board_t));
+ info->tag = malloc(sizeof(chc_tag_data_t));
+
+ chc_init_board(info->board);
+ ((chc_tag_data_t*) info->tag)->selected = 0;
+
+ return info;
+}
diff --git a/pttbbs/mbbsd/chc_tab.c b/pttbbs/mbbsd/chc_tab.c
new file mode 100644
index 00000000..4bed88ab
--- /dev/null
+++ b/pttbbs/mbbsd/chc_tab.c
@@ -0,0 +1,106 @@
+/* $Id$ */
+
+/* generated by perl -le 'print 1/(1+10**($_/400)),"," for 0 ..999' */
+/* TODO reduce table size */
+const double elo_exp_tab[1000]={
+0.5, 0.498560888290847, 0.497121800425189, 0.49568276024494, 0.494243791588855, 0.492804918290949, 0.491366164178917, 0.489927553072562, 0.488489108782208, 0.487050855107136,
+0.485612815834001, 0.484175014735265, 0.482737475567624, 0.481300222070441, 0.479863277964184, 0.478426666948855, 0.476990412702438, 0.475554538879339, 0.474119069108831, 0.472684026993507,
+0.471249436107731, 0.469815319996098, 0.468381702171893, 0.466948606115559, 0.465516055273168, 0.464084073054898, 0.462652682833507, 0.461221907942828, 0.459791771676254, 0.458362297285237,
+0.456933507977788, 0.455505426916992, 0.454078077219516, 0.452651481954135, 0.451225664140258, 0.449800646746463, 0.448376452689039, 0.446953104830538, 0.445530625978324, 0.444109038883147,
+0.442688366237707, 0.441268630675239, 0.439849854768097, 0.438432061026354, 0.437015271896404, 0.435599509759579, 0.434184796930767, 0.432771155657048, 0.431358608116332, 0.429947176416012,
+0.428536882591619, 0.427127748605496, 0.425719796345476, 0.42431304762357, 0.422907524174671, 0.421503247655257, 0.420100239642119, 0.418698521631085, 0.41729811503577, 0.415899041186322,
+0.414501321328191, 0.4131049766209, 0.411710028136837, 0.41031649686005, 0.408924403685057, 0.407533769415668, 0.406144614763822, 0.404756960348428, 0.403370826694226, 0.401986234230656,
+0.400603203290743, 0.399221754109989, 0.397841906825283, 0.396463681473822, 0.395087097992043, 0.393712176214572, 0.39233893587318, 0.39096739659576, 0.389597577905311, 0.388229499218936,
+0.386863179846857, 0.385498638991442, 0.384135895746244, 0.382774969095054, 0.381415877910969, 0.380058640955477, 0.378703276877545, 0.377349804212738, 0.375998241382333, 0.374648606692463,
+0.373300918333268, 0.371955194378058, 0.370611452782498, 0.369269711383798, 0.367929987899926, 0.366592299928832, 0.365256664947683, 0.363923100312118, 0.362591623255515, 0.361262250888275,
+0.359935000197115, 0.358609888044382, 0.35728693116738, 0.355966146177707, 0.354647549560618, 0.353331157674385, 0.352016986749694, 0.350705052889036, 0.349395372066127, 0.348087960125336,
+0.34678283278113, 0.345480005617534, 0.344179494087605, 0.342881313512921, 0.341585479083087, 0.340292005855252, 0.339000908753642, 0.337712202569111, 0.336425901958705, 0.335142021445235,
+0.333860575416878, 0.332581578126777, 0.331305043692668, 0.330030986096517, 0.328759419184168, 0.327490356665015, 0.326223812111678, 0.324959798959701, 0.323698330507263, 0.322439419914899,
+0.321183080205243, 0.319929324262779, 0.318678164833606, 0.317429614525228, 0.316183685806341, 0.31494039100665, 0.313699742316688, 0.312461751787658, 0.311226431331287, 0.309993792719686,
+0.308763847585237, 0.307536607420482, 0.306312083578035, 0.305090287270497, 0.3038712295704, 0.302654921410147, 0.301441373581977, 0.300230596737942, 0.299022601389892, 0.297817397909479,
+0.296614996528171, 0.295415407337279, 0.294218640287997, 0.293024705191459, 0.291833611718799, 0.290645369401237, 0.289459987630162, 0.288277475657245, 0.287097842594546, 0.28592109741465,
+0.284747248950801, 0.283576305897061, 0.282408276808469, 0.281243170101218, 0.280080994052848, 0.27892175680244, 0.277765466350829, 0.276612130560829, 0.275461757157464, 0.274314353728213,
+0.273169927723269, 0.272028486455804, 0.270890037102247, 0.269754586702576, 0.268622142160612, 0.267492710244332, 0.266366297586193, 0.265242910683453, 0.264122555898524, 0.263005239459311,
+0.261890967459581, 0.260779745859332, 0.25967158048517, 0.2585664770307, 0.25746444105693, 0.256365477992672, 0.255269593134965, 0.254176791649501, 0.253087078571057, 0.252000458803946,
+0.250916937122464, 0.249836518171358, 0.248759206466289, 0.247685006394318, 0.246613922214385, 0.245545958057814, 0.244481117928803, 0.243419405704947, 0.242360825137748, 0.241305379853144,
+0.240253073352042, 0.239203909010858, 0.238157890082064, 0.237115019694746, 0.236075300855161, 0.235038736447311, 0.234005329233511, 0.232975081854979, 0.231947996832416, 0.230924076566607,
+0.229903323339018, 0.228885739312403, 0.227871326531416, 0.226860086923233, 0.225852022298169, 0.224847134350316, 0.223845424658169, 0.222846894685274, 0.221851545780868, 0.22085937918053,
+0.219870396006841, 0.218884597270038, 0.217901983868681, 0.216922556590324, 0.215946316112185, 0.214973263001828, 0.214003397717843, 0.213036720610531, 0.212073231922598, 0.211112931789845,
+0.210155820241869, 0.209201897202762, 0.208251162491816, 0.207303615824231, 0.206359256811831, 0.205418084963771, 0.204480099687258, 0.203545300288274, 0.202613685972296, 0.201685255845022,
+0.200760008913102, 0.199837944084864, 0.198919060171054, 0.198003355885567, 0.197090829846184, 0.196181480575316, 0.195275306500741, 0.19437230595635, 0.193472477182892, 0.192575818328721,
+0.191682327450541, 0.190792002514162, 0.189904841395243, 0.189020841880052, 0.188140001666213, 0.187262318363465, 0.186387789494413, 0.185516412495288, 0.184648184716699, 0.183783103424397,
+0.182921165800026, 0.182062368941884, 0.181206709865685, 0.180354185505311, 0.179504792713577, 0.178658528262988, 0.177815388846498, 0.176975371078268, 0.176138471494428, 0.175304686553832,
+0.174474012638818, 0.173646446055968, 0.17282198303686, 0.17200061973883, 0.171182352245724, 0.170367176568657, 0.169555088646763, 0.168746084347953, 0.167940159469667, 0.167137309739621,
+0.166337530816562, 0.165540818291017, 0.164747167686039, 0.163956574457953, 0.163169033997105, 0.162384541628603, 0.161603092613057, 0.160824682147324, 0.160049305365247, 0.159276957338385,
+0.158507633076758, 0.157741327529574, 0.156978035585964, 0.156217752075707, 0.155460471769965, 0.154706189382, 0.153954899567905, 0.153206596927319, 0.15246127600415, 0.151718931287288,
+0.150979557211323, 0.150243148157254, 0.149509698453197, 0.148779202375097, 0.148051654147426, 0.147327047943889, 0.146605377888118, 0.145886638054376, 0.14517082246824, 0.144457925107303,
+0.14374793990185, 0.143040860735552, 0.142336681446143, 0.141635395826102, 0.140936997623326, 0.140241480541804, 0.139548838242288, 0.138859064342957, 0.138172152420082, 0.137488096008689,
+0.13680688860321, 0.136128523658142, 0.135452994588696, 0.134780294771442, 0.134110417544956, 0.13344335621046, 0.132779104032456, 0.132117654239364, 0.131459000024148, 0.130803134544946,
+0.130150050925691, 0.12949974225673, 0.128852201595444, 0.128207421966856, 0.12756539636424, 0.12692611774973, 0.126289579054919, 0.125655773181454, 0.125024693001636, 0.124396331359007,
+0.123770681068935, 0.123147734919203, 0.12252748567058, 0.121909926057404, 0.121295048788149, 0.120682846545992, 0.120073311989383, 0.119466437752599, 0.118862216446302, 0.118260640658093,
+0.117661702953059, 0.117065395874318, 0.116471711943561, 0.115880643661586, 0.115292183508836, 0.114706323945921, 0.11412305741415, 0.113542376336049, 0.112964273115878, 0.112388740140145,
+0.111815769778117, 0.111245354382322, 0.110677486289053, 0.110112157818867, 0.109549361277075, 0.108989088954235, 0.108431333126633, 0.107876086056773, 0.107323339993846, 0.106773087174209,
+0.106225319821854, 0.105680030148872, 0.10513721035592, 0.104596852632671, 0.104058949158278, 0.103523492101814, 0.102990473622727, 0.102459885871276, 0.101931720988974, 0.101405971109018,
+0.100882628356723, 0.100361684849949, 0.0998431326995192, 0.0993269640096439, 0.0988131708783323, 0.098301745397805, 0.0977926796549003, 0.0972859657314782, 0.0967815957048192, 0.0962795616480201,
+0.095779855630386, 0.0952824697178176, 0.0947873959731961, 0.0942946264567625, 0.0938041532264949, 0.0933159683384805, 0.0928300638472852, 0.092346431806318, 0.091865064268193, 0.0913859532850863,
+0.0909090909090909, 0.090434469192566, 0.0899620801884838, 0.0894919159507723, 0.0890239685346547, 0.0885582299969842, 0.0880946923965764, 0.087633347794537, 0.0871741882545869, 0.0867172058433825,
+0.0862623926308338, 0.0858097406904174, 0.0853592420994873, 0.0849108889395813, 0.0844646732967243, 0.0840205872617279, 0.0835786229304866, 0.0831387724042701, 0.0827010277900133, 0.0822653812006012,
+0.081831824755152, 0.0814003505792954, 0.0809709508054486, 0.0805436175730883, 0.0801183430290193, 0.0796951193276405, 0.0792739386312066, 0.0788547931100871, 0.0784376749430217, 0.0780225763173731,
+0.0776094894293755, 0.0771984064843805, 0.0767893196971002, 0.0763822212918461, 0.0759771035027654, 0.0755739585740745, 0.0751727787602884, 0.0747735563264481, 0.0743762835483439, 0.0739809527127362,
+0.0735875561175735, 0.0731960860722064, 0.0728065348975995, 0.0724188949265399, 0.0720331585038426, 0.0716493179865537, 0.0712673657441495, 0.0708872941587333, 0.0705090956252296, 0.070132762551575,
+0.0697582873589063, 0.0693856624817454, 0.0690148803681826, 0.0686459334800556, 0.0682788142931266, 0.0679135152972565, 0.0675500289965762, 0.0671883479096555, 0.0668284645696687, 0.0664703715245584,
+0.0661140613371956, 0.0657595265855382, 0.065406759862786, 0.0650557537775339, 0.0647065009539218, 0.0643589940317823, 0.064013225666786, 0.063669188530584, 0.0633268753109479, 0.0629862787119077,
+0.0626473914538869, 0.062310206273835, 0.061974715925358, 0.0616409131788465, 0.0613087908216012, 0.0609783416579556, 0.0606495585093978, 0.0603224342146881, 0.059996961629976, 0.0596731336289135,
+0.0593509431027676, 0.059030382960529, 0.05871144612902, 0.0583941255529991, 0.0580784141952641, 0.057764305036753, 0.0574517910766422, 0.0571408653324433, 0.0568315208400973, 0.0565237506540668,
+0.0562175478474267, 0.0559129055119519, 0.0556098167582034, 0.055308274715613, 0.0550082725325648, 0.0547098033764762, 0.0544128604338752, 0.0541174369104776, 0.0538235260312609, 0.0535311210405369,
+0.0532402152020224, 0.0529508017989083, 0.0526628741339259, 0.0523764255294125, 0.0520914493273749, 0.0518079388895503, 0.0515258875974668, 0.0512452888525009, 0.0509661360759343, 0.0506884227090081,
+0.0504121422129759, 0.0501372880691551, 0.0498638537789762, 0.0495918328640312, 0.0493212188661195, 0.0490520053472927, 0.048784185889898, 0.0485177540966194, 0.0482527035905178, 0.0479890280150698,
+0.0477267210342039, 0.0474657763323368, 0.0472061876144066, 0.0469479486059058, 0.0466910530529121, 0.0464354947221181, 0.0461812674008591, 0.0459283648971404, 0.0456767810396624, 0.0454265096778444,
+0.0451775446818476, 0.0449298799425962, 0.0446835093717973, 0.0444384269019598, 0.0441946264864115, 0.0439521020993153, 0.043710847735684, 0.0434708574113939, 0.0432321251631971, 0.0429946450487326,
+0.0427584111465361, 0.0425234175560489, 0.0422896583976254, 0.0420571278125395, 0.0418258199629894, 0.0415957290321026, 0.0413668492239378, 0.0411391747634878, 0.0409126998966793, 0.0406874188903736,
+0.0404633260323643, 0.0402404156313758, 0.0400186820170589, 0.0397981195399872, 0.0395787225716511, 0.0393604855044517, 0.039143402751693, 0.0389274687475736, 0.0387126779471775, 0.0384990248264637,
+0.0382865038822547, 0.0380751096322249, 0.0378648366148868, 0.0376556793895778, 0.0374476325364447, 0.0372406906564285, 0.0370348483712476, 0.0368301003233803, 0.0366264411760469, 0.0364238656131903,
+0.0362223683394563, 0.0360219440801729, 0.035822587581329, 0.0356242936095519, 0.0354270569520846, 0.035230872416762, 0.0350357348319863, 0.0348416390467019, 0.0346485799303695, 0.0344565523729396,
+0.034265551284825, 0.0340755715968728, 0.0338866082603359, 0.0336986562468435, 0.0335117105483713, 0.0333257661772106, 0.0331408181659374, 0.0329568615673801, 0.0327738914545875, 0.0325919029207952,
+0.0324108910793922, 0.0322308510638868, 0.0320517780278711, 0.0318736671449865, 0.0316965136088869, 0.0315203126332028, 0.0313450594515039, 0.0311707493172619, 0.030997377503812, 0.0308249393043144,
+0.0306534300317155, 0.0304828450187082, 0.0303131796176917, 0.0301444292007309, 0.029976589159516, 0.0298096549053202, 0.0296436218689585, 0.0294784855007451, 0.0293142412704504, 0.0291508846672584,
+0.0289884111997226, 0.0288268163957223, 0.0286660958024179, 0.0285062449862065, 0.0283472595326764, 0.0281891350465617, 0.0280318671516965, 0.0278754514909685, 0.0277198837262722, 0.0275651595384624,
+0.0274112746273065, 0.027258224711437, 0.0271060055283037, 0.0269546128341251, 0.0268040424038402, 0.0266542900310593, 0.0265053515280152, 0.0263572227255133, 0.0262098994728823, 0.0260633776379237,
+0.0259176531068621, 0.0257727217842942, 0.0256285795931381, 0.0254852224745825, 0.0253426463880351, 0.0252008473110712, 0.0250598212393821, 0.0249195641867229, 0.0247800721848603, 0.0246413412835205,
+0.024503367550336, 0.0243661470707936, 0.0242296759481806, 0.0240939503035322, 0.0239589662755777, 0.0238247200206873, 0.023691207712818, 0.0235584255434599, 0.0234263697215824, 0.0232950364735793,
+0.0231644220432153, 0.0230345226915706, 0.022905334696987, 0.0227768543550127, 0.0226490779783474, 0.0225220018967874, 0.0223956224571704, 0.0222699360233201, 0.0221449389759909, 0.0220206277128122,
+0.0218969986482334, 0.0217740482134672, 0.0216517728564348, 0.0215301690417093, 0.0214092332504601, 0.0212889619803967, 0.0211693517457127, 0.0210503990770294, 0.0209321005213396, 0.0208144526419513,
+0.0206974520184315, 0.0205810952465492, 0.0204653789382196, 0.0203502997214468, 0.020235854240268, 0.0201220391546963, 0.0200088511406639, 0.0198962868899661, 0.0197843431102039, 0.0196730165247275,
+0.0195623038725795, 0.0194522019084381, 0.0193427074025603, 0.0192338171407248, 0.0191255279241757, 0.0190178365695651, 0.0189107399088966, 0.0188042347894684, 0.0186983180738161, 0.0185929866396565,
+0.0184882373798301, 0.018384067202245, 0.0182804730298193, 0.018177451800425, 0.0180750004668308, 0.0179731159966456, 0.0178717953722618, 0.0177710355907984, 0.0176708336640446, 0.0175711866184031,
+0.0174720914948334, 0.0173735453487957, 0.0172755452501937, 0.0171780882833188, 0.0170811715467935, 0.0169847921535149, 0.0168889472305988, 0.0167936339193229, 0.0166988493750711, 0.0166045907672773,
+0.0165108552793692, 0.0164176401087122, 0.016324942466554, 0.0162327595779682, 0.0161410886817987, 0.0160499270306043, 0.0159592718906025, 0.0158691205416144, 0.015779470277009, 0.0156903184036477,
+0.0156016622418296, 0.0155134991252354, 0.0154258264008728, 0.0153386414290214, 0.0152519415831777, 0.0151657242500001, 0.0150799868292543, 0.0149947267337584, 0.0149099413893287, 0.0148256282347247,
+0.0147417847215951, 0.0146584083144236, 0.0145754964904743, 0.0144930467397378, 0.0144110565648775, 0.0143295234811753, 0.0142484450164782, 0.0141678187111446, 0.0140876421179904, 0.0140079128022362,
+0.0139286283414535, 0.0138497863255119, 0.0137713843565254, 0.0136934200488004, 0.0136158910287821, 0.0135387949350018, 0.0134621294180251, 0.0133858921403984, 0.0133100807765973, 0.013234693012974,
+0.0131597265477055, 0.0130851790907414, 0.0130110483637521, 0.0129373321000772, 0.012864028044674, 0.0127911339540658, 0.0127186475962907, 0.0126465667508506, 0.0125748892086599, 0.0125036127719948,
+0.0124327352544424, 0.0123622544808501, 0.0122921682872752, 0.0122224745209343, 0.0121531710401534, 0.0120842557143176, 0.0120157264238212, 0.011947581060018, 0.0118798175251715, 0.0118124337324056,
+0.011745427605655, 0.011678797079616, 0.0116125400996976, 0.0115466546219725, 0.0114811386131282, 0.0114159900504184, 0.0113512069216147, 0.0112867872249579, 0.0112227289691102, 0.0111590301731066,
+0.0110956888663077, 0.0110327030883515, 0.0109700708891056, 0.0109077903286204, 0.0108458594770813, 0.0107842764147616, 0.0107230392319756, 0.0106621460290318, 0.010601594916186, 0.0105413840135952,
+0.0104815114512704, 0.0104219753690314, 0.0103627739164598, 0.0103039052528536, 0.0102453675471813, 0.0101871589780362, 0.010129277733591, 0.0100717220115526, 0.0100144900191167, 0.00995757997292287,
+0.0099009900990099, 0.00984471863277092, 0.00978876381890891, 0.00973312391139234, 0.00967779717341093, 0.00962278187733161, 0.0095680763046546, 0.00951367874596964, 0.00945958750091243, 0.00940580087812116,
+0.00935231719519326, 0.00929913477864222, 0.00924625196385471, 0.0091936670950477, 0.00914137852522583, 0.00908938461613893, 0.00903768373823964, 0.00898627427064129, 0.0089351546010758, 0.0088843231258519,
+0.00883377824981334, 0.0087835183862974, 0.00873354195709348, 0.00868384739240183, 0.0086344331307925, 0.00858529761916441, 0.00853643931270459, 0.00848785667484757, 0.00843954817723491, 0.00839151229967492,
+0.00834374753010252, 0.00829625236453928, 0.00824902530705355, 0.00820206486972079, 0.00815536957258412, 0.0081089379436149, 0.00806276851867354, 0.0080168598414705, 0.00797121046352733, 0.00792581894413801,
+0.00788068385033028, 0.00783580375682733, 0.00779117724600942, 0.00774680290787585, 0.00770267934000696, 0.00765880514752633, 0.00761517894306313, 0.00757179934671464, 0.0075286649860089, 0.00748577449586748,
+0.00744312651856853, 0.00740071970370979, 0.00735855270817197, 0.0073166241960821, 0.00727493283877711, 0.00723347731476759, 0.00719225630970166, 0.00715126851632898, 0.00711051263446494, 0.00706998737095503,
+0.00702969143963924, 0.0069896235613168, 0.00694978246371089, 0.00691016688143358, 0.00687077555595097, 0.00683160723554835, 0.00679266067529564, 0.00675393463701289, 0.00671542788923597, 0.0066771392071824,
+0.00663906737271731, 0.00660121117431959, 0.00656356940704816, 0.00652614087250834, 0.00648892437881847, 0.00645191874057659, 0.00641512277882729, 0.00637853532102875, 0.00634215520101984, 0.00630598125898744,
+0.00627001234143384, 0.00623424730114438, 0.00619868499715511, 0.0061633242947207, 0.00612816406528242, 0.0060932031864363, 0.00605844054190145, 0.00602387502148844, 0.00598950552106793, 0.00595533094253937,
+0.00592135019379986, 0.00588756218871312, 0.00585396584707868, 0.00582056009460114, 0.00578734386285955, 0.00575431608927702, 0.00572147571709038, 0.00568882169532006, 0.00565635297873998, 0.00562406852784773,
+0.00559196730883478, 0.00556004829355687, 0.00552831045950453, 0.00549675278977371, 0.00546537427303661, 0.00543417390351256, 0.00540315068093909, 0.00537230361054314, 0.00534163170301236, 0.00531113397446655,
+0.00528080944642931, 0.0052506571457997, 0.00522067610482413, 0.00519086536106833, 0.00516122395738946, 0.00513175094190839, 0.00510244536798201, 0.00507330629417583, 0.00504433278423651, 0.00501552390706472,
+0.00498687873668797, 0.00495839635223365, 0.00493007583790219, 0.00490191628294032, 0.00487391678161447, 0.00484607643318431, 0.00481839434187639, 0.00479086961685795, 0.00476350137221078, 0.00473628872690529,
+0.00470923080477461, 0.00468232673448895, 0.00465557564952992, 0.00462897668816509, 0.00460252899342262, 0.00457623171306605, 0.00455008399956917, 0.00452408501009101, 0.00449823390645103, 0.00447252985510432,
+0.00444697202711696, 0.00442155959814155, 0.00439629174839279, 0.00437116766262323, 0.00434618653009909, 0.00432134754457622, 0.0042966499042762, 0.00427209281186252, 0.0042476754744169, 0.00422339710341572,
+0.00419925691470652, 0.00417525412848474, 0.0041513879692704, 0.00412765766588505, 0.00410406245142876, 0.00408060156325719, 0.00405727424295889, 0.00403407973633253, 0.00401101729336447, 0.00398808616820623,
+0.0039652856191522, 0.00394261490861742, 0.00392007330311544, 0.00389766007323639, 0.00387537449362501, 0.00385321584295892, 0.00383118340392695, 0.00380927646320752, 0.00378749431144725, 0.00376583624323957,
+0.00374430155710349, 0.00372288955546247, 0.00370159954462335, 0.00368043083475547, 0.00365938273986982, 0.00363845457779834, 0.00361764567017327, 0.0035969553424067, 0.0035763829236701, 0.00355592774687406,
+0.00353558914864806, 0.00351536646932039, 0.00349525905289814, 0.0034752662470473, 0.00345538740307297, 0.00343562187589965, 0.00341596902405165, 0.00339642820963361, 0.00337699879831106, 0.00335768015929115,
+0.00333847166530343, 0.00331937269258077, 0.00330038262084031, 0.00328150083326457, 0.00326272671648265, 0.00324405966055149, 0.00322549905893724, 0.00320704430849675, 0.00318869480945912, 0.00317044996540738,
+};
diff --git a/pttbbs/mbbsd/chess.c b/pttbbs/mbbsd/chess.c
new file mode 100644
index 00000000..d1ad3ad0
--- /dev/null
+++ b/pttbbs/mbbsd/chess.c
@@ -0,0 +1,1758 @@
+/* $Id$ */
+#include "bbs.h"
+#include "chess.h"
+#include <setjmp.h>
+
+#define assert_not_reached() assert(!"Should never be here!!!")
+#define dim(x) (sizeof(x) / sizeof(x[0]))
+
+#define CHESS_HISTORY_INITIAL_BUFFER_SIZE 300
+#define CHESS_HISTORY_BUFFER_INCREMENT 50
+
+#define CHESS_DRAWING_SIDE_ROW 7
+#define CHESS_DRAWING_REAL_TURN_ROW 8
+#define CHESS_DRAWING_REAL_STEP_ROW 9
+#define CHESS_DRAWING_REAL_TIME_ROW1 10
+#define CHESS_DRAWING_REAL_TIME_ROW2 11
+#define CHESS_DRAWING_REAL_WARN_ROW 13
+#define CHESS_DRAWING_MYWIN_ROW 17
+#define CHESS_DRAWING_HISWIN_ROW 18
+#define CHESS_DRAWING_PHOTOED_STEP_ROW 18
+#define CHESS_DRAWING_PHOTOED_TURN_ROW 19
+#define CHESS_DRAWING_PHOTOED_TIME_ROW1 20
+#define CHESS_DRAWING_PHOTOED_TIME_ROW2 21
+#define CHESS_DRAWING_PHOTOED_WARN_ROW 22
+
+#define CONNECT_PEER() add_io(info->sock, 0)
+#define IGNORE_PEER() add_io(0, 0)
+
+#define DO_WITHOUT_PEER(TIMEOUT,ACT,ELSE) \
+ do { \
+ void (*orig_alarm_handler)(int) = \
+ Signal(SIGALRM, &SigjmpEnv); \
+ IGNORE_PEER(); \
+ if(sigsetjmp(sigjmpEnv, 1)) \
+ ELSE; \
+ else { \
+ alarm(TIMEOUT); \
+ ACT; \
+ } \
+ CONNECT_PEER(); \
+ Signal(SIGALRM, orig_alarm_handler); \
+ } while(0)
+
+static const char * const ChessHintStr[] = {
+ " q 認輸離開",
+ " p 要求和棋",
+ "方向鍵 移動遊標",
+ "Enter 選擇/移動"
+};
+
+static const struct {
+ const char* name;
+ int name_len;
+ ChessInfo* (*func)(FILE* fp);
+} ChessReplayMap[] = {
+ { "gomoku", 6, &gomoku_replay },
+ { "chc", 3, &chc_replay },
+ { "go", 2, &gochess_replay },
+ { "reversi",7, &reversi_replay },
+ { NULL }
+};
+
+static ChessInfo * CurrentPlayingGameInfo;
+static sigjmp_buf sigjmpEnv;
+
+/* XXX: This is a BAD way to pass information.
+ * Fix this by handling chess request ourselves.
+ */
+static ChessTimeLimit * _current_time_limit;
+
+static void SigjmpEnv(int sig) { siglongjmp(sigjmpEnv, 1); }
+
+#define CHESS_HISTORY_ENTRY(INFO,N) \
+ ((INFO)->history.body + (N) * (INFO)->constants->step_entry_size)
+static void
+ChessHistoryInit(ChessHistory* history, int entry_size)
+{
+ history->size = CHESS_HISTORY_INITIAL_BUFFER_SIZE;
+ history->used = 0;
+ history->body =
+ calloc(CHESS_HISTORY_INITIAL_BUFFER_SIZE,
+ entry_size);
+}
+
+const void*
+ChessHistoryRetrieve(ChessInfo* info, int n)
+{
+ assert(n >= 0 && n < info->history.used);
+ return CHESS_HISTORY_ENTRY(info, n);
+}
+
+void
+ChessHistoryAppend(ChessInfo* info, void* step)
+{
+ if (info->history.used == info->history.size)
+ info->history.body = realloc(info->history.body,
+ (info->history.size += CHESS_HISTORY_BUFFER_INCREMENT)
+ * info->constants->step_entry_size);
+
+ memmove(CHESS_HISTORY_ENTRY(info, info->history.used),
+ step, info->constants->step_entry_size);
+ info->history.used++;
+}
+
+static void
+ChessBroadcastListInit(ChessBroadcastList* list)
+{
+ list->head.next = NULL;
+}
+
+static void
+ChessBroadcastListClear(ChessBroadcastList* list)
+{
+ ChessBroadcastListNode* p = list->head.next;
+ while (p) {
+ ChessBroadcastListNode* t = p->next;
+ close(p->sock);
+ free(p);
+ p = t;
+ }
+}
+
+static ChessBroadcastListNode*
+ChessBroadcastListInsert(ChessBroadcastList* list)
+{
+ ChessBroadcastListNode* p =
+ (ChessBroadcastListNode*) malloc(sizeof(ChessBroadcastListNode));
+
+ p->next = list->head.next;
+ list->head.next = p;
+ return p;
+}
+
+static void
+ChessDrawHelpLine(const ChessInfo* info)
+{
+ const static char* const HelpStr[] =
+ {
+ /* CHESS_MODE_VERSUS, 對奕 */
+ ANSI_COLOR(1;33;42) " 下棋 "
+ ANSI_COLOR(;31;47) " (←↑↓→)" ANSI_COLOR(30) " 移動 "
+ ANSI_COLOR(31) "(空白鍵/ENTER)" ANSI_COLOR(30) " 下子 "
+ ANSI_COLOR(31) "(q)" ANSI_COLOR(30) "認輸 "
+ ANSI_COLOR(31) "(p)" ANSI_COLOR(30) "虛手/和棋 "
+ ANSI_COLOR(31) "(u)" ANSI_COLOR(30) "悔棋 "
+ ANSI_RESET,
+
+ /* CHESS_MODE_WATCH, 觀棋 */
+ ANSI_COLOR(1;33;42) " 觀棋 "
+ ANSI_COLOR(;31;47) " (←→)" ANSI_COLOR(30) " 前後一步 "
+ ANSI_COLOR(31) "(↑↓)" ANSI_COLOR(30) " 前後十步 "
+ ANSI_COLOR(31) "(PGUP/PGDN)" ANSI_COLOR(30) " 最初/目前盤面 "
+ ANSI_COLOR(31) "(q)" ANSI_COLOR(30) "離開 "
+ ANSI_RESET,
+
+ /* CHESS_MODE_PERSONAL, 打譜 */
+ ANSI_COLOR(1;33;42) " 打譜 "
+ ANSI_COLOR(;31;47) " (←↑↓→)" ANSI_COLOR(30) " 移動 "
+ ANSI_COLOR(31) "(空白鍵/ENTER)" ANSI_COLOR(30) " 下子 "
+ ANSI_COLOR(31) "(q)" ANSI_COLOR(30) "離開 "
+ ANSI_COLOR(31) "(u)" ANSI_COLOR(30) "悔棋 "
+ ANSI_RESET,
+
+ /* CHESS_MODE_REPLAY, 看譜 */
+ ANSI_COLOR(1;33;42) " 看譜 "
+ ANSI_COLOR(;31;47) " (←→)" ANSI_COLOR(30) " 前後一步 "
+ ANSI_COLOR(31) "(↑↓)" ANSI_COLOR(30) " 前後十步 "
+ ANSI_COLOR(31) "(PGUP/PGDN)" ANSI_COLOR(30) " 最初/目前盤面 "
+ ANSI_COLOR(31) "(q)" ANSI_COLOR(30) "離開 "
+ ANSI_RESET,
+ };
+
+ mouts(b_lines, 0, HelpStr[info->mode]);
+ info->actions->drawline(info, b_lines);
+}
+
+void
+ChessDrawLine(const ChessInfo* info, int line)
+{
+#define DRAWLINE(LINE) \
+ do { \
+ move((LINE), 0); \
+ clrtoeol(); \
+ info->actions->drawline(info, (LINE)); \
+ } while (0)
+
+ if (line == b_lines) {
+ ChessDrawHelpLine(info);
+ return;
+ } else if (line == CHESS_DRAWING_TURN_ROW)
+ line = info->photo ?
+ CHESS_DRAWING_PHOTOED_TURN_ROW :
+ CHESS_DRAWING_REAL_TURN_ROW;
+ else if (line == CHESS_DRAWING_TIME_ROW) {
+ if(info->photo) {
+ DRAWLINE(CHESS_DRAWING_PHOTOED_TIME_ROW1);
+ DRAWLINE(CHESS_DRAWING_PHOTOED_TIME_ROW2);
+ } else {
+ DRAWLINE(CHESS_DRAWING_REAL_TIME_ROW1);
+ DRAWLINE(CHESS_DRAWING_REAL_TIME_ROW2);
+ }
+ return;
+ } else if (line == CHESS_DRAWING_WARN_ROW)
+ line = info->photo ?
+ CHESS_DRAWING_PHOTOED_WARN_ROW :
+ CHESS_DRAWING_REAL_WARN_ROW;
+ else if (line == CHESS_DRAWING_STEP_ROW)
+ line = info->photo ?
+ CHESS_DRAWING_PHOTOED_STEP_ROW :
+ CHESS_DRAWING_REAL_STEP_ROW;
+
+ DRAWLINE(line);
+
+#undef DRAWLINE
+}
+
+void
+ChessRedraw(const ChessInfo* info)
+{
+ int i;
+ clear();
+ for (i = 0; i <= b_lines; ++i)
+ ChessDrawLine(info, i);
+}
+
+inline static int
+ChessTimeCountDownCalc(ChessInfo* info, int who, int length)
+{
+ info->lefttime[who] -= length;
+
+ if (!info->timelimit) /* traditional mode, only left time is considered */
+ return info->lefttime[who] < 0;
+
+ if (info->lefttime[who] < 0) { /* only allowed when in free time */
+ if (info->lefthand[who])
+ return 1;
+ info->lefttime[who] += info->timelimit->limit_time;
+ info->lefthand[who] = info->timelimit->limit_hand;
+
+ return (info->lefttime[who] < 0);
+ }
+
+ return 0;
+}
+
+int
+ChessTimeCountDown(ChessInfo* info, int who, int length)
+{
+ int result = ChessTimeCountDownCalc(info, who, length);
+ ChessDrawLine(info, CHESS_DRAWING_TIME_ROW);
+ return result;
+}
+
+void
+ChessStepMade(ChessInfo* info, int who)
+{
+ if (!info->timelimit)
+ info->lefttime[who] = info->constants->traditional_timeout;
+ else if (
+ (info->lefthand[who] && (--(info->lefthand[who]) == 0) &&
+ info->timelimit->time_mode == CHESS_TIMEMODE_COUNTING)
+ ||
+ (info->lefthand[who] == 0 && info->lefttime[who] <= 0)
+ ) {
+ info->lefthand[who] = info->timelimit->limit_hand;
+ info->lefttime[who] = info->timelimit->limit_time;
+ }
+}
+
+/*
+ * Start of the network communication function.
+ */
+inline static ChessStepType
+ChessRecvMove(ChessInfo* info, int sock, void *step)
+{
+ if (read(sock, step, info->constants->step_entry_size)
+ != info->constants->step_entry_size)
+ return CHESS_STEP_FAILURE;
+ return *(ChessStepType*) step;
+}
+
+inline static int
+ChessSendMove(ChessInfo* info, int sock, const void *step)
+{
+ if (write(sock, step, info->constants->step_entry_size)
+ != info->constants->step_entry_size)
+ return 0;
+ return 1;
+}
+
+inline static int
+ChessStepSendOpposite(ChessInfo* info, const void* step)
+{
+ void (*orig_handler)(int);
+ int result = 1;
+
+ /* fd 0 is the socket to user, it means no oppisite available.
+ * (Might be personal play) */
+ if (info->sock == 0)
+ return 1;
+
+ orig_handler = Signal(SIGPIPE, SIG_IGN);
+
+ if (!ChessSendMove(info, info->sock, step))
+ result = 0;
+
+ Signal(SIGPIPE, orig_handler);
+ return result;
+}
+
+inline static void
+ChessStepBroadcast(ChessInfo* info, const void *step)
+{
+ ChessBroadcastListNode *p = &(info->broadcast_list.head);
+ void (*orig_handler)(int);
+
+ orig_handler = Signal(SIGPIPE, SIG_IGN);
+
+ while(p->next){
+ if (!ChessSendMove(info, p->next->sock, step)) {
+ /* remove viewer */
+ ChessBroadcastListNode *tmp = p->next->next;
+ free(p->next);
+ p->next = tmp;
+ } else
+ p = p->next;
+ }
+
+ Signal(SIGPIPE, orig_handler);
+}
+
+int
+ChessStepSend(ChessInfo* info, const void* step)
+{
+ /* send to opposite... */
+ if (!ChessStepSendOpposite(info, step))
+ return 0;
+
+ /* and watchers */
+ ChessStepBroadcast(info, step);
+
+ return 1;
+}
+
+int
+ChessMessageSend(ChessInfo* info, ChessStepType type)
+{
+ return ChessStepSend(info, &type);
+}
+
+static inline int
+ChessCheckAlive(ChessInfo* info)
+{
+ ChessStepType type = CHESS_STEP_NOP;
+ return ChessStepSendOpposite(info, &type);
+}
+
+ChessStepType
+ChessStepReceive(ChessInfo* info, void* step)
+{
+ ChessStepType result = ChessRecvMove(info, info->sock, step);
+
+ /* automatical routing */
+ if (result != CHESS_STEP_FAILURE)
+ ChessStepBroadcast(info, step);
+
+ /* and logging */
+ if (result == CHESS_STEP_NORMAL || result == CHESS_STEP_PASS)
+ ChessHistoryAppend(info, step);
+
+ return result;
+}
+
+inline static void
+ChessReplayUntil(ChessInfo* info, int n)
+{
+ const void* step;
+
+ if (n <= info->current_step)
+ return;
+
+ while (info->current_step < n - 1) {
+ info->actions->apply_step(info->board,
+ ChessHistoryRetrieve(info, info->current_step));
+ info->current_step++;
+ }
+
+ /* spcial for last one to maintian information correct */
+ step = ChessHistoryRetrieve(info, info->current_step);
+
+ if (info->mode == CHESS_MODE_WATCH || info->mode == CHESS_MODE_REPLAY)
+ info->turn = info->current_step & 1;
+ info->actions->prepare_step(info, step);
+ info->actions->apply_step(info->board, step);
+ info->current_step++;
+}
+
+static int
+ChessAnswerRequest(ChessInfo* info, const char* req_name)
+{
+ char buf[4];
+ char msg[64];
+
+ snprintf(info->warnmsg, sizeof(info->warnmsg),
+ ANSI_COLOR(1;31) "要求%s!" ANSI_RESET, req_name);
+ ChessDrawLine(info, CHESS_DRAWING_WARN_ROW);
+ bell();
+
+ snprintf(msg, sizeof(msg),
+ "對方要求%s,是否接受?(y/N)", req_name);
+ DO_WITHOUT_PEER(30,
+ getdata(b_lines, 0, msg, buf, sizeof(buf), DOECHO),
+ buf[0] = 'n');
+ ChessDrawHelpLine(info);
+
+ info->warnmsg[0] = 0;
+ ChessDrawLine(info, CHESS_DRAWING_WARN_ROW);
+
+ if (buf[0] == 'y' || buf[0] == 'Y')
+ return 1;
+ else
+ return 0;
+}
+
+ChessGameResult
+ChessPlayFuncMy(ChessInfo* info)
+{
+ int last_time = now;
+ int endturn = 0;
+ ChessGameResult game_result = CHESS_RESULT_CONTINUE;
+ int ch;
+#ifdef DBCSAWARE
+ int move_count = 0;
+#endif
+
+ info->pass[(int) info->turn] = 0;
+ bell();
+
+ while (!endturn) {
+ ChessStepType result;
+
+ ChessDrawLine(info, CHESS_DRAWING_TIME_ROW);
+ info->actions->movecur(info->cursor.r, info->cursor.c);
+ oflush();
+
+ ch = igetch();
+ if (ChessTimeCountDown(info, 0, now - last_time)) {
+ /* ran out of time */
+ game_result = CHESS_RESULT_LOST;
+ endturn = 1;
+ break;
+ }
+ last_time = now;
+
+ switch (ch) {
+ case I_OTHERDATA:
+ result = ChessStepReceive(info, &info->step_tmp);
+
+ if (result == CHESS_STEP_FAILURE ||
+ result == CHESS_STEP_DROP) {
+ game_result = CHESS_RESULT_WIN;
+ endturn = 1;
+ } else if (result == CHESS_STEP_TIE_ACC) {
+ game_result = CHESS_RESULT_TIE;
+ endturn = 1;
+ } else if (result == CHESS_STEP_TIE_REJ) {
+ strcpy(info->warnmsg, ANSI_COLOR(1;31) "求和被拒!" ANSI_RESET);
+ ChessDrawLine(info, CHESS_DRAWING_WARN_ROW);
+ } else if (result == CHESS_STEP_UNDO) {
+ if (ChessAnswerRequest(info, "悔棋")) {
+ ChessMessageSend(info, CHESS_STEP_UNDO_ACC);
+
+ info->actions->init_board(info->board);
+ info->current_step = 0;
+ ChessReplayUntil(info, info->history.used - 1);
+ info->history.used--;
+
+ ChessRedraw(info);
+
+ endturn = 1;
+ } else
+ ChessMessageSend(info, CHESS_STEP_UNDO_REJ);
+ } else if (result == CHESS_STEP_NORMAL ||
+ result == CHESS_STEP_SPECIAL) {
+ info->actions->prepare_step(info, &info->step_tmp);
+ game_result =
+ info->actions->apply_step(info->board,
+ &info->step_tmp);
+ info->actions->drawstep(info, &info->step_tmp);
+ endturn = 1;
+ ChessStepMade(info, 0);
+ }
+ break;
+
+ case KEY_UP:
+ info->cursor.r--;
+ if (info->cursor.r < 0)
+ info->cursor.r = info->constants->board_height - 1;
+ break;
+
+ case KEY_DOWN:
+ info->cursor.r++;
+ if (info->cursor.r >= info->constants->board_height)
+ info->cursor.r = 0;
+ break;
+
+ case KEY_LEFT:
+#ifdef DBCSAWARE
+ if (!ISDBCSAWARE()) {
+ if (++move_count >= 2)
+ move_count = 0;
+ else
+ break;
+ }
+#endif /* defined(DBCSAWARE) */
+
+ info->cursor.c--;
+ if (info->cursor.c < 0)
+ info->cursor.c = info->constants->board_width - 1;
+ break;
+
+ case KEY_RIGHT:
+#ifdef DBCSAWARE
+ if (!ISDBCSAWARE()) {
+ if (++move_count >= 2)
+ move_count = 0;
+ else
+ break;
+ }
+#endif /* defined(DBCSAWARE) */
+
+ info->cursor.c++;
+ if (info->cursor.c >= info->constants->board_width)
+ info->cursor.c = 0;
+ break;
+
+ case 'q':
+ {
+ char buf[4];
+
+ DO_WITHOUT_PEER(30,
+ getdata(b_lines, 0,
+ info->mode == CHESS_MODE_PERSONAL ?
+ "是否真的要離開?(y/N)" :
+ "是否真的要認輸?(y/N)",
+ buf, sizeof(buf), DOECHO),
+ buf[0] = 'n');
+ ChessDrawHelpLine(info);
+
+ if (buf[0] == 'y' || buf[0] == 'Y') {
+ game_result = CHESS_RESULT_LOST;
+ endturn = 1;
+ }
+ }
+ break;
+
+ case 'p':
+ if (info->constants->pass_is_step) {
+ ChessStepType type = CHESS_STEP_PASS;
+ ChessHistoryAppend(info, &type);
+ strcpy(info->last_movestr, "虛手");
+
+ info->pass[(int) info->turn] = 1;
+ ChessMessageSend(info, CHESS_STEP_PASS);
+ endturn = 1;
+ } else if (info->mode != CHESS_MODE_PERSONAL) {
+ char buf[4];
+
+ DO_WITHOUT_PEER(30,
+ getdata(b_lines, 0, "是否真的要和棋?(y/N)",
+ buf, sizeof(buf), DOECHO),
+ buf[0] = 'n');
+ ChessDrawHelpLine(info);
+
+ if (buf[0] == 'y' || buf[1] == 'Y') {
+ ChessMessageSend(info, CHESS_STEP_TIE);
+ strlcpy(info->warnmsg,
+ ANSI_COLOR(1;33) "要求和棋!" ANSI_RESET,
+ sizeof(info->warnmsg));
+ ChessDrawLine(info, CHESS_DRAWING_WARN_ROW);
+ bell();
+ }
+ }
+ break;
+
+ case 'u':
+ if (info->mode == CHESS_MODE_PERSONAL && info->history.used > 0) {
+ ChessMessageSend(info, CHESS_STEP_UNDO_ACC);
+
+ info->actions->init_board(info->board);
+ info->current_step = 0;
+ ChessReplayUntil(info, info->history.used - 1);
+ info->history.used--;
+
+ ChessRedraw(info);
+
+ endturn = 1;
+ }
+ break;
+
+ case '\r':
+ case '\n':
+ case ' ':
+ endturn = info->actions->select(info, info->cursor, &game_result);
+ break;
+
+ case I_TIMEOUT:
+ break;
+
+ case KEY_UNKNOWN:
+ break;
+
+ default:
+ if (info->actions->process_key) {
+ DO_WITHOUT_PEER(30,
+ endturn =
+ info->actions->process_key(info, ch, &game_result),
+ );
+ }
+ }
+ }
+ ChessTimeCountDown(info, 0, now - last_time);
+ ChessStepMade(info, 0);
+ ChessDrawLine(info, CHESS_DRAWING_TIME_ROW);
+ ChessDrawLine(info, CHESS_DRAWING_STEP_ROW);
+ return game_result;
+}
+
+static ChessGameResult
+ChessPlayFuncHis(ChessInfo* info)
+{
+ int last_time = now;
+ int endturn = 0;
+ ChessGameResult game_result = CHESS_RESULT_CONTINUE;
+
+ while (!endturn) {
+ ChessStepType result;
+ int ch;
+
+ if (ChessTimeCountDown(info, 1, now - last_time)) {
+ info->lefttime[1] = 0;
+
+ /* to make him break out igetch() */
+ ChessMessageSend(info, CHESS_STEP_NOP);
+ }
+ last_time = now;
+
+ ChessDrawLine(info, CHESS_DRAWING_TIME_ROW);
+ move(1, 0);
+ oflush();
+
+ switch (ch = igetch()) {
+ case 'q':
+ {
+ char buf[4];
+ DO_WITHOUT_PEER(30,
+ getdata(b_lines, 0, "是否真的要認輸?(y/N)",
+ buf, sizeof(buf), DOECHO),
+ buf[0] = 'n');
+ ChessDrawHelpLine(info);
+
+ if (buf[0] == 'y' || buf[0] == 'Y') {
+ game_result = CHESS_RESULT_LOST;
+ endturn = 1;
+ }
+ }
+ break;
+
+ case 'u':
+ if (info->history.used > 0) {
+ strcpy(info->warnmsg, ANSI_COLOR(1;31) "要求悔棋!" ANSI_RESET);
+ ChessDrawLine(info, CHESS_DRAWING_WARN_ROW);
+
+ ChessMessageSend(info, CHESS_STEP_UNDO);
+ }
+ break;
+
+ case I_OTHERDATA:
+ result = ChessStepReceive(info, &info->step_tmp);
+
+ if (result == CHESS_STEP_FAILURE ||
+ result == CHESS_STEP_DROP) {
+ game_result = CHESS_RESULT_WIN;
+ endturn = 1;
+ } else if (result == CHESS_STEP_PASS) {
+ strcpy(info->last_movestr, "虛手");
+
+ info->pass[(int) info->turn] = 1;
+ endturn = 1;
+ } else if (result == CHESS_STEP_TIE) {
+ if (ChessAnswerRequest(info, "和棋")) {
+ ChessMessageSend(info, CHESS_STEP_TIE_ACC);
+
+ game_result = CHESS_RESULT_TIE;
+ endturn = 1;
+ } else
+ ChessMessageSend(info, CHESS_STEP_TIE_REJ);
+ } else if (result == CHESS_STEP_NORMAL ||
+ result == CHESS_STEP_SPECIAL) {
+ info->actions->prepare_step(info, &info->step_tmp);
+ switch (info->actions->apply_step(info->board, &info->step_tmp)) {
+ case CHESS_RESULT_LOST:
+ game_result = CHESS_RESULT_WIN;
+ break;
+
+ case CHESS_RESULT_WIN:
+ game_result = CHESS_RESULT_LOST;
+ break;
+
+ default:
+ game_result = CHESS_RESULT_CONTINUE;
+ }
+ endturn = 1;
+ info->pass[(int) info->turn] = 0;
+ ChessStepMade(info, 1);
+ info->actions->drawstep(info, &info->step_tmp);
+ } else if (result == CHESS_STEP_UNDO_ACC) {
+ strcpy(info->warnmsg, ANSI_COLOR(1;31) "接受悔棋!" ANSI_RESET);
+
+ info->actions->init_board(info->board);
+ info->current_step = 0;
+ ChessReplayUntil(info, info->history.used - 1);
+ info->history.used--;
+
+ ChessRedraw(info);
+ bell();
+
+ endturn = 1;
+ } else if (result == CHESS_STEP_UNDO_REJ) {
+ strcpy(info->warnmsg, ANSI_COLOR(1;31) "悔棋被拒!" ANSI_RESET);
+ ChessDrawLine(info, CHESS_DRAWING_WARN_ROW);
+ }
+
+ case I_TIMEOUT:
+ break;
+
+ case KEY_UNKNOWN:
+ break;
+
+ default:
+ if (info->actions->process_key) {
+ DO_WITHOUT_PEER(30,
+ endturn =
+ info->actions->process_key(info, ch, &game_result),
+ );
+ }
+ }
+ }
+ ChessTimeCountDown(info, 1, now - last_time);
+ ChessDrawLine(info, CHESS_DRAWING_TIME_ROW);
+ ChessDrawLine(info, CHESS_DRAWING_STEP_ROW);
+ return game_result;
+}
+
+static ChessGameResult
+ChessPlayFuncWatch(ChessInfo* info)
+{
+ int end_watch = 0;
+
+ while (!end_watch) {
+ ChessStepType result;
+
+ info->actions->prepare_play(info);
+ if (info->sock == -1)
+ strlcpy(info->warnmsg, ANSI_COLOR(1;33) "棋局已結束" ANSI_RESET,
+ sizeof(info->warnmsg));
+
+ ChessDrawLine(info, CHESS_DRAWING_WARN_ROW);
+ ChessDrawLine(info, CHESS_DRAWING_STEP_ROW);
+ move(1, 0);
+
+ switch (igetch()) {
+ case I_OTHERDATA: /* new step */
+ result = ChessStepReceive(info, &info->step_tmp);
+
+ if (result == CHESS_STEP_FAILURE) {
+ IGNORE_PEER();
+ info->sock = -1;
+ break;
+ } else if (result == CHESS_STEP_UNDO_ACC) {
+ if (info->current_step == info->history.used) {
+ /* at head but redo-ed */
+ info->actions->init_board(info->board);
+ info->current_step = 0;
+ ChessReplayUntil(info, info->history.used - 1);
+ ChessRedraw(info);
+ }
+ info->history.used--;
+ } else if (result == CHESS_STEP_NORMAL ||
+ result == CHESS_STEP_SPECIAL) {
+ if (info->current_step == info->history.used - 1) {
+ /* was watching up-to-date board */
+ info->turn = info->current_step++ & 1;
+ info->actions->prepare_step(info, &info->step_tmp);
+ info->actions->apply_step(info->board, &info->step_tmp);
+ info->actions->drawstep(info, &info->step_tmp);
+ }
+ } else if (result == CHESS_STEP_PASS)
+ strcpy(info->last_movestr, "虛手");
+
+ break;
+
+ case KEY_LEFT: /* 往前一步 */
+ if (info->current_step == 0)
+ bell();
+ else {
+ /* TODO: implement without re-apply all steps */
+ int current = info->current_step;
+
+ info->actions->init_board(info->board);
+ info->current_step = 0;
+
+ ChessReplayUntil(info, current - 1);
+ ChessRedraw(info);
+ }
+ break;
+
+ case KEY_RIGHT: /* 往後一步 */
+ if (info->current_step == info->history.used)
+ bell();
+ else {
+ const void* step =
+ ChessHistoryRetrieve(info, info->current_step);
+ info->turn = info->current_step++ & 1;
+ info->actions->prepare_step(info, step);
+ info->actions->apply_step(info->board, step);
+ info->actions->drawstep(info, step);
+ }
+ break;
+
+ case KEY_UP: /* 往前十步 */
+ if (info->current_step == 0)
+ bell();
+ else {
+ /* TODO: implement without re-apply all steps */
+ int current = info->current_step;
+
+ info->actions->init_board(info->board);
+ info->current_step = 0;
+
+ ChessReplayUntil(info, current - 10);
+
+ ChessRedraw(info);
+ }
+ break;
+
+ case KEY_DOWN: /* 往後十步 */
+ if (info->current_step == info->history.used)
+ bell();
+ else {
+ ChessReplayUntil(info,
+ MIN(info->current_step + 10, info->history.used));
+ ChessRedraw(info);
+ }
+ break;
+
+ case KEY_PGUP: /* 起始盤面 */
+ if (info->current_step == 0)
+ bell();
+ else {
+ info->actions->init_board(info->board);
+ info->current_step = 0;
+ ChessRedraw(info);
+ }
+ break;
+
+ case KEY_PGDN: /* 最新盤面 */
+ if (info->current_step == info->history.used)
+ bell();
+ else {
+ ChessReplayUntil(info, info->history.used);
+ ChessRedraw(info);
+ }
+ break;
+
+ case 'q':
+ end_watch = 1;
+ }
+ }
+
+ return CHESS_RESULT_END;
+}
+
+static void
+ChessWatchRequest(int sig)
+{
+ int sock = establish_talk_connection(&SHM->uinfo[currutmp->destuip]);
+ ChessBroadcastListNode* node;
+
+ if (sock < 0)
+ return;
+
+ assert(CurrentPlayingGameInfo);
+ node = ChessBroadcastListInsert(&CurrentPlayingGameInfo->broadcast_list);
+ node->sock = sock;
+
+#define SEND(X) write(sock, &(X), sizeof(X))
+ SEND(CurrentPlayingGameInfo->myturn);
+ SEND(CurrentPlayingGameInfo->turn);
+
+ if (!CurrentPlayingGameInfo->timelimit)
+ write(sock, "T", 1);
+ else {
+ write(sock, "L", 1);
+ SEND(*(CurrentPlayingGameInfo->timelimit));
+ }
+
+ SEND(CurrentPlayingGameInfo->history.used);
+ write(sock, CurrentPlayingGameInfo->history.body,
+ CurrentPlayingGameInfo->constants->step_entry_size
+ * CurrentPlayingGameInfo->history.used);
+#undef SEND
+}
+
+static void
+ChessReceiveWatchInfo(ChessInfo* info)
+{
+ char time_mode;
+#define RECV(X) read(info->sock, &(X), sizeof(X))
+ RECV(info->myturn);
+ RECV(info->turn);
+
+ RECV(time_mode);
+ if (time_mode == 'L') {
+ info->timelimit = (ChessTimeLimit*) malloc(sizeof(ChessTimeLimit));
+ RECV(*(info->timelimit));
+ }
+
+ RECV(info->history.used);
+ for (info->history.size = CHESS_HISTORY_INITIAL_BUFFER_SIZE;
+ info->history.size < info->history.used;
+ info->history.size += CHESS_HISTORY_BUFFER_INCREMENT);
+ info->history.body =
+ calloc(info->history.size, info->constants->step_entry_size);
+ read(info->sock, info->history.body,
+ info->history.used * info->constants->step_entry_size);
+#undef RECV
+}
+
+static void
+ChessGenLogGlobal(ChessInfo* info, ChessGameResult result)
+{
+ fileheader_t log_header;
+ FILE *fp;
+ char fname[PATHLEN];
+ int bid;
+
+ if ((bid = getbnum(info->constants->log_board)) == 0)
+ return;
+
+ setbpath(fname, info->constants->log_board);
+ stampfile(fname, &log_header);
+
+ fp = fopen(fname, "w");
+ if (fp != NULL) {
+ strlcpy(log_header.owner, "[棋譜機器人]", sizeof(log_header.owner));
+ snprintf(log_header.title, sizeof(log_header.title), "[棋譜] %s VS %s",
+ info->user1.userid, info->user2.userid);
+
+ fprintf(fp, "作者: %s 看板: %s\n標題: %s \n", log_header.owner, info->constants->log_board, log_header.title);
+ fprintf(fp, "時間: %s\n", ctime4(&now));
+
+ info->actions->genlog(info, fp, result);
+ fclose(fp);
+
+ setbdir(fname, info->constants->log_board);
+ append_record(fname, &log_header, sizeof(log_header));
+
+ setbtotal(bid);
+ }
+}
+
+static void
+ChessGenLogUser(ChessInfo* info, ChessGameResult result)
+{
+ fileheader_t log_header;
+ FILE *fp;
+ char fname[PATHLEN];
+
+ sethomepath(fname, cuser.userid);
+ stampfile(fname, &log_header);
+
+ fp = fopen(fname, "w");
+ if (fp != NULL) {
+ info->actions->genlog(info, fp, result);
+ fclose(fp);
+
+ snprintf(log_header.owner, sizeof(log_header.owner), "[%s]",
+ info->constants->chess_name);
+ if(info->myturn == 0)
+ sprintf(log_header.title, "%s V.S. %s",
+ info->user1.userid, info->user2.userid);
+ else
+ sprintf(log_header.title, "%s V.S. %s",
+ info->user2.userid, info->user1.userid);
+ log_header.filemode = 0;
+
+ sethomedir(fname, cuser.userid);
+ append_record_forward(fname, &log_header, sizeof(log_header),
+ cuser.userid);
+ }
+}
+
+static void
+ChessGenLog(ChessInfo* info, ChessGameResult result)
+{
+ if (info->mode == CHESS_MODE_VERSUS && info->myturn == 0 &&
+ info->constants->log_board) {
+ ChessGenLogGlobal(info, result);
+ }
+
+ if (getans("是否將棋譜寄回信箱?[N/y]") == 'y')
+ ChessGenLogUser(info, result);
+}
+
+void
+ChessPlay(ChessInfo* info)
+{
+ ChessGameResult game_result;
+ void (*old_handler)(int);
+ const char* game_result_str = 0;
+ sigset_t old_sigset;
+
+ if (info == NULL)
+ return;
+
+ if (!ChessCheckAlive(info)) {
+ if (info->sock)
+ close(info->sock);
+ return;
+ }
+
+ /* XXX */
+ if (!info->timelimit) {
+ info->timelimit = _current_time_limit;
+ _current_time_limit = NULL;
+ }
+
+ CurrentPlayingGameInfo = info;
+
+ {
+ char buf[4] = "";
+ sigset_t sigset;
+
+ if(info->mode == CHESS_MODE_VERSUS)
+ getdata(b_lines, 0, "是否接受觀棋? (Y/n)", buf, sizeof(buf), DOECHO);
+ if(buf[0] == 'n' || buf[0] == 'N')
+ old_handler = Signal(SIGUSR1, SIG_IGN);
+ else
+ old_handler = Signal(SIGUSR1, &ChessWatchRequest);
+
+ sigemptyset(&sigset);
+ sigaddset(&sigset, SIGUSR1);
+ sigprocmask(SIG_UNBLOCK, &sigset, &old_sigset);
+ }
+
+ if (info->mode == CHESS_MODE_WATCH) {
+ int i;
+ for (i = 0; i < info->history.used; ++i)
+ info->actions->apply_step(info->board,
+ ChessHistoryRetrieve(info, i));
+ info->current_step = info->history.used;
+ }
+
+ /* playing initialization */
+ ChessRedraw(info);
+ info->turn = 1;
+ info->lefttime[0] = info->lefttime[1] = info->timelimit ?
+ info->timelimit->free_time : info->constants->traditional_timeout;
+ info->lefthand[0] = info->lefthand[1] = 0;
+
+ /* main loop */
+ CONNECT_PEER();
+ for (game_result = CHESS_RESULT_CONTINUE;
+ game_result == CHESS_RESULT_CONTINUE;
+ info->turn ^= 1) {
+ if (info->actions->prepare_play(info))
+ info->pass[(int) info->turn] = 1;
+ else {
+ ChessDrawLine(info, CHESS_DRAWING_TURN_ROW);
+ ChessDrawLine(info, CHESS_DRAWING_WARN_ROW);
+ game_result = info->play_func[(int) info->turn](info);
+ }
+
+ if (info->pass[0] && info->pass[1])
+ game_result = CHESS_RESULT_END;
+ }
+
+ if (game_result == CHESS_RESULT_END &&
+ info->actions->post_game &&
+ (info->mode == CHESS_MODE_VERSUS ||
+ info->mode == CHESS_MODE_PERSONAL))
+ game_result = info->actions->post_game(info);
+
+ IGNORE_PEER();
+
+ if (info->sock)
+ close(info->sock);
+
+ /* end processing */
+ if (info->mode == CHESS_MODE_VERSUS) {
+ switch (game_result) {
+ case CHESS_RESULT_WIN:
+ game_result_str = "對方認輸了!";
+ break;
+
+ case CHESS_RESULT_LOST:
+ game_result_str = "你認輸了!";
+ break;
+
+ case CHESS_RESULT_TIE:
+ game_result_str = "和棋";
+ break;
+
+ default:
+ assert_not_reached();
+ }
+ } else if (info->mode == CHESS_MODE_WATCH)
+ game_result_str = "結束觀棋";
+ else if (info->mode == CHESS_MODE_PERSONAL)
+ game_result_str = "結束打譜";
+ else if (info->mode == CHESS_MODE_REPLAY)
+ game_result_str = "結束看譜";
+
+ if (game_result_str) {
+ strlcpy(info->warnmsg, game_result_str, sizeof(info->warnmsg));
+ ChessDrawLine(info, CHESS_DRAWING_WARN_ROW);
+ }
+
+ info->actions->gameend(info, game_result);
+
+ if (info->mode != CHESS_MODE_REPLAY)
+ ChessGenLog(info, game_result);
+
+ // currutmp->sig = -1;
+ sigprocmask(SIG_SETMASK, &old_sigset, NULL);
+ Signal(SIGUSR1, old_handler);
+
+ CurrentPlayingGameInfo = NULL;
+}
+
+static userinfo_t*
+ChessSearchUser(int sig, const char* title)
+{
+ char uident[16];
+ userinfo_t *uin;
+
+ stand_title(title);
+ CompleteOnlineUser(msg_uid, uident);
+ if (uident[0] == '\0')
+ return NULL;
+
+ if ((uin = search_ulist_userid(uident)) == NULL)
+ return NULL;
+
+ if (sig >= 0)
+ uin->sig = sig;
+ return uin;
+}
+
+int
+ChessStartGame(char func_char, int sig, const char* title)
+{
+ userinfo_t *uin;
+ char buf[4];
+
+ if ((uin = ChessSearchUser(sig, title)) == NULL)
+ return -1;
+ uin->turn = 1;
+ currutmp->turn = 0;
+ strlcpy(uin->mateid, currutmp->userid, sizeof(uin->mateid));
+ strlcpy(currutmp->mateid, uin->userid, sizeof(currutmp->mateid));
+
+ stand_title(title);
+ buf[0] = 0;
+ getdata(2, 0, "使用傳統模式 (T), 限時限步模式 (L) 或是 讀秒模式 (C)? (T/l/c)",
+ buf, 3, DOECHO);
+
+ if (buf[0] == 'l' || buf[0] == 'L' ||
+ buf[0] == 'c' || buf[0] == 'C') {
+
+ _current_time_limit = (ChessTimeLimit*) malloc(sizeof(ChessTimeLimit));
+ if (buf[0] == 'l' || buf[0] == 'L')
+ _current_time_limit->time_mode = CHESS_TIMEMODE_MULTIHAND;
+ else
+ _current_time_limit->time_mode = CHESS_TIMEMODE_COUNTING;
+
+ do {
+ getdata_str(3, 0, "請設定局時 (自由時間) 以分鐘為單位:",
+ buf, 3, DOECHO, "30");
+ _current_time_limit->free_time = atoi(buf);
+ } while (_current_time_limit->free_time < 0 || _current_time_limit->free_time > 90);
+ _current_time_limit->free_time *= 60; /* minute -> second */
+
+ if (_current_time_limit->time_mode == CHESS_TIMEMODE_MULTIHAND) {
+ char display_buf[128];
+
+ do {
+ getdata_str(4, 0, "請設定步時, 以分鐘為單位:",
+ buf, 3, DOECHO, "5");
+ _current_time_limit->limit_time = atoi(buf);
+ } while (_current_time_limit->limit_time < 0 || _current_time_limit->limit_time > 30);
+ _current_time_limit->limit_time *= 60; /* minute -> second */
+
+ snprintf(display_buf, sizeof(display_buf),
+ "請設定限步 (每 %d 分鐘需走幾步):",
+ _current_time_limit->limit_time / 60);
+ do {
+ getdata_str(5, 0, display_buf, buf, 3, DOECHO, "10");
+ _current_time_limit->limit_hand = atoi(buf);
+ } while (_current_time_limit->limit_hand < 1);
+ } else {
+ _current_time_limit->limit_hand = 1;
+
+ do {
+ getdata_str(4, 0, "請設定讀秒, 以秒為單位",
+ buf, 3, DOECHO, "60");
+ _current_time_limit->limit_time = atoi(buf);
+ } while (_current_time_limit->limit_time < 0);
+ }
+ } else
+ _current_time_limit = NULL;
+
+ my_talk(uin, friend_stat(currutmp, uin), func_char);
+ return 0;
+}
+
+int
+ChessWatchGame(void (*play)(int, ChessGameMode), int game, const char* title)
+{
+ int sock, msgsock;
+ userinfo_t *uin;
+
+ if ((uin = ChessSearchUser(-1, title)) == NULL)
+ return -1;
+
+ if (uin->uid == currutmp->uid || uin->mode != game) {
+ vmsg("無法建立連線");
+ return -1;
+ }
+
+ if (getans("是否進行觀棋? [N/y]") != 'y')
+ return 0;
+
+ if ((sock = make_connection_to_somebody(uin, 10)) < 0) {
+ vmsg("無法建立連線");
+ return -1;
+ }
+#if defined(Solaris) && __OS_MAJOR_VERSION__ == 5 && __OS_MINOR_VERSION__ < 7
+ msgsock = accept(sock, (struct sockaddr *) 0, 0);
+#else
+ msgsock = accept(sock, (struct sockaddr *) 0, (socklen_t *) 0);
+#endif
+ close(sock);
+ if (msgsock < 0)
+ return -1;
+
+ strlcpy(currutmp->mateid, uin->userid, sizeof(currutmp->mateid));
+ play(msgsock, CHESS_MODE_WATCH);
+ close(msgsock);
+ return 0;
+}
+
+int
+ChessReplayGame(const char* fname)
+{
+ ChessInfo *info;
+ FILE *fp = fopen(fname, "r");
+ int found = -1;
+ char buf[256];
+ screen_backup_t oldscreen;
+
+ if(fp == NULL) {
+ vmsg("檔案無法開啟, 可能被刪除了");
+ return -1;
+ }
+
+ while (found == -1 && fgets(buf, sizeof(buf), fp)) {
+ if (buf[0] == '<') {
+ const int line_len = strlen(buf);
+ if (strcmp(buf + line_len - 5, "log>\n") == 0) {
+ int i;
+ for (i = 0; ChessReplayMap[i].name; ++i)
+ if (ChessReplayMap[i].name_len == line_len - 6 &&
+ strncmp(buf + 1, ChessReplayMap[i].name,
+ ChessReplayMap[i].name_len) == 0) {
+ found = i;
+ break;
+ }
+ }
+ }
+ }
+
+ if (found == -1) {
+ fclose(fp);
+ return -1;
+ }
+
+ info = ChessReplayMap[found].func(fp);
+ fclose(fp);
+
+ if (info) {
+ screen_backup(&oldscreen);
+ ChessPlay(info);
+ screen_restore(&oldscreen);
+
+ DeleteChessInfo(info);
+ }
+
+ return 0;
+}
+
+static void
+ChessInitUser(ChessInfo* info)
+{
+ char userid[2][IDLEN + 1];
+ const userinfo_t* uinfo;
+ userec_t urec;
+
+ switch (info->mode) {
+ case CHESS_MODE_PERSONAL:
+ strlcpy(userid[0], cuser.userid, sizeof(userid[0]));
+ strlcpy(userid[1], cuser.userid, sizeof(userid[1]));
+ break;
+
+ case CHESS_MODE_WATCH:
+ uinfo = search_ulist_userid(currutmp->mateid);
+ if (uinfo) {
+ strlcpy(userid[0], uinfo->userid, sizeof(userid[0]));
+ strlcpy(userid[1], uinfo->mateid, sizeof(userid[1]));
+ } else {
+ strlcpy(userid[0], currutmp->mateid, sizeof(userid[0]));
+ userid[1][0] = 0;
+ }
+ break;
+
+ case CHESS_MODE_VERSUS:
+ strlcpy(userid[0], cuser.userid, sizeof(userid[0]));
+ strlcpy(userid[1], currutmp->mateid, sizeof(userid[1]));
+ break;
+
+ case CHESS_MODE_REPLAY:
+ return;
+ }
+
+ uinfo = search_ulist_userid(userid[0]);
+ if (uinfo)
+ info->actions->init_user(uinfo, &info->user1);
+ else if (getuser(userid[0], &urec))
+ info->actions->init_user_rec(&urec, &info->user1);
+
+ uinfo = search_ulist_userid(userid[1]);
+ if (uinfo)
+ info->actions->init_user(uinfo, &info->user2);
+ else if (getuser(userid[1], &urec))
+ info->actions->init_user_rec(&urec, &info->user2);
+}
+
+#ifdef CHESSCOUNTRY
+static char*
+ChessPhotoInitial(ChessInfo* info)
+{
+ char genbuf[256];
+ int line;
+ FILE* fp;
+ static const char * const blank_photo[6] = {
+ "┌──────┐",
+ "│ 空 │",
+ "│ 白 │",
+ "│ 照 │",
+ "│ 片│",
+ "└──────┘"
+ };
+ char country[5], level[11];
+ userec_t xuser;
+ char* photo;
+ int hasphoto = 0;
+
+ if (info->mode == CHESS_MODE_REPLAY)
+ return NULL;
+
+ if(is_validuserid(info->user1.userid)) {
+ sethomefile(genbuf, info->user1.userid, info->constants->photo_file_name);
+ if (dashf(genbuf))
+ hasphoto++;
+ }
+ if(is_validuserid(info->user2.userid)) {
+ sethomefile(genbuf, info->user2.userid, info->constants->photo_file_name);
+ if (dashf(genbuf))
+ hasphoto++;
+ }
+ if(hasphoto==0)
+ return NULL;
+
+ photo = (char*) calloc(
+ CHESS_PHOTO_LINE * CHESS_PHOTO_COLUMN, sizeof(char));
+
+ /* simulate photo as two dimensional array */
+#define PHOTO(X) (photo + (X) * CHESS_PHOTO_COLUMN)
+
+ fp = NULL;
+ if(getuser(info->user2.userid, &xuser)) {
+ sethomefile(genbuf, info->user2.userid, info->constants->photo_file_name);
+ fp = fopen(genbuf, "r");
+ }
+
+ if (fp == NULL) {
+ strcpy(country, "無");
+ level[0] = 0;
+ } else {
+ int i, j;
+ for (line = 1; line < 8; ++line)
+ fgets(genbuf, sizeof(genbuf), fp);
+
+ fgets(genbuf, sizeof(genbuf), fp);
+ chomp(genbuf);
+ strip_ansi(genbuf + 11, genbuf + 11,
+ STRIP_ALL); /* country name may have color */
+ for (i = 11, j = 0; genbuf[i] && j < 4; ++i)
+ if (genbuf[i] != ' ') /* and spaces */
+ country[j++] = genbuf[i];
+ country[j] = 0; /* two chinese words */
+
+ fgets(genbuf, sizeof(genbuf), fp);
+ chomp(genbuf);
+ strlcpy(level, genbuf + 11, 11); /* five chinese words*/
+ rewind(fp);
+ }
+
+ for (line = 0; line < 6; ++line) {
+ if (fp != NULL) {
+ if (fgets(genbuf, sizeof(genbuf), fp)) {
+ chomp(genbuf);
+ sprintf(PHOTO(line), "%s", genbuf);
+ } else
+ strcpy(PHOTO(line), " ");
+ } else
+ strcpy(PHOTO(line), blank_photo[line]);
+
+ switch (line) {
+ case 0: sprintf(genbuf, " <代號> %s", xuser.userid); break;
+ case 1: sprintf(genbuf, " <暱稱> %.16s", xuser.nickname); break;
+ case 2: sprintf(genbuf, " <上站> %d", xuser.numlogins); break;
+ case 3: sprintf(genbuf, " <文章> %d", xuser.numposts); break;
+ case 4: sprintf(genbuf, " <職位> %-4s %s", country, level); break;
+ case 5: sprintf(genbuf, " <來源> %.16s", xuser.lasthost); break;
+ default: genbuf[0] = 0;
+ }
+ strcat(PHOTO(line), genbuf);
+ }
+ if (fp != NULL)
+ fclose(fp);
+
+ sprintf(PHOTO(6), " %s%2.2s棋" ANSI_RESET,
+ info->constants->turn_color[(int) info->myturn ^ 1],
+ info->constants->turn_str[(int) info->myturn ^ 1]);
+ strcpy(PHOTO(7), " V.S ");
+ sprintf(PHOTO(8), " %s%2.2s棋" ANSI_RESET,
+ info->constants->turn_color[(int) info->myturn],
+ info->constants->turn_str[(int) info->myturn]);
+
+ fp = NULL;
+ if(getuser(info->user1.userid, &xuser)) {;
+ sethomefile(genbuf, info->user1.userid, info->constants->photo_file_name);
+ fp = fopen(genbuf, "r");
+ }
+
+ if (fp == NULL) {
+ strcpy(country, "無");
+ level[0] = 0;
+ } else {
+ int i, j;
+ for (line = 1; line < 8; ++line)
+ fgets(genbuf, sizeof(genbuf), fp);
+
+ fgets(genbuf, sizeof(genbuf), fp);
+ chomp(genbuf);
+ strip_ansi(genbuf + 11, genbuf + 11,
+ STRIP_ALL); /* country name may have color */
+ for (i = 11, j = 0; genbuf[i] && j < 4; ++i)
+ if (genbuf[i] != ' ') /* and spaces */
+ country[j++] = genbuf[i];
+ country[j] = 0; /* two chinese words */
+
+ fgets(genbuf, sizeof(genbuf), fp);
+ chomp(genbuf);
+ strlcpy(level, genbuf + 11, 11); /* five chinese words*/
+ rewind(fp);
+ }
+
+ for (line = 9; line < 15; ++line) {
+ move(line, 37);
+ switch (line - 9) {
+ case 0: sprintf(PHOTO(line), "<代號> %-16.16s ", xuser.userid); break;
+ case 1: sprintf(PHOTO(line), "<暱稱> %-16.16s ", xuser.nickname); break;
+ case 2: sprintf(PHOTO(line), "<上站> %-16d ", xuser.numlogins); break;
+ case 3: sprintf(PHOTO(line), "<文章> %-16d ", xuser.numposts); break;
+ case 4: sprintf(PHOTO(line), "<職位> %-4s %-10s ", country, level); break;
+ case 5: sprintf(PHOTO(line), "<來源> %-16.16s ", xuser.lasthost); break;
+ }
+
+ if (fp != NULL) {
+ if (fgets(genbuf, 200, fp)) {
+ chomp(genbuf);
+ strcat(PHOTO(line), genbuf);
+ } else
+ strcat(PHOTO(line), " ");
+ } else
+ strcat(PHOTO(line), blank_photo[line - 9]);
+ }
+ if (fp != NULL)
+ fclose(fp);
+#undef PHOTO
+
+ return photo;
+}
+#endif /* defined(CHESSCOUNTRY) */
+
+static void
+ChessInitPlayFunc(ChessInfo* info)
+{
+ switch (info->mode) {
+ case CHESS_MODE_VERSUS:
+ info->play_func[(int) info->myturn] = &ChessPlayFuncMy;
+ info->play_func[info->myturn ^ 1] = &ChessPlayFuncHis;
+ break;
+
+ case CHESS_MODE_WATCH:
+ case CHESS_MODE_REPLAY:
+ info->play_func[0] = info->play_func[1] = &ChessPlayFuncWatch;
+ break;
+
+ case CHESS_MODE_PERSONAL:
+ info->play_func[0] = info->play_func[1] = &ChessPlayFuncMy;
+ break;
+ }
+}
+
+ChessInfo*
+NewChessInfo(const ChessActions* actions, const ChessConstants* constants,
+ int sock, ChessGameMode mode)
+{
+ /* allocate memory for the structure and extra space for temporary
+ * steping information storage (step_tmp[0]). */
+ ChessInfo* info =
+ (ChessInfo*) calloc(1, sizeof(ChessInfo) + constants->step_entry_size);
+
+ if (mode == CHESS_MODE_PERSONAL)
+ strcpy(currutmp->mateid, cuser.userid);
+
+ /* compiler don't know it's actually const... */
+ info->actions = (ChessActions*) actions;
+ info->constants = (ChessConstants*) constants;
+ info->mode = mode;
+ info->sock = sock;
+
+ if (mode == CHESS_MODE_VERSUS)
+ info->myturn = currutmp->turn;
+ else if (mode == CHESS_MODE_PERSONAL)
+ info->myturn = 1;
+ else if (mode == CHESS_MODE_REPLAY)
+ info->myturn = 1;
+ else if (mode == CHESS_MODE_WATCH)
+ ChessReceiveWatchInfo(info);
+
+ ChessInitUser(info);
+
+#ifdef CHESSCOUNTRY
+ info->photo = ChessPhotoInitial(info);
+#endif
+
+ if (mode != CHESS_MODE_WATCH)
+ ChessHistoryInit(&info->history, constants->step_entry_size);
+
+ ChessBroadcastListInit(&info->broadcast_list);
+ ChessInitPlayFunc(info);
+
+ return info;
+}
+
+void
+DeleteChessInfo(ChessInfo* info)
+{
+#define NULL_OR_FREE(X) if (X) free(X); else (void) 0
+ NULL_OR_FREE(info->timelimit);
+ NULL_OR_FREE(info->photo);
+ NULL_OR_FREE(info->history.body);
+
+ ChessBroadcastListClear(&info->broadcast_list);
+#undef NULL_OR_FREE
+}
+
+void
+ChessEstablishRequest(int sock)
+{
+ /* XXX */
+ if (!_current_time_limit)
+ write(sock, "T", 1); /* traditional */
+ else {
+ write(sock, "L", 1); /* limited */
+ write(sock, _current_time_limit, sizeof(ChessTimeLimit));
+ }
+}
+
+void
+ChessAcceptingRequest(int sock)
+{
+ /* XXX */
+ char mode;
+ read(sock, &mode, 1);
+ if (mode == 'T')
+ _current_time_limit = NULL;
+ else {
+ _current_time_limit = (ChessTimeLimit*) malloc(sizeof(ChessTimeLimit));
+ read(sock, _current_time_limit, sizeof(ChessTimeLimit));
+ }
+}
+
+void
+ChessShowRequest(void)
+{
+ /* XXX */
+ if (!_current_time_limit)
+ mouts(10, 5, "使用傳統計時方式, 單步限時五分鐘");
+ else if (_current_time_limit->time_mode == CHESS_TIMEMODE_MULTIHAND) {
+ mouts(10, 5, "使用限時限步規則:");
+ move(12, 8);
+ prints("局時 (自由時間): %2d 分 %02d 秒",
+ _current_time_limit->free_time / 60,
+ _current_time_limit->free_time % 60);
+ move(13, 8);
+ prints("限時步時: %2d 分 %02d 秒 / %2d 手",
+ _current_time_limit->limit_time / 60,
+ _current_time_limit->limit_time % 60,
+ _current_time_limit->limit_hand);
+ } else if (_current_time_limit->time_mode == CHESS_TIMEMODE_COUNTING) {
+ mouts(10, 5, "使用讀秒規則:");
+ move(12, 8);
+ prints("局時 (自由時間): %2d 分 %02d 秒",
+ _current_time_limit->free_time / 60,
+ _current_time_limit->free_time % 60);
+ move(13, 8);
+ prints("讀秒時間: 每手 %2d 秒", _current_time_limit->limit_time);
+ }
+}
+
+inline static const char*
+ChessTimeStr(int second)
+{
+ static char buf[10];
+ snprintf(buf, sizeof(buf), "%d:%02d", second / 60, second % 60);
+ return buf;
+}
+
+void
+ChessDrawExtraInfo(const ChessInfo* info, int line, int space)
+{
+ if (line == b_lines || line == 0)
+ return;
+
+ if (info->photo) {
+ if (line >= 3 && line < 3 + CHESS_PHOTO_LINE) {
+ if (space > 3)
+ outs(" ");
+ outs(info->photo + (line - 3) * CHESS_PHOTO_COLUMN);
+ } else if (line >= CHESS_DRAWING_PHOTOED_STEP_ROW &&
+ line <= CHESS_DRAWING_PHOTOED_WARN_ROW) {
+ prints("%*s", space, "");
+ if (line == CHESS_DRAWING_PHOTOED_STEP_ROW)
+ outs(info->last_movestr);
+ else if (line == CHESS_DRAWING_PHOTOED_TURN_ROW)
+ prints(ANSI_COLOR(1;33) "%s" ANSI_RESET,
+ info->myturn == info->turn ? "輪到你下棋了" : "等待對方下棋");
+ else if (line == CHESS_DRAWING_PHOTOED_TIME_ROW1) {
+ if (info->mode == CHESS_MODE_WATCH) {
+ if (!info->timelimit)
+ prints("每手限時五分鐘");
+ else
+ prints("局時: %5s",
+ ChessTimeStr(info->timelimit->free_time));
+ } else if (info->lefthand[0])
+ prints("我方剩餘時間 %s / %2d 步",
+ ChessTimeStr(info->lefttime[0]),
+ info->lefthand[0]);
+ else
+ prints("我方剩餘時間 %s",
+ ChessTimeStr(info->lefttime[0]));
+ } else if (line == CHESS_DRAWING_PHOTOED_TIME_ROW2) {
+ if (info->mode == CHESS_MODE_WATCH) {
+ if (info->timelimit) {
+ if (info->timelimit->time_mode ==
+ CHESS_TIMEMODE_MULTIHAND)
+ prints("步時: %s / %2d 步",
+ ChessTimeStr(info->timelimit->limit_time),
+ info->timelimit->limit_hand);
+ else
+ prints("讀秒: %5d 秒",
+ info->timelimit->limit_time);
+ }
+ } else if (info->lefthand[1])
+ prints("對方剩餘時間 %s / %2d 步",
+ ChessTimeStr(info->lefttime[1]),
+ info->lefthand[1]);
+ else
+ prints("對方剩餘時間 %s",
+ ChessTimeStr(info->lefttime[1]));
+ } else if (line == CHESS_DRAWING_PHOTOED_WARN_ROW)
+ outs(info->warnmsg);
+ }
+ } else if (line >= 3 && line <= CHESS_DRAWING_HISWIN_ROW) {
+ prints("%*s", space, "");
+ if (line >= 3 && line < 3 + (int)dim(ChessHintStr)) {
+ outs(ChessHintStr[line - 3]);
+ } else if (line == CHESS_DRAWING_SIDE_ROW) {
+ prints(ANSI_COLOR(1) "你是%s%s" ANSI_RESET,
+ info->constants->turn_color[(int) info->myturn],
+ info->constants->turn_str[(int) info->myturn]);
+ } else if (line == CHESS_DRAWING_REAL_TURN_ROW) {
+ prints(ANSI_COLOR(1;33) "%s" ANSI_RESET,
+ info->myturn == info->turn ?
+ "輪到你下棋了" : "等待對方下棋");
+ } else if (line == CHESS_DRAWING_REAL_STEP_ROW && info->last_movestr) {
+ outs(info->last_movestr);
+ } else if (line == CHESS_DRAWING_REAL_TIME_ROW1) {
+ if (info->lefthand[0])
+ prints("我方剩餘時間 %s / %2d 步",
+ ChessTimeStr(info->lefttime[0]),
+ info->lefthand[0]);
+ else
+ prints("我方剩餘時間 %s",
+ ChessTimeStr(info->lefttime[0]));
+ } else if (line == CHESS_DRAWING_REAL_TIME_ROW2) {
+ if (info->lefthand[1])
+ prints("對方剩餘時間 %s / %2d 步",
+ ChessTimeStr(info->lefttime[1]),
+ info->lefthand[1]);
+ else
+ prints("對方剩餘時間 %s",
+ ChessTimeStr(info->lefttime[1]));
+ } else if (line == CHESS_DRAWING_REAL_WARN_ROW) {
+ outs(info->warnmsg);
+ } else if (line == CHESS_DRAWING_MYWIN_ROW) {
+ prints(ANSI_COLOR(1;33) "%12.12s "
+ ANSI_COLOR(1;31) "%2d" ANSI_COLOR(37) "勝 "
+ ANSI_COLOR(34) "%2d" ANSI_COLOR(37) "敗 "
+ ANSI_COLOR(36) "%2d" ANSI_COLOR(37) "和" ANSI_RESET,
+ info->user1.userid,
+ info->user1.win, info->user1.lose - 1, info->user1.tie);
+ } else if (line == CHESS_DRAWING_HISWIN_ROW) {
+ prints(ANSI_COLOR(1;33) "%12.12s "
+ ANSI_COLOR(1;31) "%2d" ANSI_COLOR(37) "勝 "
+ ANSI_COLOR(34) "%2d" ANSI_COLOR(37) "敗 "
+ ANSI_COLOR(36) "%2d" ANSI_COLOR(37) "和" ANSI_RESET,
+ info->user2.userid,
+ info->user2.win, info->user2.lose, info->user2.tie);
+ }
+ }
+}
diff --git a/pttbbs/mbbsd/chicken.c b/pttbbs/mbbsd/chicken.c
new file mode 100644
index 00000000..d57a3bfe
--- /dev/null
+++ b/pttbbs/mbbsd/chicken.c
@@ -0,0 +1,1016 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define NUM_KINDS 15 /* 有多少種動物 */
+
+static const char * const cage[17] = {
+ "誕生", "週歲", "幼年", "少年", "青春", "青年",
+ "青年", "活力", "壯年", "壯年", "壯年", "中年",
+ "中年", "老年", "老年", "老摳摳", "古希"};
+static const char * const chicken_type[NUM_KINDS] = {
+ "小雞", "美少女", "勇士", "蜘蛛",
+ "恐龍", "老鷹", "貓", "蠟筆小新",
+ "狗狗", "惡魔", "忍者", "ㄚ扁",
+ "馬英九", "就可人", "羅莉"};
+static const char * const chicken_food[NUM_KINDS] = {
+ "雞飼料", "營養厚片", "雞排便當", "死蝴蝶",
+ "屍體", "小雞", "貓餅乾", "小熊餅乾",
+ "寶錄", "靈氣", "飯團", "便當",
+ "雞腿", "笑話文章", "水果沙拉"};
+static const int egg_price[NUM_KINDS] = {
+ 5, 25, 30, 40,
+ 80, 50, 15, 35,
+ 17, 100, 85, 200,
+ 200, 100, 77};
+static const int food_price[NUM_KINDS] = {
+ 4, 6, 8, 10,
+ 12, 12, 5, 6,
+ 5, 20, 15, 23,
+ 23, 10, 19};
+static const char * const attack_type[NUM_KINDS] = {
+ "啄", "鞭打", "槌", "咬",
+ "撞擊", "啄", "抓", "踢",
+ "咬", "燃燒", "暗擊", "棍打",
+ "劍擊", "冷凍光線", "香吻一枚"};
+
+static const char * const damage_degree[] = {
+ "蚊子似的", "騷癢似的", "小力的", "輕微的",
+ "有點疼的", "使力的", "傷人的", "重重的",
+ "使全力的", "惡狠狠的", "危險的", "瘋狂的",
+ "猛烈的", "狂風暴雨似的", "驚天動地的",
+ "致命的", NULL};
+
+enum {
+ OO, FOOD, WEIGHT, CLEAN, RUN, ATTACK, BOOK, HAPPY, SATIS,
+ TEMPERAMENT, TIREDSTRONG, SICK, HP_MAX, MM_MAX
+};
+
+static const short time_change[NUM_KINDS][14] =
+/* 補品 食物 體重 乾淨 敏捷 攻擊力 知識 快樂 滿意 氣質 疲勞 病氣 滿血 滿法 */
+{
+ /* 雞 */
+ {1, 1, 30, 3, 8, 3, 3, 40, 9, 1, 7, 3, 30, 1},
+ /* 美少女 */
+ {1, 1, 110, 1, 4, 7, 41, 20, 9, 25, 25, 7, 110, 15},
+ /* 勇士 */
+ {1, 1, 200, 5, 4, 10, 33, 20, 15, 10, 27, 1, 200, 9},
+ /* 蜘蛛 */
+ {1, 1, 10, 5, 8, 1, 1, 5, 3, 1, 4, 1, 10, 30},
+ /* 恐龍 */
+ {1, 1, 1000, 9, 1, 13, 4, 12, 3, 1, 200, 1, 1000, 3},
+ /* 老鷹 */
+ {1, 1, 90, 7, 10, 7, 4, 12, 3, 30, 20, 5, 90, 20},
+ /* 貓 */
+ {1, 1, 30, 5, 5, 6, 4, 8, 3, 15, 7, 4, 30, 21},
+ /* 蠟筆小新 */
+ {1, 1, 100, 9, 7, 7, 20, 50, 10, 8, 24, 4, 100, 9},
+ /* 狗 */
+ {1, 1, 45, 8, 7, 9, 3, 40, 20, 3, 9, 5, 45, 1},
+ /* 惡魔 */
+ {1, 1, 45, 10, 11, 11, 5, 21, 11, 1, 9, 5, 45, 25},
+ /* 忍者 */
+ {1, 1, 45, 2, 12, 10, 25, 1, 1, 10, 9, 5, 45, 26},
+ /* 阿扁 */
+ {1, 1, 150, 4, 8, 13, 95, 25, 7, 10, 25, 5, 175, 85},
+ /* 馬英九 */
+ {1, 1, 147, 2, 10, 10, 85, 20, 4, 25, 25, 5, 145, 95},
+ /* 就可人 */
+ {1, 1, 200, 3, 15, 15, 50, 50, 10, 5, 10, 2, 300, 0},
+ /* 羅利 */
+ {1, 1, 80, 2, 9, 10, 2, 5, 7, 8, 12, 1, 135, 5},
+};
+
+int
+reload_chicken(void)
+{
+ userec_t xuser;
+ chicken_t *mychicken = &cuser.mychicken;
+
+ passwd_query(usernum, &xuser);
+ memcpy(mychicken, &xuser.mychicken, sizeof(chicken_t));
+ if (!mychicken->name[0])
+ return 0;
+ else
+ return 1;
+}
+
+#define CHICKENLOG "etc/chicken"
+
+static int
+new_chicken(void)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ int price, i;
+
+ clear();
+ move(2, 0);
+ outs("歡迎光臨 " ANSI_COLOR(33) "◎" ANSI_COLOR(37;44) " Ptt寵物市場 " ANSI_COLOR(33;40) "◎" ANSI_RESET ".. "
+ "目前蛋價:\n"
+ "(a)小雞 $5 (b)美少女 $25 (c)勇士 $30 (d)蜘蛛 $40 "
+ "(e)恐龍 $80\n"
+ "(f)老鷹 $50 (g)貓 $15 (h)蠟筆小新$35 (i)狗狗 $17 "
+ "(j)惡魔 $100\n"
+ "(k)忍者 $85 (l)阿扁 $200 (m)馬英九 $200 (n)就可人$100 "
+ "[o]羅莉 $77\n"
+ "[0]自己 $0\n");
+ i = getans("請選擇你要養的動物:");
+
+ i -= 'a';
+ if (i < 0 || i > NUM_KINDS - 1)
+ return 0;
+
+ mychicken->type = i;
+
+ reload_money();
+ price = egg_price[(int)mychicken->type];
+ if (cuser.money < price) {
+ vmsgf("錢不夠買蛋蛋,蛋蛋要 %d 元", price);
+ return 0;
+ }
+ vice(price, "寵物蛋");
+ while (strlen(mychicken->name) < 3)
+ getdata(8, 0, "幫牠取個好名字:", mychicken->name,
+ sizeof(mychicken->name), DOECHO);
+
+ log_file(CHICKENLOG, LOG_CREAT | LOG_VF,
+ ANSI_COLOR(31) "%s " ANSI_RESET "養了一隻叫" ANSI_COLOR(33) " %s " ANSI_RESET "的 "
+ ANSI_COLOR(32) "%s" ANSI_RESET " 於 %s\n", cuser.userid,
+ mychicken->name, chicken_type[(int)mychicken->type], ctime4(&now));
+ mychicken->lastvisit = mychicken->birthday = mychicken->cbirth = now;
+ mychicken->food = 0;
+ mychicken->weight = time_change[(int)mychicken->type][WEIGHT] / 3;
+ mychicken->clean = 0;
+ mychicken->run = time_change[(int)mychicken->type][RUN];
+ mychicken->attack = time_change[(int)mychicken->type][ATTACK];
+ mychicken->book = time_change[(int)mychicken->type][BOOK];
+ mychicken->happy = time_change[(int)mychicken->type][HAPPY];
+ mychicken->satis = time_change[(int)mychicken->type][SATIS];
+ mychicken->temperament = time_change[(int)mychicken->type][TEMPERAMENT];
+ mychicken->tiredstrong = 0;
+ mychicken->sick = 0;
+ mychicken->hp = time_change[(int)mychicken->type][WEIGHT];
+ mychicken->hp_max = time_change[(int)mychicken->type][WEIGHT];
+ mychicken->mm = 0;
+ mychicken->mm_max = 0;
+ return 1;
+}
+
+static void
+show_chicken_stat(const chicken_t * thechicken, int age)
+{
+ struct tm *ptime;
+
+ ptime = localtime4(&thechicken->birthday);
+ prints(" Name :" ANSI_COLOR(33) "%s" ANSI_RESET " (" ANSI_COLOR(32) "%s" ANSI_RESET ")%*s生日 "
+ ":" ANSI_COLOR(31) "%02d" ANSI_RESET "年" ANSI_COLOR(31) "%2d" ANSI_RESET "月" ANSI_COLOR(31) "%2d" ANSI_RESET "日 "
+ "(" ANSI_COLOR(32) "%s %d歲" ANSI_RESET ")\n"
+ " 體:" ANSI_COLOR(33) "%5d/%-5d" ANSI_RESET " 法:" ANSI_COLOR(33) "%5d/%-5d" ANSI_RESET " 攻擊力:"
+ ANSI_COLOR(33) "%-7d" ANSI_RESET " 敏捷 :" ANSI_COLOR(33) "%-7d" ANSI_RESET " 知識 :" ANSI_COLOR(33) "%-7d"
+ ANSI_RESET " \n"
+ " 快樂 :" ANSI_COLOR(33) "%-7d" ANSI_RESET " 滿意 :" ANSI_COLOR(33) "%-7d" ANSI_RESET " 疲勞 :"
+ ANSI_COLOR(33) "%-7d" ANSI_RESET " 氣質 :" ANSI_COLOR(33) "%-7d " ANSI_RESET "體重 :"
+ ANSI_COLOR(33) "%-5.2f" ANSI_RESET " \n"
+ " 病氣 :" ANSI_COLOR(33) "%-7d" ANSI_RESET " 乾淨 :" ANSI_COLOR(33) "%-7d" ANSI_RESET " 食物 :"
+ ANSI_COLOR(33) "%-7d" ANSI_RESET " 大補丸:" ANSI_COLOR(33) "%-7d" ANSI_RESET " 藥品 :" ANSI_COLOR(33) "%-7d"
+ ANSI_RESET " \n",
+ thechicken->name, chicken_type[(int)thechicken->type],
+ strlen(thechicken->name) >= 15 ? 0 : (int)(15 - strlen(thechicken->name)), "",
+ ptime->tm_year % 100, ptime->tm_mon + 1, ptime->tm_mday,
+ cage[age > 16 ? 16 : age], age, thechicken->hp, thechicken->hp_max,
+ thechicken->mm, thechicken->mm_max,
+ thechicken->attack, thechicken->run, thechicken->book,
+ thechicken->happy, thechicken->satis, thechicken->tiredstrong,
+ thechicken->temperament,
+ ((float)(thechicken->hp_max + (thechicken->weight / 50))) / 100,
+ thechicken->sick, thechicken->clean, thechicken->food,
+ thechicken->oo, thechicken->medicine);
+}
+
+#define CHICKEN_PIC "etc/chickens"
+
+void
+show_chicken_data(chicken_t * thechicken, chicken_t * pkchicken)
+{
+ char buf[1024];
+ int age = ((now - thechicken->cbirth) / (60 * 60 * 24));
+ if (age < 0) {
+ thechicken->birthday = thechicken->cbirth = now - 10 * (60 * 60 * 24);
+ age = 10;
+ }
+ /* Ptt:debug */
+ thechicken->type %= NUM_KINDS;
+ clear();
+ showtitle(pkchicken ? "Ptt鬥雞場" : "Ptt養雞場", BBSName);
+ move(1, 0);
+
+ show_chicken_stat(thechicken, age);
+
+ snprintf(buf, sizeof(buf), CHICKEN_PIC "/%c%d", thechicken->type + 'a',
+ age > 16 ? 16 : age);
+ show_file(buf, 5, 14, NO_RELOAD);
+
+ move(18, 0);
+
+ if (thechicken->sick)
+ outs("生病了...");
+ if (thechicken->sick > thechicken->hp / 5)
+ outs(ANSI_COLOR(5;31) "擔心...病重!!" ANSI_RESET);
+
+ if (thechicken->clean > 150)
+ outs(ANSI_COLOR(31) "又臭又髒的.." ANSI_RESET);
+ else if (thechicken->clean > 80)
+ outs("有點髒..");
+ else if (thechicken->clean < 20)
+ outs(ANSI_COLOR(32) "很乾淨.." ANSI_RESET);
+
+ if (thechicken->weight > thechicken->hp_max * 4)
+ outs(ANSI_COLOR(31) "快飽死了!." ANSI_RESET);
+ else if (thechicken->weight > thechicken->hp_max * 3)
+ outs(ANSI_COLOR(32) "飽嘟嘟.." ANSI_RESET);
+ else if (thechicken->weight < (thechicken->hp_max / 4))
+ outs(ANSI_COLOR(31) "快餓死了!.." ANSI_RESET);
+ else if (thechicken->weight < (thechicken->hp_max / 2))
+ outs("餓了..");
+
+ if (thechicken->tiredstrong > thechicken->hp * 1.7)
+ outs(ANSI_COLOR(31) "累得昏迷了..." ANSI_RESET);
+ else if (thechicken->tiredstrong > thechicken->hp)
+ outs("累了..");
+ else if (thechicken->tiredstrong < thechicken->hp / 4)
+ outs(ANSI_COLOR(32) "精力旺盛..." ANSI_RESET);
+
+ if (thechicken->hp < thechicken->hp_max / 4)
+ outs(ANSI_COLOR(31) "體力用盡..奄奄一息.." ANSI_RESET);
+ if (thechicken->happy > 500)
+ outs(ANSI_COLOR(32) "很快樂.." ANSI_RESET);
+ else if (thechicken->happy < 100)
+ outs("不快樂..");
+ if (thechicken->satis > 500)
+ outs(ANSI_COLOR(32) "很滿足.." ANSI_RESET);
+ else if (thechicken->satis < 50)
+ outs("不滿足..");
+
+ if (pkchicken) {
+ outc('\n');
+ show_chicken_stat(pkchicken, age);
+ outs("[任意鍵] 攻擊對方 [q] 落跑 [o] 吃大補丸");
+ }
+}
+
+static void
+ch_eat(void)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ if (mychicken->food) {
+ mychicken->weight += time_change[(int)mychicken->type][WEIGHT] +
+ mychicken->hp_max / 5;
+ mychicken->tiredstrong +=
+ time_change[(int)mychicken->type][TIREDSTRONG] / 2;
+ mychicken->hp_max++;
+ mychicken->happy += 5;
+ mychicken->satis += 7;
+ mychicken->food--;
+ move(10, 10);
+
+ show_file(CHICKEN_PIC "/eat", 5, 14, NO_RELOAD);
+ pressanykey();
+ }
+}
+
+static void
+ch_clean(void)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ mychicken->clean = 0;
+ mychicken->tiredstrong +=
+ time_change[(int)mychicken->type][TIREDSTRONG] / 3;
+ show_file(CHICKEN_PIC "/clean", 5, 14, NO_RELOAD);
+ pressanykey();
+}
+
+static void
+ch_guess(void)
+{
+ char *guess[3] = {"剪刀", "石頭", "布"}, me, ch, win;
+
+ chicken_t *mychicken = &cuser.mychicken;
+ mychicken->happy += time_change[(int)mychicken->type][HAPPY] * 1.5;
+ mychicken->satis += time_change[(int)mychicken->type][SATIS];
+ mychicken->tiredstrong += time_change[(int)mychicken->type][TIREDSTRONG];
+ mychicken->attack += time_change[(int)mychicken->type][ATTACK] / 4;
+ move(20, 0);
+ clrtobot();
+ outs("你要出[" ANSI_COLOR(32) "1" ANSI_RESET "]" ANSI_COLOR(33) "剪刀" ANSI_RESET "(" ANSI_COLOR(32) "2" ANSI_RESET ")"
+ ANSI_COLOR(33) "石頭" ANSI_RESET "(" ANSI_COLOR(32) "3" ANSI_RESET ")" ANSI_COLOR(33) "布" ANSI_RESET ":\n");
+ me = igetch();
+ me -= '1';
+ if (me > 2 || me < 0)
+ me = 0;
+ win = (int)(3.0 * random() / (RAND_MAX + 1.0)) - 1;
+ ch = (me + win + 3) % 3;
+ prints("%s:%s ! %s:%s !.....%s",
+ cuser.userid, guess[(int)me], mychicken->name, guess[(int)ch],
+ win == 0 ? "平手" : win < 0 ? "耶..贏了 :D!!" : "嗚..我輸了 :~");
+ pressanykey();
+}
+
+static void
+ch_book(void)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ mychicken->book += time_change[(int)mychicken->type][BOOK];
+ mychicken->tiredstrong += time_change[(int)mychicken->type][TIREDSTRONG];
+ show_file(CHICKEN_PIC "/read", 5, 14, NO_RELOAD);
+ pressanykey();
+}
+
+static void
+ch_kiss(void)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ mychicken->happy += time_change[(int)mychicken->type][HAPPY];
+ mychicken->satis += time_change[(int)mychicken->type][SATIS];
+ mychicken->tiredstrong +=
+ time_change[(int)mychicken->type][TIREDSTRONG] / 2;
+ show_file(CHICKEN_PIC "/kiss", 5, 14, NO_RELOAD);
+ pressanykey();
+}
+
+static void
+ch_hit(void)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ mychicken->attack += time_change[(int)mychicken->type][ATTACK];
+ mychicken->run += time_change[(int)mychicken->type][RUN];
+ mychicken->mm_max += time_change[(int)mychicken->type][MM_MAX] / 15;
+ mychicken->weight -= mychicken->hp_max / 15;
+ mychicken->hp -= (int)((float)time_change[(int)mychicken->type][HP_MAX] *
+ random() / (RAND_MAX + 1.0)) / 2 + 1;
+
+ if (mychicken->book > 2)
+ mychicken->book -= 2;
+ if (mychicken->happy > 2)
+ mychicken->happy -= 2;
+ if (mychicken->satis > 2)
+ mychicken->satis -= 2;
+ mychicken->tiredstrong += time_change[(int)mychicken->type][TIREDSTRONG];
+ show_file(CHICKEN_PIC "/hit", 5, 14, NO_RELOAD);
+ pressanykey();
+}
+
+void
+ch_buyitem(int money, const char *picture, int *item, int haveticket)
+{
+ int num = 0;
+ char buf[5];
+
+ getdata_str(b_lines - 1, 0, "要買多少份呢:",
+ buf, sizeof(buf), DOECHO, "1");
+ num = atoi(buf);
+ if (num < 1)
+ return;
+ reload_money();
+ if (cuser.money/money >= num) {
+ *item += num;
+ if( haveticket )
+ vice(money * num, "購買寵物,賭盤項目");
+ else
+ demoney(-money * num);
+ show_file(picture, 5, 14, NO_RELOAD);
+ pressanykey();
+ } else {
+ vmsg("現金不夠 !!!");
+ }
+ usleep(100000); // sleep 0.1s
+}
+
+static void
+ch_eatoo(void)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ if (mychicken->oo > 0) {
+ mychicken->oo--;
+ mychicken->tiredstrong = 0;
+ if (mychicken->happy > 5)
+ mychicken->happy -= 5;
+ show_file(CHICKEN_PIC "/oo", 5, 14, NO_RELOAD);
+ pressanykey();
+ }
+}
+
+static void
+ch_eatmedicine(void)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ if (mychicken->medicine > 0) {
+ mychicken->medicine--;
+ mychicken->sick = 0;
+ if (mychicken->hp_max > 10)
+ mychicken->hp_max -= 3;
+ mychicken->hp = mychicken->hp_max;
+ if (mychicken->happy > 10)
+ mychicken->happy -= 10;
+ show_file(CHICKEN_PIC "/medicine", 5, 14, NO_RELOAD);
+ pressanykey();
+ }
+}
+
+static void
+ch_kill(void)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ int ans;
+
+ ans = getans("棄養要被罰 100 元, 是否要棄養?(y/N)");
+ if (ans == 'y') {
+
+ vice(100, "棄養寵物費");
+ more(CHICKEN_PIC "/deadth", YEA);
+ log_file(CHICKENLOG, LOG_CREAT | LOG_VF,
+ ANSI_COLOR(31) "%s " ANSI_RESET "把 " ANSI_COLOR(33) "%s" ANSI_RESET ANSI_COLOR(32) " %s "
+ ANSI_RESET "宰了 於 %s\n", cuser.userid, mychicken->name,
+ chicken_type[(int)mychicken->type], ctime4(&now));
+ mychicken->name[0] = 0;
+ }
+}
+
+static int
+ch_sell(int age)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ /*
+ * int money = (mychicken->weight -
+ * time_change[(int)mychicken->type][WEIGHT])
+ * (food_price[(int)mychicken->type])/4 + ( + ((mychicken->clean /
+ * time_change[(int)mychicken->type][CLEAN]) + (mychicken->run /
+ * time_change[(int)mychicken->type][RUN]) + (mychicken->attack /
+ * time_change[(int)mychicken->type][ATTACK]) + (mychicken->book /
+ * time_change[(int)mychicken->type][BOOK]) + (mychicken->happy /
+ * time_change[(int)mychicken->type][HAPPY]) + (mychicken->satis /
+ * time_change[(int)mychicken->type][SATIS]) + (mychicken->temperament /
+ * time_change[(int)mychicken->type][TEMPERAMENT]) -
+ * (mychicken->tiredstrong /
+ * time_change[(int)mychicken->type][TIREDSTRONG]) - (mychicken->sick /
+ * time_change[(int)mychicken->type][SICK]) + (mychicken->hp /
+ * time_change[(int)mychicken->type][HP_MAX]) + (mychicken->mm /
+ * time_change[(int)mychicken->type][MM_MAX]) + 7 - abs(age - 7)) * 3 ;
+ */
+ int money = (age * food_price[(int)mychicken->type] * 3
+ + (mychicken->hp_max * 10 + mychicken->weight) /
+ time_change[(int)mychicken->type][HP_MAX]) * 3 / 2 -
+ mychicken->sick, ans;
+
+ if (money < 0)
+ money = 0;
+ else if (money > MAX_CHICKEN_MONEY)
+ money = MAX_CHICKEN_MONEY;
+ //防止怪雞
+ if (mychicken->type == 1 || mychicken->type == 7) {
+ outs("\n" ANSI_COLOR(31) " ㄜ..親愛的..販賣人口是會犯法的唷.." ANSI_RESET);
+ pressanykey();
+ return 0;
+ }
+ if (age < 5) {
+ outs("\n 還未成年不能賣");
+ pressanykey();
+ return 0;
+ }
+ if (age > 30) {
+ outs("\n" ANSI_COLOR(31) " 這..太老沒人要了" ANSI_RESET);
+ pressanykey();
+ return 0;
+ }
+ ans = getans("這隻%d歲%s可以賣 %d 元, 是否要賣?(y/N)", age,
+ chicken_type[(int)mychicken->type], money);
+ if (ans == 'y') {
+ log_file(CHICKENLOG, LOG_CREAT | LOG_VF,
+ ANSI_COLOR(31) "%s" ANSI_RESET " 把 " ANSI_COLOR(33) "%s" ANSI_RESET " "
+ ANSI_COLOR(32) "%s" ANSI_RESET " 用 " ANSI_COLOR(36) "%d" ANSI_RESET " 賣了 於 %s\n",
+ cuser.userid, mychicken->name,
+ chicken_type[(int)mychicken->type], money, ctime4(&now));
+ mychicken->lastvisit = mychicken->name[0] = 0;
+ passwd_update(usernum, &cuser);
+ more(CHICKEN_PIC "/sell", YEA);
+ demoney(money);
+ return 1;
+ }
+ return 0;
+}
+
+static void
+geting_old(int *hp, int *weight, int diff, int age)
+{
+ float ex = 0.9;
+
+ if (age > 70)
+ ex = 0.1;
+ else if (age > 30)
+ ex = 0.5;
+ else if (age > 20)
+ ex = 0.7;
+
+ diff /= 60 * 6;
+ while (diff--) {
+ *hp *= ex;
+ *weight *= ex;
+ }
+}
+
+/* 依時間變動的資料 */
+void
+time_diff(chicken_t * thechicken)
+{
+ int diff;
+ int theage = ((now - thechicken->cbirth) / (60 * 60 * 24));
+
+ thechicken->type %= NUM_KINDS;
+ diff = (now - thechicken->lastvisit) / 60;
+
+ if ((diff) < 1)
+ return;
+
+ if (theage > 13) /* 老死 */
+ geting_old(&thechicken->hp_max, &thechicken->weight, diff, theage);
+
+ thechicken->lastvisit = now;
+ thechicken->weight -= thechicken->hp_max * diff / 540; /* 體重 */
+ if (thechicken->weight < 1) {
+ thechicken->sick -= thechicken->weight / 10; /* 餓得病氣上升 */
+ thechicken->weight = 1;
+ }
+ /* 清潔度 */
+ thechicken->clean += diff * time_change[(int)thechicken->type][CLEAN] / 30;
+
+ /* 快樂度 */
+ thechicken->happy -= diff / 60;
+ if (thechicken->happy < 0)
+ thechicken->happy = 0;
+ thechicken->attack -=
+ time_change[(int)thechicken->type][ATTACK] * diff / (60 * 32);
+ if (thechicken->attack < 0)
+ thechicken->attack = 0;
+ /* 攻擊力 */
+ thechicken->run -= time_change[(int)thechicken->type][RUN] * diff / (60 * 32);
+ /* 敏捷 */
+ if (thechicken->run < 0)
+ thechicken->run = 0;
+ thechicken->book -= time_change[(int)thechicken->type][BOOK] * diff / (60 * 32);
+ /* 知識 */
+ if (thechicken->book < 0)
+ thechicken->book = 0;
+ /* 氣質 */
+ thechicken->temperament++;
+
+ thechicken->satis -= diff / 60 / 3 * time_change[(int)thechicken->type][SATIS];
+ /* 滿意度 */
+ if (thechicken->satis < 0)
+ thechicken->satis = 0;
+
+ /* 髒病的 */
+ if (thechicken->clean > 1000)
+ thechicken->sick += (thechicken->clean - 400) / 10;
+
+ if (thechicken->weight > 1)
+ thechicken->sick -= diff / 60;
+ /* 病氣恢護 */
+ if (thechicken->sick < 0)
+ thechicken->sick = 0;
+ thechicken->tiredstrong -= diff *
+ time_change[(int)thechicken->type][TIREDSTRONG] / 4;
+ /* 疲勞 */
+ if (thechicken->tiredstrong < 0)
+ thechicken->tiredstrong = 0;
+ /* hp_max */
+ if (thechicken->hp >= thechicken->hp_max / 2)
+ thechicken->hp_max +=
+ time_change[(int)thechicken->type][HP_MAX] * diff / (60 * 12);
+ /* hp恢護 */
+ if (!thechicken->sick)
+ thechicken->hp +=
+ time_change[(int)thechicken->type][HP_MAX] * diff / (60 * 6);
+ if (thechicken->hp > thechicken->hp_max)
+ thechicken->hp = thechicken->hp_max;
+ /* mm_max */
+ if (thechicken->mm >= thechicken->mm_max / 2)
+ thechicken->mm_max +=
+ time_change[(int)thechicken->type][MM_MAX] * diff / (60 * 8);
+ /* mm恢護 */
+ if (!thechicken->sick)
+ thechicken->mm += diff;
+ if (thechicken->mm > thechicken->mm_max)
+ thechicken->mm = thechicken->mm_max;
+}
+
+static void
+check_sick(void)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ /* 髒病的 */
+ if (mychicken->tiredstrong > mychicken->hp * 0.3 && mychicken->clean > 150)
+ mychicken->sick += (mychicken->clean - 150) / 10;
+ /* 累病的 */
+ if (mychicken->tiredstrong > mychicken->hp * 1.3)
+ mychicken->sick += time_change[(int)mychicken->type][SICK];
+ /* 病氣太重還做事減hp */
+ if (mychicken->sick > mychicken->hp / 5) {
+ mychicken->hp -= (mychicken->sick - mychicken->hp / 5) / 4;
+ if (mychicken->hp < 0)
+ mychicken->hp = 0;
+ }
+}
+
+static int
+deadtype(const chicken_t * thechicken)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ int i;
+
+ if (thechicken->hp <= 0) /* hp用盡 */
+ i = 1;
+ else if (thechicken->tiredstrong > thechicken->hp * 3) /* 操勞過度 */
+ i = 2;
+ else if (thechicken->weight > thechicken->hp_max * 5) /* 肥胖過度 */
+ i = 3;
+ else if (thechicken->weight == 1 &&
+ thechicken->sick > thechicken->hp_max / 4)
+ i = 4; /* 餓死了 */
+ else if (thechicken->satis <= 0) /* 很不滿意 */
+ i = 5;
+ else
+ return 0;
+
+ if (thechicken == mychicken) {
+ log_file(CHICKENLOG, LOG_CREAT | LOG_VF,
+ ANSI_COLOR(31) "%s" ANSI_RESET " 所疼愛的" ANSI_COLOR(33) " %s" ANSI_COLOR(32) " %s "
+ ANSI_RESET "掛了 於 %s\n", cuser.userid, thechicken->name,
+ chicken_type[(int)thechicken->type], ctime4(&now));
+ mychicken->name[0] = 0;
+ passwd_update(usernum, &cuser);
+ }
+ return i;
+}
+
+int
+showdeadth(int type)
+{
+ switch (type) {
+ case 1:
+ more(CHICKEN_PIC "/nohp", YEA);
+ break;
+ case 2:
+ more(CHICKEN_PIC "/tootired", YEA);
+ break;
+ case 3:
+ more(CHICKEN_PIC "/toofat", YEA);
+ break;
+ case 4:
+ more(CHICKEN_PIC "/nofood", YEA);
+ break;
+ case 5:
+ more(CHICKEN_PIC "/nosatis", YEA);
+ break;
+ default:
+ return 0;
+ }
+ more(CHICKEN_PIC "/deadth", YEA);
+ return type;
+}
+
+int
+isdeadth(const chicken_t * thechicken)
+{
+ int i;
+
+ if (!(i = deadtype(thechicken)))
+ return 0;
+ return showdeadth(i);
+}
+
+static void
+ch_changename(void)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ char newname[20] = "";
+
+ getdata_str(b_lines - 1, 0, "嗯..改個好名字吧:", newname, 18, DOECHO,
+ mychicken->name);
+
+ if (strlen(newname) >= 3 && strcmp(newname, mychicken->name)) {
+ strlcpy(mychicken->name, newname, sizeof(mychicken->name));
+ log_file(CHICKENLOG, LOG_CREAT | LOG_VF,
+ ANSI_COLOR(31) "%s" ANSI_RESET " 把疼愛的" ANSI_COLOR(33) " %s" ANSI_COLOR(32) " %s "
+ ANSI_RESET "改名為" ANSI_COLOR(33) " %s" ANSI_RESET " 於 %s\n",
+ cuser.userid, mychicken->name,
+ chicken_type[(int)mychicken->type], newname, ctime4(&now));
+ }
+}
+
+static int
+select_menu(int age)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ char ch;
+
+ reload_money();
+ move(19, 0);
+ prints(ANSI_COLOR(44;37) " 錢 :" ANSI_COLOR(33) " %-10d "
+ " " ANSI_RESET "\n"
+ ANSI_COLOR(33) "(" ANSI_COLOR(37) "1" ANSI_COLOR(33) ")清理 (" ANSI_COLOR(37) "2" ANSI_COLOR(33) ")吃飯 "
+ "(" ANSI_COLOR(37) "3" ANSI_COLOR(33) ")猜拳 (" ANSI_COLOR(37) "4" ANSI_COLOR(33) ")唸書 "
+ "(" ANSI_COLOR(37) "5" ANSI_COLOR(33) ")親他 (" ANSI_COLOR(37) "6" ANSI_COLOR(33) ")打他 "
+ "(" ANSI_COLOR(37) "7" ANSI_COLOR(33) ")買%s$%d (" ANSI_COLOR(37) "8" ANSI_COLOR(33) ")吃補丸\n"
+ "(" ANSI_COLOR(37) "9" ANSI_COLOR(33) ")吃病藥 (" ANSI_COLOR(37) "o" ANSI_COLOR(33) ")買大補丸$100 "
+ "(" ANSI_COLOR(37) "m" ANSI_COLOR(33) ")買藥$10 (" ANSI_COLOR(37) "k" ANSI_COLOR(33) ")棄養 "
+ "(" ANSI_COLOR(37) "s" ANSI_COLOR(33) ")賣掉 (" ANSI_COLOR(37) "n" ANSI_COLOR(33) ")改名 "
+ "(" ANSI_COLOR(37) "q" ANSI_COLOR(33) ")離開:" ANSI_RESET,
+ cuser.money,
+ /*
+ * chicken_food[(int)mychicken->type],
+ * chicken_type[(int)mychicken->type],
+ * chicken_type[(int)mychicken->type],
+ */
+ chicken_food[(int)mychicken->type],
+ food_price[(int)mychicken->type]);
+ do {
+ switch (ch = igetch()) {
+ case '1':
+ ch_clean();
+ check_sick();
+ break;
+ case '2':
+ ch_eat();
+ check_sick();
+ break;
+ case '3':
+ ch_guess();
+ check_sick();
+ break;
+ case '4':
+ ch_book();
+ check_sick();
+ break;
+ case '5':
+ ch_kiss();
+ break;
+ case '6':
+ ch_hit();
+ check_sick();
+ break;
+ case '7':
+ ch_buyitem(food_price[(int)mychicken->type], CHICKEN_PIC "/food",
+ &mychicken->food, 1);
+ break;
+ case '8':
+ ch_eatoo();
+ break;
+ case '9':
+ ch_eatmedicine();
+ break;
+ case 'O':
+ case 'o':
+ ch_buyitem(100, CHICKEN_PIC "/buyoo", &mychicken->oo, 1);
+ break;
+ case 'M':
+ case 'm':
+ ch_buyitem(10, CHICKEN_PIC "/buymedicine", &mychicken->medicine, 1);
+ break;
+ case 'N':
+ case 'n':
+ ch_changename();
+ break;
+ case 'K':
+ case 'k':
+ ch_kill();
+ return 0;
+ case 'S':
+ case 's':
+ if (!ch_sell(age))
+ break;
+ case 'Q':
+ case 'q':
+ return 0;
+ }
+ } while (ch < ' ' || ch > 'z');
+ return 1;
+}
+
+static int
+recover_chicken(chicken_t * thechicken)
+{
+ char buf[200];
+ int price = egg_price[(int)thechicken->type], money = price + (random() % price);
+
+ if (now - thechicken->lastvisit > (60 * 60 * 24 * 7))
+ return 0;
+ outmsg(ANSI_COLOR(33;44) "★靈界守衛" ANSI_COLOR(37;45) " 別害怕 我是來幫你的 " ANSI_RESET);
+ bell();
+ igetch();
+ outmsg(ANSI_COLOR(33;44) "★靈界守衛" ANSI_COLOR(37;45) " 你無法丟到我水球 因為我是聖靈, "
+ "最近缺錢想賺外快 " ANSI_RESET);
+ bell();
+ igetch();
+ snprintf(buf, sizeof(buf), ANSI_COLOR(33;44) "★靈界守衛" ANSI_COLOR(37;45) " "
+ "你有一個剛走不久的%s要招換回來嗎? 只要%d元唷 " ANSI_RESET,
+ chicken_type[(int)thechicken->type], price * 2);
+ outmsg(buf);
+ bell();
+ getdata_str(21, 0, " 選擇:(N:坑人嘛/y:請幫幫我)", buf, 3, LCECHO, "N");
+ if (buf[0] == 'y' || buf[0] == 'Y') {
+ reload_money();
+ if (cuser.money < price * 2) {
+ outmsg(ANSI_COLOR(33;44) "★靈界守衛" ANSI_COLOR(37;45) " 什麼 錢沒帶夠 "
+ "沒錢的小鬼 快去籌錢吧 " ANSI_RESET);
+ bell();
+ igetch();
+ return 0;
+ }
+ strlcpy(thechicken->name, "[撿回來的]", sizeof(thechicken->name));
+ thechicken->hp = thechicken->hp_max;
+ thechicken->sick = 0;
+ thechicken->satis = 2;
+ vice(money, "靈界守衛");
+ snprintf(buf, sizeof(buf),
+ ANSI_COLOR(33;44) "★靈界守衛" ANSI_COLOR(37;45) " OK了 記得餵他點東西 "
+ "不然可能失效 念在我也有玩Ptt 拿你%d就好 " ANSI_RESET, money);
+ outmsg(buf);
+ bell();
+ igetch();
+ return 1;
+ }
+ outmsg(ANSI_COLOR(33;44) "★靈界守衛" ANSI_COLOR(37;45) " 竟然說我坑人! 這年頭命真不值錢 "
+ "除非我再來找你 你再也沒機會了 " ANSI_RESET);
+ bell();
+ igetch();
+ thechicken->lastvisit = 0;
+ passwd_update(usernum, &cuser);
+ return 0;
+}
+
+#define lockreturn0(unmode, state) if(lockutmpmode(unmode, state)) return 0
+
+int
+chicken_main(void)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ int age;
+ lockreturn0(CHICKEN, LOCK_MULTI);
+ reload_chicken();
+ if (!mychicken->name[0] && !recover_chicken(mychicken) && !new_chicken()) {
+ unlockutmpmode();
+ return 0;
+ }
+ age = ((now - mychicken->cbirth) / (60 * 60 * 24));
+ do {
+ time_diff(mychicken);
+ if (isdeadth(mychicken))
+ break;
+ show_chicken_data(mychicken, NULL);
+ } while (select_menu(age));
+ reload_money();
+ passwd_update(usernum, &cuser);
+ unlockutmpmode();
+ return 0;
+}
+
+int
+chickenpk(int fd)
+{
+ chicken_t *mychicken = &cuser.mychicken;
+ char mateid[IDLEN + 1], data[200], buf[200];
+ int ch = 0;
+
+ userinfo_t *uin = &SHM->uinfo[currutmp->destuip];
+ userec_t ouser;
+ chicken_t *ochicken = &ouser.mychicken;
+ int r, attmax, i, datac, duid = currutmp->destuid, catched = 0,
+ count = 0;
+
+ lockreturn0(CHICKEN, LOCK_MULTI);
+
+ strlcpy(mateid, currutmp->mateid, sizeof(mateid));
+ /* 把對手的id用local buffer記住 */
+
+ getuser(mateid, &ouser);
+ reload_chicken();
+ if (!ochicken->name[0] || !mychicken->name[0]) {
+ bell();
+ vmsg("有一方沒有寵物"); /* Ptt:妨止page時把寵物賣掉 */
+ add_io(0, 0);
+ close(fd);
+ unlockutmpmode();
+ return 0;
+ }
+ show_chicken_data(ochicken, mychicken);
+ add_io(fd, 3); /* 把fd加到igetch監視 */
+ while (1) {
+ r = random();
+ ch = igetch();
+ getuser(mateid, &ouser);
+ reload_chicken();
+ show_chicken_data(ochicken, mychicken);
+ time_diff(mychicken);
+
+ i = mychicken->attack * mychicken->hp / mychicken->hp_max;
+ for (attmax = 2; (i = i * 9 / 10); attmax++);
+
+ if (ch == I_OTHERDATA) {
+ count = 0;
+ datac = recv(fd, data, sizeof(data), 0);
+ if (datac <= 1)
+ break;
+ move(17, 0);
+ outs(data + 1);
+ switch (data[0]) {
+ case 'c':
+ catched = 1;
+ move(16, 0);
+ outs("要放他走嗎?(y/N)");
+ break;
+ case 'd':
+ move(16, 0);
+ outs("阿~倒下了!!");
+ break;
+ }
+ if (data[0] == 'd' || data[0] == 'q' || data[0] == 'l')
+ break;
+ continue;
+ } else if (currutmp->turn) {
+ count = 0;
+ currutmp->turn = 0;
+ uin->turn = 1;
+ mychicken->tiredstrong++;
+ switch (ch) {
+ case 'y':
+ if (catched == 1) {
+ snprintf(data, sizeof(data),
+ "l讓 %s 落跑了\n", ochicken->name);
+ }
+ break;
+ case 'n':
+ catched = 0;
+ default:
+ case 'k':
+ r = r % (attmax + 2);
+ if (r) {
+ snprintf(data, sizeof(data),
+ "M%s %s%s %s 傷了 %d 點\n", mychicken->name,
+ damage_degree[r / 3 > 15 ? 15 : r / 3],
+ attack_type[(int)mychicken->type],
+ ochicken->name, r);
+ ochicken->hp -= r;
+ } else
+ snprintf(data, sizeof(data),
+ "M%s 覺得手軟出擊無效\n", mychicken->name);
+ break;
+ case 'o':
+ if (mychicken->oo > 0) {
+ mychicken->oo--;
+ mychicken->hp += 300;
+ if (mychicken->hp > mychicken->hp_max)
+ mychicken->hp = mychicken->hp_max;
+ mychicken->tiredstrong = 0;
+ snprintf(data, sizeof(data), "M%s 吃了顆大補丸補充體力\n",
+ mychicken->name);
+ } else
+ snprintf(data, sizeof(data),
+ "M%s 想吃大補丸, 可是沒有大補丸可吃\n",
+ mychicken->name);
+ break;
+ case 'q':
+ if (r % (mychicken->run + 1) > r % (ochicken->run + 1))
+ snprintf(data, sizeof(data), "q%s 落跑了\n",
+ mychicken->name);
+ else
+ snprintf(data, sizeof(data),
+ "c%s 想落跑, 但被 %s 抓到了\n",
+ mychicken->name, ochicken->name);
+ break;
+ }
+ if (deadtype(ochicken)) {
+ char *p = strchr(data, '\n');
+ if(p) *p = '\0';
+ strlcpy(buf, data, sizeof(buf));
+ snprintf(data, sizeof(data), "d%s , %s 被 %s 打死了\n",
+ buf + 1, ochicken->name, mychicken->name);
+ }
+ move(17, 0);
+ outs(data + 1);
+ i = strlen(data) + 1;
+ passwd_update(duid, &ouser);
+ passwd_update(usernum, &cuser);
+ send(fd, data, i, 0);
+ if (data[0] == 'q' || data[0] == 'd')
+ break;
+ } else {
+ move(17, 0);
+ if (count++ > 30)
+ break;
+ }
+ }
+ add_io(0, 0); /* 把igetch恢復回 */
+ pressanykey();
+ close(fd);
+ showdeadth(deadtype(mychicken));
+ unlockutmpmode();
+ return 0;
+}
diff --git a/pttbbs/mbbsd/convert.c b/pttbbs/mbbsd/convert.c
new file mode 100644
index 00000000..6a102230
--- /dev/null
+++ b/pttbbs/mbbsd/convert.c
@@ -0,0 +1,118 @@
+/* $Id$ */
+#include "bbs.h"
+
+#ifdef CONVERT
+
+unsigned char *gb2big(unsigned char *, int *, int);
+unsigned char *big2gb(unsigned char *, int *, int);
+unsigned char *utf8_uni(unsigned char *, int *, int);
+unsigned char *uni_utf8(unsigned char *, int *, int);
+unsigned char *uni2big(unsigned char *, int *, int);
+unsigned char *big2uni(unsigned char *, int *, int);
+
+static ssize_t
+gb_input(void *buf, ssize_t icount)
+{
+ /* is sizeof(ssize_t) == sizeof(int)? not sure */
+ int ic = (int) icount;
+ gb2big((unsigned char *)buf, &ic, 0);
+ return (ssize_t)ic;
+}
+
+static ssize_t
+gb_read(int fd, void *buf, size_t count)
+{
+ ssize_t icount = read(fd, buf, count);
+ if (icount > 0)
+ icount = gb_input(buf, icount);
+ return icount;
+}
+
+static ssize_t
+gb_write(int fd, void *buf, size_t count)
+{
+ int icount = (int)count;
+ big2gb((unsigned char *)buf, &icount, 0);
+ if(icount > 0)
+ return write(fd, buf, (size_t)icount);
+ else
+ return count; /* fake */
+}
+
+static ssize_t
+utf8_input (void *buf, ssize_t icount)
+{
+ /* is sizeof(ssize_t) == sizeof(int)? not sure */
+ int ic = (int) icount;
+ utf8_uni(buf, &ic, 0);
+ uni2big(buf, &ic, 0);
+ return (ssize_t)ic;
+}
+
+static ssize_t
+utf8_read(int fd, void *buf, size_t count)
+{
+ ssize_t icount = read(fd, buf, count);
+ if (icount > 0)
+ icount = utf8_input(buf, icount);
+ return icount;
+}
+
+static ssize_t
+utf8_write(int fd, void *buf, size_t count)
+{
+ int icount = (int)count;
+ static unsigned char *mybuf = NULL;
+ static int cmybuf = 0;
+
+ /* utf8 output is a special case because
+ * we need larger buffer which can be
+ * tripple or more in size.
+ * Current implementation uses 128 for each block.
+ */
+
+ if(cmybuf < count * 4) {
+ cmybuf = (count*4+0x80) & (~0x7f) ;
+ mybuf = (unsigned char*) realloc (mybuf, cmybuf);
+ }
+ memcpy(mybuf, buf, count);
+ big2uni(mybuf, &icount, 0);
+ uni_utf8(mybuf, &icount, 0);
+ if(icount > 0)
+ return write(fd, mybuf, (size_t)icount);
+ else
+ return count; /* fake */
+}
+
+static ssize_t
+norm_input(void *buf, ssize_t icount)
+{
+ return icount;
+}
+
+/* global function pointers */
+read_write_type write_type = (read_write_type)write;
+read_write_type read_type = read;
+convert_type input_type = norm_input;
+
+void set_converting_type(int which)
+{
+ if (which == CONV_NORMAL) {
+ read_type = read;
+ write_type = (read_write_type)write;
+ /* for speed up, NULL is better.. */
+ input_type = NULL; /* norm_input; */
+ }
+ else if (which == CONV_GB) {
+ read_type = gb_read;
+ write_type = gb_write;
+ input_type = gb_input;
+ }
+ else if (which == CONV_UTF8) {
+ read_type = utf8_read;
+ write_type = utf8_write;
+ input_type = utf8_input;
+ }
+}
+
+#endif
diff --git a/pttbbs/mbbsd/crypt.c b/pttbbs/mbbsd/crypt.c
new file mode 100644
index 00000000..57d78533
--- /dev/null
+++ b/pttbbs/mbbsd/crypt.c
@@ -0,0 +1,647 @@
+/* $Id */
+/* This file is crypt.c taken from ssh 1.2.33, only modified for compile */
+/**
+ * FreeBSD 及 Linux glibc 附的 crypt() 都會用到大 table 加速多次 crypt().
+ * 但 bbs 僅上站檢查密碼時使用一次, 不值得為此花 100kb memory.
+ * libdes 的 crypt() 僅需 4kb 的 constant lookup table.
+ *
+ * 不過要注意 libdes 4.01 的 license 跟 GPL 不合, 因此此處採用 ssh 1.2.33 裡頭
+ * 附的 crypt.c derived from libdes 3.06, 該版的 libdes 是 GPL 的.
+ */
+#define PROTO
+/* This file is fcrypt.c taken from SSLeay-0.4.3a. This file is only compiled
+ in on those systems that don't have crypt() in the system libraries.
+ //ylo */
+
+/* fcrypt.c */
+/* Copyright (C) 1995 Eric Young (eay@mincom.oz.au).
+ * All rights reserved.
+ * Copyright remains Eric Young's, and as such any Copyright notices in
+ * the code are not to be removed.
+ * See the COPYRIGHT file in the libdes distribution for more details.
+ */
+
+#include <stdio.h>
+#define _LIBC
+
+/* Eric Young.
+ * This version of crypt has been developed from my MIT compatable
+ * DES library.
+ * The library is available at pub/Crypto/DES at ftp.psy.uq.oz.au
+ * eay@mincom.oz.au or eay@psych.psy.uq.oz.au
+ */
+
+#if !defined(_LIBC) || defined(NOCONST)
+#define const
+#endif
+
+typedef unsigned char des_cblock[8];
+
+typedef struct des_ks_struct
+ {
+ union {
+ des_cblock _;
+ /* make sure things are correct size on machines with
+ * 8 byte longs */
+ unsigned long pad[2];
+ } ks;
+#define _ ks._
+ } des_key_schedule[16];
+
+#define DES_KEY_SZ (sizeof(des_cblock))
+#define DES_ENCRYPT 1
+#define DES_DECRYPT 0
+
+#define ITERATIONS 16
+#define HALF_ITERATIONS 8
+
+#define c2l(c,l) (l =((unsigned long)(*((c)++))) , \
+ l|=((unsigned long)(*((c)++)))<< 8, \
+ l|=((unsigned long)(*((c)++)))<<16, \
+ l|=((unsigned long)(*((c)++)))<<24)
+
+#define l2c(l,c) (*((c)++)=(unsigned char)(((l) )&0xff), \
+ *((c)++)=(unsigned char)(((l)>> 8)&0xff), \
+ *((c)++)=(unsigned char)(((l)>>16)&0xff), \
+ *((c)++)=(unsigned char)(((l)>>24)&0xff))
+
+static const unsigned long SPtrans[8][64]={
+/* nibble 0 */
+{
+0x00820200, 0x00020000, 0x80800000, 0x80820200,
+0x00800000, 0x80020200, 0x80020000, 0x80800000,
+0x80020200, 0x00820200, 0x00820000, 0x80000200,
+0x80800200, 0x00800000, 0x00000000, 0x80020000,
+0x00020000, 0x80000000, 0x00800200, 0x00020200,
+0x80820200, 0x00820000, 0x80000200, 0x00800200,
+0x80000000, 0x00000200, 0x00020200, 0x80820000,
+0x00000200, 0x80800200, 0x80820000, 0x00000000,
+0x00000000, 0x80820200, 0x00800200, 0x80020000,
+0x00820200, 0x00020000, 0x80000200, 0x00800200,
+0x80820000, 0x00000200, 0x00020200, 0x80800000,
+0x80020200, 0x80000000, 0x80800000, 0x00820000,
+0x80820200, 0x00020200, 0x00820000, 0x80800200,
+0x00800000, 0x80000200, 0x80020000, 0x00000000,
+0x00020000, 0x00800000, 0x80800200, 0x00820200,
+0x80000000, 0x80820000, 0x00000200, 0x80020200,
+},
+/* nibble 1 */
+{
+0x10042004, 0x00000000, 0x00042000, 0x10040000,
+0x10000004, 0x00002004, 0x10002000, 0x00042000,
+0x00002000, 0x10040004, 0x00000004, 0x10002000,
+0x00040004, 0x10042000, 0x10040000, 0x00000004,
+0x00040000, 0x10002004, 0x10040004, 0x00002000,
+0x00042004, 0x10000000, 0x00000000, 0x00040004,
+0x10002004, 0x00042004, 0x10042000, 0x10000004,
+0x10000000, 0x00040000, 0x00002004, 0x10042004,
+0x00040004, 0x10042000, 0x10002000, 0x00042004,
+0x10042004, 0x00040004, 0x10000004, 0x00000000,
+0x10000000, 0x00002004, 0x00040000, 0x10040004,
+0x00002000, 0x10000000, 0x00042004, 0x10002004,
+0x10042000, 0x00002000, 0x00000000, 0x10000004,
+0x00000004, 0x10042004, 0x00042000, 0x10040000,
+0x10040004, 0x00040000, 0x00002004, 0x10002000,
+0x10002004, 0x00000004, 0x10040000, 0x00042000,
+},
+/* nibble 2 */
+{
+0x41000000, 0x01010040, 0x00000040, 0x41000040,
+0x40010000, 0x01000000, 0x41000040, 0x00010040,
+0x01000040, 0x00010000, 0x01010000, 0x40000000,
+0x41010040, 0x40000040, 0x40000000, 0x41010000,
+0x00000000, 0x40010000, 0x01010040, 0x00000040,
+0x40000040, 0x41010040, 0x00010000, 0x41000000,
+0x41010000, 0x01000040, 0x40010040, 0x01010000,
+0x00010040, 0x00000000, 0x01000000, 0x40010040,
+0x01010040, 0x00000040, 0x40000000, 0x00010000,
+0x40000040, 0x40010000, 0x01010000, 0x41000040,
+0x00000000, 0x01010040, 0x00010040, 0x41010000,
+0x40010000, 0x01000000, 0x41010040, 0x40000000,
+0x40010040, 0x41000000, 0x01000000, 0x41010040,
+0x00010000, 0x01000040, 0x41000040, 0x00010040,
+0x01000040, 0x00000000, 0x41010000, 0x40000040,
+0x41000000, 0x40010040, 0x00000040, 0x01010000,
+},
+/* nibble 3 */
+{
+0x00100402, 0x04000400, 0x00000002, 0x04100402,
+0x00000000, 0x04100000, 0x04000402, 0x00100002,
+0x04100400, 0x04000002, 0x04000000, 0x00000402,
+0x04000002, 0x00100402, 0x00100000, 0x04000000,
+0x04100002, 0x00100400, 0x00000400, 0x00000002,
+0x00100400, 0x04000402, 0x04100000, 0x00000400,
+0x00000402, 0x00000000, 0x00100002, 0x04100400,
+0x04000400, 0x04100002, 0x04100402, 0x00100000,
+0x04100002, 0x00000402, 0x00100000, 0x04000002,
+0x00100400, 0x04000400, 0x00000002, 0x04100000,
+0x04000402, 0x00000000, 0x00000400, 0x00100002,
+0x00000000, 0x04100002, 0x04100400, 0x00000400,
+0x04000000, 0x04100402, 0x00100402, 0x00100000,
+0x04100402, 0x00000002, 0x04000400, 0x00100402,
+0x00100002, 0x00100400, 0x04100000, 0x04000402,
+0x00000402, 0x04000000, 0x04000002, 0x04100400,
+},
+/* nibble 4 */
+{
+0x02000000, 0x00004000, 0x00000100, 0x02004108,
+0x02004008, 0x02000100, 0x00004108, 0x02004000,
+0x00004000, 0x00000008, 0x02000008, 0x00004100,
+0x02000108, 0x02004008, 0x02004100, 0x00000000,
+0x00004100, 0x02000000, 0x00004008, 0x00000108,
+0x02000100, 0x00004108, 0x00000000, 0x02000008,
+0x00000008, 0x02000108, 0x02004108, 0x00004008,
+0x02004000, 0x00000100, 0x00000108, 0x02004100,
+0x02004100, 0x02000108, 0x00004008, 0x02004000,
+0x00004000, 0x00000008, 0x02000008, 0x02000100,
+0x02000000, 0x00004100, 0x02004108, 0x00000000,
+0x00004108, 0x02000000, 0x00000100, 0x00004008,
+0x02000108, 0x00000100, 0x00000000, 0x02004108,
+0x02004008, 0x02004100, 0x00000108, 0x00004000,
+0x00004100, 0x02004008, 0x02000100, 0x00000108,
+0x00000008, 0x00004108, 0x02004000, 0x02000008,
+},
+/* nibble 5 */
+{
+0x20000010, 0x00080010, 0x00000000, 0x20080800,
+0x00080010, 0x00000800, 0x20000810, 0x00080000,
+0x00000810, 0x20080810, 0x00080800, 0x20000000,
+0x20000800, 0x20000010, 0x20080000, 0x00080810,
+0x00080000, 0x20000810, 0x20080010, 0x00000000,
+0x00000800, 0x00000010, 0x20080800, 0x20080010,
+0x20080810, 0x20080000, 0x20000000, 0x00000810,
+0x00000010, 0x00080800, 0x00080810, 0x20000800,
+0x00000810, 0x20000000, 0x20000800, 0x00080810,
+0x20080800, 0x00080010, 0x00000000, 0x20000800,
+0x20000000, 0x00000800, 0x20080010, 0x00080000,
+0x00080010, 0x20080810, 0x00080800, 0x00000010,
+0x20080810, 0x00080800, 0x00080000, 0x20000810,
+0x20000010, 0x20080000, 0x00080810, 0x00000000,
+0x00000800, 0x20000010, 0x20000810, 0x20080800,
+0x20080000, 0x00000810, 0x00000010, 0x20080010,
+},
+/* nibble 6 */
+{
+0x00001000, 0x00000080, 0x00400080, 0x00400001,
+0x00401081, 0x00001001, 0x00001080, 0x00000000,
+0x00400000, 0x00400081, 0x00000081, 0x00401000,
+0x00000001, 0x00401080, 0x00401000, 0x00000081,
+0x00400081, 0x00001000, 0x00001001, 0x00401081,
+0x00000000, 0x00400080, 0x00400001, 0x00001080,
+0x00401001, 0x00001081, 0x00401080, 0x00000001,
+0x00001081, 0x00401001, 0x00000080, 0x00400000,
+0x00001081, 0x00401000, 0x00401001, 0x00000081,
+0x00001000, 0x00000080, 0x00400000, 0x00401001,
+0x00400081, 0x00001081, 0x00001080, 0x00000000,
+0x00000080, 0x00400001, 0x00000001, 0x00400080,
+0x00000000, 0x00400081, 0x00400080, 0x00001080,
+0x00000081, 0x00001000, 0x00401081, 0x00400000,
+0x00401080, 0x00000001, 0x00001001, 0x00401081,
+0x00400001, 0x00401080, 0x00401000, 0x00001001,
+},
+/* nibble 7 */
+{
+0x08200020, 0x08208000, 0x00008020, 0x00000000,
+0x08008000, 0x00200020, 0x08200000, 0x08208020,
+0x00000020, 0x08000000, 0x00208000, 0x00008020,
+0x00208020, 0x08008020, 0x08000020, 0x08200000,
+0x00008000, 0x00208020, 0x00200020, 0x08008000,
+0x08208020, 0x08000020, 0x00000000, 0x00208000,
+0x08000000, 0x00200000, 0x08008020, 0x08200020,
+0x00200000, 0x00008000, 0x08208000, 0x00000020,
+0x00200000, 0x00008000, 0x08000020, 0x08208020,
+0x00008020, 0x08000000, 0x00000000, 0x00208000,
+0x08200020, 0x08008020, 0x08008000, 0x00200020,
+0x08208000, 0x00000020, 0x00200020, 0x08008000,
+0x08208020, 0x00200000, 0x08200000, 0x08000020,
+0x00208000, 0x00008020, 0x08008020, 0x08200000,
+0x00000020, 0x08208000, 0x00208020, 0x00000000,
+0x08000000, 0x08200020, 0x00008000, 0x00208020}};
+static const unsigned long skb[8][64]={
+/* for C bits (numbered as per FIPS 46) 1 2 3 4 5 6 */
+{
+0x00000000,0x00000010,0x20000000,0x20000010,
+0x00010000,0x00010010,0x20010000,0x20010010,
+0x00000800,0x00000810,0x20000800,0x20000810,
+0x00010800,0x00010810,0x20010800,0x20010810,
+0x00000020,0x00000030,0x20000020,0x20000030,
+0x00010020,0x00010030,0x20010020,0x20010030,
+0x00000820,0x00000830,0x20000820,0x20000830,
+0x00010820,0x00010830,0x20010820,0x20010830,
+0x00080000,0x00080010,0x20080000,0x20080010,
+0x00090000,0x00090010,0x20090000,0x20090010,
+0x00080800,0x00080810,0x20080800,0x20080810,
+0x00090800,0x00090810,0x20090800,0x20090810,
+0x00080020,0x00080030,0x20080020,0x20080030,
+0x00090020,0x00090030,0x20090020,0x20090030,
+0x00080820,0x00080830,0x20080820,0x20080830,
+0x00090820,0x00090830,0x20090820,0x20090830,
+},
+/* for C bits (numbered as per FIPS 46) 7 8 10 11 12 13 */
+{
+0x00000000,0x02000000,0x00002000,0x02002000,
+0x00200000,0x02200000,0x00202000,0x02202000,
+0x00000004,0x02000004,0x00002004,0x02002004,
+0x00200004,0x02200004,0x00202004,0x02202004,
+0x00000400,0x02000400,0x00002400,0x02002400,
+0x00200400,0x02200400,0x00202400,0x02202400,
+0x00000404,0x02000404,0x00002404,0x02002404,
+0x00200404,0x02200404,0x00202404,0x02202404,
+0x10000000,0x12000000,0x10002000,0x12002000,
+0x10200000,0x12200000,0x10202000,0x12202000,
+0x10000004,0x12000004,0x10002004,0x12002004,
+0x10200004,0x12200004,0x10202004,0x12202004,
+0x10000400,0x12000400,0x10002400,0x12002400,
+0x10200400,0x12200400,0x10202400,0x12202400,
+0x10000404,0x12000404,0x10002404,0x12002404,
+0x10200404,0x12200404,0x10202404,0x12202404,
+},
+/* for C bits (numbered as per FIPS 46) 14 15 16 17 19 20 */
+{
+0x00000000,0x00000001,0x00040000,0x00040001,
+0x01000000,0x01000001,0x01040000,0x01040001,
+0x00000002,0x00000003,0x00040002,0x00040003,
+0x01000002,0x01000003,0x01040002,0x01040003,
+0x00000200,0x00000201,0x00040200,0x00040201,
+0x01000200,0x01000201,0x01040200,0x01040201,
+0x00000202,0x00000203,0x00040202,0x00040203,
+0x01000202,0x01000203,0x01040202,0x01040203,
+0x08000000,0x08000001,0x08040000,0x08040001,
+0x09000000,0x09000001,0x09040000,0x09040001,
+0x08000002,0x08000003,0x08040002,0x08040003,
+0x09000002,0x09000003,0x09040002,0x09040003,
+0x08000200,0x08000201,0x08040200,0x08040201,
+0x09000200,0x09000201,0x09040200,0x09040201,
+0x08000202,0x08000203,0x08040202,0x08040203,
+0x09000202,0x09000203,0x09040202,0x09040203,
+},
+/* for C bits (numbered as per FIPS 46) 21 23 24 26 27 28 */
+{
+0x00000000,0x00100000,0x00000100,0x00100100,
+0x00000008,0x00100008,0x00000108,0x00100108,
+0x00001000,0x00101000,0x00001100,0x00101100,
+0x00001008,0x00101008,0x00001108,0x00101108,
+0x04000000,0x04100000,0x04000100,0x04100100,
+0x04000008,0x04100008,0x04000108,0x04100108,
+0x04001000,0x04101000,0x04001100,0x04101100,
+0x04001008,0x04101008,0x04001108,0x04101108,
+0x00020000,0x00120000,0x00020100,0x00120100,
+0x00020008,0x00120008,0x00020108,0x00120108,
+0x00021000,0x00121000,0x00021100,0x00121100,
+0x00021008,0x00121008,0x00021108,0x00121108,
+0x04020000,0x04120000,0x04020100,0x04120100,
+0x04020008,0x04120008,0x04020108,0x04120108,
+0x04021000,0x04121000,0x04021100,0x04121100,
+0x04021008,0x04121008,0x04021108,0x04121108,
+},
+/* for D bits (numbered as per FIPS 46) 1 2 3 4 5 6 */
+{
+0x00000000,0x10000000,0x00010000,0x10010000,
+0x00000004,0x10000004,0x00010004,0x10010004,
+0x20000000,0x30000000,0x20010000,0x30010000,
+0x20000004,0x30000004,0x20010004,0x30010004,
+0x00100000,0x10100000,0x00110000,0x10110000,
+0x00100004,0x10100004,0x00110004,0x10110004,
+0x20100000,0x30100000,0x20110000,0x30110000,
+0x20100004,0x30100004,0x20110004,0x30110004,
+0x00001000,0x10001000,0x00011000,0x10011000,
+0x00001004,0x10001004,0x00011004,0x10011004,
+0x20001000,0x30001000,0x20011000,0x30011000,
+0x20001004,0x30001004,0x20011004,0x30011004,
+0x00101000,0x10101000,0x00111000,0x10111000,
+0x00101004,0x10101004,0x00111004,0x10111004,
+0x20101000,0x30101000,0x20111000,0x30111000,
+0x20101004,0x30101004,0x20111004,0x30111004,
+},
+/* for D bits (numbered as per FIPS 46) 8 9 11 12 13 14 */
+{
+0x00000000,0x08000000,0x00000008,0x08000008,
+0x00000400,0x08000400,0x00000408,0x08000408,
+0x00020000,0x08020000,0x00020008,0x08020008,
+0x00020400,0x08020400,0x00020408,0x08020408,
+0x00000001,0x08000001,0x00000009,0x08000009,
+0x00000401,0x08000401,0x00000409,0x08000409,
+0x00020001,0x08020001,0x00020009,0x08020009,
+0x00020401,0x08020401,0x00020409,0x08020409,
+0x02000000,0x0A000000,0x02000008,0x0A000008,
+0x02000400,0x0A000400,0x02000408,0x0A000408,
+0x02020000,0x0A020000,0x02020008,0x0A020008,
+0x02020400,0x0A020400,0x02020408,0x0A020408,
+0x02000001,0x0A000001,0x02000009,0x0A000009,
+0x02000401,0x0A000401,0x02000409,0x0A000409,
+0x02020001,0x0A020001,0x02020009,0x0A020009,
+0x02020401,0x0A020401,0x02020409,0x0A020409,
+},
+/* for D bits (numbered as per FIPS 46) 16 17 18 19 20 21 */
+{
+0x00000000,0x00000100,0x00080000,0x00080100,
+0x01000000,0x01000100,0x01080000,0x01080100,
+0x00000010,0x00000110,0x00080010,0x00080110,
+0x01000010,0x01000110,0x01080010,0x01080110,
+0x00200000,0x00200100,0x00280000,0x00280100,
+0x01200000,0x01200100,0x01280000,0x01280100,
+0x00200010,0x00200110,0x00280010,0x00280110,
+0x01200010,0x01200110,0x01280010,0x01280110,
+0x00000200,0x00000300,0x00080200,0x00080300,
+0x01000200,0x01000300,0x01080200,0x01080300,
+0x00000210,0x00000310,0x00080210,0x00080310,
+0x01000210,0x01000310,0x01080210,0x01080310,
+0x00200200,0x00200300,0x00280200,0x00280300,
+0x01200200,0x01200300,0x01280200,0x01280300,
+0x00200210,0x00200310,0x00280210,0x00280310,
+0x01200210,0x01200310,0x01280210,0x01280310,
+},
+/* for D bits (numbered as per FIPS 46) 22 23 24 25 27 28 */
+{
+0x00000000,0x04000000,0x00040000,0x04040000,
+0x00000002,0x04000002,0x00040002,0x04040002,
+0x00002000,0x04002000,0x00042000,0x04042000,
+0x00002002,0x04002002,0x00042002,0x04042002,
+0x00000020,0x04000020,0x00040020,0x04040020,
+0x00000022,0x04000022,0x00040022,0x04040022,
+0x00002020,0x04002020,0x00042020,0x04042020,
+0x00002022,0x04002022,0x00042022,0x04042022,
+0x00000800,0x04000800,0x00040800,0x04040800,
+0x00000802,0x04000802,0x00040802,0x04040802,
+0x00002800,0x04002800,0x00042800,0x04042800,
+0x00002802,0x04002802,0x00042802,0x04042802,
+0x00000820,0x04000820,0x00040820,0x04040820,
+0x00000822,0x04000822,0x00040822,0x04040822,
+0x00002820,0x04002820,0x00042820,0x04042820,
+0x00002822,0x04002822,0x00042822,0x04042822,
+}
+};
+
+/* See ecb_encrypt.c for a pseudo description of these macros. */
+#define PERM_OP(a,b,t,n,m) ((t)=((((a)>>(n))^(b))&(m)),\
+ (b)^=(t),\
+ (a)^=((t)<<(n)))
+
+#define HPERM_OP(a,t,n,m) ((t)=((((a)<<(16-(n)))^(a))&(m)),\
+ (a)=(a)^(t)^(t>>(16-(n))))\
+
+static const char shifts2[16]={0,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0};
+
+#ifdef PROTO
+static int body(unsigned long *out0, unsigned long *out1, des_key_schedule ks, unsigned long Eswap0, unsigned long Eswap1);
+static int des_set_key(des_cblock (*key), struct des_ks_struct *schedule);
+#else
+static int body();
+static int des_set_key();
+#endif
+
+static int des_set_key(key, schedule)
+des_cblock (*key);
+struct des_ks_struct *schedule;
+ {
+ register unsigned long c,d,t,s;
+ register unsigned char *in;
+ register unsigned long *k;
+ register int i;
+
+ k=(unsigned long *)schedule;
+ in=(unsigned char *)key;
+
+ c2l(in,c);
+ c2l(in,d);
+
+ /* I now do it in 47 simple operations :-)
+ * Thanks to John Fletcher (john_fletcher@lccmail.ocf.llnl.gov)
+ * for the inspiration. :-) */
+ PERM_OP (d,c,t,4,0x0f0f0f0f);
+ HPERM_OP(c,t,-2,0xcccc0000);
+ HPERM_OP(d,t,-2,0xcccc0000);
+ PERM_OP (d,c,t,1,0x55555555);
+ PERM_OP (c,d,t,8,0x00ff00ff);
+ PERM_OP (d,c,t,1,0x55555555);
+ d= (((d&0x000000ff)<<16)| (d&0x0000ff00) |
+ ((d&0x00ff0000)>>16)|((c&0xf0000000)>>4));
+ c&=0x0fffffff;
+
+ for (i=0; i<ITERATIONS; i++)
+ {
+ if (shifts2[i])
+ { c=((c>>2)|(c<<26)); d=((d>>2)|(d<<26)); }
+ else
+ { c=((c>>1)|(c<<27)); d=((d>>1)|(d<<27)); }
+ c&=0x0fffffff;
+ d&=0x0fffffff;
+ /* could be a few less shifts but I am to lazy at this
+ * point in time to investigate */
+ s= skb[0][ (c )&0x3f ]|
+ skb[1][((c>> 6)&0x03)|((c>> 7)&0x3c)]|
+ skb[2][((c>>13)&0x0f)|((c>>14)&0x30)]|
+ skb[3][((c>>20)&0x01)|((c>>21)&0x06) |
+ ((c>>22)&0x38)];
+ t= skb[4][ (d )&0x3f ]|
+ skb[5][((d>> 7)&0x03)|((d>> 8)&0x3c)]|
+ skb[6][ (d>>15)&0x3f ]|
+ skb[7][((d>>21)&0x0f)|((d>>22)&0x30)];
+
+ /* table contained 0213 4657 */
+ *(k++)=((t<<16)|(s&0x0000ffff))&0xffffffff;
+ s= ((s>>16)|(t&0xffff0000));
+
+ s=(s<<4)|(s>>28);
+ *(k++)=s&0xffffffff;
+ }
+ return(0);
+ }
+
+/******************************************************************
+ * modified stuff for crypt.
+ ******************************************************************/
+
+/* The changes to this macro may help or hinder, depending on the
+ * compiler and the achitecture. gcc2 always seems to do well :-).
+ * Inspired by Dana How <how@isl.stanford.edu>
+ * DO NOT use the alternative version on machines with 8 byte longs.
+ */
+#ifdef ALT_ECB
+#define D_ENCRYPT(L,R,S) \
+ t=(R^(R>>16)); \
+ u=(t&E0); \
+ t=(t&E1); \
+ u=((u^(u<<16))^R^s[S ])<<2; \
+ t=(t^(t<<16))^R^s[S+1]; \
+ t=(t>>2)|(t<<30); \
+ L^= \
+ *(unsigned long *)(des_SP+0x0100+((t )&0xfc))+ \
+ *(unsigned long *)(des_SP+0x0300+((t>> 8)&0xfc))+ \
+ *(unsigned long *)(des_SP+0x0500+((t>>16)&0xfc))+ \
+ *(unsigned long *)(des_SP+0x0700+((t>>24)&0xfc))+ \
+ *(unsigned long *)(des_SP+ ((u )&0xfc))+ \
+ *(unsigned long *)(des_SP+0x0200+((u>> 8)&0xfc))+ \
+ *(unsigned long *)(des_SP+0x0400+((u>>16)&0xfc))+ \
+ *(unsigned long *)(des_SP+0x0600+((u>>24)&0xfc));
+#else /* original version */
+#define D_ENCRYPT(L,R,S) \
+ t=(R^(R>>16)); \
+ u=(t&E0); \
+ t=(t&E1); \
+ u=(u^(u<<16))^R^s[S ]; \
+ t=(t^(t<<16))^R^s[S+1]; \
+ t=(t>>4)|(t<<28); \
+ L^= SPtrans[1][(t )&0x3f]| \
+ SPtrans[3][(t>> 8)&0x3f]| \
+ SPtrans[5][(t>>16)&0x3f]| \
+ SPtrans[7][(t>>24)&0x3f]| \
+ SPtrans[0][(u )&0x3f]| \
+ SPtrans[2][(u>> 8)&0x3f]| \
+ SPtrans[4][(u>>16)&0x3f]| \
+ SPtrans[6][(u>>24)&0x3f];
+#endif
+
+static unsigned const char con_salt[128]={
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
+0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,
+0x0A,0x0B,0x05,0x06,0x07,0x08,0x09,0x0A,
+0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,
+0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,
+0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,
+0x23,0x24,0x25,0x20,0x21,0x22,0x23,0x24,
+0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,
+0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0x34,
+0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,
+0x3D,0x3E,0x3F,0x00,0x00,0x00,0x00,0x00,
+};
+
+static unsigned const char cov_2char[64]={
+0x2E,0x2F,0x30,0x31,0x32,0x33,0x34,0x35,
+0x36,0x37,0x38,0x39,0x41,0x42,0x43,0x44,
+0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,
+0x4D,0x4E,0x4F,0x50,0x51,0x52,0x53,0x54,
+0x55,0x56,0x57,0x58,0x59,0x5A,0x61,0x62,
+0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,
+0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,
+0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A
+};
+
+#ifdef PERL5
+char *des_crypt(buf,salt)
+#else
+char *crypt(buf, salt)
+char *buf;
+char *salt;
+
+
+#endif
+
+
+ {
+ unsigned int i,j,x,y;
+ unsigned long Eswap0=0,Eswap1=0;
+ unsigned long out[2],ll;
+ des_cblock key;
+ des_key_schedule ks;
+ static unsigned char buff[20];
+ unsigned char bb[9];
+ unsigned char *b=bb;
+ unsigned char c,u;
+
+ /* eay 25/08/92
+ * If you call crypt("pwd","*") as often happens when you
+ * have * as the pwd field in /etc/passwd, the function
+ * returns *\0XXXXXXXXX
+ * The \0 makes the string look like * so the pwd "*" would
+ * crypt to "*". This was found when replacing the crypt in
+ * our shared libraries. People found that the disbled
+ * accounts effectivly had no passwd :-(. */
+ x=buff[0]=((salt[0] == '\0')?'A':salt[0]);
+ Eswap0=con_salt[x];
+ x=buff[1]=((salt[1] == '\0')?'A':salt[1]);
+ Eswap1=con_salt[x]<<4;
+
+ for (i=0; i<8; i++)
+ {
+ c= *(buf++);
+ if (!c) break;
+ key[i]=(c<<1);
+ }
+ for (; i<8; i++)
+ key[i]=0;
+
+ des_set_key((des_cblock *)(key),ks);
+ body(&(out[0]),&(out[1]),ks,Eswap0,Eswap1);
+
+ ll=out[0]; l2c(ll,b);
+ ll=out[1]; l2c(ll,b);
+ y=0;
+ u=0x80;
+ bb[8]=0;
+ for (i=2; i<13; i++)
+ {
+ c=0;
+ for (j=0; j<6; j++)
+ {
+ c<<=1;
+ if (bb[y] & u) c|=1;
+ u>>=1;
+ if (!u)
+ {
+ y++;
+ u=0x80;
+ }
+ }
+ buff[i]=cov_2char[c];
+ }
+ buff[13]='\0';
+ return((char *)buff);
+ }
+
+static int body(out0, out1, ks, Eswap0, Eswap1)
+unsigned long *out0;
+unsigned long *out1;
+des_key_schedule ks;
+unsigned long Eswap0;
+unsigned long Eswap1;
+ {
+ register unsigned long l,r,t,u;
+#ifdef ALT_ECB
+ register unsigned char *des_SP=(unsigned char *)SPtrans;
+#endif
+ register unsigned long *s;
+ register int i,j;
+ register unsigned long E0,E1;
+
+ l=0;
+ r=0;
+
+ s=(unsigned long *)ks;
+ E0=Eswap0;
+ E1=Eswap1;
+
+ for (j=0; j<25; j++)
+ {
+ for (i=0; i<(ITERATIONS*2); i+=4)
+ {
+ D_ENCRYPT(l,r, i); /* 1 */
+ D_ENCRYPT(r,l, i+2); /* 2 */
+ }
+ t=l;
+ l=r;
+ r=t;
+ }
+ t=r;
+ r=(l>>1)|(l<<31);
+ l=(t>>1)|(t<<31);
+ /* clear the top bits on machines with 8byte longs */
+ l&=0xffffffff;
+ r&=0xffffffff;
+
+ PERM_OP(r,l,t, 1,0x55555555);
+ PERM_OP(l,r,t, 8,0x00ff00ff);
+ PERM_OP(r,l,t, 2,0x33333333);
+ PERM_OP(l,r,t,16,0x0000ffff);
+ PERM_OP(r,l,t, 4,0x0f0f0f0f);
+
+ *out0=l;
+ *out1=r;
+ return(0);
+ }
+
diff --git a/pttbbs/mbbsd/dark.c b/pttbbs/mbbsd/dark.c
new file mode 100644
index 00000000..f19184dc
--- /dev/null
+++ b/pttbbs/mbbsd/dark.c
@@ -0,0 +1,565 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define RED 1
+#define BLACK 0
+typedef short int sint;
+
+typedef struct item {
+ short int color, value, die, out;
+} item;
+
+typedef struct cur {
+ short int y, x, end;
+} cur;
+
+struct DarkData {
+ item brd[4][8];
+ cur curr;
+ sint rcount, bcount, cont, fix; /* cont:是否可連吃 */
+ sint my, mx, mly, mlx; /* 移動的座標 標 */
+
+ sint cur_eaty, cur_eatx; /* 吃掉對方其子的秀出座標 */
+};
+
+static char * const rname[] = {"兵", "炮", "傌", "車", "相", "仕", "帥"};
+static char * const bname[] = {"卒", "包", "馬", "車", "象", "士", "將"};
+
+static const sint cury[] = {3, 5, 7, 9}, curx[] = {5, 9, 13, 17, 21, 25, 29, 33};
+
+static void
+brdswap(struct DarkData *dd, sint y, sint x, sint ly, sint lx)
+{
+ memcpy(&dd->brd[y][x], &dd->brd[ly][lx], sizeof(item));
+ dd->brd[ly][lx].die = 1;
+ dd->brd[ly][lx].color = -1; /* 沒這個color */
+ dd->brd[ly][lx].value = -1;
+}
+
+static sint
+Is_win(struct DarkData *dd, item att, item det, sint y, sint x, sint ly, sint lx)
+{
+ sint i, c = 0, min, max;
+ if (att.value == 1) { /* 砲 */
+ if (y != ly && x != lx)
+ return 0;
+ if ((abs(ly - y) == 1 && dd->brd[y][x].die == 0) ||
+ (abs(lx - x) == 1 && dd->brd[y][x].die == 0))
+ return 0;
+ if (y == ly) {
+ if (x > lx) {
+ max = x;
+ min = lx;
+ } else {
+ max = lx;
+ min = x;
+ }
+ for (i = min + 1; i < max; i++)
+ if (dd->brd[y][i].die == 0)
+ c++;
+ } else if (x == lx) {
+ if (y > ly) {
+ max = y;
+ min = ly;
+ } else {
+ max = ly;
+ min = y;
+ }
+ for (i = min + 1; i < max; i++)
+ if (dd->brd[i][x].die == 0)
+ c++;
+ }
+ if (c != 1)
+ return 0;
+ if (det.die == 1)
+ return 0;
+ return 1;
+ }
+ /* 非砲 */
+ if (((abs(ly - y) == 1 && x == lx) || (abs(lx - x) == 1 && ly == y)) && dd->brd[y][x].out == 1) {
+ if (att.value == 0 && det.value == 6)
+ return 1;
+ else if (att.value == 6 && det.value == 0)
+ return 0;
+ else if (att.value >= det.value)
+ return 1;
+ else
+ return 0;
+ }
+ return 0;
+}
+
+static sint
+Is_move(struct DarkData *dd, sint y, sint x, sint ly, sint lx)
+{
+ if (dd->brd[y][x].die == 1 && ((abs(ly - y) == 1 && x == lx) || (abs(lx - x) == 1 && ly == y)))
+ return 1;
+ return 0;
+}
+
+static void
+brd_rand(struct DarkData *dd)
+{
+ sint y, x, index;
+ sint tem[32];
+ sint value[32] = {
+ 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6,
+ 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6
+ };
+
+ bzero(dd->brd, sizeof(dd->brd));
+ bzero(tem, sizeof(tem));
+ bzero(&dd->curr, sizeof(dd->curr));
+ for (y = 0; y < 4; y++)
+ for (x = 0; x < 8; x++)
+ while (1) {
+ index = random() % 32;
+ if (tem[index])
+ continue;
+ dd->brd[y][x].color = (index > 15) ? 0 : 1;
+ dd->brd[y][x].value = value[index];
+ tem[index] = 1;
+ break;
+ }
+}
+
+static void
+brd_prints(void)
+{
+ clear();
+ move(1, 0);
+ outs("\n"
+ " " ANSI_COLOR(43;30) "╭─┬─┬─┬─┬─┬─┬─┬─╮" ANSI_RESET "\n"
+ " " ANSI_COLOR(43;30) "│●│●│●│●│●│●│●│●│" ANSI_RESET "\n"
+ " " ANSI_COLOR(43;30) "├─┼─┼─┼─┼─┼─┼─┼─┤" ANSI_RESET "\n"
+ " " ANSI_COLOR(43;30) "│●│●│●│●│●│●│●│●│" ANSI_RESET "\n"
+ " " ANSI_COLOR(43;30) "├─┼─┼─┼─┼─┼─┼─┼─┤" ANSI_RESET "\n"
+ " " ANSI_COLOR(43;30) "│●│●│●│●│●│●│●│●│" ANSI_RESET "\n"
+ " " ANSI_COLOR(43;30) "├─┼─┼─┼─┼─┼─┼─┼─┤" ANSI_RESET "\n"
+ " " ANSI_COLOR(43;30) "│●│●│●│●│●│●│●│●│" ANSI_RESET "\n"
+ " " ANSI_COLOR(43;30) "╰─┴─┴─┴─┴─┴─┴─┴─╯" ANSI_RESET "\n"
+ " ");
+}
+
+static void
+draw_line(struct DarkData *dd, sint y, sint f)
+{
+ sint i;
+ char buf[1024], tmp[256];
+
+ *buf = 0;
+ *tmp = 0;
+ strlcpy(buf, ANSI_COLOR(43;30), sizeof(buf));
+ for (i = 0; i < 8; i++) {
+ if (dd->brd[y][i].die == 1)
+ snprintf(tmp, sizeof(tmp), "│ ");
+ else if (dd->brd[y][i].out == 0)
+ snprintf(tmp, sizeof(tmp), "│●");
+ else {
+ snprintf(tmp, sizeof(tmp), "│" ANSI_COLOR(%s1;%d) "%s" ANSI_RESET ANSI_COLOR(43;30) "",
+ (f == i) ? "1;47;" : "", (dd->brd[y][i].color) ? 31 : 34,
+ (dd->brd[y][i].color) ? rname[dd->brd[y][i].value] :
+ bname[dd->brd[y][i].value]);
+ }
+ strcat(buf, tmp);
+ }
+ strcat(buf, "│" ANSI_RESET);
+
+ move(cury[y], 3);
+ clrtoeol();
+ outs(buf);
+}
+
+static void
+redraw(struct DarkData *dd)
+{
+ sint i = 0;
+ for (; i < 4; i++)
+ draw_line(dd, i, -1);
+}
+
+static sint
+playing(struct DarkData *dd, sint fd, sint color, sint ch, sint * b, userinfo_t * uin)
+{
+ dd->curr.end = 0;
+ move(cury[dd->my], curx[dd->mx]);
+
+ if (dd->fix) {
+ if (ch == 's') {
+ dd->fix = 0;
+ *b = 0;
+ return 0;
+ } else {
+ draw_line(dd, dd->mly, -1);
+ }
+ }
+ switch (ch) {
+ case KEY_LEFT:
+ if (dd->mx == 0)
+ dd->mx = 7;
+ else
+ dd->mx--;
+ move(cury[dd->my], curx[dd->mx]);
+ *b = -1;
+ break;
+ case KEY_RIGHT:
+ if (dd->mx == 7)
+ dd->mx = 0;
+ else
+ dd->mx++;
+ move(cury[dd->my], curx[dd->mx]);
+ *b = -1;
+ break;
+ case KEY_UP:
+ if (dd->my == 0)
+ dd->my = 3;
+ else
+ dd->my--;
+ move(cury[dd->my], curx[dd->mx]);
+ *b = -1;
+ break;
+ case KEY_DOWN:
+ if (dd->my == 3)
+ dd->my = 0;
+ else
+ dd->my++;
+ move(cury[dd->my], curx[dd->mx]);
+ *b = -1;
+ break;
+ case 'q':
+ case 'Q':
+ if (!color)
+ dd->bcount = 0;
+ else
+ dd->rcount = 0;
+ *b = 0;
+ return -2;
+ case 'p':
+ case 'P':
+ return -3;
+ case 'c':
+ return -4;
+ case 'g':
+ return -5;
+ case 's': /* 翻開棋子 或是選擇棋子 */
+ /* 選擇棋子 */
+ if (dd->brd[dd->my][dd->mx].out == 1) {
+ if (dd->brd[dd->my][dd->mx].color != color) {
+ *b = -1;
+ break;
+ }
+ if (dd->mly < 0) { /* 可以選擇 */
+ dd->mly = dd->my;
+ dd->mlx = dd->mx;
+ draw_line(dd, dd->my, dd->mx);
+ *b = -1;
+ break;
+ } else if (dd->mly == dd->my && dd->mlx == dd->mx) { /* 不選了 */
+ dd->mly = -1;
+ dd->mlx = -1;
+ draw_line(dd, dd->my, -1);
+ } else {
+ draw_line(dd, dd->mly, -1);
+ dd->mly = dd->my;
+ dd->mlx = dd->mx;
+ if (dd->brd[dd->mly][dd->mlx].value == 1)
+ dd->fix = 1;
+ draw_line(dd, dd->my, dd->mx);
+ }
+ *b = -1;
+ break;
+ }
+ /* 翻開棋子 */
+ if (dd->mly >= 0) {
+ *b = -1;
+ break;
+ } /* 本來就是翻開的 */
+ /* 決定一開始的顏色 */
+ if (currutmp->color == '.') {
+ if (uin->color != '1' && uin->color != '0')
+ currutmp->color = (dd->brd[dd->my][dd->mx].color) ? '1' : '0';
+ else
+ currutmp->color = (uin->color == '0') ? '1' : '0';
+ }
+ dd->brd[dd->my][dd->mx].out = 1;
+ draw_line(dd, dd->my, -1);
+ move(cury[dd->my], curx[dd->mx]);
+ *b = 0;
+ break;
+ case 'u':
+ move(0, 0);
+ clrtoeol();
+ prints("%s色%s cont=%d",
+ (dd->brd[dd->my][dd->mx].color == RED) ? "紅" : "黑",
+ rname[dd->brd[dd->my][dd->mx].value], dd->cont);
+ *b = -1;
+ break;
+ case '\r': /* 吃 or 移動 ly跟lx必須大於0 */
+ case '\n':
+ if (
+ dd->mly >= 0 /* 要先選子 */
+ &&
+ dd->brd[dd->mly][dd->mlx].color != dd->brd[dd->my][dd->mx].color /* 同色不能移動也不能吃 */
+ &&
+ (Is_move(dd, dd->my, dd->mx, dd->mly, dd->mlx) ||
+ Is_win(dd, dd->brd[dd->mly][dd->mlx], dd->brd[dd->my][dd->mx], dd->my, dd->mx, dd->mly, dd->mlx))
+ ) {
+ if (dd->fix && dd->brd[dd->my][dd->mx].value < 0) {
+ *b = -1;
+ return 0;
+ }
+ if (dd->brd[dd->my][dd->mx].value >= 0 && dd->brd[dd->my][dd->mx].die == 0) {
+ if (!color)
+ dd->bcount--;
+ else
+ dd->rcount--;
+ move(dd->cur_eaty, dd->cur_eatx);
+ if(color)
+ outs(bname[dd->brd[dd->my][dd->mx].value]);
+ else
+ outs(rname[dd->brd[dd->my][dd->mx].value]);
+ if (dd->cur_eatx >= 26) {
+ dd->cur_eatx = 5;
+ dd->cur_eaty++;
+ } else
+ dd->cur_eatx += 3;
+ }
+ brdswap(dd, dd->my, dd->mx, dd->mly, dd->mlx);
+ draw_line(dd, dd->mly, -1);
+ draw_line(dd, dd->my, -1);
+ if (dd->fix == 1)
+ *b = -1;
+ else {
+ dd->mly = -1;
+ dd->mlx = -1;
+ *b = 0;
+ }
+ } else
+ *b = -1;
+ break;
+ default:
+ *b = -1;
+ }
+
+ if (!dd->rcount)
+ return -1;
+ else if (!dd->bcount)
+ return -1;
+ if (*b == -1)
+ return 0;
+ dd->curr.y = dd->my;
+ dd->curr.x = dd->mx;
+ dd->curr.end = (!*b) ? 1 : 0;
+ send(fd, &dd->curr, sizeof(dd->curr), 0);
+ send(fd, &dd->brd, sizeof(dd->brd), 0);
+ return 0;
+}
+
+int
+main_dark(int fd, userinfo_t * uin)
+{
+ sint end = 0, ch = 1, i = 0;
+ char buf[16];
+ struct DarkData dd;
+
+ memset(&dd, 0, sizeof(dd));
+ dd.my=dd.mx=0;
+ dd.mly=dd.mlx=-1;
+
+ *buf = 0;
+ dd.fix = 0;
+ currutmp->color = '.';
+ /* '.' 表示還沒決定顏色 */
+ dd.rcount = 16;
+ dd.bcount = 16;
+ //initialize
+ dd.cur_eaty = 18, dd.cur_eatx = 5;
+ setutmpmode(DARK);
+ brd_prints();
+ if (currutmp->turn) {
+ brd_rand(&dd);
+ send(fd, &dd.brd, sizeof(dd.brd), 0);
+ mouts(21, 0, " " ANSI_COLOR(1;37) ANSI_COLOR(1;33) "◆" ANSI_COLOR(1;37) "你是先手" ANSI_RESET);
+ mouts(22, 0, " " ANSI_COLOR(1;33) "◆" ANSI_COLOR(5;35) "輪到你下了" ANSI_RESET);
+ } else {
+ recv(fd, &dd.brd, sizeof(dd.brd), 0);
+ mouts(21, 0, " " ANSI_COLOR(1;33) "◆" ANSI_COLOR(1;37) "你是後手" ANSI_RESET);
+ }
+ move(12, 3);
+ prints("%s[0勝0敗]" ANSI_COLOR(5;31) "vs" ANSI_COLOR(1;37) "." ANSI_RESET "%s[0勝0敗]", currutmp->userid, currutmp->mateid);
+ outs("\n"
+ " " ANSI_COLOR(1;36) "╳╱" ANSI_COLOR(1;31) "功\能表" ANSI_COLOR(1;36) "╲╳╲╱╳╲" ANSI_RESET "\n"
+ " " ANSI_COLOR(1;36) "╱" ANSI_COLOR(1;33) " ↑←↓→" ANSI_COLOR(1;37) ": " ANSI_COLOR(1;35) "移動" ANSI_RESET "\n"
+ " " ANSI_COLOR(1;36) "╳" ANSI_COLOR(1;33) " s" ANSI_COLOR(1;37) ": " ANSI_COLOR(1;35) " 選子,翻子" ANSI_RESET "\n"
+ " " ANSI_COLOR(1;36) "╱" ANSI_COLOR(1;33) " enter" ANSI_COLOR(1;37) ": " ANSI_COLOR(1;35) " 吃棋,放棋" ANSI_RESET "\n"
+ " " ANSI_COLOR(1;33) "已經解決的" ANSI_COLOR(1;37) ":" ANSI_COLOR(1;36) "   ╳" ANSI_COLOR(1;33) " p" ANSI_COLOR(1;37) ": " ANSI_COLOR(1;35) " 合棋" ANSI_RESET "\n"
+ "    " ANSI_COLOR(1;36) "╱" ANSI_COLOR(1;33) " q" ANSI_COLOR(1;37) ": " ANSI_COLOR(1;35) " 認輸" ANSI_RESET "\n"
+ " " ANSI_COLOR(1;36) "╳" ANSI_COLOR(1;33) " c" ANSI_COLOR(1;37) ": " ANSI_COLOR(1;35) " 換邊" ANSI_RESET);
+
+ if (currutmp->turn)
+ move(cury[0], curx[0]);
+
+ add_io(fd, 0);
+ while (end <= 0) {
+ if (uin->turn == 'w' || currutmp->turn == 'w') {
+ end = -1;
+ break;
+ }
+ ch = igetch();
+ if (ch == I_OTHERDATA) {
+ ch = recv(fd, &dd.curr, sizeof(dd.curr), 0);
+ if (ch != sizeof(dd.curr)) {
+ if (uin->turn == 'e') {
+ end = -3;
+ break;
+ } else if (uin->turn != 'w') {
+ end = -1;
+ currutmp->turn = 'w';
+ break;
+ }
+ end = -1;
+ break;
+ }
+ if (dd.curr.end == -3)
+ mouts(23, 30, ANSI_COLOR(33) "要求合棋" ANSI_RESET);
+ else if (dd.curr.end == -4)
+ mouts(23, 30, ANSI_COLOR(33) "要求換邊" ANSI_RESET);
+ else if (dd.curr.end == -5)
+ mouts(23, 30, ANSI_COLOR(33) "要求連吃" ANSI_RESET);
+ else
+ mouts(23, 30, "");
+
+ recv(fd, &dd.brd, sizeof(dd.brd), 0);
+ dd.my = dd.curr.y;
+ dd.mx = dd.curr.x;
+ redraw(&dd);
+ if (dd.curr.end)
+ mouts(22, 0, " " ANSI_COLOR(1;33) "◆" ANSI_COLOR(5;35) "輪到你下了" ANSI_RESET);
+ move(cury[dd.my], curx[dd.mx]);
+ } else {
+ if (currutmp->turn == 'p') {
+ if (ch == 'y') {
+ end = -3;
+ currutmp->turn = 'e';
+ break;
+ } else {
+ mouts(23, 30, "");
+ *buf = 0;
+ currutmp->turn = (uin->turn) ? 0 : 1;
+ }
+ } else if (currutmp->turn == 'c') {
+ if (ch == 'y') {
+ currutmp->color = (currutmp->color == '1') ? '0' : '1';
+ uin->color = (uin->color == '1') ? '0' : '1';
+ mouts(21, 0, (currutmp->color == '1') ? " " ANSI_COLOR(1;33) "◆" ANSI_COLOR(1;31) "你持紅色棋" ANSI_RESET : " " ANSI_COLOR(1;33) "◆" ANSI_COLOR(1;36) "你持黑色棋" ANSI_RESET);
+ } else {
+ mouts(23, 30, "");
+ currutmp->turn = (uin->turn) ? 0 : 1;
+ }
+ } else if (currutmp->turn == 'g') {
+ if (ch == 'y') {
+ dd.cont = 1;
+ mouts(21, 0, " " ANSI_COLOR(1;33) "◆" ANSI_COLOR(1;31) "你持紅色棋" ANSI_RESET " 可連吃");
+ } else {
+ mouts(23, 30, "");
+ currutmp->turn = (uin->turn) ? 0 : 1;
+ }
+ }
+ if (currutmp->turn == 1) {
+ sint go_on = 0;
+ if (uin->turn == 'g') {
+ dd.cont = 1;
+ uin->turn = (currutmp->turn) ? 0 : 1;
+ mouts(21, 10, "可連吃");
+ }
+ end = playing(&dd, fd, currutmp->color - '0', ch, &go_on, uin);
+
+ if (end == -1) {
+ currutmp->turn = 'w';
+ break;
+ } else if (end == -2) {
+ uin->turn = 'w';
+ break;
+ } else if (end == -3) {
+ uin->turn = 'p';
+ dd.curr.end = -3;
+ send(fd, &dd.curr, sizeof(dd.curr), 0);
+ send(fd, &dd.brd, sizeof(buf), 0);
+ continue;
+ } else if (end == -4) {
+ if (currutmp->color != '1' && currutmp->color != '0')
+ continue;
+ uin->turn = 'c';
+ i = 0;
+ dd.curr.end = -4;
+ send(fd, &dd.curr, sizeof(dd.curr), 0);
+ send(fd, &dd.brd, sizeof(buf), 0);
+ continue;
+ } else if (end == -5) {
+ uin->turn = 'g';
+ dd.curr.end = -5;
+ send(fd, &dd.curr, sizeof(dd.curr), 0);
+ send(fd, &dd.brd, sizeof(buf), 0);
+ continue;
+ }
+ if (!i && currutmp->color == '1') {
+ mouts(21, 0, " " ANSI_COLOR(1;33) "◆" ANSI_COLOR(1;31) "你持紅色棋" ANSI_RESET);
+ i++;
+ move(cury[dd.my], curx[dd.mx]);
+ }
+ if (!i && currutmp->color == '0') {
+ mouts(21, 0, " " ANSI_COLOR(1;33) "◆" ANSI_COLOR(1;36) "你持黑色棋" ANSI_RESET);
+ i++;
+ move(cury[dd.my], curx[dd.mx]);
+ }
+ if (uin->turn == 'e') {
+ end = -3;
+ break;
+ }
+ if (go_on < 0)
+ continue;
+
+ move(22, 0);
+ clrtoeol();
+ prints(" " ANSI_COLOR(1;33) "◆" ANSI_COLOR(1;37) "輪到%s下 別怕別怕 他算啥米" ANSI_RESET, currutmp->mateid);
+ currutmp->turn = 0;
+ uin->turn = 1;
+ } else {
+ if (ch == 'q') {
+ uin->turn = 'w';
+ break;
+ }
+ move(22, 0);
+ clrtoeol();
+ prints(" " ANSI_COLOR(1;33) "◆" ANSI_COLOR(1;37) "輪到%s下 別怕別怕 他算啥米" ANSI_RESET, currutmp->mateid);
+ }
+ }
+ }
+
+ switch (end) {
+ case -1:
+ case -2:
+ if (currutmp->turn == 'w') {
+ move(22, 0);
+ clrtoeol();
+ outs(ANSI_COLOR(1;31) "你贏了.. 真是恭喜~~" ANSI_RESET);
+ } else {
+ move(22, 0);
+ clrtoeol();
+ outs(ANSI_COLOR(1;31) "輸掉了啦.....下次讓他好看!!" ANSI_RESET);
+ }
+ break;
+ case -3:
+ mouts(22, 0, ANSI_COLOR(1;31) "合棋唷!! 下次在分高下吧 ^_^" ANSI_RESET);
+ break;
+ default:
+ add_io(0, 0);
+ close(fd);
+ pressanykey();
+ return 0;
+ }
+ add_io(0, 0);
+ close(fd);
+ pressanykey();
+ return 0;
+}
diff --git a/pttbbs/mbbsd/dice.c b/pttbbs/mbbsd/dice.c
new file mode 100644
index 00000000..b44f1744
--- /dev/null
+++ b/pttbbs/mbbsd/dice.c
@@ -0,0 +1,483 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define DICE_TXT BBSHOME "/etc/dice.txt"
+#define DICE_DATA BBSHOME "/etc/dice.data"
+#define DICE_WIN BBSHOME "/etc/windice.log"
+#define DICE_LOST BBSHOME "/etc/lostdice.log"
+
+#define B_MAX 500
+#define B_MIN 10
+#define B_COMMON 1
+#define B_TIMES 5
+#define B_THIRD 3
+
+
+typedef struct dicedata_t {
+ int mybet;
+ int mymoney;
+} dicedata_t;
+
+static void
+set_bingo(int flag[100],int bet[])
+{
+ int i, j = 0, k = 0, m = 0;
+
+ for (i = 0; i < 3; i++)
+ for (j = 2; j > i; j--)
+ if (bet[j] < bet[j - 1]) {
+ m = bet[j];
+ bet[j] = bet[j - 1];
+ bet[j - 1] = m;
+ }
+ for (i = 0; i < 100; i++)
+ flag[i] = 0;
+
+ for (i = 0; i < 3; i++)
+ flag[bet[i]]++;
+ j = bet[0] + bet[1] + bet[2];
+
+ if ((abs(bet[1] - bet[0]) == 1 && abs(bet[2] - bet[0]) == 2) ||
+ (abs(bet[2] - bet[0]) == 1 && abs(bet[1] - bet[0]) == 2))
+ flag[66] = B_TIMES;
+
+ if (j < 10) {
+ flag[7] = B_COMMON;
+ for (i = 0; i < 3; i++)
+ if (bet[i] == 4)
+ flag[74] = B_TIMES;
+ } else if (j > 11) {
+ flag[8] = B_COMMON;
+ for (i = 0; i < 3; i++)
+ if (bet[i] == 3)
+ flag[83] = B_TIMES;
+ } else
+ flag[11] = B_THIRD;
+
+ for (i = 0; i < 3; i++)
+ for (j = i; j < 3; j++) {
+ m = bet[i];
+ k = bet[j];
+ if (m != k)
+ flag[m * 10 + k] = B_TIMES;
+ }
+}
+
+static int
+bingo(int flag[100],int mybet)
+{
+ return flag[mybet];
+}
+
+int
+IsNum(const char *a, int n)
+{
+ int i;
+
+ for (i = 0; i < n; i++)
+ if (a[i] > '9' || a[i] < '0')
+ return 0;
+ return 1;
+}
+
+#if 0
+static int
+IsSNum(char *a)
+{
+ int i;
+
+ for (i = 0; a[i]; i++)
+ if (a[i] > '9' || a[i] < '0')
+ return 0;
+ return 1;
+}
+#endif
+
+static void
+show_data(void)
+{
+ move(0, 0);
+ outs(ANSI_COLOR(31) " ┌───────────────────────"
+ "──────────┐" ANSI_RESET "\n");
+ outs(ANSI_COLOR(45;37) "倍率一" ANSI_RESET ANSI_COLOR(31) " │ " ANSI_COLOR(33) "[1]押一點 [2]押二點 "
+ "[3]押三點 [4]押四點 [5]押五點 [6]押六點 " ANSI_COLOR(31) " │" ANSI_RESET "\n");
+ outs(ANSI_COLOR(31) " │ " ANSI_COLOR(33) "[7]押小 [8]押大 "
+ " " ANSI_COLOR(31) " │" ANSI_RESET "\n");
+ outs(ANSI_COLOR(31) " │ "
+ " │" ANSI_RESET "\n");
+ outs(ANSI_COLOR(45;37) "賠率三" ANSI_RESET ANSI_COLOR(31) " │ " ANSI_COLOR(33) "[11]押中(總點數等於11"
+ "或10) " ANSI_COLOR(31) " │" ANSI_RESET "\n");
+ outs(ANSI_COLOR(31) " │ "
+ " │" ANSI_RESET "\n");
+ outs(ANSI_COLOR(45;37) "賠率五" ANSI_RESET ANSI_COLOR(31) " │ " ANSI_COLOR(33) "[74]押小且四點 [83]押"
+ "大且三點 [66]押連號 " ANSI_COLOR(31) " │" ANSI_RESET "\n");
+ outs(ANSI_COLOR(31) " │ "
+ " │" ANSI_RESET "\n");
+ outs(ANSI_COLOR(31) " │ " ANSI_COLOR(33) "[12]押一二點 [13]押一三點 [14]押一四點"
+ " [15]押一五點 [16]押一六點" ANSI_COLOR(31) " │" ANSI_RESET "\n");
+ outs(ANSI_COLOR(31) " │ " ANSI_COLOR(33) "[23]押二三點 [24]押二四點 [25]押二五點"
+ " [26]押二六點 [34]押三四點" ANSI_COLOR(31) " │" ANSI_RESET "\n");
+ outs(ANSI_COLOR(31) " │ " ANSI_COLOR(33) "[35]押三五點 [36]押三六點 [45]押四五點"
+ " [46]押四六點 [56]押五六點" ANSI_COLOR(31) " │" ANSI_RESET "\n");
+ outs(ANSI_COLOR(31) " └────────────────────────"
+ "─────────┘" ANSI_RESET "\n");
+}
+
+static void
+show_count(int value[100],int index, int money)
+{
+ int i = 0, count = 2, j, k;
+
+ value[index] += money;
+ move(14, 0);
+ clrtoline(18);
+ for (i = 1, j = 13; i <= 8; i++, count += 12) {
+ if (i == 6) {
+ j = 14;
+ count = 2;
+ }
+ move(j, count);
+ prints("[%2d]:%d ", i, value[i]);
+ }
+
+ count = 2;
+ i = 15;
+ for (j = 1; j <= 5; j++)
+ for (k = j + 1; k <= 6; k++, count += 12) {
+ if (j == 2 && k == 4) {
+ i = 16;
+ count = 2;
+ } else if (j == 4 && k == 5) {
+ i = 17;
+ count = 2;
+ }
+ move(i, count);
+ prints("[%d%d]:%d ", j, k, value[j * 10 + k]);
+ }
+
+ move(18, 2);
+ prints("[11]:%d", value[11]);
+ move(18, 14);
+ prints("[66]:%d", value[66]);
+ move(18, 26);
+ prints("[74]:%d", value[74]);
+ move(18, 38);
+ prints("[83]:%d", value[83]);
+}
+
+static int
+check_index(int index)
+{
+ int i, tp[] = {1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16, 23, 24, 25,
+ 26, 34, 35, 36, 45, 46, 56, 66, 74, 83};
+ if (index < 0 || index > 100)
+ return 0;
+ for (i = 0; i < 27; i++)
+ if (index == tp[i])
+ return 1;
+ return 0;
+}
+
+static int
+del(int value[100],int total, dicedata_t * table)
+{
+ int index, money;
+ char data[10];
+ int i;
+
+ while (1) {
+ do {
+ move(22, 0);
+ clrtoeol();
+ getdata(21, 0, "輸入退選的數字(打q離開): ", data, 3, LCECHO);
+ if (data[0] == 'q' || data[0] == 'Q')
+ return 0;
+ } while (!IsNum(data, strlen(data)));
+
+ index = atoi(data);
+ for (i = 0; i < total; i++) {
+ if (table[i].mybet == index) {
+ do {
+ getdata(21, 0, "多少錢: ", data, 10, LCECHO);
+ } while (!IsNum(data, strlen(data)));
+ money = atoi(data);
+ if (money > table[i].mymoney) {
+ move(22, 0);
+ clrtoeol();
+ outs("不夠扣啦");
+ i--;
+ continue;
+ }
+ demoney(money);
+ move(19, 0);
+ clrtoeol();
+ prints("你現在有 %u Ptt$歐", cuser.money);
+ table[i].mymoney -= money;
+ show_count(value, index, -money);
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+static int
+IsLegal(const char *data)
+{
+ int money = atoi(data);
+ if (IsNum(data, strlen(data)) && money <= B_MAX && money >= B_MIN)
+ return money;
+ return 0;
+}
+
+static void
+show_output(int bet[])
+{
+ int i, j = 10;
+
+ move(12, 0);
+ clrtoline(17);
+ /* 暫時降啦 因為那各clrtoline怪怪的 */ // XXX 哪裡怪?
+ for (i = 13; i <= 18; i++) {
+ move(i, 0);
+ outs(" ");
+ }
+ move(12, 0);
+ outs(ANSI_COLOR(1;31) " ┌──────────────────────"
+ "─┐" ANSI_RESET "\n\n\n\n\n\n");
+ outs(ANSI_COLOR(1;31) " └──────────────────────"
+ "─┘" ANSI_RESET);
+ for (i = 0; i < 3; i++, j += 25) {
+ switch (bet[i]) {
+ case 1:
+ move(13, j);
+ outs(ANSI_COLOR(37) "╭────╮" ANSI_RESET);
+ move(14, j);
+ outs(ANSI_COLOR(37) "│ │" ANSI_RESET);
+ move(15, j);
+ outs(ANSI_COLOR(37) "│ ● │" ANSI_RESET);
+ move(16, j);
+ outs(ANSI_COLOR(37) "│ │" ANSI_RESET);
+ move(17, j);
+ outs(ANSI_COLOR(37) "╰────╯" ANSI_RESET);
+ break;
+ case 2:
+ move(13, j);
+ outs(ANSI_COLOR(37) "╭────╮" ANSI_RESET);
+ move(14, j);
+ outs(ANSI_COLOR(37) "│ ●│" ANSI_RESET);
+ move(15, j);
+ outs(ANSI_COLOR(37) "│ │" ANSI_RESET);
+ move(16, j);
+ outs(ANSI_COLOR(37) "│● │" ANSI_RESET);
+ move(17, j);
+ outs(ANSI_COLOR(37) "╰────╯" ANSI_RESET);
+ break;
+ case 3:
+ move(13, j);
+ outs(ANSI_COLOR(37) "╭────╮" ANSI_RESET);
+ move(14, j);
+ outs(ANSI_COLOR(37) "│ ●│" ANSI_RESET);
+ move(15, j);
+ outs(ANSI_COLOR(37) "│ ● │" ANSI_RESET);
+ move(16, j);
+ outs(ANSI_COLOR(37) "│● │" ANSI_RESET);
+ move(17, j);
+ outs(ANSI_COLOR(37) "╰────╯" ANSI_RESET);
+ break;
+ case 4:
+ move(13, j);
+ outs(ANSI_COLOR(37) "╭────╮" ANSI_RESET);
+ move(14, j);
+ outs(ANSI_COLOR(37) "│● ●│" ANSI_RESET);
+ move(15, j);
+ outs(ANSI_COLOR(37) "│ │" ANSI_RESET);
+ move(16, j);
+ outs(ANSI_COLOR(37) "│● ●│" ANSI_RESET);
+ move(17, j);
+ outs(ANSI_COLOR(37) "╰────╯" ANSI_RESET);
+ break;
+ case 5:
+ move(13, j);
+ outs(ANSI_COLOR(37) "╭────╮" ANSI_RESET);
+ move(14, j);
+ outs(ANSI_COLOR(37) "│● ●│" ANSI_RESET);
+ move(15, j);
+ outs(ANSI_COLOR(37) "│ ● │" ANSI_RESET);
+ move(16, j);
+ outs(ANSI_COLOR(37) "│● ●│" ANSI_RESET);
+ move(17, j);
+ outs(ANSI_COLOR(37) "╰────╯" ANSI_RESET);
+ break;
+ case 6:
+ move(13, j);
+ outs(ANSI_COLOR(37) "╭────╮" ANSI_RESET);
+ move(14, j);
+ outs(ANSI_COLOR(37) "│● ●│" ANSI_RESET);
+ move(15, j);
+ outs(ANSI_COLOR(37) "│● ●│" ANSI_RESET);
+ move(16, j);
+ outs(ANSI_COLOR(37) "│● ●│" ANSI_RESET);
+ move(17, j);
+ outs(ANSI_COLOR(37) "╰────╯" ANSI_RESET);
+ break;
+ }
+ }
+}
+
+#define lockreturn0(unmode, state) if(lockutmpmode(unmode, state)) return 0
+
+int
+dice_main(void)
+{
+ int flag[100], value[100];
+ char input[10], data[256], ch;
+ dicedata_t table[256];
+ int bet[3], index, money = 0, i, ya = 0, j, total, sig = 0;
+ FILE *winfp;
+
+ more(DICE_TXT, NA);
+ reload_money();
+ if (cuser.money < 10) {
+ move(19, 0);
+ outs(ANSI_COLOR(1;37) "超過十元再來玩吧~~" ANSI_RESET);
+ pressanykey();
+ return 0;
+ }
+ lockreturn0(DICE, LOCK_MULTI);
+ winfp = fopen(DICE_WIN, "a");
+ if (!winfp)
+ return 0;
+
+ do {
+ total = 0;
+ i = 0;
+ ch = 'y';
+ clear();
+ show_data();
+ for (j = 0; j < 3; j++)
+ bet[j] = random() % 6 + 1;
+
+ for (j = 0; j < 100; j++)
+ value[j] = 0;
+
+ while (1) {
+ move(19, 0);
+ prints(ANSI_COLOR(1;32) "你現在有" ANSI_COLOR(1;31) " %u " ANSI_COLOR(1;32) "Ptt$歐" ANSI_RESET,
+ cuser.money);
+ getdata(20, 0, ANSI_COLOR(1;37) "數字:加選 d:退選 s:開始或離開" ANSI_RESET ": ",
+ input, 5, LCECHO);
+ reload_money();
+ if (input[0] != 's' && input[0] != 'd' && cuser.money < 10) {
+ move(21, 0);
+ clrtoeol();
+ outs(ANSI_COLOR(1;37) "超過十元才能賭~" ANSI_RESET);
+ continue;
+ }
+ if (input[0] == 'd' || input[0] == 'D') {
+ del(value, i, table);
+ continue;
+ }
+ if (input[0] == 's' || input[0] == 'S')
+ break;
+
+ if (!IsNum(input, strlen(input)))
+ continue;
+
+ index = atoi(input);
+ if (check_index(index) == 0)
+ continue;
+ /* 輸入錢的loop */
+ while (1) {
+ if (cuser.money < 10)
+ break;
+ getdata(21, 0, ANSI_COLOR(1;32) "賭多少錢呢" ANSI_COLOR(1;37) "(大於10 小於500)"
+ ANSI_RESET ": ", input, sizeof(input), LCECHO);
+ if (!(money = IsLegal(input)) || input[0] == '0')
+ continue;
+ reload_money();
+ if (money > cuser.money)
+ continue;
+ for (j = 0, sig = 0; j < i; j++)
+ if (table[j].mybet == index) {
+ if (table[j].mymoney == B_MAX)
+ sig = 2;
+ else if (table[j].mymoney + money > B_MAX) {
+ sig = 1;
+ break;
+ } else {
+ vice(money, "骰子");
+ table[j].mymoney += money;
+ j = -1;
+ break;
+ }
+ }
+ if (sig == 2)
+ break;
+ if (sig == 1)
+ continue;
+ if (j != -1) {
+ bzero((char *)&table[i], sizeof(dicedata_t));
+ table[i].mybet = index;
+ table[i++].mymoney = money;
+ vice(money, "骰子");
+ }
+ break;
+ }
+ reload_money();
+ move(19, 0);
+ prints(ANSI_COLOR(1;32) "你現在有 " ANSI_COLOR(1;31) "%u" ANSI_COLOR(1;32) " Ptt$歐",
+ cuser.money);
+ if (sig != 2)
+ show_count(value,index, money);
+ }
+
+ if (i == 0) {
+ fclose(winfp);
+ unlockutmpmode();
+ return 0;
+ }
+ show_output(bet);
+ set_bingo(flag, bet);
+
+ for (j = 0; j < i; j++) {
+ if (table[j].mymoney <= 0)
+ continue;
+ ya = bingo(flag, table[j].mybet);
+ if (ya == 0) {
+ continue;
+ }
+ demoney(table[j].mymoney * ya + table[j].mymoney);
+ total += table[j].mymoney * ya;
+ if (table[j].mymoney * ya > 500) { /* 超過500塊錢才做log 減少io */
+ snprintf(data, sizeof(data),
+ "%-15s 押%-2d選項%-8d塊錢 中了%d倍 淨賺:%-8d\n",
+ cuser.userid, table[j].mybet,
+ table[j].mymoney, ya, table[j].mymoney * ya);
+ fputs(data, winfp);
+ }
+ ya = 0;
+ }
+
+ if (total > 0) {
+ move(21, 0);
+ prints(ANSI_COLOR(1;32) "你贏了 " ANSI_COLOR(1;31) "%d" ANSI_COLOR(1;32) " Ptt$ 唷~~"
+ " " ANSI_RESET, total);
+ } else {
+ move(21, 0);
+ clrtoeol();
+ outs(ANSI_COLOR(1;32) "真可惜 下次再來碰碰運氣吧" ANSI_RESET);
+ }
+
+ move(19, 0);
+ clrtoeol();
+ prints(ANSI_COLOR(1;32) "你現在有 " ANSI_COLOR(1;31) "%u" ANSI_COLOR(1;32) " Ptt$歐" ANSI_RESET,
+ cuser.money);
+
+ getdata(23, 0, ANSI_COLOR(1;32) "繼續奮鬥[" ANSI_COLOR(1;37) "y/n" ANSI_COLOR(1;32) "]" ANSI_RESET ": ",
+ input, 2, LCECHO);
+ } while (input[0] != 'n' && input[0] != 'N');
+ fclose(winfp);
+ unlockutmpmode();
+ return 0;
+}
diff --git a/pttbbs/mbbsd/edit.c b/pttbbs/mbbsd/edit.c
new file mode 100644
index 00000000..e46f72e5
--- /dev/null
+++ b/pttbbs/mbbsd/edit.c
@@ -0,0 +1,3355 @@
+/* $Id$ */
+/**
+ * edit.c, 用來提供 bbs上的文字編輯器, 即 ve.
+ * 現在這一個是惡搞過的版本, 比較不穩定, 用比較多的 cpu, 但是可以省下許多
+ * 的記憶體 (以 Ptt為例, 在九千人上站的時候, 約可省下 50MB 的記憶體)
+ * 如果您認為「拿 cpu換記憶體」並不合乎您的須求, 您可以考慮改使用修正前的
+ * 版本 (Revision 782)
+ *
+ * 原本 ve 的做法是, 因為每一行最大可以輸入 WRAPMARGIN 個字, 於是就替每一
+ * 行保留了 WRAPMARGIN 這麼大的空間 (約 512 bytes) . 但是實際上, 站在修正
+ * 成本最小的考量上, 我們只須要使得游標所在這一行維持 WRAPMARGIN 這麼大,
+ * 其他每一行其實不須要這麼多的空間. 於是這個 patch就在每次游標在行間移動
+ * 的時候, 將原本的那行記憶體縮小, 再將新移到的那行重新加大, 以達成最小的
+ * 記憶體用量.
+ * 以上說的這個動作在 adjustline() 中完成, adjustline()另外包括修正數個
+ * global pointer, 以避免 dangling pointer .
+ * 另外若定義 DEBUG, 在 textline_t 結構中將加入 mlength, 表示該行實際佔的
+ * 記憶體大小. 以方便測試結果.
+ * 這個版本似乎還有地方沒有修正好, 可能導致 segmentation fault .
+ *
+ * FIXME 在區塊標記模式(blockln>=0)中對增刪修改可能會造成 blockln, blockpnt,
+ * and/or blockline 錯誤. 甚至把 blockline 砍掉會 access 到已被 free 掉的
+ * memory. 可能要改成標記模式 readonly, 或是做某些動作時自動取消標記模式
+ * (blockln=-1)
+ */
+#include "bbs.h"
+
+#if 0
+#define register
+#define DEBUG
+#define inline
+#endif
+
+/**
+ * data 欄位的用法:
+ * 每次 allocate 一個 textline_t 時,會配給他 (sizeof(textline_t) + string
+ * length - 1) 的大小。如此可直接存取 data 而不需額外的 malloc。
+ */
+typedef struct textline_t {
+ struct textline_t *prev;
+ struct textline_t *next;
+ short len;
+#ifdef DEBUG
+ short mlength;
+#endif
+ char data[1];
+} textline_t;
+
+#define KEEP_EDITING -2
+
+enum {
+ NOBODY, MANAGER, SYSOP
+};
+
+
+/**
+ * 這個說明會將整個 edit.c 運作的概念帶過,主要會從 editor_internal_t 的
+ * data structure 談起。對於每一個 data member 的詳細功能,請見 sturcture
+ * 中的註解。
+ *
+ * 文章的內容 (以下稱 content) 以「行」為單位,主要以 firstline, lastline,
+ * totaln 來記錄。
+ *
+ * User 在畫面中看到的畫面,置於一個「 window 」中,這個 window 會在
+ * content 上移動,window 裡面的範圍即為要 show 出來的範圍。它用了不少
+ * 欄位來記錄,包括 currline, top_of_win, currln, currpnt, curr_window_line,
+ * edit_margin。顯示出來的效果當然不只是靠這幾個資料,還會跟其他欄位有交互
+ * 作用,例如 彩色編輯模式、特殊符號編輯 等等。其中最複雜的部分是在選取 block
+ * (見後)的時候。比較不直覺的行為是:除非游標在開始選取跟目前(結束)的位置
+ * 是同一個(此時這個範圍是選取的範圍),否則就是從開始那一列一直到目前(結束)
+ * 這一列。
+ *
+ * editor 的使用上目前有五種 inclusive 的 mode:
+ * insert mode:
+ * 插入/取代
+ * ansi mode:
+ * 彩色編輯
+ * indent mode:
+ * 自動縮排
+ * phone mode:
+ * 特殊符號編輯
+ * raw mode:
+ * ignore Ctrl('S'), Ctrl('Q'), Ctrl('T')
+ * 贊曰: 這有什麼用? 看起來是 modem 上傳用 (沒人在用這個了吧)
+ * 拿來當 dbcs option 吧
+ *
+ * editor 支援了區塊選擇的功能(多行選取 或 單行中的片段),對於一個 selected
+ * block,可以 cut, copy, cancel, 或者存到暫取檔,甚至是往左/右 shift。詳見
+ * block_XXX。
+ *
+ * 用 Ctrl('Y') 刪除的那一行會被記到 deleted_line 這個欄位中。undelete_line()
+ * 可以做 undelete 的動作。 deleted_line 初始值為 NULL,每次只允許存一行,所以
+ * 在存下來時,發現值不為 NULL 會先做 free 的動作。editor 結束時,在
+ * edit_buffer_destructor() 中如果發現有 deleted_line,會在這邊釋放掉。其他地
+ * 方也有相同的作法,例如 searched_string。searched_string 是在搜尋文章內容
+ * 時,要尋找的 key word。
+ *
+ * 還有一個有趣的特點,「括號匹配」!行為就如同在 vim 裡面一樣。呃,當然沒那
+ * 麼強啦,但至少在含有 c-style comment 跟 c-style string 時是對的喔。這個動
+ * 作定義於 match_paren() 中。
+ *
+ * 另外,如果有需要新增新的欄位,請將初始化(有需要的話)的動作寫在
+ * edit_buffer_constructor 中。當然也有個 edit_buffer_destructor 可以使用。
+ *
+ * 此外,為了提供一個 reentrant 的 editor,prev 指向前一個 editor 的
+ * editor_internal_t。enter_edit_buffer 跟 exit_edit_buffer 提供進出的介面,
+ * 裡面分別會呼叫 constructor 跟 destructor。
+ *
+ * TODO
+ * vedit 裡面有個 curr_buf->oldcurrline,用來記上一次的 currline。由於只有 currline 擁
+ * 有 WRAPMARGIN 的空間,所以目前的作法是當 curr_buf->oldcurrline != currline 時,就
+ * resize curr_buf->oldcurrline 跟 currline。但是糟糕的是目前必須人工追蹤 currline 的行
+ * 為,而且若不幸遇到 curr_buf->oldcurrline 指到的那一行已經被 free 掉,就完了。最好是
+ * 把這些東西包起來。不過我沒空做了,patch is welcome :P
+ *
+ * Victor Hsieh <victor@csie.org>
+ * Thu, 03 Feb 2005 15:18:00 +0800
+ */
+typedef struct editor_internal_t {
+
+ textline_t *firstline; /* first line of the article. */
+ textline_t *lastline; /* last line of the article. */
+
+ textline_t *currline; /* current line of the article(window). */
+ textline_t *blockline; /* the first selected line of the block. */
+ textline_t *top_of_win; /* top line of the article in the window. */
+
+ textline_t *deleted_line; /* deleted line. Just keep one deleted line. */
+ textline_t *oldcurrline;
+
+ short currln; /* current line of the article. */
+ short currpnt; /* current column of the article. */
+ short totaln; /* total lines of the article. */
+ short curr_window_line; /* current line to the window. */
+ short last_margin;
+ short edit_margin; /* when the cursor moves out of range (say,
+ t_columns), shift this length of the string
+ so you won't see the first edit_margin-th
+ character. */
+ short lastindent;
+ short blockln; /* the row you started to select block. */
+ short blockpnt; /* the column you started to select block. */
+ char insert_c; /* insert this character when shift something
+ in order to compensate the new space. */
+ char last_phone_mode;
+
+ char ifuseanony :1;
+ char redraw_everything :1;
+
+ char insert_mode :1;
+ char ansimode :1;
+ char indent_mode :1;
+ char phone_mode :1;
+ char raw_mode :1;
+
+ char *searched_string;
+ char *(*substr_fp) ();
+
+ struct editor_internal_t *prev;
+
+} editor_internal_t;
+// } __attribute__ ((packed))
+
+static editor_internal_t *curr_buf = NULL;
+
+static const char fp_bak[] = "bak";
+
+static const char * const BIG5[13] = {
+ ",;:、、。?!•﹗()〝〞‵′",
+ "▁▂▃▄▅▆▇█▏▎▍▌▋▊▉ ",
+ "○☉◎●☆★□■▼▲▽△◇◆♀♂",
+ "﹌﹏\︴‾_—∥∣▕/\╳╱╲/\",
+ "+-×÷√±=≡≠≒≦≧<>∵∴",
+ "∞∼∩∪∫∮&⊥∠∟⊿﹢﹣﹤﹥﹦",
+ "↑↓←→↖↗↙↘",
+ "【】「」『』〈〉《》〔〕{}︵︶",
+ "︹︺︷︸︻︼︿﹀︽︾﹁﹂﹃﹄",
+ "◢◣◥◤﹡*※§@♁㊣…‥﹉﹍",
+ "α\βγδεζηθικλμνξοπ",
+ "ρστυφχψωΔΘΛΠΣΦΨΩ",
+ "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ"
+};
+
+static const char * const BIG_mode[13] = {
+ "標點",
+ "圖塊",
+ "標記",
+ "標線",
+ "數一",
+ "數二",
+ "箭頭",
+ "括一",
+ "括二",
+ "其他",
+ "希一",
+ "希二",
+ "數字"
+};
+
+static const char *table[8] = {
+ "│─└┴┘├┼┤┌┬┐",
+ "齰片裺嘵潁僓朅禊歈稙",
+ "齰w蘮穱蠮譀齍鐒爁蹠",
+ "│═檛薋謖失弛杜翦踛",
+ "│─╰┴╯├┼┤╭┬╮",
+ "齰丐q銚僓朅潳~煍",
+ "齰w蘮穱蠮譀齍鐒爁蹠",
+ "│═檛薋謖失弛杜翦踛"
+};
+
+static const char *table_mode[6] = {
+ "直角",
+ "彎弧",
+ "┼",
+ "",
+ "",
+ "╪"
+};
+
+#ifdef DBCSAWARE
+static char mbcs_mode =1;
+
+#define IS_BIG5_HI(x) (0x81 <= (x) && (x) <= 0xfe)
+#define IS_BIG5_LOS(x) (0x40 <= (x) && (x) <= 0x7e)
+#define IS_BIG5_LOE(x) (0x80 <= (x) && (x) <= 0xfe)
+#define IS_BIG5_LO(x) (IS_BIG5_LOS(x) || IS_BIG5_LOE(x))
+#define IS_BIG5(hi,lo) (IS_BIG5_HI(hi) && IS_BIG5_LO(lo))
+
+int mchar_len(unsigned char *str)
+{
+ return ((str[0] != '\0' && str[1] != '\0' && IS_BIG5(str[0], str[1])) ?
+ 2 :
+ 1);
+}
+
+#define FC_RIGHT (0)
+#define FC_LEFT (~FC_RIGHT)
+
+int fix_cursor(char *str, int pos, unsigned int dir)
+{
+ int newpos, w;
+
+ for(newpos = 0;
+ *str != '\0' &&
+ (w = mchar_len((unsigned char*)str),
+ newpos + 1 + (dir & (w - 1))) <= pos;
+ str += w, newpos += w)
+ ;
+
+ return newpos;
+}
+
+#endif
+
+
+/* 記憶體管理與編輯處理 */
+static void
+indigestion(int i)
+{
+ vmsgf("嚴重內傷 (%d)\n", i);
+ u_exit("EDITOR FAILED");
+ assert(0);
+ exit(0);
+}
+
+static inline void
+edit_buffer_constructor(editor_internal_t *buf)
+{
+ /* all unspecified columns are 0 */
+ buf->blockln = -1;
+ buf->insert_c = ' ';
+ buf->insert_mode = 1;
+ buf->redraw_everything = 1;
+ buf->lastindent = -1;
+}
+
+static inline void
+enter_edit_buffer(void)
+{
+ editor_internal_t *p = curr_buf;
+ curr_buf = (editor_internal_t *)malloc(sizeof(editor_internal_t));
+ memset(curr_buf, 0, sizeof(editor_internal_t));
+ curr_buf->prev = p;
+ edit_buffer_constructor(curr_buf);
+}
+
+static inline void
+free_line(textline_t *p)
+{
+ p->next = (textline_t*)0x12345678;
+ p->prev = (textline_t*)0x87654321;
+ p->len = -12345;
+ free(p);
+}
+
+static inline void
+edit_buffer_destructor(void)
+{
+ if (curr_buf->deleted_line != NULL)
+ free_line(curr_buf->deleted_line);
+
+ if (curr_buf->searched_string != NULL)
+ free(curr_buf->searched_string);
+}
+
+static inline void
+exit_edit_buffer(void)
+{
+ editor_internal_t *p = curr_buf;
+
+ edit_buffer_destructor();
+ curr_buf = p->prev;
+ free(p);
+}
+
+/**
+ * transform position ansix in an ansi string of textline_t to the same
+ * string without escape code.
+ * @return position in the string without escape code.
+ */
+static int
+ansi2n(int ansix, textline_t * line)
+{
+ register char *data, *tmp;
+ register char ch;
+
+ data = tmp = line->data;
+
+ while (*tmp) {
+ if (*tmp == KEY_ESC) {
+ while ((ch = *tmp) && !isalpha((int)ch))
+ tmp++;
+ if (ch)
+ tmp++;
+ continue;
+ }
+ if (ansix <= 0)
+ break;
+ tmp++;
+ ansix--;
+ }
+ return tmp - data;
+}
+
+/**
+ * opposite to ansi2n, according to given textline_t.
+ * @return position in the string with escape code.
+ */
+static short
+n2ansi(short nx, textline_t * line)
+{
+ register short ansix = 0;
+ register char *tmp, *nxp;
+ register char ch;
+
+ tmp = nxp = line->data;
+ nxp += nx;
+
+ while (*tmp) {
+ if (*tmp == KEY_ESC) {
+ while ((ch = *tmp) && !isalpha((int)ch))
+ tmp++;
+ if (ch)
+ tmp++;
+ continue;
+ }
+ if (tmp >= nxp)
+ break;
+ tmp++;
+ ansix++;
+ }
+ return ansix;
+}
+
+/* 螢幕處理:輔助訊息、顯示編輯內容 */
+
+static inline void
+show_phone_mode_panel(void)
+{
+ int i;
+
+ move(b_lines - 1, 0);
+ clrtoeol();
+
+ if (curr_buf->last_phone_mode < 20) {
+ int len;
+ prints(ANSI_COLOR(1;46) "【%s輸入】 ", BIG_mode[curr_buf->last_phone_mode - 1]);
+ len = strlen(BIG5[curr_buf->last_phone_mode - 1]) / 2;
+ for (i = 0; i < len; i++)
+ prints(ANSI_COLOR(37) "%c" ANSI_COLOR(34) "%2.2s",
+ i + 'A', BIG5[curr_buf->last_phone_mode - 1] + i * 2);
+ for (i = 0; i < 16 - len; i++)
+ outs(" ");
+ outs(ANSI_COLOR(37) " `1~9-=切換 Z表格" ANSI_RESET);
+ }
+ else {
+ prints(ANSI_COLOR(1;46) "【表格繪製】 /=%s *=%s形 ",
+ table_mode[(curr_buf->last_phone_mode - 20) / 4],
+ table_mode[(curr_buf->last_phone_mode - 20) % 4 + 2]);
+ for (i = 0;i < 11;i++)
+ prints(ANSI_COLOR(37) "%c" ANSI_COLOR(34) "%2.2s", i ? i + '/' : '.',
+ table[curr_buf->last_phone_mode - 20] + i * 2);
+ outs(ANSI_COLOR(37) " Z內碼 " ANSI_RESET);
+ }
+}
+
+/**
+ * Show the bottom status/help bar, and BIG5/table in phone_mode.
+ */
+static void
+edit_msg(void)
+{
+ int n = curr_buf->currpnt;
+
+ if (curr_buf->ansimode) /* Thor: 作 ansi 編輯 */
+ n = n2ansi(n, curr_buf->currline);
+
+ if (curr_buf->phone_mode)
+ show_phone_mode_panel();
+
+ move(b_lines, 0);
+ clrtoeol();
+ outs( ANSI_COLOR(37;44) " 編輯文章 "
+ ANSI_COLOR(31;47) " (^Z/F1)" ANSI_COLOR(30) "說明 "
+ ANSI_COLOR(31;47) "(^P/^G)" ANSI_COLOR(30) "插入符號/圖片 "
+ ANSI_COLOR(31) "(^X/^Q)" ANSI_COLOR(30) "離開");
+
+ prints( "%s│%c%c%c%c %3d:%3d ",
+ curr_buf->insert_mode ? "插入" : "取代",
+ curr_buf->ansimode ? 'A' : 'a',
+ curr_buf->indent_mode ? 'I' : 'i',
+ curr_buf->phone_mode ? 'P' : 'p',
+ curr_buf->raw_mode ? 'R' : 'r',
+ curr_buf->currln + 1, n + 1);
+ outslr("", 78, ANSI_RESET, 0);
+}
+
+/**
+ * return the middle line of the window.
+ */
+static inline int
+middle_line(void)
+{
+ return p_lines / 2 + 1;
+}
+
+/**
+ * Return the previous 'num' line. Stop at the first line if there's
+ * not enough lines.
+ */
+static textline_t *
+back_line(textline_t * pos, int num)
+{
+ while (num-- > 0) {
+ register textline_t *item;
+
+ if (pos && (item = pos->prev)) {
+ pos = item;
+ curr_buf->currln--;
+ }
+ else
+ break;
+ }
+ return pos;
+}
+
+/* calculate if cursor is at bottom, scroll required?
+ * currently vedit does NOT handle if curr_window_line > b_lines,
+ * take care if you changed curr_window_line!
+ */
+static inline int
+cursor_at_bottom_line(void)
+{
+ return curr_buf->curr_window_line == b_lines ||
+ (curr_buf->phone_mode && curr_buf->curr_window_line == b_lines - 1);
+}
+
+
+/**
+ * Return the next 'num' line. Stop at the last line if there's not
+ * enough lines.
+ */
+static textline_t *
+forward_line(textline_t * pos, int num)
+{
+ while (num-- > 0) {
+ register textline_t *item;
+
+ if (pos && (item = pos->next)) {
+ pos = item;
+ curr_buf->currln++;
+ }
+ else
+ break;
+ }
+ return pos;
+}
+
+/**
+ * move the cursor to the next line with ansimode fixed.
+ */
+static inline void
+cursor_to_next_line(void)
+{
+ short pos;
+
+ if (curr_buf->currline->next == NULL)
+ return;
+
+ curr_buf->currline = curr_buf->currline->next;
+ curr_buf->curr_window_line++;
+ curr_buf->currln++;
+
+ if (curr_buf->ansimode) {
+ pos = n2ansi(curr_buf->currpnt, curr_buf->currline->prev);
+ curr_buf->currpnt = ansi2n(pos, curr_buf->currline);
+ }
+ else {
+ curr_buf->currpnt = (curr_buf->currline->len > curr_buf->lastindent)
+ ? curr_buf->lastindent : curr_buf->currline->len;
+ }
+}
+
+/**
+ * opposite to cursor_to_next_line.
+ */
+static inline void
+cursor_to_prev_line(void)
+{
+ short pos;
+
+ if (curr_buf->currline->prev == NULL)
+ return;
+
+ curr_buf->curr_window_line--;
+ curr_buf->currln--;
+ curr_buf->currline = curr_buf->currline->prev;
+
+ if (curr_buf->ansimode) {
+ pos = n2ansi(curr_buf->currpnt, curr_buf->currline->next);
+ curr_buf->currpnt = ansi2n(pos, curr_buf->currline);
+ }
+ else {
+ curr_buf->currpnt = (curr_buf->currline->len > curr_buf->lastindent)
+ ? curr_buf->lastindent : curr_buf->currline->len;
+ }
+}
+
+static inline void
+window_scroll_down(void)
+{
+ curr_buf->curr_window_line = 0;
+
+ if (!curr_buf->top_of_win->prev)
+ indigestion(6);
+ else {
+ curr_buf->top_of_win = curr_buf->top_of_win->prev;
+ rscroll();
+ }
+}
+
+static inline void
+window_scroll_up(void)
+{
+ curr_buf->curr_window_line = b_lines - (curr_buf->phone_mode ? 2 : 1);
+
+ if (unlikely(!curr_buf->top_of_win->next))
+ indigestion(7);
+ else {
+ curr_buf->top_of_win = curr_buf->top_of_win->next;
+ if(curr_buf->phone_mode)
+ move(b_lines-1, 0);
+ else
+ move(b_lines, 0);
+ clrtoeol();
+ scroll();
+ }
+}
+
+/**
+ * Get the current line number in the window now.
+ */
+static int
+get_lineno_in_window(void)
+{
+ int cnt = 0;
+ textline_t *p = curr_buf->currline;
+
+ while (p && (p != curr_buf->top_of_win)) {
+ cnt++;
+ p = p->prev;
+ }
+ return cnt;
+}
+
+/**
+ * shift given raw data s with length len to left by one byte.
+ */
+static void
+raw_shift_left(char *s, int len)
+{
+ int i;
+ for (i = 0; i < len && s[i] != 0; ++i)
+ s[i] = s[i + 1];
+}
+
+/**
+ * shift given raw data s with length len to right by one byte.
+ */
+static void
+raw_shift_right(char *s, int len)
+{
+ int i;
+ for (i = len - 1; i >= 0; --i)
+ s[i + 1] = s[i];
+}
+
+/**
+ * Return the pointer to the next non-space position.
+ */
+static char *
+next_non_space_char(char *s)
+{
+ while (*s == ' ')
+ s++;
+ return s;
+}
+
+/**
+ * allocate a textline_t with length length.
+ */
+static textline_t *
+alloc_line(short length)
+{
+ textline_t *p;
+
+ if ((p = (textline_t *) malloc(length + sizeof(textline_t)))) {
+ memset(p, 0, length + sizeof(textline_t));
+#ifdef DEBUG
+ p->mlength = length;
+#endif
+ return p;
+ }
+ indigestion(13);
+ abort_bbs(0);
+ return NULL;
+}
+
+/**
+ * Insert p after line in list. Keeps up with last line
+ */
+static void
+insert_line(textline_t *line, textline_t *p)
+{
+ textline_t *n;
+
+ if ((p->next = n = line->next))
+ n->prev = p;
+ else
+ curr_buf->lastline = p;
+ line->next = p;
+ p->prev = line;
+}
+
+/**
+ * delete_line deletes 'line' from the line list.
+ * @param saved true if you want to keep the line in deleted_line
+ */
+static void
+delete_line(textline_t * line, int saved)
+{
+ register textline_t *p = line->prev;
+ register textline_t *n = line->next;
+
+ if (!p && !n) {
+ line->data[0] = line->len = 0;
+ return;
+ }
+ assert(line != curr_buf->top_of_win);
+ if (n)
+ n->prev = p;
+ else
+ curr_buf->lastline = p;
+ if (p)
+ p->next = n;
+ else
+ curr_buf->firstline = n;
+
+ curr_buf->totaln--;
+
+ if (saved) {
+ if (curr_buf->deleted_line != NULL)
+ free_line(curr_buf->deleted_line);
+ curr_buf->deleted_line = line;
+ curr_buf->deleted_line->next = NULL;
+ curr_buf->deleted_line->prev = NULL;
+ }
+ else {
+ free_line(line);
+ }
+}
+
+static int
+ask(const char *prompt)
+{
+ int ch;
+
+ move(0, 0);
+ clrtoeol();
+ standout();
+ outs(prompt);
+ standend();
+ ch = igetch();
+ move(0, 0);
+ clrtoeol();
+ return (ch);
+}
+
+/**
+ * Return the indent space number according to CURRENT line and the FORMER
+ * line. It'll be the first line contains non-space character.
+ * @return space number from the beginning to the first non-space character,
+ * return 0 if non or not in indent mode.
+ */
+static int
+indent_space(void)
+{
+ textline_t *p;
+ int spcs;
+
+ if (!curr_buf->indent_mode)
+ return 0;
+
+ for (p = curr_buf->currline; p; p = p->prev) {
+ for (spcs = 0; p->data[spcs] == ' '; ++spcs);
+ /* empty loop */
+ if (p->data[spcs])
+ return spcs;
+ }
+ return 0;
+}
+
+/**
+ * adjustline(oldp, len);
+ * 用來將 oldp 指到的那一行, 重新修正成 len這麼長.
+ *
+ * 呼叫了 adjustline 後記得檢查有動到 currline, 如果是的話 oldcurrline 也要動
+ *
+ * In FreeBSD:
+ * 在這邊一共做了兩次的 memcpy() , 第一次從 heap 拷到 stack ,
+ * 把原來記憶體 free() 後, 又重新在 stack上 malloc() 一次,
+ * 然後再拷貝回來.
+ * 主要是用 sbrk() 觀察到的結果, 這樣子才真的能縮減記憶體用量.
+ * 詳見 /usr/share/doc/papers/malloc.ascii.gz (in FreeBSD)
+ */
+static textline_t *
+adjustline(textline_t *oldp, short len)
+{
+ // XXX write a generic version ?
+ char tmpl[sizeof(textline_t) + WRAPMARGIN];
+ textline_t *newp;
+
+#ifdef deBUG
+ if(oldp->len > WRAPMARGIN || oldp->len < 0) {
+ kill(currpid, SIGSEGV);
+ }
+#endif
+
+ memcpy(tmpl, oldp, oldp->len + sizeof(textline_t));
+ free_line(oldp);
+
+ newp = alloc_line(len);
+ memcpy(newp, tmpl, len + sizeof(textline_t));
+#ifdef DEBUG
+ newp->mlength = len;
+#endif
+ if( oldp == curr_buf->firstline ) curr_buf->firstline = newp;
+ if( oldp == curr_buf->lastline ) curr_buf->lastline = newp;
+ if( oldp == curr_buf->currline ) curr_buf->currline = newp;
+ if( oldp == curr_buf->blockline ) curr_buf->blockline = newp;
+ if( oldp == curr_buf->top_of_win) curr_buf->top_of_win= newp;
+ if( newp->prev != NULL ) newp->prev->next = newp;
+ if( newp->next != NULL ) newp->next->prev = newp;
+ // vmsg("adjust %x to %x, length: %d", (int)oldp, (int)newp, len);
+ return newp;
+}
+
+/**
+ * split 'line' right before the character pos
+ *
+ * @return the latter line after splitting
+ */
+static textline_t *
+split(textline_t * line, int pos)
+{
+ if (pos <= line->len) {
+ register textline_t *p = alloc_line(WRAPMARGIN);
+ register char *ptr;
+ int spcs = indent_space();
+
+ curr_buf->totaln++;
+
+ p->len = line->len - pos + spcs;
+ line->len = pos;
+
+ memset(p->data, ' ', spcs);
+ p->data[spcs] = 0;
+
+ ptr = line->data + pos;
+ if (curr_buf->indent_mode)
+ ptr = next_non_space_char(ptr);
+ strcat(p->data + spcs, ptr);
+ ptr[0] = '\0';
+
+ if (line == curr_buf->currline && pos <= curr_buf->currpnt) {
+ line = adjustline(line, line->len);
+ insert_line(line, p);
+ // because p is allocated with fullsize, we can skip adjust.
+ // curr_buf->oldcurrline = line;
+ curr_buf->oldcurrline = curr_buf->currline = p;
+ if (pos == curr_buf->currpnt)
+ curr_buf->currpnt = spcs;
+ else
+ curr_buf->currpnt -= pos;
+ curr_buf->curr_window_line++;
+ curr_buf->currln++;
+
+ /* split may cause cursor hit bottom */
+ if (cursor_at_bottom_line())
+ window_scroll_up();
+ } else {
+ p = adjustline(p, p->len);
+ insert_line(line, p);
+ }
+ curr_buf->redraw_everything = YEA;
+ }
+ return line;
+}
+
+/**
+ * Insert a character ch to current line.
+ *
+ * The line will be split if the length is >= WRAPMARGIN. It'll be split
+ * from the last space if any, or start a new line after the last character.
+ */
+static void
+insert_char(int ch)
+{
+ register textline_t *p = curr_buf->currline;
+ register int i = p->len;
+ register char *s;
+ int wordwrap = YEA;
+
+ if (curr_buf->currpnt > i) {
+ indigestion(1);
+ return;
+ }
+ if (curr_buf->currpnt < i && !curr_buf->insert_mode) {
+ p->data[curr_buf->currpnt++] = ch;
+ /* Thor: ansi 編輯, 可以overwrite, 不蓋到 ansi code */
+ if (curr_buf->ansimode)
+ curr_buf->currpnt = ansi2n(n2ansi(curr_buf->currpnt, p), p);
+ } else {
+ raw_shift_right(p->data + curr_buf->currpnt, i - curr_buf->currpnt + 1);
+ p->data[curr_buf->currpnt++] = ch;
+ i = ++(p->len);
+ }
+ if (i < WRAPMARGIN)
+ return;
+ s = p->data + (i - 1);
+ while (s != p->data && *s == ' ')
+ s--;
+ while (s != p->data && *s != ' ')
+ s--;
+ if (s == p->data) {
+ wordwrap = NA;
+ s = p->data + (i - 2);
+ }
+ p = split(p, (s - p->data) + 1);
+ p = p->next;
+ i = p->len;
+ if (wordwrap && i >= 1) {
+ if (p->data[i - 1] != ' ') {
+ p->data[i] = ' ';
+ p->data[i + 1] = '\0';
+ p->len++;
+ }
+ }
+}
+
+/**
+ * insert_char twice.
+ */
+static void
+insert_dchar(const char *dchar)
+{
+ insert_char(*dchar);
+ insert_char(*(dchar+1));
+}
+
+static void
+insert_tab(void)
+{
+ do {
+ insert_char(' ');
+ } while (curr_buf->currpnt & 0x7);
+}
+
+/**
+ * Insert a string.
+ *
+ * All printable and ESC_CHR will be directly printed out.
+ * '\t' will be printed to align every 8 byte.
+ * '\n' will split the line.
+ * The other character will be ignore.
+ */
+static void
+insert_string(const char *str)
+{
+ char ch;
+
+ while ((ch = *str++)) {
+ if (isprint2(ch) || ch == ESC_CHR)
+ insert_char(ch);
+ else if (ch == '\t')
+ insert_tab();
+ else if (ch == '\n')
+ split(curr_buf->currline, curr_buf->currpnt);
+ }
+}
+
+/**
+ * undelete the deleted line.
+ *
+ * return NULL if there's no deleted_line, otherwise, return currline.
+ */
+static textline_t *
+undelete_line(void)
+{
+ editor_internal_t tmp;
+
+ if (!curr_buf->deleted_line)
+ return NULL;
+
+ tmp.top_of_win = curr_buf->top_of_win;
+ tmp.indent_mode = curr_buf->indent_mode;
+ tmp.curr_window_line = curr_buf->curr_window_line;
+
+ curr_buf->indent_mode = 0;
+ curr_buf->currpnt = 0;
+ curr_buf->currln++;
+ insert_string(curr_buf->deleted_line->data);
+ insert_string("\n");
+
+ curr_buf->top_of_win = tmp.top_of_win;
+ curr_buf->indent_mode = tmp.indent_mode;
+ curr_buf->curr_window_line = tmp.curr_window_line;
+
+ assert(curr_buf->currline->prev);
+ curr_buf->currline = adjustline(curr_buf->currline, curr_buf->currline->len);
+ curr_buf->currline = curr_buf->currline->prev;
+ curr_buf->currline = adjustline(curr_buf->currline, WRAPMARGIN);
+ curr_buf->oldcurrline = curr_buf->currline;
+
+ if (curr_buf->currline->prev == NULL) {
+ curr_buf->top_of_win = curr_buf->currline;
+ curr_buf->currln = 0;
+ }
+ return curr_buf->currline;
+}
+
+/*
+ * join $line and $line->next
+ *
+ * line: A1 A2
+ * next: B1 B2
+ * ....: C1 C2
+ *
+ * case B=empty:
+ * return YEA
+ *
+ * case A+B < WRAPMARGIN:
+ * line: A1 A2 B1 B2
+ * next: C1 C2
+ * return YEA
+ * NOTE It assumes $line has allocated WRAPMARGIN length of data buffer.
+ *
+ * case A+B1+B2 > WRAPMARGIN, A+B1<WRAPMARGIN
+ * line: A1 A2 B1
+ * next: B2 " "
+ * call join($next)
+ */
+static int
+join(textline_t * line)
+{
+ register textline_t *n;
+ register int ovfl;
+
+ if (!(n = line->next))
+ return YEA;
+ if (!*next_non_space_char(n->data))
+ return YEA;
+
+ ovfl = line->len + n->len - WRAPMARGIN;
+ if (ovfl < 0) {
+ strcat(line->data, n->data);
+ line->len += n->len;
+ delete_line(n, 0);
+ return YEA;
+ } else {
+ register char *s; /* the split point */
+
+ s = n->data + n->len - ovfl - 1;
+ while (s != n->data && *s == ' ')
+ s--;
+ while (s != n->data && *s != ' ')
+ s--;
+ if (s == n->data)
+ return YEA;
+ split(n, (s - n->data) + 1);
+ if (line->len + line->next->len >= WRAPMARGIN) {
+ indigestion(0);
+ return YEA;
+ }
+ join(line);
+ n = line->next;
+ ovfl = n->len - 1;
+ if (ovfl >= 0 && ovfl < WRAPMARGIN - 2) {
+ s = &(n->data[ovfl]);
+ if (*s != ' ') {
+ strcpy(s, " ");
+ n->len++;
+ }
+ }
+ line->next=adjustline(line->next, WRAPMARGIN);
+ join(line->next);
+ line->next=adjustline(line->next, line->next->len);
+ return NA;
+ }
+}
+
+static void
+delete_char(void)
+{
+ register int len;
+
+ if ((len = curr_buf->currline->len)) {
+ if (unlikely(curr_buf->currpnt >= len)) {
+ indigestion(1);
+ return;
+ }
+ raw_shift_left(curr_buf->currline->data + curr_buf->currpnt, curr_buf->currline->len - curr_buf->currpnt + 1);
+ curr_buf->currline->len--;
+ }
+}
+
+static void
+load_file(FILE * fp)
+{
+ char buf[WRAPMARGIN + 2];
+ int indent_mode0 = curr_buf->indent_mode;
+
+ assert(fp);
+ curr_buf->indent_mode = 0;
+ while (fgets(buf, sizeof(buf), fp))
+ insert_string(buf);
+ curr_buf->indent_mode = indent_mode0;
+}
+
+/* 暫存檔 */
+char *
+ask_tmpbuf(int y)
+{
+ static char fp_buf[10] = "buf.0";
+ static char msg[] = "請選擇暫存檔 (0-9)[0]: ";
+
+ msg[19] = fp_buf[4];
+ do {
+ if (!getdata(y, 0, msg, fp_buf + 4, 4, DOECHO))
+ fp_buf[4] = msg[19];
+ } while (fp_buf[4] < '0' || fp_buf[4] > '9');
+ return fp_buf;
+}
+
+static void
+read_tmpbuf(int n)
+{
+ FILE *fp;
+ char fp_tmpbuf[80];
+ char tmpfname[] = "buf.0";
+ char *tmpf;
+ char ans[4] = "y";
+
+ if (0 <= n && n <= 9) {
+ tmpfname[4] = '0' + n;
+ tmpf = tmpfname;
+ } else {
+ tmpf = ask_tmpbuf(3);
+ n = tmpf[4] - '0';
+ }
+
+ setuserfile(fp_tmpbuf, tmpf);
+ if (n != 0 && n != 5 && more(fp_tmpbuf, NA) != -1)
+ getdata(b_lines - 1, 0, "確定讀入嗎(Y/N)?[Y]", ans, sizeof(ans), LCECHO);
+ if (*ans != 'n' && (fp = fopen(fp_tmpbuf, "r"))) {
+ load_file(fp);
+ fclose(fp);
+ while (curr_buf->curr_window_line >= b_lines) {
+ curr_buf->curr_window_line--;
+ curr_buf->top_of_win = curr_buf->top_of_win->next;
+ }
+ }
+}
+
+static void
+write_tmpbuf(void)
+{
+ FILE *fp;
+ char fp_tmpbuf[80], ans[4];
+ textline_t *p;
+
+ setuserfile(fp_tmpbuf, ask_tmpbuf(3));
+ if (dashf(fp_tmpbuf)) {
+ more(fp_tmpbuf, NA);
+ getdata(b_lines - 1, 0, "暫存檔已有資料 (A)附加 (W)覆寫 (Q)取消?[A] ",
+ ans, sizeof(ans), LCECHO);
+
+ if (ans[0] == 'q')
+ return;
+ }
+ if ((fp = fopen(fp_tmpbuf, (ans[0] == 'w' ? "w" : "a+")))) {
+ for (p = curr_buf->firstline; p; p = p->next) {
+ if (p->next || p->data[0])
+ fprintf(fp, "%s\n", p->data);
+ }
+ fclose(fp);
+ }
+}
+
+static void
+erase_tmpbuf(void)
+{
+ char fp_tmpbuf[80];
+ char ans[4] = "n";
+
+ setuserfile(fp_tmpbuf, ask_tmpbuf(3));
+ if (more(fp_tmpbuf, NA) != -1)
+ getdata(b_lines - 1, 0, "確定刪除嗎(Y/N)?[N]",
+ ans, sizeof(ans), LCECHO);
+ if (*ans == 'y')
+ unlink(fp_tmpbuf);
+}
+
+/**
+ * 編輯器自動備份
+ *(最多備份 512 行 (?))
+ */
+void
+auto_backup(void)
+{
+ if (curr_buf == NULL)
+ return;
+
+ if (curr_buf->currline) {
+ FILE *fp;
+ textline_t *p, *v;
+ char bakfile[PATHLEN];
+ int count = 0;
+
+ setuserfile(bakfile, fp_bak);
+ if ((fp = fopen(bakfile, "w"))) {
+ for (p = curr_buf->firstline; p != NULL && count < 512; p = v, count++) {
+ v = p->next;
+ fprintf(fp, "%s\n", p->data);
+ free_line(p);
+ }
+ fclose(fp);
+ }
+ curr_buf->currline = NULL;
+ }
+}
+
+/**
+ * 取回編輯器備份
+ */
+void
+restore_backup(void)
+{
+ char bakfile[80], buf[80];
+
+ setuserfile(bakfile, fp_bak);
+ if (dashf(bakfile)) {
+ stand_title("編輯器自動復原");
+ getdata(1, 0, "您有一篇文章尚未完成,(S)寫入暫存檔 (Q)算了?[S] ",
+ buf, 4, LCECHO);
+ if (buf[0] != 'q') {
+ setuserfile(buf, ask_tmpbuf(3));
+ Rename(bakfile, buf);
+ } else
+ unlink(bakfile);
+ }
+}
+
+/* 引用文章 */
+
+static int
+garbage_line(const char *str)
+{
+ int qlevel = 0;
+
+ while (*str == ':' || *str == '>') {
+ if (*(++str) == ' ')
+ str++;
+ if (qlevel++ >= 1)
+ return 1;
+ }
+ while (*str == ' ' || *str == '\t')
+ str++;
+ if (qlevel >= 1) {
+ if (!strncmp(str, "※ ", 3) || !strncmp(str, "==>", 3) ||
+ strstr(str, ") 提到:\n"))
+ return 1;
+ }
+ return (*str == '\n');
+}
+
+static void
+quote_strip_ansi_inline(unsigned char *is)
+{
+ unsigned char *os = is;
+
+ while (*is)
+ {
+ if(*is != ESC_CHR)
+ *os++ = *is;
+ else
+ {
+ is ++;
+ if(*is == '*')
+ {
+ /* ptt prints, keep it as normal */
+ *os++ = '*';
+ *os++ = '*';
+ }
+ else
+ {
+ /* normal ansi, strip them out. */
+ while (*is && ANSI_IN_ESCAPE(*is))
+ is++;
+ }
+ }
+ is++;
+
+ }
+
+ *os = 0;
+}
+
+static void
+do_quote(void)
+{
+ int op;
+ char buf[256];
+
+ getdata(b_lines - 1, 0, "請問要引用原文嗎(Y/N/All/Repost)?[Y] ",
+ buf, 3, LCECHO);
+ op = buf[0];
+
+ if (op != 'n') {
+ FILE *inf;
+
+ if ((inf = fopen(quote_file, "r"))) {
+ char *ptr;
+ int indent_mode0 = curr_buf->indent_mode;
+
+ fgets(buf, 256, inf);
+ if ((ptr = strrchr(buf, ')')))
+ ptr[1] = '\0';
+ else if ((ptr = strrchr(buf, '\n')))
+ ptr[0] = '\0';
+
+ if ((ptr = strchr(buf, ':'))) {
+ char *str;
+
+ while (*(++ptr) == ' ');
+
+ /* 順手牽羊,取得 author's address */
+ if ((curredit & EDIT_BOTH) && (str = strchr(quote_user, '.'))) {
+ strcpy(++str, ptr);
+ str = strchr(str, ' ');
+ assert(str);
+ str[0] = '\0';
+ }
+ } else
+ ptr = quote_user;
+
+ curr_buf->indent_mode = 0;
+ insert_string("※ 引述《");
+ insert_string(ptr);
+ insert_string("》之銘言:\n");
+
+ if (op != 'a') /* 去掉 header */
+ while (fgets(buf, 256, inf) && buf[0] != '\n');
+ /* FIXME by MH:
+ 如果 header 到內文中間沒有空行分隔,會造成 All 以外的模式
+ 都引不到內文。
+ */
+
+ if (op == 'a')
+ while (fgets(buf, 256, inf)) {
+ insert_char(':');
+ insert_char(' ');
+ quote_strip_ansi_inline((unsigned char *)buf);
+ insert_string(buf);
+ }
+ else if (op == 'r')
+ while (fgets(buf, 256, inf)) {
+ /* repost, keep anything */
+ // quote_strip_ansi_inline((unsigned char *)buf);
+ insert_string(buf);
+ }
+ else {
+ if (curredit & EDIT_LIST) /* 去掉 mail list 之 header */
+ while (fgets(buf, 256, inf) && (!strncmp(buf, "※ ", 3)));
+ while (fgets(buf, 256, inf)) {
+ if (!strcmp(buf, "--\n"))
+ break;
+ if (!garbage_line(buf)) {
+ insert_char(':');
+ insert_char(' ');
+ quote_strip_ansi_inline((unsigned char *)buf);
+ insert_string(buf);
+ }
+ }
+ }
+ curr_buf->indent_mode = indent_mode0;
+ fclose(inf);
+ }
+ }
+}
+
+/**
+ * 審查 user 引言的使用
+ */
+static int
+check_quote(void)
+{
+ register textline_t *p = curr_buf->firstline;
+ register char *str;
+ int post_line;
+ int included_line;
+
+ post_line = included_line = 0;
+ while (p) {
+ if (!strcmp(str = p->data, "--"))
+ break;
+ if (str[1] == ' ' && ((str[0] == ':') || (str[0] == '>')))
+ included_line++;
+ else {
+ while (*str == ' ' || *str == '\t')
+ str++;
+ if (*str)
+ post_line++;
+ }
+ p = p->next;
+ }
+
+ if ((included_line >> 2) > post_line) {
+ move(4, 0);
+ outs("本篇文章的引言比例超過 80%,請您做些微的修正:\n\n"
+ ANSI_COLOR(1;33) "1) 增加一些文章 或 2) 刪除不必要之引言" ANSI_RESET);
+ {
+ char ans[4];
+
+ getdata(12, 12, "(E)繼續編輯 (W)強制寫入?[E] ",
+ ans, sizeof(ans), LCECHO);
+ if (ans[0] == 'w')
+ return 0;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+/* 檔案處理:讀檔、存檔、標題、簽名檔 */
+static void
+read_file(const char *fpath)
+{
+ FILE *fp;
+
+ if ((fp = fopen(fpath, "r")) == NULL) {
+ int fd;
+ if ((fd = creat(fpath, 0600)) >= 0) {
+ close(fd);
+ return;
+ }
+ indigestion(4);
+ abort_bbs(0);
+ }
+ load_file(fp);
+ fclose(fp);
+}
+
+void
+write_header(FILE * fp, char *mytitle) // FIXME unused
+{
+
+ if (curredit & EDIT_MAIL || curredit & EDIT_LIST) {
+ fprintf(fp, "%s %s (%s)\n", str_author1, cuser.userid,
+ cuser.nickname
+ );
+ } else {
+ char *ptr = mytitle;
+ struct {
+ char author[IDLEN + 1];
+ char board[IDLEN + 1];
+ char title[66];
+ time4_t date; /* last post's date */
+ int number; /* post number */
+ } postlog;
+
+ memset(&postlog, 0, sizeof(postlog));
+ strlcpy(postlog.author, cuser.userid, sizeof(postlog.author));
+ if (curr_buf)
+ curr_buf->ifuseanony = 0;
+#ifdef HAVE_ANONYMOUS
+ if (currbrdattr & BRD_ANONYMOUS) {
+ int defanony = (currbrdattr & BRD_DEFAULTANONYMOUS);
+ if (defanony)
+ getdata(3, 0, "請輸入你想用的ID,也可直接按[Enter],"
+ "或是按[r]用真名:", real_name, sizeof(real_name), DOECHO);
+ else
+ getdata(3, 0, "請輸入你想用的ID,也可直接按[Enter]使用原ID:",
+ real_name, sizeof(real_name), DOECHO);
+ if (!real_name[0] && defanony) {
+ strlcpy(real_name, "Anonymous", sizeof(real_name));
+ strlcpy(postlog.author, real_name, sizeof(postlog.author));
+ if (curr_buf)
+ curr_buf->ifuseanony = 1;
+ } else {
+ if (!strcmp("r", real_name) || (!defanony && !real_name[0]))
+ strlcpy(postlog.author, cuser.userid, sizeof(postlog.author));
+ else {
+ snprintf(postlog.author, sizeof(postlog.author),
+ "%s.", real_name);
+ if (curr_buf)
+ curr_buf->ifuseanony = 1;
+ }
+ }
+ }
+#endif
+ strlcpy(postlog.board, currboard, sizeof(postlog.board));
+ if (!strncmp(ptr, str_reply, 4))
+ ptr += 4;
+ strlcpy(postlog.title, ptr, sizeof(postlog.title));
+ postlog.date = now;
+ postlog.number = 1;
+ append_record(".post", (fileheader_t *) & postlog, sizeof(postlog));
+#ifdef HAVE_ANONYMOUS
+ if (currbrdattr & BRD_ANONYMOUS) {
+ int defanony = (currbrdattr & BRD_DEFAULTANONYMOUS);
+
+ fprintf(fp, "%s %s (%s) %s %s\n", str_author1, postlog.author,
+ (((!strcmp(real_name, "r") && defanony) ||
+ (!real_name[0] && (!defanony))) ? cuser.nickname :
+ "猜猜我是誰 ? ^o^"),
+ local_article ? str_post2 : str_post1, currboard);
+ } else {
+ fprintf(fp, "%s %s (%s) %s %s\n", str_author1, cuser.userid,
+ cuser.nickname,
+ local_article ? str_post2 : str_post1, currboard);
+ }
+#else /* HAVE_ANONYMOUS */
+ fprintf(fp, "%s %s (%s) %s %s\n", str_author1, cuser.userid,
+ cuser.nickname,
+ local_article ? str_post2 : str_post1, currboard);
+#endif /* HAVE_ANONYMOUS */
+
+ }
+ mytitle[72] = '\0';
+ fprintf(fp, "標題: %s\n時間: %s\n", mytitle, ctime4(&now));
+}
+
+void
+addsignature(FILE * fp, int ifuseanony)
+{
+ FILE *fs;
+ int i;
+ char buf[WRAPMARGIN + 1];
+ char fpath[STRLEN];
+
+ char ch;
+
+ if (!strcmp(cuser.userid, STR_GUEST)) {
+ fprintf(fp, "\n--\n※ 發信站 :" BBSNAME "(" MYHOSTNAME
+ ") \n◆ From: %s\n", fromhost);
+ return;
+ }
+ if (!ifuseanony) {
+
+ int browsing = 0;
+ SigInfo si;
+ memset(&si, 0, sizeof(si));
+
+browse_sigs:
+ showsignature(fpath, &i, &si);
+
+ if (si.total > 0){
+ char msg[64];
+
+ ch = isdigit(cuser.signature) ? cuser.signature : 'x';
+ sprintf(msg,
+ (browsing || (si.max > si.show_max)) ?
+ "請選擇簽名檔 (1-9, 0=不加 n=翻頁 x=隨機)[%c]: ":
+ "請選擇簽名檔 (1-9, 0=不加 x=隨機)[%c]: ",
+ ch);
+ getdata(0, 0, msg, buf, 4, LCECHO);
+
+ if(buf[0] == 'n')
+ {
+ si.show_start = si.show_max + 1;
+ if(si.show_start > si.max)
+ si.show_start = 0;
+ browsing = 1;
+ goto browse_sigs;
+ }
+
+ if (!buf[0])
+ buf[0] = ch;
+
+ if (isdigit((int)buf[0]))
+ ch = buf[0];
+ else
+ ch = '1' + random() % (si.max+1);
+ cuser.signature = buf[0];
+
+ if (ch != '0') {
+ fpath[i] = ch;
+ do
+ {
+ if ((fs = fopen(fpath, "r"))) {
+ fputs("\n--\n", fp);
+ for (i = 0; i < MAX_SIGLINES &&
+ fgets(buf, sizeof(buf), fs); i++)
+ fputs(buf, fp);
+ fclose(fs);
+ fpath[i] = ch;
+ }
+ else
+ fpath[i] = '1' + (fpath[i] - '1' + 1) % (si.max+1);
+ } while (!isdigit((int)buf[0]) && si.max > 0 && ch != fpath[i]);
+ }
+ }
+ }
+#ifdef HAVE_ORIGIN
+#ifdef HAVE_ANONYMOUS
+ if (ifuseanony)
+ fprintf(fp, "\n--\n※ 發信站: " BBSNAME "(" MYHOSTNAME
+ ") \n◆ From: %s\n", "匿名天使的家");
+ else
+#endif
+ {
+ char temp[33];
+
+ strlcpy(temp, fromhost, sizeof(temp));
+ fprintf(fp, "\n--\n※ 發信站: " BBSNAME "(" MYHOSTNAME
+ ") \n◆ From: %s\n", temp);
+ }
+#endif
+}
+
+static int
+write_file(char *fpath, int saveheader, int *islocal, char *mytitle)
+{
+ struct tm *ptime;
+ FILE *fp = NULL;
+ textline_t *p, *v;
+ char ans[TTLEN], *msg;
+ int aborted = 0, line = 0, checksum[3], sum = 0, po = 1;
+
+ stand_title("檔案處理");
+ if (currstat == SMAIL)
+ msg = "[S]儲存 (A)放棄 (T)改標題 (E)繼續 (R/W/D)讀寫刪暫存檔?";
+ else if (local_article)
+ msg = "[L]站內信件 (S)儲存 (A)放棄 (T)改標題 (E)繼續 "
+ "(R/W/D)讀寫刪暫存檔?";
+ else
+ msg = "[S]儲存 (L)站內信件 (A)放棄 (T)改標題 (E)繼續 "
+ "(R/W/D)讀寫刪暫存檔?";
+ getdata(1, 0, msg, ans, 2, LCECHO);
+
+ switch (ans[0]) {
+ case 'a':
+ outs("文章" ANSI_COLOR(1) " 沒有 " ANSI_RESET "存入");
+ aborted = -1;
+ break;
+ case 'r':
+ read_tmpbuf(-1);
+ case 'e':
+ return KEEP_EDITING;
+ case 'w':
+ write_tmpbuf();
+ return KEEP_EDITING;
+ case 'd':
+ erase_tmpbuf();
+ return KEEP_EDITING;
+ case 't':
+ move(3, 0);
+ prints("舊標題:%s", mytitle);
+ strlcpy(ans, mytitle, sizeof(ans));
+ if (getdata_buf(4, 0, "新標題:", ans, sizeof(ans), DOECHO))
+ strlcpy(mytitle, ans, STRLEN);
+ return KEEP_EDITING;
+ case 's':
+ if (!HasUserPerm(PERM_LOGINOK)) {
+ local_article = 1;
+ move(2, 0);
+ outs("您尚未通過身份確認,只能 Local Save。\n");
+ pressanykey();
+ } else
+ local_article = 0;
+ break;
+ case 'l':
+ local_article = 1;
+ }
+
+ if (!aborted) {
+
+ if (saveheader && !(curredit & EDIT_MAIL) && check_quote())
+ return KEEP_EDITING;
+
+ if (!(*fpath))
+ setuserfile(fpath, "ve_XXXXXX");
+ if ((fp = fopen(fpath, "w")) == NULL) {
+ indigestion(5);
+ abort_bbs(0);
+ }
+ if (saveheader)
+ write_header(fp, mytitle);
+ }
+ for (p = curr_buf->firstline; p; p = v) {
+ v = p->next;
+ if (!aborted) {
+ assert(fp);
+ msg = p->data;
+ if (v || msg[0]) {
+ trim(msg);
+
+ line++;
+ /* check crosspost */
+ if (currstat == POSTING && po ) {
+ int msgsum = StringHash(msg);
+ if (msgsum) {
+ if (postrecord.last_bid != currbid &&
+ postrecord.checksum[po] == msgsum) {
+ po++;
+ if (po > 3) {
+ postrecord.times++;
+ postrecord.last_bid = currbid;
+ po = 0;
+ }
+ } else
+ po = 1;
+ if (line >= curr_buf->totaln / 2 && sum < 3) {
+ checksum[sum++] = msgsum;
+ }
+ }
+ }
+ fprintf(fp, "%s\n", msg);
+ }
+ }
+ free_line(p);
+ }
+ curr_buf->currline = NULL;
+
+ if (postrecord.times > MAX_CROSSNUM-1 && hbflcheck(currbid, currutmp->uid))
+ anticrosspost();
+
+ if (po && sum == 3) {
+ memcpy(&postrecord.checksum[1], checksum, sizeof(int) * 3);
+ if(postrecord.last_bid != currbid)
+ postrecord.times = 0;
+ }
+ if (!aborted) {
+ if (islocal)
+ *islocal = local_article;
+ if (currstat == POSTING || currstat == SMAIL)
+ {
+ addsignature(fp, curr_buf->ifuseanony);
+ }
+ else if (currstat == REEDIT
+#ifndef ALL_REEDIT_LOG
+ && strcmp(currboard, str_sysop) == 0
+#endif
+ ) {
+ ptime = localtime4(&now);
+ fprintf(fp,
+ "※ 編輯: %-15s 來自: %-20s (%02d/%02d %02d:%02d)\n",
+ cuser.userid, fromhost,
+ ptime->tm_mon + 1, ptime->tm_mday, ptime->tm_hour, ptime->tm_min);
+ }
+ fclose(fp);
+ }
+ return aborted;
+}
+
+static inline int
+has_block_selection(void)
+{
+ return curr_buf->blockln >= 0;
+}
+
+/**
+ * a block is continual lines of the article.
+ */
+
+/**
+ * stop the block selection.
+ */
+static void
+block_cancel(void)
+{
+ if (has_block_selection()) {
+ curr_buf->blockln = -1;
+ curr_buf->redraw_everything = YEA;
+ }
+}
+
+static inline void
+setup_block_begin_end(textline_t **begin, textline_t **end)
+{
+ if (curr_buf->currln >= curr_buf->blockln) {
+ *begin = curr_buf->blockline;
+ *end = curr_buf->currline;
+ } else {
+ *begin = curr_buf->currline;
+ *end = curr_buf->blockline;
+ }
+}
+
+static inline void
+setup_block_begin_end_number(short *begin, short *end)
+{
+ if (curr_buf->currpnt > curr_buf->blockpnt) {
+ *begin = curr_buf->blockpnt;
+ *end = curr_buf->currpnt;
+ } else {
+ *begin = curr_buf->currpnt;
+ *end = curr_buf->blockpnt;
+ }
+}
+
+#define BLOCK_TRUNCATE 0
+#define BLOCK_APPEND 1
+/**
+ * save the selected block to file 'fname.'
+ * mode: BLOCK_TRUNCATE truncate mode
+ * BLOCK_APPEND append mode
+ */
+static void
+block_save_to_file(const char *fname, int mode)
+{
+ textline_t *begin, *end;
+ char fp_tmpbuf[80];
+ FILE *fp;
+
+ if (!has_block_selection())
+ return;
+
+ setup_block_begin_end(&begin, &end);
+
+ setuserfile(fp_tmpbuf, fname);
+ if ((fp = fopen(fp_tmpbuf, mode == BLOCK_APPEND ? "a+" : "w+"))) {
+ if (begin == end && curr_buf->currpnt != curr_buf->blockpnt) {
+ char buf[WRAPMARGIN + 2];
+
+ if (curr_buf->currpnt > curr_buf->blockpnt) {
+ strlcpy(buf, begin->data + curr_buf->blockpnt, sizeof(buf));
+ buf[curr_buf->currpnt - curr_buf->blockpnt] = 0;
+ } else {
+ strlcpy(buf, begin->data + curr_buf->currpnt, sizeof(buf));
+ buf[curr_buf->blockpnt - curr_buf->currpnt] = 0;
+ }
+ fputs(buf, fp);
+ } else {
+ textline_t *p;
+
+ for (p = begin; p != end; p = p->next)
+ fprintf(fp, "%s\n", p->data);
+ fprintf(fp, "%s\n", end->data);
+ }
+ fclose(fp);
+ }
+}
+
+/**
+ * delete selected block
+ */
+static void
+block_delete(void)
+{
+ textline_t *begin, *end;
+
+ if (!has_block_selection())
+ return;
+
+ setup_block_begin_end(&begin, &end);
+
+ if (begin == end && curr_buf->currpnt != curr_buf->blockpnt) {
+ short min, max;
+
+ setup_block_begin_end_number(&min, &max);
+ strcpy(begin->data + min, begin->data + max);
+ begin->len -= max - min;
+ curr_buf->currpnt = min;
+
+ } else {
+ textline_t *p;
+
+ if (curr_buf->currln >= curr_buf->blockln) {
+ curr_buf->curr_window_line -= (curr_buf->currln - curr_buf->blockln + 1);
+ if (curr_buf->curr_window_line < 0) {
+ curr_buf->curr_window_line = 0;
+ if (end->next)
+ (curr_buf->top_of_win = end->next)->prev = begin->prev;
+ else
+ curr_buf->top_of_win = (curr_buf->lastline = begin->prev);
+ }
+ curr_buf->currln -= (curr_buf->currln - curr_buf->blockln);
+ }
+
+ if (begin->prev)
+ begin->prev->next = end->next;
+ else if (end->next)
+ curr_buf->top_of_win = curr_buf->firstline = end->next;
+ else {
+ curr_buf->currline = curr_buf->top_of_win = curr_buf->firstline = curr_buf->lastline = alloc_line(WRAPMARGIN);
+ curr_buf->currln = curr_buf->curr_window_line = curr_buf->edit_margin = 0;
+ }
+
+ if (end->next) {
+ curr_buf->currline = end->next;
+ curr_buf->currline->prev = begin->prev;
+ }
+ else if (begin->prev) {
+ curr_buf->currline = (curr_buf->lastline = begin->prev);
+ curr_buf->currln--;
+ if (curr_buf->curr_window_line > 0)
+ curr_buf->curr_window_line--;
+ }
+
+ for (p = begin; p != end; curr_buf->totaln--)
+ free_line((p = p->next)->prev);
+ free_line(end);
+ curr_buf->totaln--;
+
+ curr_buf->currpnt = 0;
+ }
+}
+
+static void
+block_cut(void)
+{
+ if (!has_block_selection())
+ return;
+
+ block_save_to_file("buf.0", BLOCK_TRUNCATE);
+ block_delete();
+
+ curr_buf->blockln = -1;
+ curr_buf->redraw_everything = YEA;
+}
+
+static void
+block_copy(void)
+{
+ if (!has_block_selection())
+ return;
+
+ block_save_to_file("buf.0", BLOCK_TRUNCATE);
+
+ curr_buf->blockln = -1;
+ curr_buf->redraw_everything = YEA;
+}
+
+static void
+block_prompt(void)
+{
+ char fp_tmpbuf[80];
+ char tmpfname[] = "buf.0";
+ char mode[2];
+
+ move(b_lines - 1, 0);
+ clrtoeol();
+
+ if (!getdata(b_lines - 1, 0, "把區塊移至暫存檔 (0:Cut, 5:Copy, 6-9, q: Cancel)[0] ", tmpfname + 4, 4, LCECHO))
+ tmpfname[4] = '0';
+
+ if (tmpfname[4] < '0' || tmpfname[4] > '9')
+ goto cancel_block;
+
+ if (tmpfname[4] == '0') {
+ block_cut();
+ return;
+ }
+ else if (tmpfname[4] == '5') {
+ block_copy();
+ return;
+ }
+
+ setuserfile(fp_tmpbuf, tmpfname);
+ if (dashf(fp_tmpbuf)) {
+ more(fp_tmpbuf, NA);
+ getdata(b_lines - 1, 0, "暫存檔已有資料 (A)附加 (W)覆寫 (Q)取消?[W] ", mode, sizeof(mode), LCECHO);
+ if (mode[0] == 'q')
+ goto cancel_block;
+ else if (mode[0] != 'a')
+ mode[0] = 'w';
+ }
+
+ if (getans("刪除區塊(Y/N)?[N] ") != 'y')
+ goto cancel_block;
+
+ block_save_to_file(tmpfname, mode[0] == 'a' ? BLOCK_APPEND : BLOCK_TRUNCATE);
+
+cancel_block:
+ curr_buf->blockln = -1;
+ curr_buf->redraw_everything = YEA;
+}
+
+static void
+block_select(void)
+{
+ curr_buf->blockln = curr_buf->currln;
+ curr_buf->blockpnt = curr_buf->currpnt;
+ curr_buf->blockline = curr_buf->currline;
+}
+
+/**
+ * Just like outs, but print out '*' instead of 27(decimal) in the given string.
+ *
+ * FIXME column could not start from 0
+ */
+
+void
+edit_outs(const char *text)
+{
+ edit_outs_n(text, scr_cols);
+}
+
+void
+edit_outs_n(const char *text, int n)
+{
+ int column = 0;
+
+ register unsigned char inAnsi = 0;
+ register unsigned char ch;
+
+#ifdef DBCSAWARE
+ /* 0 = N/A, 1 = leading byte printed, 2 = ansi in middle */
+ register unsigned char isDBCS = 0;
+#endif
+
+ while ((ch = *text++) && (++column < t_columns) && n-- > 0)
+ {
+ if(inAnsi)
+ {
+ if(ch == ESC_CHR)
+ outc('*');
+ else
+ {
+ outc(ch);
+
+ if(!ANSI_IN_ESCAPE(ch))
+ {
+ inAnsi = 0;
+ outs(ANSI_RESET);
+ }
+ }
+
+ }
+ else if(ch == ESC_CHR)
+ {
+ inAnsi = 1;
+#ifdef DBCSAWARE
+ if(isDBCS == 1)
+ {
+ isDBCS = 2;
+ outs(//ESC_STR "[1D"
+ ANSI_COLOR(1;33) "?" ANSI_RESET);
+ }
+#endif
+ outs(ANSI_COLOR(1) "*");
+ }
+ else
+ {
+#ifdef DBCSAWARE
+ if(isDBCS == 1)
+ isDBCS = 0;
+ else if (isDBCS == 2)
+ {
+ /* ansi in middle. */
+ outs(ANSI_COLOR(0;33) "?" ANSI_RESET);
+ isDBCS = 0;
+ continue;
+ }
+ else
+ if(IS_BIG5_HI(ch))
+ {
+ isDBCS = 1;
+ // peak next char
+ if(n > 0 && *text == ESC_CHR)
+ continue;
+ }
+#endif
+ outc(ch);
+ }
+ }
+
+ if(inAnsi)
+ outs(ANSI_RESET);
+}
+
+static void
+edit_ansi_outs(const char *str)
+{
+ char c;
+ while ((c = *str++)) {
+ if(c == ESC_CHR && *str == '*')
+ {
+ // ptt prints
+ /* Because moving within ptt_prints is too hard
+ * let's just display it as-is.
+ */
+ outc('*');
+ /*
+ char buf[64] = ESC_STR "*x";
+
+ str ++;
+ buf[2] = *str++;
+ Ptt_prints(buf, NO_RELOAD);
+ outs(buf);
+ */
+ } else {
+ outc(c);
+ }
+ }
+}
+
+static void
+edit_ansi_outs_n(const char *str, int n)
+{
+ char c;
+ while (n-- > 0 && (c = *str++)) {
+ if(c == ESC_CHR && *str == '*')
+ {
+ // ptt prints
+ /* Because moving within ptt_prints is too hard
+ * let's just display it as-is.
+ */
+ outc('*');
+ /*
+ char buf[64] = ESC_STR "*x";
+
+ str ++;
+ buf[2] = *str++;
+ Ptt_prints(buf, NO_RELOAD);
+ if(strlen(buf) > n+1)
+ buf[n+1] = 0;
+ outs(buf);
+ n -= strlen(buf);
+ */
+ } else {
+ outc(c);
+ }
+ }
+}
+
+static inline void
+display_textline_internal(textline_t *p, int i, int min, int max)
+{
+ char inblock;
+ short tmp;
+ void (*output)(const char *);
+ void (*output_n)(const char *, int);
+
+ move(i, 0);
+ clrtoeol();
+
+ if (!p) {
+ outc('~');
+ return;
+ }
+
+ if (curr_buf->ansimode) {
+ output = edit_ansi_outs;
+ output_n = edit_ansi_outs_n;
+ }
+ else {
+ output = edit_outs;
+ output_n = edit_outs_n;
+ }
+
+ tmp = curr_buf->currln - curr_buf->curr_window_line + i;
+
+ /* if line 'i' is in block's range */
+ if (has_block_selection() && (
+ (curr_buf->blockln <= curr_buf->currln &&
+ curr_buf->blockln <= tmp && tmp <= curr_buf->currln) ||
+ (curr_buf->currln <= tmp && tmp <= curr_buf->blockln)) ) {
+ outs(ANSI_COLOR(7));
+ inblock = 1;
+ } else
+ inblock = 0;
+
+ if (curr_buf->currln == curr_buf->blockln && p == curr_buf->currline && max > min) {
+ outs(ANSI_RESET);
+ (*output_n)(p->data, min);
+ outs(ANSI_COLOR(7));
+ (*output_n)(p->data + min, max - min);
+ outs(ANSI_RESET);
+ (*output)(p->data + max);
+ } else
+
+#ifdef DBCSAWARE
+ if(mbcs_mode && curr_buf->edit_margin > 0)
+ {
+ if(curr_buf->edit_margin >= p->len)
+ {
+ (*output)("");
+ } else {
+ int newpnt = curr_buf->edit_margin;
+ unsigned char *pdata = (unsigned char*)(&p->data[0] + curr_buf->edit_margin);
+ if(mbcs_mode)
+ newpnt = fix_cursor(p->data, newpnt, FC_LEFT);
+ if(newpnt == curr_buf->edit_margin-1)
+ {
+ /* this should be always 'outs'? */
+ // (*output)(ANSI_COLOR(1) "<" ANSI_RESET);
+ outs(ANSI_COLOR(1) "<" ANSI_RESET);
+ pdata++;
+ }
+ (*output)((char*)pdata);
+ }
+
+ } else
+#endif
+ (*output)((curr_buf->edit_margin < p->len) ? &p->data[curr_buf->edit_margin] : "");
+
+ if (inblock)
+ outs(ANSI_RESET);
+}
+/**
+ * given a textline_t 'text' and the line number 'n' in the content,
+ * display this line.
+ *
+ * this is not called... why? */
+/*
+static void
+display_textline(textline_t *text, int n)
+{
+ short begin, end;
+
+ setup_block_begin_end_number(&begin, &end);
+ display_textline_internal(text, n, begin, end);
+}
+*/
+
+static void
+refresh_window(void)
+{
+ register textline_t *p;
+ register int i;
+ short begin, end;
+
+ setup_block_begin_end_number(&begin, &end);
+
+ for (p = curr_buf->top_of_win, i = 0; i < b_lines; i++) {
+ display_textline_internal(p, i, begin, end);
+
+ if (p)
+ p = p->next;
+ }
+ edit_msg();
+}
+
+static void
+goto_line(int lino)
+{
+ if (lino > 0 && lino <= curr_buf->totaln + 1) {
+ textline_t *p;
+
+ p = curr_buf->firstline;
+ curr_buf->currln = lino - 1;
+
+ while (--lino && p->next)
+ p = p->next;
+
+ if (p)
+ curr_buf->currline = p;
+ else {
+ curr_buf->currln = curr_buf->totaln;
+ curr_buf->currline = curr_buf->lastline;
+ }
+
+ curr_buf->currpnt = 0;
+
+ /* move window */
+ if (curr_buf->currln < middle_line()) {
+ curr_buf->top_of_win = curr_buf->firstline;
+ curr_buf->curr_window_line = curr_buf->currln;
+ } else {
+ int i;
+ curr_buf->curr_window_line = middle_line();
+ for (i = curr_buf->curr_window_line; i; i--)
+ p = p->prev;
+ curr_buf->top_of_win = p;
+ }
+ }
+ curr_buf->redraw_everything = YEA;
+}
+
+static void
+prompt_goto_line(void)
+{
+ char buf[10];
+
+ if (getdata(b_lines - 1, 0, "跳至第幾行:", buf, sizeof(buf), DOECHO))
+ goto_line(atoi(buf));
+}
+
+/**
+ * search string interactively.
+ * @param mode 0: prompt
+ * 1: forward
+ * -1: backward
+ */
+static void
+search_str(int mode)
+{
+ const int max_keyword = 65;
+ char *str;
+ char ans[4] = "n";
+
+ if (curr_buf->searched_string == NULL) {
+ if (mode != 0)
+ return;
+ curr_buf->searched_string = (char *)malloc(max_keyword * sizeof(char));
+ curr_buf->searched_string[0] = 0;
+ }
+
+ str = curr_buf->searched_string;
+
+ if (!mode) {
+ if (getdata_buf(b_lines - 1, 0, "[搜尋]關鍵字:",
+ str, max_keyword, DOECHO))
+ if (*str) {
+ if (getdata(b_lines - 1, 0, "區分大小寫(Y/N/Q)? [N] ",
+ ans, sizeof(ans), LCECHO) && *ans == 'y')
+ curr_buf->substr_fp = strstr;
+ else
+ curr_buf->substr_fp = strcasestr;
+ }
+ }
+ if (*str && *ans != 'q') {
+ textline_t *p;
+ char *pos = NULL;
+ int lino;
+
+ if (mode >= 0) {
+ for (lino = curr_buf->currln, p = curr_buf->currline; p; p = p->next, lino++)
+ if ((pos = (*curr_buf->substr_fp)(p->data + (lino == curr_buf->currln ? curr_buf->currpnt + 1 : 0),
+ str)) && (lino != curr_buf->currln ||
+ pos - p->data != curr_buf->currpnt))
+ break;
+ } else {
+ for (lino = curr_buf->currln, p = curr_buf->currline; p; p = p->prev, lino--)
+ if ((pos = (*curr_buf->substr_fp)(p->data, str)) &&
+ (lino != curr_buf->currln || pos - p->data != curr_buf->currpnt))
+ break;
+ }
+ if (pos) {
+ /* move window */
+ curr_buf->currline = p;
+ curr_buf->currln = lino;
+ curr_buf->currpnt = pos - p->data;
+ if (lino < middle_line()) {
+ curr_buf->top_of_win = curr_buf->firstline;
+ curr_buf->curr_window_line = curr_buf->currln;
+ } else {
+ int i;
+
+ curr_buf->curr_window_line = middle_line();
+ for (i = curr_buf->curr_window_line; i; i--)
+ p = p->prev;
+ curr_buf->top_of_win = p;
+ }
+ curr_buf->redraw_everything = YEA;
+ }
+ }
+ if (!mode)
+ curr_buf->redraw_everything = YEA;
+}
+
+/**
+ * move the cursor from bracket to corresponding bracket.
+ */
+static void
+match_paren(void)
+{
+ char *parens = "()[]{}";
+ int type;
+ int parenum = 0;
+ char *ptype;
+ textline_t *p;
+ int lino;
+ int c, i = 0;
+
+ if (!(ptype = strchr(parens, curr_buf->currline->data[curr_buf->currpnt])))
+ return;
+
+ type = (ptype - parens) / 2;
+ parenum = ((ptype - parens) % 2) ? -1 : 1;
+
+ /* FIXME CRASH */
+ /* FIXME refactoring */
+ if (parenum > 0) {
+ for (lino = curr_buf->currln, p = curr_buf->currline; p; p = p->next, lino++) {
+ int len = strlen(p->data);
+ for (i = (lino == curr_buf->currln) ? curr_buf->currpnt + 1 : 0; i < len; i++) {
+ if (p->data[i] == '/' && p->data[++i] == '*') {
+ ++i;
+ while (1) {
+ while (i < len &&
+ !(p->data[i] == '*' && p->data[i + 1] == '/')) {
+ i++;
+ }
+ if (i >= len && p->next) {
+ p = p->next;
+ len = strlen(p->data);
+ ++lino;
+ i = 0;
+ } else
+ break;
+ }
+ } else if ((c = p->data[i]) == '\'' || c == '"') {
+ while (1) {
+ while (i < len - 1) {
+ if (p->data[++i] == '\\' && (size_t)i < len - 2)
+ ++i;
+ else if (p->data[i] == c)
+ goto end_quote;
+ }
+ if ((size_t)i >= len - 1 && p->next) {
+ p = p->next;
+ len = strlen(p->data);
+ ++lino;
+ i = -1;
+ } else
+ break;
+ }
+ end_quote:
+ ;
+ } else if ((ptype = strchr(parens, p->data[i])) &&
+ (ptype - parens) / 2 == type) {
+ if (!(parenum += ((ptype - parens) % 2) ? -1 : 1))
+ goto p_outscan;
+ }
+ }
+ }
+ } else {
+ for (lino = curr_buf->currln, p = curr_buf->currline; p; p = p->prev, lino--) {
+ int len = strlen(p->data);
+ for (i = ((lino == curr_buf->currln) ? curr_buf->currpnt - 1 : len - 1); i >= 0; i--) {
+ if (p->data[i] == '/' && p->data[--i] == '*' && i > 0) {
+ --i;
+ while (1) {
+ while (i > 0 &&
+ !(p->data[i] == '*' && p->data[i - 1] == '/')) {
+ i--;
+ }
+ if (i <= 0 && p->prev) {
+ p = p->prev;
+ len = strlen(p->data);
+ --lino;
+ i = len - 1;
+ } else
+ break;
+ }
+ } else if ((c = p->data[i]) == '\'' || c == '"') {
+ while (1) {
+ while (i > 0)
+ if (i > 1 && p->data[i - 2] == '\\')
+ i -= 2;
+ else if ((p->data[--i]) == c)
+ goto begin_quote;
+ if (i <= 0 && p->prev) {
+ p = p->prev;
+ len = strlen(p->data);
+ --lino;
+ i = len;
+ } else
+ break;
+ }
+begin_quote:
+ ;
+ } else if ((ptype = strchr(parens, p->data[i])) &&
+ (ptype - parens) / 2 == type) {
+ if (!(parenum += ((ptype - parens) % 2) ? -1 : 1))
+ goto p_outscan;
+ }
+ }
+ }
+ }
+p_outscan:
+ if (!parenum) {
+ int top = curr_buf->currln - curr_buf->curr_window_line;
+ int bottom = curr_buf->currln - curr_buf->curr_window_line + b_lines - 1;
+
+ curr_buf->currpnt = i;
+ curr_buf->currline = p;
+ curr_buf->curr_window_line += lino - curr_buf->currln;
+ curr_buf->currln = lino;
+
+ if (lino < top || lino > bottom) {
+ if (lino < middle_line()) {
+ curr_buf->top_of_win = curr_buf->firstline;
+ curr_buf->curr_window_line = curr_buf->currln;
+ } else {
+ int i;
+
+ curr_buf->curr_window_line = middle_line();
+ for (i = curr_buf->curr_window_line; i; i--)
+ p = p->prev;
+ curr_buf->top_of_win = p;
+ }
+ curr_buf->redraw_everything = YEA;
+ }
+ }
+}
+
+static void
+currline_shift_left(void)
+{
+ int currpnt0;
+
+ if (curr_buf->currline->len <= 0)
+ return;
+
+ currpnt0 = curr_buf->currpnt;
+ curr_buf->currpnt = 0;
+ delete_char();
+ curr_buf->currpnt = (currpnt0 <= curr_buf->currline->len) ? currpnt0 : currpnt0 - 1;
+ if (curr_buf->ansimode)
+ curr_buf->currpnt = ansi2n(n2ansi(curr_buf->currpnt, curr_buf->currline), curr_buf->currline);
+}
+
+static void
+currline_shift_right(void)
+{
+ int currpnt0;
+
+ if (curr_buf->currline->len >= WRAPMARGIN - 1)
+ return;
+
+ currpnt0 = curr_buf->currpnt;
+ curr_buf->currpnt = 0;
+ insert_char(' ');
+ curr_buf->currpnt = currpnt0;
+}
+
+static void
+cursor_to_next_word(void)
+{
+ while (curr_buf->currpnt < curr_buf->currline->len &&
+ isalnum((int)curr_buf->currline->data[++curr_buf->currpnt]));
+ while (curr_buf->currpnt < curr_buf->currline->len &&
+ isspace((int)curr_buf->currline->data[++curr_buf->currpnt]));
+}
+
+static void
+cursor_to_prev_word(void)
+{
+ while (curr_buf->currpnt && isspace((int)curr_buf->currline->data[--curr_buf->currpnt]));
+ while (curr_buf->currpnt && isalnum((int)curr_buf->currline->data[--curr_buf->currpnt]));
+ if (curr_buf->currpnt > 0)
+ curr_buf->currpnt++;
+}
+
+static void
+delete_current_word(void)
+{
+ while (curr_buf->currpnt < curr_buf->currline->len) {
+ delete_char();
+ if (!isalnum((int)curr_buf->currline->data[curr_buf->currpnt]))
+ break;
+ }
+ while (curr_buf->currpnt < curr_buf->currline->len) {
+ delete_char();
+ if (!isspace((int)curr_buf->currline->data[curr_buf->currpnt]))
+ break;
+ }
+}
+
+/**
+ * transform every "*[" in given string to KEY_ESC "["
+ */
+static void
+transform_to_color(char *line)
+{
+ while (line[0] && line[1])
+ if (line[0] == '*' && line[1] == '[') {
+ line[0] = KEY_ESC;
+ line += 2;
+ } else
+ ++line;
+}
+
+static void
+block_color(void)
+{
+ textline_t *begin, *end, *p;
+
+ setup_block_begin_end(&begin, &end);
+
+ p = begin;
+ while (1) {
+ // FIXME CRASH p will be NULL here.
+ assert(p);
+ transform_to_color(p->data);
+ if (p == end)
+ break;
+ else
+ p = p->next;
+ }
+ block_cancel();
+}
+
+/**
+ * insert ansi code
+ */
+static void
+insert_ansi_code(void)
+{
+ int ch = curr_buf->insert_mode;
+ curr_buf->insert_mode = curr_buf->redraw_everything = YEA;
+ if (!curr_buf->ansimode)
+ insert_string(reset_color);
+ else {
+ char ans[4];
+ move(b_lines - 2, 55);
+ outs(ANSI_COLOR(1;33;40) "B" ANSI_COLOR(41) "R" ANSI_COLOR(42) "G" ANSI_COLOR(43) "Y" ANSI_COLOR(44) "L"
+ ANSI_COLOR(45) "P" ANSI_COLOR(46) "C" ANSI_COLOR(47) "W" ANSI_RESET);
+ if (getdata(b_lines - 1, 0,
+ "請輸入 亮度/前景/背景[正常白字黑底][0wb]:",
+ ans, sizeof(ans), LCECHO))
+ {
+ const char t[] = "BRGYLPCW";
+ char color[15];
+ char *tmp, *apos = ans;
+ int fg, bg;
+
+ strcpy(color, ESC_STR "[");
+ if (isdigit((int)*apos)) {
+ sprintf(color,"%s%c", color, *(apos++));
+ if (*apos)
+ strcat(color, ";");
+ }
+ if (*apos) {
+ if ((tmp = strchr(t, toupper(*(apos++)))))
+ fg = tmp - t + 30;
+ else
+ fg = 37;
+ sprintf(color, "%s%d", color, fg);
+ }
+ if (*apos) {
+ if ((tmp = strchr(t, toupper(*(apos++)))))
+ bg = tmp - t + 40;
+ else
+ bg = 40;
+ sprintf(color, "%s;%d", color, bg);
+ }
+ strcat(color, "m");
+ insert_string(color);
+ } else
+ insert_string(reset_color);
+ }
+ curr_buf->insert_mode = ch;
+}
+
+static inline void
+phone_mode_switch(void)
+{
+ if (curr_buf->phone_mode)
+ curr_buf->phone_mode = 0;
+ else {
+ curr_buf->phone_mode = 1;
+ if (!curr_buf->last_phone_mode)
+ curr_buf->last_phone_mode = 2;
+ }
+}
+
+/**
+ * return coresponding phone char of given key c
+ */
+static const char*
+phone_char(char c)
+{
+ if (curr_buf->last_phone_mode > 0 && curr_buf->last_phone_mode < 20) {
+ if (tolower(c)<'a'||(tolower(c)-'a') >= strlen(BIG5[curr_buf->last_phone_mode - 1]) / 2)
+ return 0;
+ return BIG5[curr_buf->last_phone_mode - 1] + (tolower(c) - 'a') * 2;
+ }
+ else if (curr_buf->last_phone_mode >= 20) {
+ if (c == '.') c = '/';
+
+ if (c < '/' || c > '9')
+ return 0;
+
+ return table[curr_buf->last_phone_mode - 20] + (c - '/') * 2;
+ }
+ return 0;
+}
+
+/**
+ * When get the key for phone mode, handle it (e.g. edit_msg) and return the
+ * key. Otherwise return 0.
+ */
+static inline char
+phone_mode_filter(char ch)
+{
+ if (!curr_buf->phone_mode)
+ return 0;
+
+ switch (ch) {
+ case 'z':
+ case 'Z':
+ if (curr_buf->last_phone_mode < 20)
+ curr_buf->last_phone_mode = 20;
+ else
+ curr_buf->last_phone_mode = 2;
+ edit_msg();
+ return ch;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (curr_buf->last_phone_mode < 20) {
+ curr_buf->last_phone_mode = ch - '0' + 1;
+ curr_buf->redraw_everything = YEA;
+ return ch;
+ }
+ break;
+ case '-':
+ if (curr_buf->last_phone_mode < 20) {
+ curr_buf->last_phone_mode = 11;
+ curr_buf->redraw_everything = YEA;
+ return ch;
+ }
+ break;
+ case '=':
+ if (curr_buf->last_phone_mode < 20) {
+ curr_buf->last_phone_mode = 12;
+ curr_buf->redraw_everything = YEA;
+ return ch;
+ }
+ break;
+ case '`':
+ if (curr_buf->last_phone_mode < 20) {
+ curr_buf->last_phone_mode = 13;
+ curr_buf->redraw_everything = YEA;
+ return ch;
+ }
+ break;
+ case '/':
+ if (curr_buf->last_phone_mode >= 20) {
+ curr_buf->last_phone_mode += 4;
+ if (curr_buf->last_phone_mode > 27)
+ curr_buf->last_phone_mode -= 8;
+ curr_buf->redraw_everything = YEA;
+ return ch;
+ }
+ break;
+ case '*':
+ if (curr_buf->last_phone_mode >= 20) {
+ curr_buf->last_phone_mode++;
+ if ((curr_buf->last_phone_mode - 21) % 4 == 3)
+ curr_buf->last_phone_mode -= 4;
+ curr_buf->redraw_everything = YEA;
+ return ch;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* 編輯處理:主程式、鍵盤處理 */
+int
+vedit(char *fpath, int saveheader, int *islocal)
+{
+ char last = 0; /* the last key you press */
+ int ch, tmp;
+
+ int mode0 = currutmp->mode;
+ int destuid0 = currutmp->destuid;
+ int money = 0;
+ int interval = 0;
+ time4_t th = now;
+ int count = 0, tin = 0, quoted = 0;
+ char trans_buffer[256];
+ char mytitle[STRLEN];
+
+ STATINC(STAT_VEDIT);
+ currutmp->mode = EDITING;
+ currutmp->destuid = currstat;
+
+ strlcpy(mytitle, save_title, sizeof(mytitle));
+
+#ifdef DBCSAWARE
+ mbcs_mode = (cuser.uflag & DBCSAWARE_FLAG) ? 1 : 0;
+#endif
+
+ enter_edit_buffer();
+
+ curr_buf->oldcurrline = curr_buf->currline = curr_buf->top_of_win =
+ curr_buf->firstline = curr_buf->lastline = alloc_line(WRAPMARGIN);
+
+ if (*fpath) {
+ read_file(fpath);
+ }
+
+ if (*quote_file) {
+ do_quote();
+ *quote_file = '\0';
+ quoted = 1;
+ }
+
+ if( curr_buf->oldcurrline != curr_buf->firstline ||
+ curr_buf->currline != curr_buf->firstline) {
+ /* we must adjust because cursor (currentline) moved. */
+ curr_buf->oldcurrline = curr_buf->currline = curr_buf->top_of_win =
+ curr_buf->firstline= adjustline(curr_buf->firstline, WRAPMARGIN);
+ }
+
+ /* No matter you quote or not, just start the cursor from (0,0) */
+ curr_buf->currpnt = curr_buf->currln = curr_buf->curr_window_line =
+ curr_buf->edit_margin = curr_buf->last_margin = 0;
+
+ /* if quote, move to end of file. */
+ if(quoted)
+ {
+ /* maybe do this in future. */
+ }
+
+ while (1) {
+ if (curr_buf->redraw_everything || has_block_selection()) {
+ refresh_window();
+ curr_buf->redraw_everything = NA;
+ }
+ if( curr_buf->oldcurrline != curr_buf->currline ){
+ curr_buf->oldcurrline = adjustline(curr_buf->oldcurrline, curr_buf->oldcurrline->len);
+ curr_buf->oldcurrline = curr_buf->currline = adjustline(curr_buf->currline, WRAPMARGIN);
+ }
+
+ if (curr_buf->ansimode)
+ ch = n2ansi(curr_buf->currpnt, curr_buf->currline);
+ else
+ ch = curr_buf->currpnt - curr_buf->edit_margin;
+ move(curr_buf->curr_window_line, ch);
+
+#if 0 // DEPRECATED, it's really not a well known expensive feature
+ if (!curr_buf->line_dirty && strcmp(editline, curr_buf->currline->data))
+ strcpy(editline, curr_buf->currline->data);
+#endif
+
+ ch = igetch();
+ /* jochang debug */
+ if ((interval = (now - th))) {
+ th = now;
+ if ((char)ch != last) {
+ money++;
+ last = (char)ch;
+ }
+ }
+ if (interval && interval == tin)
+ { // Ptt : +- 1 秒也算
+ count++;
+ if(count>60)
+ {
+ money = 0;
+ count = 0;
+/*
+ log_file("etc/illegal_money", LOG_CREAT | LOG_VF,
+ ANSI_COLOR(1;33;46) "%s " ANSI_COLOR(37;45) " 用機器人發表文章 " ANSI_COLOR(37) " %s" ANSI_RESET "\n",
+ cuser.userid, ctime4(&now));
+ post_violatelaw(cuser.userid, "Ptt系統警察",
+ "用機器人發表文章", "強制離站");
+ abort_bbs(0);
+*/
+ }
+ }
+ else if(interval){
+ count = 0;
+ tin = interval;
+ }
+#ifndef DBCSAWARE
+ /* this is almost useless! */
+ if (curr_buf->raw_mode) {
+ switch (ch) {
+ case Ctrl('S'):
+ case Ctrl('Q'):
+ case Ctrl('T'):
+ continue;
+ }
+ }
+#endif
+
+ if (phone_mode_filter(ch))
+ continue;
+
+ if (ch < 0x100 && isprint2(ch)) {
+ const char *pstr;
+ if(curr_buf->phone_mode && (pstr=phone_char(ch)))
+ insert_dchar(pstr);
+ else
+ insert_char(ch);
+ curr_buf->lastindent = -1;
+ } else {
+ if (ch == KEY_UP || ch == KEY_DOWN ){
+ if (curr_buf->lastindent == -1)
+ curr_buf->lastindent = curr_buf->currpnt;
+ } else
+ curr_buf->lastindent = -1;
+ if (ch == KEY_ESC)
+ switch (KEY_ESC_arg) {
+ case ',':
+ ch = Ctrl(']');
+ break;
+ case '.':
+ ch = Ctrl('T');
+ break;
+ case 'v':
+ ch = KEY_PGUP;
+ break;
+ case 'a':
+ case 'A':
+ ch = Ctrl('V');
+ break;
+ case 'X':
+ ch = Ctrl('X');
+ break;
+ case 'q':
+ ch = Ctrl('Q');
+ break;
+ case 'o':
+ ch = Ctrl('O');
+ break;
+#if 0 // DEPRECATED, it's really not a well known expensive feature
+ case '-':
+ ch = Ctrl('_');
+ break;
+#endif
+ case 's':
+ ch = Ctrl('S');
+ break;
+ }
+
+ switch (ch) {
+ case KEY_F10:
+ case Ctrl('X'): /* Save and exit */
+ tmp = write_file(fpath, saveheader, islocal, mytitle);
+ if (tmp != KEEP_EDITING) {
+ strlcpy(save_title, mytitle, sizeof(save_title));
+ save_title[STRLEN-1] = 0;
+ currutmp->mode = mode0;
+ currutmp->destuid = destuid0;
+
+ exit_edit_buffer();
+ if (!tmp)
+ return money;
+ else
+ return tmp;
+ }
+ curr_buf->oldcurrline = curr_buf->currline;
+ curr_buf->redraw_everything = YEA;
+ break;
+ case KEY_F5:
+ prompt_goto_line();
+ curr_buf->redraw_everything = YEA;
+ break;
+ case KEY_F8:
+ t_users();
+ curr_buf->redraw_everything = YEA;
+ break;
+ case Ctrl('W'):
+ block_cut();
+ // curr_buf->oldcurrline is freed in block_cut, and currline is
+ // well adjusted now. This will avoid re-adjusting later.
+ // It's not a good implementation, try to find a better
+ // solution!
+ curr_buf->oldcurrline = curr_buf->currline;
+ break;
+ case Ctrl('Q'): /* Quit without saving */
+ ch = ask("結束但不儲存 (Y/N)? [N]: ");
+ if (ch == 'y' || ch == 'Y') {
+ currutmp->mode = mode0;
+ currutmp->destuid = destuid0;
+ exit_edit_buffer();
+ return -1;
+ }
+ curr_buf->redraw_everything = YEA;
+ break;
+ case Ctrl('C'):
+ insert_ansi_code();
+ break;
+ case KEY_ESC:
+ switch (KEY_ESC_arg) {
+ case 'U':
+ t_users();
+ curr_buf->redraw_everything = YEA;
+ break;
+ case 'i':
+ t_idle();
+ curr_buf->redraw_everything = YEA;
+ break;
+ case 'n':
+ search_str(1);
+ break;
+ case 'p':
+ search_str(-1);
+ break;
+ case 'L':
+ case 'J':
+ prompt_goto_line();
+ curr_buf->redraw_everything = YEA;
+ break;
+ case ']':
+ match_paren();
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ read_tmpbuf(KEY_ESC_arg - '0');
+ curr_buf->oldcurrline = curr_buf->currline;
+ curr_buf->redraw_everything = YEA;
+ break;
+ case 'l': /* block delete */
+ case ' ':
+ if (has_block_selection()) {
+ block_prompt();
+ // curr_buf->oldcurrline is freed in block_cut, and currline is
+ // well adjusted now. This will avoid re-adjusting later.
+ // It's not a good implementation, try to find a better
+ // solution!
+ curr_buf->oldcurrline = curr_buf->currline;
+ }
+ else
+ block_select();
+ break;
+ case 'u':
+ block_cancel();
+ break;
+ case 'c':
+ block_copy();
+ break;
+ case 'y':
+ curr_buf->oldcurrline = undelete_line();
+ if (curr_buf->oldcurrline == NULL)
+ curr_buf->oldcurrline = curr_buf->currline;
+ break;
+ case 'R':
+#ifdef DBCSAWARE
+ case 'r':
+ mbcs_mode =! mbcs_mode;
+#endif
+ curr_buf->raw_mode ^= 1;
+ break;
+ case 'I':
+ curr_buf->indent_mode ^= 1;
+ break;
+ case 'j':
+ currline_shift_left();
+ break;
+ case 'k':
+ currline_shift_right();
+ break;
+ case 'f':
+ cursor_to_next_word();
+ break;
+ case 'b':
+ cursor_to_prev_word();
+ break;
+ case 'd':
+ delete_current_word();
+ break;
+ }
+ break;
+#if 0 // DEPRECATED, it's really not a well known expensive feature
+ case Ctrl('_'):
+ // swap editline and currline's data
+ if (strcmp(editline, curr_buf->currline->data)) {
+ char buf[WRAPMARGIN];
+
+ strlcpy(buf, curr_buf->currline->data, sizeof(buf));
+ strcpy(curr_buf->currline->data, editline);
+ strcpy(editline, buf);
+ curr_buf->currline->len = strlen(curr_buf->currline->data);
+ curr_buf->currpnt = 0;
+ curr_buf->line_dirty = 1;
+ }
+ break;
+#endif
+ case Ctrl('S'):
+ case KEY_F3:
+ search_str(0);
+ break;
+ case Ctrl('U'):
+ insert_char(ESC_CHR);
+ break;
+ case Ctrl('V'): /* Toggle ANSI color */
+ curr_buf->ansimode ^= 1;
+ if (curr_buf->ansimode && has_block_selection())
+ block_color();
+ clear();
+ curr_buf->redraw_everything = YEA;
+ break;
+ case Ctrl('I'):
+ insert_tab();
+ break;
+ case '\r':
+ case '\n':
+#ifdef MAX_EDIT_LINE
+ if( curr_buf->totaln == MAX_EDIT_LINE ){
+ outs("MAX_EDIT_LINE exceed");
+ break;
+ }
+#endif
+ split(curr_buf->currline, curr_buf->currpnt);
+ curr_buf->oldcurrline = curr_buf->currline;
+ break;
+ case Ctrl('G'):
+ {
+ unsigned int currstat0 = currstat;
+ setutmpmode(EDITEXP);
+ a_menu("編輯輔助器", "etc/editexp",
+ (HasUserPerm(PERM_SYSOP) ? SYSOP : NOBODY),
+ trans_buffer);
+ currstat = currstat0;
+ }
+ if (trans_buffer[0]) {
+ FILE *fp1;
+ if ((fp1 = fopen(trans_buffer, "r"))) {
+ int indent_mode0 = curr_buf->indent_mode;
+ char buf[WRAPMARGIN + 2];
+
+ curr_buf->indent_mode = 0;
+ while (fgets(buf, sizeof(buf), fp1)) {
+ if (!strncmp(buf, "作者:", 5) ||
+ !strncmp(buf, "標題:", 5) ||
+ !strncmp(buf, "時間:", 5))
+ continue;
+ insert_string(buf);
+ }
+ fclose(fp1);
+ curr_buf->indent_mode = indent_mode0;
+ while (curr_buf->curr_window_line >= b_lines) {
+ curr_buf->curr_window_line--;
+ curr_buf->top_of_win = curr_buf->top_of_win->next;
+ }
+ }
+ }
+ curr_buf->redraw_everything = YEA;
+ break;
+ case Ctrl('P'):
+ phone_mode_switch();
+ curr_buf->redraw_everything = YEA;
+ break;
+
+ case KEY_F1:
+ case Ctrl('Z'): /* Help */
+ more("etc/ve.hlp", YEA);
+ curr_buf->redraw_everything = YEA;
+ break;
+ case Ctrl('L'):
+ clear();
+ curr_buf->redraw_everything = YEA;
+ break;
+ case KEY_LEFT:
+ if (curr_buf->currpnt) {
+ if (curr_buf->ansimode)
+ curr_buf->currpnt = n2ansi(curr_buf->currpnt, curr_buf->currline);
+ curr_buf->currpnt--;
+ if (curr_buf->ansimode)
+ curr_buf->currpnt = ansi2n(curr_buf->currpnt, curr_buf->currline);
+#ifdef DBCSAWARE
+ if(mbcs_mode)
+ curr_buf->currpnt = fix_cursor(curr_buf->currline->data, curr_buf->currpnt, FC_LEFT);
+#endif
+ } else if (curr_buf->currline->prev) {
+ curr_buf->curr_window_line--;
+ curr_buf->currln--;
+ curr_buf->currline = curr_buf->currline->prev;
+ curr_buf->currpnt = curr_buf->currline->len;
+ }
+ break;
+ case KEY_RIGHT:
+ if (curr_buf->currline->len != curr_buf->currpnt) {
+ if (curr_buf->ansimode)
+ curr_buf->currpnt = n2ansi(curr_buf->currpnt, curr_buf->currline);
+ curr_buf->currpnt++;
+ if (curr_buf->ansimode)
+ curr_buf->currpnt = ansi2n(curr_buf->currpnt, curr_buf->currline);
+#ifdef DBCSAWARE
+ if(mbcs_mode)
+ curr_buf->currpnt = fix_cursor(curr_buf->currline->data, curr_buf->currpnt, FC_RIGHT);
+#endif
+ } else if (curr_buf->currline->next) {
+ curr_buf->currpnt = 0;
+ curr_buf->curr_window_line++;
+ curr_buf->currln++;
+ curr_buf->currline = curr_buf->currline->next;
+ }
+ break;
+ case KEY_UP:
+ cursor_to_prev_line();
+ break;
+ case KEY_DOWN:
+ cursor_to_next_line();
+ break;
+
+ case Ctrl('B'):
+ case KEY_PGUP: {
+ short tmp = curr_buf->currln;
+ curr_buf->top_of_win = back_line(curr_buf->top_of_win, t_lines - 2);
+ curr_buf->currln = tmp;
+ curr_buf->currline = back_line(curr_buf->currline, t_lines - 2);
+ curr_buf->curr_window_line = get_lineno_in_window();
+ if (curr_buf->currpnt > curr_buf->currline->len)
+ curr_buf->currpnt = curr_buf->currline->len;
+ curr_buf->redraw_everything = YEA;
+ break;
+ }
+
+ case Ctrl('F'):
+ case KEY_PGDN: {
+ short tmp = curr_buf->currln;
+ curr_buf->top_of_win = forward_line(curr_buf->top_of_win, t_lines - 2);
+ curr_buf->currln = tmp;
+ curr_buf->currline = forward_line(curr_buf->currline, t_lines - 2);
+ curr_buf->curr_window_line = get_lineno_in_window();
+ if (curr_buf->currpnt > curr_buf->currline->len)
+ curr_buf->currpnt = curr_buf->currline->len;
+ curr_buf->redraw_everything = YEA;
+ break;
+ }
+
+ case KEY_END:
+ case Ctrl('E'):
+ curr_buf->currpnt = curr_buf->currline->len;
+ break;
+ case Ctrl(']'): /* start of file */
+ curr_buf->currline = curr_buf->top_of_win = curr_buf->firstline;
+ curr_buf->currpnt = curr_buf->currln = curr_buf->curr_window_line = 0;
+ curr_buf->redraw_everything = YEA;
+ break;
+ case Ctrl('T'): /* tail of file */
+ curr_buf->top_of_win = back_line(curr_buf->lastline, t_lines - 1);
+ curr_buf->currline = curr_buf->lastline;
+ curr_buf->curr_window_line = get_lineno_in_window();
+ curr_buf->currln = curr_buf->totaln;
+ curr_buf->redraw_everything = YEA;
+ curr_buf->currpnt = 0;
+ break;
+ case KEY_HOME:
+ case Ctrl('A'):
+ curr_buf->currpnt = 0;
+ break;
+ case KEY_INS: /* Toggle insert/overwrite */
+ case Ctrl('O'):
+ if (has_block_selection() && curr_buf->insert_mode) {
+ char ans[4];
+
+ getdata(b_lines - 1, 0,
+ "區塊微調右移插入字元(預設為空白字元)",
+ ans, sizeof(ans), LCECHO);
+ curr_buf->insert_c = ans[0] ? ans[0] : ' ';
+ }
+ curr_buf->insert_mode ^= 1;
+ break;
+ case Ctrl('H'):
+ case '\177': /* backspace */
+ if (curr_buf->ansimode) {
+ curr_buf->ansimode = 0;
+ clear();
+ curr_buf->redraw_everything = YEA;
+ } else {
+ if (curr_buf->currpnt == 0) {
+ if (!curr_buf->currline->prev)
+ break;
+ curr_buf->curr_window_line--;
+ curr_buf->currln--;
+
+ curr_buf->currline = adjustline(curr_buf->currline, curr_buf->currline->len);
+ curr_buf->currline = curr_buf->currline->prev;
+ curr_buf->currline = adjustline(curr_buf->currline, WRAPMARGIN);
+ curr_buf->oldcurrline = curr_buf->currline;
+
+ curr_buf->currpnt = curr_buf->currline->len;
+ curr_buf->redraw_everything = YEA;
+ if (curr_buf->currline->next == curr_buf->top_of_win) {
+ curr_buf->top_of_win = curr_buf->currline;
+ curr_buf->curr_window_line = 0;
+ }
+ if (*next_non_space_char(curr_buf->currline->next->data) == '\0') {
+ delete_line(curr_buf->currline->next, 0);
+ break;
+ }
+ join(curr_buf->currline);
+ break;
+ }
+#ifndef DBCSAWARE
+ curr_buf->currpnt--;
+ delete_char();
+#else
+ {
+ int newpnt = curr_buf->currpnt - 1;
+
+ if(mbcs_mode)
+ newpnt = fix_cursor(curr_buf->currline->data, newpnt, FC_LEFT);
+
+ for(; curr_buf->currpnt > newpnt;)
+ {
+ curr_buf->currpnt --;
+ delete_char();
+ }
+ }
+#endif
+ }
+ break;
+ case Ctrl('D'):
+ case KEY_DEL: /* delete current character */
+ if (curr_buf->currline->len == curr_buf->currpnt) {
+ join(curr_buf->currline);
+ curr_buf->redraw_everything = YEA;
+ } else {
+#ifndef DBCSAWARE
+ delete_char();
+#else
+ {
+ int w = 1;
+
+ if(mbcs_mode)
+ w = mchar_len((unsigned char*)(curr_buf->currline->data + curr_buf->currpnt));
+
+ for(; w > 0; w --)
+ delete_char();
+ }
+#endif
+ if (curr_buf->ansimode)
+ curr_buf->currpnt = ansi2n(n2ansi(curr_buf->currpnt, curr_buf->currline), curr_buf->currline);
+ }
+ break;
+ case Ctrl('Y'): /* delete current line */
+ curr_buf->currline->len = curr_buf->currpnt = 0;
+ case Ctrl('K'): /* delete to end of line */
+ if (curr_buf->currline->len == 0) {
+ textline_t *p = curr_buf->currline->next;
+ if (!p) {
+ p = curr_buf->currline->prev;
+ if (!p) {
+ curr_buf->currline->data[0] = 0;
+ break;
+ }
+ if (curr_buf->curr_window_line > 0) {
+ curr_buf->curr_window_line--;
+ }
+ curr_buf->currln--;
+ }
+ if (curr_buf->currline == curr_buf->top_of_win)
+ curr_buf->top_of_win = p;
+
+ delete_line(curr_buf->currline, 1);
+ curr_buf->currline = p;
+ curr_buf->redraw_everything = YEA;
+ curr_buf->oldcurrline = curr_buf->currline = adjustline(curr_buf->currline, WRAPMARGIN);
+ break;
+ }
+ else if (curr_buf->currline->len == curr_buf->currpnt) {
+ join(curr_buf->currline);
+ curr_buf->redraw_everything = YEA;
+ break;
+ }
+ curr_buf->currline->len = curr_buf->currpnt;
+ curr_buf->currline->data[curr_buf->currpnt] = '\0';
+ break;
+ }
+
+ if (curr_buf->currln < 0)
+ curr_buf->currln = 0;
+
+ if (curr_buf->curr_window_line < 0)
+ window_scroll_down();
+ else if (cursor_at_bottom_line())
+ window_scroll_up();
+#ifdef DBCSAWARE
+ if(mbcs_mode)
+ curr_buf->currpnt = fix_cursor(curr_buf->currline->data, curr_buf->currpnt, FC_LEFT);
+#endif
+ }
+
+ if (curr_buf->ansimode)
+ tmp = n2ansi(curr_buf->currpnt, curr_buf->currline);
+ else
+ tmp = curr_buf->currpnt;
+
+ if (tmp < t_columns - 1)
+ curr_buf->edit_margin = 0;
+ else
+ curr_buf->edit_margin = tmp / (t_columns - 8) * (t_columns - 8);
+
+ if (!curr_buf->redraw_everything) {
+ if (curr_buf->edit_margin != curr_buf->last_margin) {
+ curr_buf->last_margin = curr_buf->edit_margin;
+ curr_buf->redraw_everything = YEA;
+ } else {
+ move(curr_buf->curr_window_line, 0);
+ clrtoeol();
+ if (curr_buf->ansimode)
+ outs(curr_buf->currline->data);
+ else
+ edit_outs(&curr_buf->currline->data[curr_buf->edit_margin]);
+ edit_msg();
+ }
+ } /* redraw */
+ } /* main event loop */
+
+ exit_edit_buffer();
+}
+
+/* vim:sw=4
+ */
diff --git a/pttbbs/mbbsd/fav.c b/pttbbs/mbbsd/fav.c
new file mode 100644
index 00000000..f7ef9fac
--- /dev/null
+++ b/pttbbs/mbbsd/fav.c
@@ -0,0 +1,1285 @@
+/* $Id$ */
+#include "bbs.h"
+
+/**
+ * Structure
+ * =========
+ * fav 檔的前兩個 byte 是版號,接下來才是真正的 data。
+ *
+ * fav 的主要架構如下:
+ *
+ * fav_t - 用來裝各種 entry(fav_type_t) 的 directory
+ * 進入我的最愛時,看到的東西就是根據 fav_t 生出來的。
+ * 裡面紀錄者,這一個 level 中有多少個看板、目錄、分隔線。(favh)
+ * 是一個 array (with pre-allocated buffer)
+ *
+ * fav_type_t - fav entry 的 base class
+ * 存取時透過 type 變數來得知正確的型態。
+ *
+ * fav_board_t / fav_line_t / fav_folder_t - derived class
+ * 詳細情形請參考 fav.h 中的定義。
+ * 以 cast_(board|line|folder)_t 來將一個 fav_type_t 作動態轉型。
+ *
+ * Policy
+ * ======
+ * 為了避免過度的資料搬移,當將一個 item 從我的最愛中移除時,只將他的
+ * FAVH_FAV flag 移除。而沒有這個 flag 的 item 也不被視為我的最愛。
+ *
+ * 我的最愛中,沒設 FAVH_FAV 的資料,將在某些時候,如寫入檔案時,呼叫
+ * rebuild_fav 清除乾淨。
+ *
+ * Others
+ * ======
+ * 站長搬移看板所用的 t ,因為不能只存在 nbrd 裡面,又不然再弄出額外的空間,
+ * 所以當站長不在我的最愛按了 t ,會把這個記錄暫存在 fav 中
+ * (FAVH_ADM_TAG == 1, FAVH_FAV == 0)。
+ */
+
+
+/* the total number of items, every level. */
+static int fav_number;
+
+/* definition of fav stack, the top one is in use now. */
+static int fav_stack_num = 0;
+static fav_t *fav_stack[FAV_MAXDEPTH] = {0};
+
+static char dirty = 0;
+
+/* fav_tmp is for recordinge while copying, moving, etc. */
+static fav_t *fav_tmp;
+//static int fav_tmp_snum; /* the sequence number in favh in fav_t */
+
+#if 1 // DEPRECATED
+static void fav4_read_favrec(FILE *frp, fav_t *fp);
+#endif
+
+static void fav_free_branch(fav_t *fp);
+
+/**
+ * cast_(board|line|folder) 一族用於將 base class 作轉型
+ * (不檢查實際 data type)
+ */
+inline static fav_board_t *cast_board(fav_type_t *p){
+ return (fav_board_t *)p->fp;
+}
+
+inline static fav_line_t *cast_line(fav_type_t *p){
+ return (fav_line_t *)p->fp;
+}
+
+inline static fav_folder_t *cast_folder(fav_type_t *p){
+ return (fav_folder_t *)p->fp;
+}
+
+/**
+ * 傳回指定的 fp(dir) 中的 fp->DataTail, 第一個沒用過的位置的 index
+ */
+inline static int get_data_tail(fav_t *fp){
+ return fp->DataTail;
+}
+
+/**
+ * 傳回指定 dir 中所用的 entry 的總數 (只算真的在裡面,而不算已被移除的)
+ */
+inline int get_data_number(fav_t *fp){
+ return fp->nBoards + fp->nLines + fp->nFolders;
+}
+
+/**
+ * 傳回目前所在的 dir pointer
+ */
+inline fav_t *get_current_fav(void){
+ if (fav_stack_num == 0)
+ return NULL;
+ return fav_stack[fav_stack_num - 1];
+}
+
+/**
+ * 將 ft(entry) cast 成一個 dir
+ */
+inline fav_t *get_fav_folder(fav_type_t *ft){
+ return cast_folder(ft)->this_folder;
+}
+
+inline int get_item_type(fav_type_t *ft){
+ return ft->type;
+}
+
+/**
+ * 將一個指定的 dir pointer 存下來,之後可用 fav_get_tmp_fav 來存用
+ */
+inline static void fav_set_tmp_folder(fav_t *fp){
+ fav_tmp = fp;
+}
+
+inline static fav_t *fav_get_tmp_fav(void){
+ return fav_tmp;
+}
+
+/**
+ * 將 fp(dir) 記的數量中,扣除一單位 ft(entry)
+ */
+static void fav_decrease(fav_t *fp, fav_type_t *ft)
+{
+ dirty = 1;
+
+ switch (get_item_type(ft)){
+ case FAVT_BOARD:
+ fp->nBoards--;
+ break;
+ case FAVT_LINE:
+ fp->nLines--;
+ break;
+ case FAVT_FOLDER:
+ fp->nFolders--;
+ break;
+ }
+ fav_number--;
+}
+
+/**
+ * 將 fp(dir) 記的數量中,增加一單位 ft(entry)
+ */
+static void fav_increase(fav_t *fp, fav_type_t *ft)
+{
+ dirty = 1;
+
+ switch (get_item_type(ft)){
+ case FAVT_BOARD:
+ fp->nBoards++;
+ break;
+ case FAVT_LINE:
+ fp->nLines++;
+ cast_line(ft)->lid = ++fp->lineID;
+ break;
+ case FAVT_FOLDER:
+ fp->nFolders++;
+ cast_folder(ft)->fid = ++fp->folderID;
+ break;
+ }
+ fav_number++;
+ fp->DataTail++;
+}
+
+inline static int get_folder_num(fav_t *fp) {
+ return fp->nFolders;
+}
+
+/**
+ * get_(folder|line)_id 傳回 fp 中一個新的 folder/line id
+ */
+inline static int get_folder_id(fav_t *fp) {
+ return fp->folderID;
+}
+
+inline static int get_line_id(fav_t *fp) {
+ return fp->lineID;
+}
+
+inline static int get_line_num(fav_t *fp) {
+ return fp->nLines;
+}
+
+/**
+ * 設定某個 flag。
+ * @bit: 目前所有 flags 有: FAVH_FAV, FAVH_TAG, FAVH_UNREAD, FAVH_ADM_TAG
+ * @param bool: FALSE: unset, TRUE: set, EXCH: opposite
+ */
+void set_attr(fav_type_t *ft, int bit, char bool){
+ if (ft == NULL)
+ return;
+ if (bool == EXCH)
+ ft->attr ^= bit;
+ else if (bool == TRUE)
+ ft->attr |= bit;
+ else
+ ft->attr &= ~bit;
+}
+
+inline int is_set_attr(fav_type_t *ft, char bit){
+ return ft->attr & bit;
+}
+
+char *get_item_title(fav_type_t *ft)
+{
+ switch (get_item_type(ft)){
+ case FAVT_BOARD:
+ assert(0<=cast_board(ft)->bid-1 && cast_board(ft)->bid-1<MAX_BOARD);
+ return bcache[cast_board(ft)->bid - 1].brdname;
+ case FAVT_FOLDER:
+ return cast_folder(ft)->title;
+ case FAVT_LINE:
+ return "----";
+ }
+ return NULL;
+}
+
+static char *get_item_class(fav_type_t *ft)
+{
+ switch (get_item_type(ft)){
+ case FAVT_BOARD:
+ assert(0<=cast_board(ft)->bid-1 && cast_board(ft)->bid-1<MAX_BOARD);
+ return bcache[cast_board(ft)->bid - 1].title;
+ case FAVT_FOLDER:
+ return "目錄";
+ case FAVT_LINE:
+ return "----";
+ }
+ return NULL;
+}
+
+
+static int get_type_size(int type)
+{
+ switch (type){
+ case FAVT_BOARD:
+ return sizeof(fav_board_t);
+ case FAVT_FOLDER:
+ return sizeof(fav_folder_t);
+ case FAVT_LINE:
+ return sizeof(fav_line_t);
+ }
+ assert(0);
+ return 0;
+}
+
+inline static void* fav_malloc(int size){
+ void *p;
+ assert(size>0);
+ p = (void *)malloc(size);
+ assert(p);
+ memset(p, 0, size);
+ return p;
+}
+
+/**
+ * 只複製 fav_type_t
+ */
+inline static void
+fav_item_copy(fav_type_t *target, const fav_type_t *source){
+ target->type = source->type;
+ target->attr = source->attr;
+ target->fp = source->fp;
+}
+
+inline fav_t *get_fav_root(void){
+ return fav_stack[0];
+}
+
+/**
+ * 是否為有效的 entry
+ */
+inline int valid_item(fav_type_t *ft){
+ return ft->attr & FAVH_FAV;
+}
+
+static int is_need_rebuild_fav(fav_t *fp)
+{
+ int i, nData;
+ fav_type_t *ft;
+
+ nData = fp->DataTail;
+
+ for (i = 0; i < nData; i++){
+ if (!valid_item(&fp->favh[i]))
+ return 1;
+
+ ft = &fp->favh[i];
+ switch (get_item_type(ft)){
+ case FAVT_BOARD:
+ case FAVT_LINE:
+ break;
+ case FAVT_FOLDER:
+ if(is_need_rebuild_fav(get_fav_folder(&fp->favh[i])))
+ return 1;
+ break;
+ default:
+ return 1;
+ }
+ }
+ return 0;
+}
+/**
+ * 清除 fp(dir) 中無效的 entry/dir。「無效」指的是沒有 FAVH_FAV flag,所以
+ * 不包含不存在的看板。
+ */
+static void rebuild_fav(fav_t *fp)
+{
+ int i, j, nData;
+ fav_type_t *ft;
+
+ fav_number -= get_data_number(fp);
+ fp->lineID = fp->folderID = 0;
+ fp->nLines = fp->nFolders = fp->nBoards = 0;
+ nData = fp->DataTail;
+ fp->DataTail = 0;
+
+ for (i = 0, j = 0; i < nData; i++){
+ if (!valid_item(&fp->favh[i]))
+ continue;
+
+ ft = &fp->favh[i];
+ switch (get_item_type(ft)){
+ case FAVT_BOARD:
+ case FAVT_LINE:
+ break;
+ case FAVT_FOLDER:
+ rebuild_fav(get_fav_folder(&fp->favh[i]));
+ break;
+ default:
+ continue;
+ }
+ fav_increase(fp, &fp->favh[i]);
+ if (i != j)
+ fav_item_copy(&fp->favh[j], &fp->favh[i]);
+ j++;
+ }
+ fp->DataTail = get_data_number(fp);
+}
+
+inline void fav_cleanup(void)
+{
+ if (is_need_rebuild_fav(get_fav_root()))
+ rebuild_fav(get_fav_root());
+}
+
+/* sort the fav */
+static int favcmp_by_name(const void *a, const void *b)
+{
+ return strcasecmp(get_item_title((fav_type_t *)a), get_item_title((fav_type_t *)b));
+}
+
+void fav_sort_by_name(void)
+{
+ fav_t *fp = get_current_fav();
+
+ if (fp == NULL)
+ return;
+
+ dirty = 1;
+ rebuild_fav(fp);
+ qsort(fp->favh, get_data_number(fp), sizeof(fav_type_t), favcmp_by_name);
+}
+
+static int favcmp_by_class(const void *a, const void *b)
+{
+ fav_type_t *f1, *f2;
+ int cmp;
+
+ f1 = (fav_type_t *)a;
+ f2 = (fav_type_t *)b;
+ if (get_item_type(f1) == FAVT_FOLDER)
+ return -1;
+ if (get_item_type(f2) == FAVT_FOLDER)
+ return 1;
+ if (get_item_type(f1) == FAVT_LINE)
+ return 1;
+ if (get_item_type(f2) == FAVT_LINE)
+ return -1;
+
+ cmp = strncasecmp(get_item_class(f1), get_item_class(f2), 4);
+ if (cmp)
+ return cmp;
+ return strcasecmp(get_item_title(f1), get_item_title(f2));
+}
+
+void fav_sort_by_class(void)
+{
+ fav_t *fp = get_current_fav();
+
+ if (fp == NULL)
+ return;
+
+ dirty = 1;
+ rebuild_fav(fp);
+ qsort(fp->favh, get_data_number(fp), sizeof(fav_type_t), favcmp_by_class);
+}
+
+/**
+ * The following is the movement operations in the user interface.
+ */
+
+/**
+ * 目錄層數是否達到最大值 FAV_MAXDEPTH
+ */
+inline int fav_stack_full(void){
+ return fav_stack_num >= FAV_MAXDEPTH;
+}
+
+inline static int fav_stack_push_fav(fav_t *fp){
+ if (fav_stack_full())
+ return -1;
+ fav_stack[fav_stack_num++] = fp;
+ return 0;
+}
+
+inline static int fav_stack_push(fav_type_t *ft){
+// if (ft->type != FAVT_FOLDER)
+// return -1;
+ return fav_stack_push_fav(get_fav_folder(ft));
+}
+
+inline static void fav_stack_pop(void){
+ fav_stack[--fav_stack_num] = NULL;
+}
+
+void fav_folder_in(int fid)
+{
+ fav_type_t *tmp = getfolder(fid);
+ if (get_item_type(tmp) == FAVT_FOLDER){
+ fav_stack_push(tmp);
+ }
+}
+
+void fav_folder_out(void)
+{
+ fav_stack_pop();
+}
+
+static int is_valid_favtype(int type)
+{
+ switch (type){
+ case FAVT_BOARD:
+ case FAVT_FOLDER:
+ case FAVT_LINE:
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * @return 0 if success, -1 if failed
+ */
+static int read_favrec(FILE *frp, fav_t *fp)
+{
+ /* TODO handle read errors */
+ int i;
+ fav_type_t *ft;
+
+ fread(&fp->nBoards, sizeof(fp->nBoards), 1, frp);
+ fread(&fp->nLines, sizeof(fp->nLines), 1, frp);
+ fread(&fp->nFolders, sizeof(fp->nFolders), 1, frp);
+ fp->DataTail = get_data_number(fp);
+ fp->nAllocs = fp->DataTail + FAV_PRE_ALLOC;
+ fp->lineID = fp->folderID = 0;
+ fp->favh = (fav_type_t *)fav_malloc(sizeof(fav_type_t) * fp->nAllocs);
+ fav_number += get_data_number(fp);
+
+ for(i = 0; i < fp->DataTail; i++){
+ ft = &fp->favh[i];
+ fread(&ft->type, sizeof(ft->type), 1, frp);
+ if(!is_valid_favtype(ft->type)) {
+ ft->type = 0;
+ return -1;
+ }
+ fread(&ft->attr, sizeof(ft->attr), 1, frp);
+ ft->fp = (void *)fav_malloc(get_type_size(ft->type));
+
+ switch (ft->type) {
+ case FAVT_FOLDER:
+ fread(&cast_folder(ft)->fid, sizeof(char), 1, frp);
+ fread(&cast_folder(ft)->title, BTLEN + 1, 1, frp);
+ break;
+ case FAVT_BOARD:
+ case FAVT_LINE:
+ fread(ft->fp, get_type_size(ft->type), 1, frp);
+ break;
+ }
+ }
+
+ for(i = 0; i < fp->DataTail; i++){
+ ft = &fp->favh[i];
+ switch (ft->type) {
+ case FAVT_FOLDER: {
+ fav_t *p = (fav_t *)fav_malloc(sizeof(fav_t));
+ if(read_favrec(frp, p)<0) {
+ fav_free_branch(p);
+ return -1;
+ }
+ cast_folder(ft)->this_folder = p;
+ cast_folder(ft)->fid = ++(fp->folderID);
+ break;
+ }
+ case FAVT_LINE:
+ cast_line(ft)->lid = ++(fp->lineID);
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ * 從記錄檔中 load 出我的最愛。
+ */
+int fav_load(void)
+{
+ FILE *frp;
+ char buf[PATHLEN];
+ unsigned short version;
+ fav_t *fp;
+ if (fav_stack_num > 0)
+ return -1;
+ setuserfile(buf, FAV);
+
+ if (!dashf(buf)) {
+#if 1 // DEPRECATED
+ char old[PATHLEN];
+ setuserfile(old, FAV4);
+ if (dashf(old)) {
+ if ((frp = fopen(old, "r")) == NULL)
+ return -1;
+ fp = (fav_t *)fav_malloc(sizeof(fav_t));
+ fav_number = 0;
+ fav4_read_favrec(frp, fp);
+ fav_stack_push_fav(fp);
+ fclose(frp);
+
+ fav_save();
+ setuserfile(old, FAV ".bak");
+ Copy(buf, old);
+ }
+ else
+#endif
+ {
+ fp = (fav_t *)fav_malloc(sizeof(fav_t));
+ fav_stack_push_fav(fp);
+ }
+ return 0;
+ }
+
+ if ((frp = fopen(buf, "r")) == NULL)
+ return -1;
+#ifdef CRITICAL_MEMORY
+ // kcwu: dirty hack, avoid 64byte slot. use 128 instead.
+ fp = (fav_t *)fav_malloc(sizeof(fav_t)+64);
+#else
+ fp = (fav_t *)fav_malloc(sizeof(fav_t));
+#endif
+ fav_number = 0;
+ fread(&version, sizeof(version), 1, frp);
+ // if (version != FAV_VERSION) { ... }
+ if(read_favrec(frp, fp)<0) {
+ // load fail
+ fav_free_branch(fp);
+ fav_number = 0;
+ fp = (fav_t *)fav_malloc(sizeof(fav_t));
+ }
+ fav_stack_push_fav(fp);
+ fclose(frp);
+ dirty = 0;
+ return 0;
+}
+
+/* write to the rec file */
+static void write_favrec(FILE *fwp, fav_t *fp)
+{
+ int i;
+ fav_type_t *ft;
+
+ if (fp == NULL)
+ return;
+
+ fwrite(&fp->nBoards, sizeof(fp->nBoards), 1, fwp);
+ fwrite(&fp->nLines, sizeof(fp->nLines), 1, fwp);
+ fwrite(&fp->nFolders, sizeof(fp->nFolders), 1, fwp);
+ fp->DataTail = get_data_number(fp);
+
+ for(i = 0; i < fp->DataTail; i++){
+ ft = &fp->favh[i];
+ fwrite(&ft->type, sizeof(ft->type), 1, fwp);
+ fwrite(&ft->attr, sizeof(ft->attr), 1, fwp);
+
+ switch (ft->type) {
+ case FAVT_FOLDER:
+ fwrite(&cast_folder(ft)->fid, sizeof(char), 1, fwp);
+ fwrite(&cast_folder(ft)->title, BTLEN + 1, 1, fwp);
+ break;
+ case FAVT_BOARD:
+ case FAVT_LINE:
+ fwrite(ft->fp, get_type_size(ft->type), 1, fwp);
+ break;
+ }
+ }
+
+ for(i = 0; i < fp->DataTail; i++){
+ if (fp->favh[i].type == FAVT_FOLDER)
+ write_favrec(fwp, get_fav_folder(&fp->favh[i]));
+ }
+}
+
+/**
+ * 把記錄檔 save 進我的最愛。
+ * @note fav_cleanup() 會先被呼叫。
+ */
+int fav_save(void)
+{
+ FILE *fwp;
+ char buf[PATHLEN], buf2[PATHLEN];
+ unsigned short version = FAV_VERSION;
+ fav_t *fp = get_fav_root();
+ if (fp == NULL)
+ return -1;
+
+ fav_cleanup();
+ if (!dirty)
+ return 0;
+
+ setuserfile(buf2, FAV);
+ snprintf(buf, sizeof(buf), "%s.tmp.%x",buf2, getpid());
+ fwp = fopen(buf, "w");
+ if(fwp == NULL)
+ return -1;
+ fwrite(&version, sizeof(version), 1, fwp);
+ write_favrec(fwp, fp);
+
+ if(fclose(fwp)==0)
+ Rename(buf, buf2);
+
+ return 0;
+}
+
+/**
+ * remove ft (設為 invalid,實際上會等到 save 時才清除)
+ */
+static inline void fav_free_item(fav_type_t *ft)
+{
+ set_attr(ft, 0xFFFF, FALSE);
+}
+
+/**
+ * delete ft from fp
+ */
+static int fav_remove(fav_t *fp, fav_type_t *ft)
+{
+ if (fp == NULL || ft == NULL)
+ return -1;
+ fav_free_item(ft);
+ fav_decrease(fp, ft);
+ return 0;
+}
+
+/**
+ * free the mem of fp recursively
+ */
+static void fav_free_branch(fav_t *fp)
+{
+ int i;
+ fav_type_t *ft;
+ if (fp == NULL)
+ return;
+ for(i = 0; i < fp->DataTail; i++){
+ ft = &fp->favh[i];
+ switch(get_item_type(ft)){
+ case FAVT_FOLDER:
+ fav_free_branch(cast_folder(ft)->this_folder);
+ break;
+ case FAVT_BOARD:
+ case FAVT_LINE:
+ if (ft->fp)
+ free(ft->fp);
+ break;
+ }
+ }
+ free(fp->favh);
+ free(fp);
+ fp = NULL;
+}
+
+/**
+ * free the mem of the whole fav tree recursively
+ */
+void fav_free(void)
+{
+ fav_free_branch(get_fav_root());
+
+ /* reset the stack */
+ fav_stack_num = 0;
+}
+
+/**
+ * 從目前的 dir 中找出特定類別 (type)、id 為 id 的 entry。
+ * 找不到傳回 NULL
+ */
+static fav_type_t *get_fav_item(int id, int type)
+{
+ int i;
+ fav_type_t *ft;
+ fav_t *fp = get_current_fav();
+
+ if (fp == NULL)
+ return NULL;
+
+ for(i = 0; i < fp->DataTail; i++){
+ ft = &fp->favh[i];
+ if (!valid_item(ft) || get_item_type(ft) != type)
+ continue;
+ if (fav_getid(ft) == id)
+ return ft;
+ }
+ return NULL;
+}
+
+/**
+ * 從目前的 dir 中 remove 特定類別 (type)、id 為 id 的 entry。
+ */
+void fav_remove_item(int id, char type)
+{
+ fav_remove(get_current_fav(), get_fav_item(id, type));
+}
+
+/**
+ * get*(bid) 傳回目前的 dir 中該類別 id == bid 的 entry。
+ */
+fav_type_t *getadmtag(int bid)
+{
+ int i;
+ fav_t *fp = get_fav_root();
+ fav_type_t *ft;
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ for (i = 0; i < fp->DataTail; i++) {
+ ft = &fp->favh[i];
+ if (get_item_type(ft) == FAVT_BOARD && cast_board(ft)->bid == bid && is_set_attr(ft, FAVH_ADM_TAG))
+ return ft;
+ }
+ return NULL;
+}
+
+fav_type_t *getboard(int bid)
+{
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ return get_fav_item(bid, FAVT_BOARD);
+}
+
+fav_type_t *getfolder(int fid)
+{
+ return get_fav_item(fid, FAVT_FOLDER);
+}
+
+char *get_folder_title(int fid)
+{
+ return get_item_title(get_fav_item(fid, FAVT_FOLDER));
+}
+
+
+char getbrdattr(int bid)
+{
+ fav_type_t *fb = getboard(bid);
+ if (!fb)
+ return 0;
+ return fb->attr;
+}
+
+int fav_getid(fav_type_t *ft)
+{
+ switch(get_item_type(ft)){
+ case FAVT_FOLDER:
+ return cast_folder(ft)->fid;
+ case FAVT_LINE:
+ return cast_line(ft)->lid;
+ case FAVT_BOARD:
+ return cast_board(ft)->bid;
+ }
+ return -1;
+}
+
+inline static int is_maxsize(void){
+ return fav_number >= MAX_FAV;
+}
+
+/**
+ * 每次一個 dir 滿時,這個 function 會將 buffer 大小調大 FAV_PRE_ALLOC 個,
+ * 直到上限為止。
+ */
+static int enlarge_if_full(fav_t *fp)
+{
+ fav_type_t * p;
+ /* enlarge the volume if need. */
+ if (is_maxsize())
+ return -1;
+ if (fp->DataTail < fp->nAllocs)
+ return 1;
+
+ /* realloc and clean the tail */
+ p = (fav_type_t *)realloc(fp->favh, sizeof(fav_type_t) * (fp->nAllocs + FAV_PRE_ALLOC));
+ if( p == NULL )
+ return -1;
+
+ fp->favh = p;
+ memset(fp->favh + fp->nAllocs, 0, sizeof(fav_type_t) * FAV_PRE_ALLOC);
+ fp->nAllocs += FAV_PRE_ALLOC;
+ return 0;
+}
+
+/**
+ * pre-append an item, and return the reference back.
+ */
+static fav_type_t *fav_preappend(fav_t *fp, int type)
+{
+ fav_type_t *item;
+ if (enlarge_if_full(fp) < 0)
+ return NULL;
+ item = &fp->favh[fp->DataTail];
+ item->fp = fav_malloc(get_type_size(type));
+ item->attr = FAVH_FAV;
+ item->type = type;
+ fav_increase(fp, item);
+ return item;
+}
+
+static void move_in_folder(fav_t *fav, int src, int dst)
+{
+ int i, count;
+ fav_type_t tmp;
+
+ if (fav == NULL)
+ return;
+ count = get_data_number(fav);
+ if (src >= fav->DataTail)
+ src = count;
+ if (dst >= fav->DataTail)
+ dst = count;
+ if (src == dst)
+ return;
+
+ dirty = 1;
+
+ /* Find real locations of src and dst in fav->favh[] */
+ for(count = i = 0; count <= src && i < fav->DataTail; i++)
+ if (valid_item(&fav->favh[i]))
+ count++;
+ if (count != src + 1) return;
+ src = i - 1;
+ for(count = i = 0; count <= dst && i < fav->DataTail; i++)
+ if (valid_item(&fav->favh[i]))
+ count++;
+ if (count != dst + 1) return;
+ dst = i - 1;
+
+ fav_item_copy(&tmp, &fav->favh[src]);
+
+ if (src < dst) {
+ for(i = src; i < dst; i++)
+ fav_item_copy(&fav->favh[i], &fav->favh[i + 1]);
+ }
+ else { // dst < src
+ for(i = src; i > dst; i--)
+ fav_item_copy(&fav->favh[i], &fav->favh[i - 1]);
+ }
+ fav_item_copy(&fav->favh[dst], &tmp);
+}
+
+/**
+ * 將目前目錄中第 src 個 entry 移到 dst。
+ * @note src/dst 是 user 實際上看到的位置,也就是不包含 invalid entry。
+ */
+void move_in_current_folder(int from, int to)
+{
+ move_in_folder(get_current_fav(), from, to);
+}
+
+/**
+ * the following defines the interface of add new fav_XXX
+ */
+
+/**
+ * allocate 一個 folder entry
+ */
+inline static fav_t *alloc_folder_item(void){
+ fav_t *fp = (fav_t *)fav_malloc(sizeof(fav_t));
+ fp->nAllocs = FAV_PRE_ALLOC;
+ fp->favh = (fav_type_t *)fav_malloc(sizeof(fav_type_t) * FAV_PRE_ALLOC);
+ return fp;
+}
+
+/**
+ * 新增一分隔線
+ * @return 加入的 entry 指標
+ */
+fav_type_t *fav_add_line(void)
+{
+ fav_t *fp = get_current_fav();
+ if (is_maxsize())
+ return NULL;
+ if (fp == NULL || get_line_num(fp) >= MAX_LINE)
+ return NULL;
+ return fav_preappend(fp, FAVT_LINE);
+}
+
+/**
+ * 新增一目錄
+ * @return 加入的 entry 指標
+ */
+fav_type_t *fav_add_folder(void)
+{
+ fav_t *fp = get_current_fav();
+ fav_type_t *ft;
+ if (is_maxsize() || fav_stack_full())
+ return NULL;
+ if (fp == NULL || get_folder_num(fp) >= MAX_FOLDER)
+ return NULL;
+ ft = fav_preappend(fp, FAVT_FOLDER);
+ if (ft == NULL)
+ return NULL;
+ cast_folder(ft)->this_folder = alloc_folder_item();
+ return ft;
+}
+
+/**
+ * 將指定看板加入目前的目錄。
+ * @return 加入的 entry 指標
+ * @note 不允許同一個板被加入兩次
+ */
+fav_type_t *fav_add_board(int bid)
+{
+ fav_t *fp = get_current_fav();
+ fav_type_t *ft = getboard(bid);
+
+ if (fp == NULL)
+ return NULL;
+ if (ft != NULL)
+ return ft;
+
+ if (is_maxsize())
+ return NULL;
+ ft = fav_preappend(fp, FAVT_BOARD);
+ if (ft == NULL)
+ return NULL;
+ cast_board(ft)->bid = bid;
+ return ft;
+}
+
+/**
+ * for administrator to move/administrate board
+ */
+fav_type_t *fav_add_admtag(int bid)
+{
+ fav_t *fp = get_fav_root();
+ fav_type_t *ft;
+ if (is_maxsize())
+ return NULL;
+ ft = fav_preappend(fp, FAVT_BOARD);
+ set_attr(ft, FAVH_FAV, FALSE);
+ cast_board(ft)->bid = bid;
+ // turn on FAVH_ADM_TAG
+ set_attr(ft, FAVH_ADM_TAG, TRUE);
+ return ft;
+}
+
+
+/* everything about the tag in fav mode.
+ * I think we don't have to implement the function 'cross-folder' tag.*/
+
+/**
+ * 將目前目錄下,由 type & id 指定的 entry 標上/取消 tag
+ * @param bool 同 set_attr
+ * @note 若同一個目錄不幸有同樣的東西,只有第一個會作用。
+ */
+void fav_tag(int id, char type, char bool) {
+ fav_type_t *ft = get_fav_item(id, type);
+ if (ft != NULL)
+ set_attr(ft, FAVH_TAG, bool);
+}
+
+static void fav_dosomething_tagged_item(fav_t *fp, int (*act)(fav_t *, fav_type_t *))
+{
+ int i;
+ for(i = 0; i < fp->DataTail; i++){
+ if (valid_item(&fp->favh[i]) && is_set_attr(&fp->favh[i], FAVH_TAG))
+ if ((*act)(fp, &fp->favh[i]) < 0)
+ break;
+ }
+}
+
+static int remove_tagged_item(fav_t *fp, fav_type_t *ft)
+{
+ // do not remove the folder if it's on the stack
+ if (get_item_type(ft) == FAVT_FOLDER) {
+ int i;
+ for(i = 0; i < FAV_MAXDEPTH && fav_stack[i] != NULL; i++) {
+ if (fav_stack[i] == get_fav_folder(ft)){
+ set_attr(ft, FAVH_TAG, FALSE);
+ return 0;
+ }
+ }
+ }
+ return fav_remove(fp, ft);
+}
+
+inline static int fav_remove_tagged_item(fav_t *fp){
+ fav_dosomething_tagged_item(fp, remove_tagged_item);
+ return 0;
+}
+
+/* add an item into a fav_t.
+ * here we must give the line and foler a new id to prevent an old one is exist.
+ */
+static int add_and_remove_tag(fav_t *fp, fav_type_t *ft)
+{
+ fav_type_t *tmp;
+ // do not remove the folder if it's on the stack
+ if (get_item_type(ft) == FAVT_FOLDER) {
+ int i;
+ for(i = 0; i < FAV_MAXDEPTH && fav_stack[i] != NULL; i++) {
+ if (fav_stack[i] == get_fav_folder(ft)){
+ set_attr(ft, FAVH_TAG, FALSE);
+ return 0;
+ }
+ }
+ }
+ tmp = fav_preappend(fav_get_tmp_fav(), ft->type);
+ if (ft->type == FAVT_FOLDER) {
+ strlcpy(cast_folder(tmp)->title, cast_folder(ft)->title, BTLEN + 1);
+ cast_folder(tmp)->this_folder = cast_folder(ft)->this_folder;
+ }
+ else {
+ memcpy(tmp->fp, ft->fp, get_type_size(ft->type));
+ }
+
+
+ free(ft->fp);
+ ft->fp = NULL;
+ set_attr(tmp, FAVH_TAG, FALSE);
+ fav_remove(fp, ft);
+ return 0;
+}
+
+inline static int fav_add_tagged_item(fav_t *fp){
+ if (fp == fav_get_tmp_fav())
+ return -1;
+ fav_dosomething_tagged_item(fp, add_and_remove_tag);
+ return 0;
+}
+
+static void fav_do_recursively(fav_t *fp, int (*act)(fav_t *))
+{
+ int i;
+ fav_type_t *ft;
+ for(i = 0; i < fp->DataTail; i++){
+ ft = &fp->favh[i];
+ if (!valid_item(ft))
+ continue;
+ if (get_item_type(ft) == FAVT_FOLDER && get_fav_folder(ft) != NULL){
+ fav_do_recursively(get_fav_folder(ft), act);
+ }
+ }
+ (*act)(fp);
+}
+
+static void fav_dosomething_all_tagged_item(int (*act)(fav_t *))
+{
+ fav_do_recursively(get_fav_root(), act);
+}
+
+/**
+ * fav_*_all_tagged_item 在整個我的最愛上對已標上 tag 的 entry 做某件事。
+ */
+void fav_remove_all_tagged_item(void)
+{
+ fav_dosomething_all_tagged_item(fav_remove_tagged_item);
+}
+
+void fav_add_all_tagged_item(void)
+{
+ fav_set_tmp_folder(get_current_fav());
+ fav_dosomething_all_tagged_item(fav_add_tagged_item);
+}
+
+inline static int remove_tag(fav_t *fp, fav_type_t *ft)
+{
+ set_attr(ft, FAVH_TAG, FALSE);
+ return 0;
+}
+
+inline static int remove_tags(fav_t *fp)
+{
+ fav_dosomething_tagged_item(fp, remove_tag);
+ return 0;
+}
+
+/**
+ * 移除我的最愛所有的 tags
+ */
+void fav_remove_all_tag(void)
+{
+ fav_dosomething_all_tagged_item(remove_tags);
+}
+
+/**
+ * 設定 folder 的中文名稱
+ */
+void fav_set_folder_title(fav_type_t *ft, char *title)
+{
+ if (get_item_type(ft) != FAVT_FOLDER)
+ return;
+ dirty = 1;
+ strlcpy(cast_folder(ft)->title, title, sizeof(cast_folder(ft)->title));
+}
+
+#define BRD_OLD 0
+#define BRD_NEW 1
+#define BRD_END 2
+/**
+ * 如果 user 開啟 FAVNEW_FLAG 的功能:
+ * mode == 1: update 看板,並將新看板加入我的最愛。
+ * mode == 0: update 資訊但不加入。
+ *
+ * @return 加入的看板數
+ * PS. count 就讓它數完,才不會在下一次 login 又從一半開始數。
+ */
+int updatenewfav(int mode)
+{
+ /* mode: 0: don't write to fav 1: write to fav */
+ int i, fd, brdnum;
+ int count = 0;
+ char fname[80], *brd;
+
+ if(!(cuser.uflag2 & FAVNEW_FLAG))
+ return 0;
+
+ setuserfile(fname, FAVNB);
+
+ if( (fd = open(fname, O_RDWR | O_CREAT, 0600)) != -1 ){
+
+ assert(numboards>=0);
+ brdnum = numboards; /* avoid race */
+
+ if ((brd = (char *)malloc((brdnum + 1) * sizeof(char))) == NULL)
+ return -1;
+ memset(brd, 0, (brdnum + 1) * sizeof(char));
+
+ i = read(fd, brd, brdnum * sizeof(char));
+ if (i < 0) {
+ free(brd);
+ close(fd);
+ vmsg("favorite subscription error");
+ return -1;
+ }
+
+ brd[i] = BRD_END;
+
+ for(i = 0; i < brdnum && brd[i] != BRD_END; i++){
+ if(brd[i] == BRD_NEW){
+ /* check the permission if the board exsits */
+ if(bcache[i].brdname[0] && HasBoardPerm(&bcache[i])){
+ if(mode && !(bcache[i].brdattr & BRD_SYMBOLIC)) {
+ fav_add_board(i + 1);
+ count++;
+ }
+ brd[i] = BRD_OLD;
+ }
+ }
+ else{
+ if(!bcache[i].brdname[0])
+ brd[i] = BRD_NEW;
+ }
+ }
+
+ if( i < brdnum) { // the board number may change
+ for(; i < brdnum; ++i){
+ if(bcache[i].brdname[0] && HasBoardPerm(&bcache[i])){
+ if(mode && !(bcache[i].brdattr & BRD_SYMBOLIC)) {
+ fav_add_board(i + 1);
+ count++;
+ }
+ brd[i] = BRD_OLD;
+ }
+ else
+ brd[i] = BRD_NEW;
+ }
+ }
+
+ brd[i] = BRD_END;
+
+ lseek(fd, 0, SEEK_SET);
+ write(fd, brd, (brdnum + 1) * sizeof(char));
+
+ free(brd);
+ close(fd);
+ }
+
+ return count;
+}
+
+void subscribe_newfav(void)
+{
+ updatenewfav(0);
+}
+
+#if 1 // DEPRECATED
+typedef struct {
+ char fid;
+ char title[BTLEN + 1];
+ int this_folder;
+} fav_folder4_t;
+
+typedef struct {
+ int bid;
+ time4_t lastvisit; /* UNUSED */
+ char attr;
+} fav4_board_t;
+
+static int fav4_get_type_size(int type)
+{
+ switch (type){
+ case FAVT_BOARD:
+ return sizeof(fav4_board_t);
+ case FAVT_FOLDER:
+ return sizeof(fav_folder_t);
+ case FAVT_LINE:
+ return sizeof(fav_line_t);
+ }
+ return 0;
+}
+
+static void fav4_read_favrec(FILE *frp, fav_t *fp)
+{
+ int i;
+ fav_type_t *ft;
+
+ fread(&fp->nBoards, sizeof(fp->nBoards), 1, frp);
+ fread(&fp->nLines, sizeof(fp->nLines), 1, frp);
+ fread(&fp->nFolders, sizeof(fp->nFolders), 1, frp);
+ fp->DataTail = get_data_number(fp);
+ fp->nAllocs = fp->DataTail + FAV_PRE_ALLOC;
+ fp->lineID = fp->folderID = 0;
+ fp->favh = (fav_type_t *)fav_malloc(sizeof(fav_type_t) * fp->nAllocs);
+ fav_number += get_data_number(fp);
+
+ for(i = 0; i < fp->DataTail; i++){
+ ft = &fp->favh[i];
+ fread(&ft->type, sizeof(ft->type), 1, frp);
+ fread(&ft->attr, sizeof(ft->attr), 1, frp);
+ ft->fp = (void *)fav_malloc(fav4_get_type_size(ft->type));
+
+ /* TODO A pointer has different size between 32 and 64-bit arch.
+ * But the pointer in fav_folder_t is irrelevant here.
+ * In order not to touch the current .fav4, fav_folder4_t is used
+ * here. It should be FIXED in the next version. */
+ switch (ft->type) {
+ case FAVT_FOLDER:
+ fread(ft->fp, sizeof(fav_folder4_t), 1, frp);
+ break;
+ case FAVT_BOARD:
+ case FAVT_LINE:
+ fread(ft->fp, fav4_get_type_size(ft->type), 1, frp);
+ break;
+ }
+ }
+
+ for(i = 0; i < fp->DataTail; i++){
+ ft = &fp->favh[i];
+ switch (ft->type) {
+ case FAVT_FOLDER: {
+ fav_t *p = (fav_t *)fav_malloc(sizeof(fav_t));
+ fav4_read_favrec(frp, p);
+ cast_folder(ft)->this_folder = p;
+ cast_folder(ft)->fid = ++(fp->folderID);
+ break;
+ }
+ case FAVT_LINE:
+ cast_line(ft)->lid = ++(fp->lineID);
+ break;
+ }
+ }
+}
+#endif
diff --git a/pttbbs/mbbsd/file.c b/pttbbs/mbbsd/file.c
new file mode 100644
index 00000000..34af866a
--- /dev/null
+++ b/pttbbs/mbbsd/file.c
@@ -0,0 +1,91 @@
+/* $Id$ */
+
+#include "bbs.h"
+
+/**
+ * file.c 是針對以"行"為單位的檔案所定義的一些 operation。
+ */
+
+/**
+ * 傳回 file 檔的行數
+ * @param file
+ */
+int file_count_line(const char *file)
+{
+ FILE *fp;
+ int count = 0;
+ char buf[200];
+
+ if ((fp = fopen(file, "r"))) {
+ while (fgets(buf, sizeof(buf), fp)) {
+ if (strchr(buf, '\n') == NULL)
+ continue;
+ count++;
+ }
+ fclose(fp);
+ }
+ return count;
+}
+
+/**
+ * 將 string append 到檔案 file 後端
+ * @param file 要被 append 的檔
+ * @param string
+ * @return 成功傳回 0,失敗傳回 -1。
+ */
+int file_append_line(const char *file, const char *string)
+{
+ FILE *fp;
+ if ((fp = fopen(file, "a")) == NULL)
+ return -1;
+ flock(fileno(fp), LOCK_EX);
+ fputs(string, fp);
+ flock(fileno(fp), LOCK_UN);
+ fclose(fp);
+ return 0;
+}
+
+/**
+ * 傳回檔案 file 中是否有 string 這個字串。
+ */
+int file_exist_record(const char *file, const char *string)
+{
+ FILE *fp;
+ char buf[STRLEN], *ptr;
+
+ 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)) {
+ fclose(fp);
+ return 1;
+ }
+ }
+ fclose(fp);
+ return 0;
+}
+
+/**
+ * 對每一筆 record 做 func 這件事。
+ * @param file
+ * @param func 處理每筆 record 的 handler,為一 function pointer。
+ * 第一個參數是檔案中的一行,第二個參數為 info。
+ * @param info 一個額外的參數。
+ */
+int file_foreach_entry(const char *file, int (*func)(char *, int), int info)
+{
+ char line[80];
+ FILE *fp;
+
+ if ((fp = fopen(file, "r")) == NULL)
+ return -1;
+
+ while (fgets(line, sizeof(line), fp)) {
+ (*func)(line, info);
+ }
+
+ fclose(fp);
+ return 0;
+}
diff --git a/pttbbs/mbbsd/friend.c b/pttbbs/mbbsd/friend.c
new file mode 100644
index 00000000..50f00887
--- /dev/null
+++ b/pttbbs/mbbsd/friend.c
@@ -0,0 +1,509 @@
+/* $Id$ */
+#include "bbs.h"
+
+/* ------------------------------------- */
+/* 特別名單 */
+/* ------------------------------------- */
+
+/* Ptt 其他特別名單的檔名 */
+char special_list[] = "list.0";
+char special_des[] = "ldes.0";
+
+/* 特別名單的上限 */
+static const unsigned int friend_max[8] = {
+ MAX_FRIEND, /* FRIEND_OVERRIDE */
+ MAX_REJECT, /* FRIEND_REJECT */
+ MAX_LOGIN_INFO, /* FRIEND_ALOHA */
+ MAX_POST_INFO, /* FRIEND_POST */
+ MAX_NAMELIST, /* FRIEND_SPECIAL */
+ MAX_FRIEND, /* FRIEND_CANVOTE */
+ MAX_FRIEND, /* BOARD_WATER */
+ MAX_FRIEND, /* BOARD_VISABLE */
+};
+/* 雖然好友跟壞人名單都是 * 2 但是一次最多load到shm只能有128 */
+
+
+/* Ptt 各種特別名單的補述 */
+static char * const friend_desc[8] = {
+ "友誼描述:",
+ "惡形惡狀:",
+ "",
+ "",
+ "描述一下:",
+ "投票者描述:",
+ "惡形惡狀:",
+ "看板好友描述"
+};
+
+/* Ptt 各種特別名單的中文敘述 */
+static char * const friend_list[8] = {
+ "好友名單",
+ "壞人名單",
+ "上線通知",
+ "新文章通知",
+ "其它特別名單",
+ "私人投票名單",
+ "看板禁聲名單",
+ "看板好友名單"
+};
+
+void
+setfriendfile(char *fpath, int type)
+{
+ if (type <= 4) /* user list Ptt */
+ setuserfile(fpath, friend_file[type]);
+ else /* board list */
+ setbfile(fpath, currboard, friend_file[type]);
+}
+
+inline static int
+friend_count(const char *fname)
+{
+ return file_count_line(fname);
+}
+
+void
+friend_add(const char *uident, int type, const char* des)
+{
+ char fpath[80];
+
+ setfriendfile(fpath, type);
+ if (friend_count(fpath) > friend_max[type])
+ return;
+
+ if ((uident[0] > ' ') && !belong(fpath, uident)) {
+ char buf[40] = "", buf2[256];
+ char t_uident[IDLEN + 1];
+
+ /* Thor: avoid uident run away when get data */
+ strlcpy(t_uident, uident, sizeof(t_uident));
+
+ if (type != FRIEND_ALOHA && type != FRIEND_POST){
+ if(!des)
+ getdata(2, 0, friend_desc[type], buf, sizeof(buf), DOECHO);
+ else
+ getdata_str(2, 0, friend_desc[type], buf, sizeof(buf), DOECHO, des);
+ }
+
+ sprintf(buf2, "%-13s%s\n", t_uident, buf);
+ file_append_line(fpath, buf2);
+ }
+}
+
+void
+friend_special(void)
+{
+ char genbuf[70], i, fname[70];
+ FILE *fp;
+ friend_file[FRIEND_SPECIAL] = special_list;
+ for (i = 0; i <= 9; i++) {
+ snprintf(genbuf, sizeof(genbuf), " (" ANSI_COLOR(36) "%d" ANSI_RESET ") .. ", i);
+ special_des[5] = i + '0';
+ setuserfile(fname, special_des);
+ if( (fp = fopen(fname, "r")) != NULL ){
+ fgets(genbuf + 15, 40, fp);
+ genbuf[47] = 0;
+ fclose(fp);
+ }
+ move(i + 12, 0);
+ clrtoeol();
+ outs(genbuf);
+ }
+ getdata(22, 0, "請選擇第幾號特別名單 (0~9)[0]?", genbuf, 3, LCECHO);
+ if (genbuf[0] >= '0' && genbuf[0] <= '9') {
+ special_list[5] = genbuf[0];
+ special_des[5] = genbuf[0];
+ } else {
+ special_list[5] = '0';
+ special_des[5] = '0';
+ }
+}
+
+static void
+friend_append(int type, int count)
+{
+ char fpath[80], i, j, buf[80], sfile[80];
+ FILE *fp, *fp1;
+
+ setfriendfile(fpath, type);
+
+ do {
+ move(2, 0);
+ clrtobot();
+ outs("要引入哪一個名單?\n");
+ for (j = i = 0; i <= 4; i++)
+ if (i != type) {
+ ++j;
+ prints(" (%d) %-s\n", j, friend_list[(int)i]);
+ }
+ if (HasUserPerm(PERM_SYSOP) || currmode & MODE_BOARD)
+ for (; i < 8; ++i)
+ if (i != type) {
+ ++j;
+ prints(" (%d) %s 板的 %s\n", j, currboard,
+ friend_list[(int)i]);
+ }
+ outs(" (S) 選擇其他看板的特別名單");
+ getdata(11, 0, "請選擇 或 直接[Enter] 放棄:", buf, 3, LCECHO);
+ if (!buf[0])
+ return;
+ if (buf[0] == 's')
+ Select();
+ j = buf[0] - '1';
+ if (j >= type)
+ j++;
+ if (!(HasUserPerm(PERM_SYSOP) || currmode & MODE_BOARD) && j >= 5)
+ return;
+ } while (buf[0] < '1' || buf[0] > '9');
+
+ if (j == FRIEND_SPECIAL)
+ friend_special();
+
+ setfriendfile(sfile, j);
+
+ if ((fp = fopen(sfile, "r")) != NULL) {
+ while (fgets(buf, 80, fp) && (unsigned)count <= friend_max[type]) {
+ char the_id[IDLEN + 1];
+
+ sscanf(buf, "%" toSTR(IDLEN) "s", the_id);
+ if (!file_exist_record(fpath, the_id)) {
+ if ((fp1 = fopen(fpath, "a"))) {
+ flock(fileno(fp1), LOCK_EX);
+ fputs(buf, fp1);
+ flock(fileno(fp1), LOCK_UN);
+ fclose(fp1);
+ }
+ }
+ }
+ fclose(fp);
+ }
+}
+
+static int
+delete_friend_from_file(const char *file, const char *string, int case_sensitive)
+{
+ FILE *fp, *nfp = NULL;
+ char fnew[80];
+ char genbuf[STRLEN + 1];
+
+ sprintf(fnew, "%s.%3.3X", file, (unsigned int)(random() & 0xFFF));
+ if ((fp = fopen(file, "r")) && (nfp = fopen(fnew, "w"))) {
+ while (fgets(genbuf, sizeof(genbuf), fp))
+ if ((genbuf[0] > ' ')) {
+ char buf[32];
+ sscanf(genbuf, " %s", buf);
+ if (((case_sensitive && strcmp(buf, string)) ||
+ (!case_sensitive && strcasecmp(buf, string))))
+ fputs(genbuf, nfp);
+ }
+ Rename(fnew, file);
+ }
+ if(fp)
+ fclose(fp);
+ if(nfp)
+ fclose(nfp);
+ return 0;
+}
+
+void
+friend_delete(const char *uident, int type)
+{
+ char fn[80];
+ setfriendfile(fn, type);
+ delete_friend_from_file(fn, uident, 0);
+}
+
+static void
+delete_user_friend(const char *uident, const char *thefriend, int type)
+{
+ char fn[80];
+#if 0
+ if (type == FRIEND_ALOHA) {
+#endif
+ sethomefile(fn, uident, "aloha");
+ delete_friend_from_file(fn, thefriend, 0);
+#if 0
+ }
+ else {
+ }
+#endif
+}
+
+void
+friend_delete_all(const char *uident, int type)
+{
+ char buf[80], line[80];
+ FILE *fp;
+
+ sethomefile(buf, uident, friend_file[type]);
+
+ if ((fp = fopen(buf, "r")) == NULL)
+ return;
+
+ while (fgets(line, sizeof(line), fp)) {
+ sscanf(line, "%s", buf);
+ delete_user_friend(buf, uident, type);
+ }
+
+ fclose(fp);
+}
+
+static void
+friend_editdesc(const char *uident, int type)
+{
+ FILE *fp=NULL, *nfp=NULL;
+ char fnnew[200], genbuf[STRLEN], fn[200];
+ setfriendfile(fn, type);
+ snprintf(fnnew, sizeof(fnnew), "%s-", fn);
+ if ((fp = fopen(fn, "r")) && (nfp = fopen(fnnew, "w"))) {
+ int length = strlen(uident);
+
+ while (fgets(genbuf, STRLEN, fp)) {
+ if ((genbuf[0] > ' ') && strncmp(genbuf, uident, length))
+ fputs(genbuf, nfp);
+ else if (!strncmp(genbuf, uident, length)) {
+ char buf[50] = "";
+ getdata(2, 0, "修改描述:", buf, 40, DOECHO);
+ fprintf(nfp, "%-13s%s\n", uident, buf);
+ }
+ }
+ Rename(fnnew, fn);
+ }
+ if(fp)
+ fclose(fp);
+ if(nfp)
+ fclose(nfp);
+}
+
+inline void friend_load_real(int tosort, int maxf,
+ short *destn, int *destar, const char *fn)
+{
+ char genbuf[200];
+ FILE *fp;
+ short nFriends = 0;
+ int uid, *tarray;
+ char *p;
+
+ setuserfile(genbuf, fn);
+ if( (fp = fopen(genbuf, "r")) == NULL ){
+ destar[0] = 0;
+ if( destn )
+ *destn = 0;
+ }
+ else{
+ char *strtok_pos;
+ tarray = (int *)malloc(sizeof(int) * maxf);
+ --maxf; /* 因為最後一個要填 0, 所以先扣一個回來 */
+ while( fgets(genbuf, STRLEN, fp) && nFriends < maxf )
+ if( (p = strtok_r(genbuf, str_space, &strtok_pos)) &&
+ (uid = searchuser(p, NULL)) )
+ tarray[nFriends++] = uid;
+ fclose(fp);
+
+ if( tosort )
+ qsort(tarray, nFriends, sizeof(int), qsort_intcompar);
+ if( destn )
+ *destn = nFriends;
+ tarray[nFriends] = 0;
+ memcpy(destar, tarray, sizeof(int) * (nFriends + 1));
+ free(tarray);
+ }
+}
+
+/* type == 0 : load all */
+void friend_load(int type)
+{
+ if (!type || type & FRIEND_OVERRIDE)
+ friend_load_real(1, MAX_FRIEND, &currutmp->nFriends,
+ currutmp->myfriend, fn_overrides);
+
+ if (!type || type & FRIEND_REJECT)
+ friend_load_real(0, MAX_REJECT, NULL, currutmp->reject, fn_reject);
+
+ if (currutmp->friendtotal)
+ logout_friend_online(currutmp);
+
+ login_friend_online();
+}
+
+static void
+friend_water(const char *message, int type)
+{ /* 群體水球 added by Ptt */
+ char fpath[80], line[80], userid[IDLEN + 1];
+ FILE *fp;
+
+ setfriendfile(fpath, type);
+ if ((fp = fopen(fpath, "r"))) {
+ while (fgets(line, 80, fp)) {
+ userinfo_t *uentp;
+ int tuid;
+
+ sscanf(line, "%" toSTR(IDLEN) "s", userid);
+ if ((tuid = searchuser(userid, NULL)) && tuid != usernum &&
+ (uentp = (userinfo_t *) search_ulist(tuid)) &&
+ isvisible_uid(tuid))
+ my_write(uentp->pid, message, uentp->userid, WATERBALL_PREEDIT, NULL);
+ }
+ fclose(fp);
+ }
+}
+
+void
+friend_edit(int type)
+{
+ char fpath[80], line[80], uident[IDLEN + 1];
+ int count, column, dirty;
+ FILE *fp;
+ char genbuf[200];
+
+ if (type == FRIEND_SPECIAL)
+ friend_special();
+ setfriendfile(fpath, type);
+
+ if (type == FRIEND_ALOHA || type == FRIEND_POST) {
+ if (dashf(fpath)) {
+ sprintf(genbuf,"%s.old",fpath);
+ Copy(fpath, genbuf);
+ }
+ }
+ dirty = 0;
+ while (1) {
+ stand_title(friend_list[type]);
+ /* TODO move (0, 40) just won't really work as it hints.
+ * The ANSI secapes will change x coordinate. */
+ move(0, 40);
+ prints("(名單上限: %d 人)", friend_max[type]);
+ count = 0;
+ CreateNameList();
+
+ if ((fp = fopen(fpath, "r"))) {
+ move(3, 0);
+ column = 0;
+ while (fgets(genbuf, STRLEN, fp)) {
+ char *space;
+ if (genbuf[0] <= ' ')
+ continue;
+ space = strpbrk(genbuf, str_space);
+ if (space) *space = '\0';
+ AddNameList(genbuf);
+ prints("%-13s", genbuf);
+ count++;
+ if (++column > 5) {
+ column = 0;
+ outc('\n');
+ }
+ }
+ fclose(fp);
+ }
+ getdata(1, 0, (count ?
+ "(A)增加(D)刪除(E)修改(P)引入(L)詳細列出"
+ "(K)刪除整個名單(W)丟水球(Q)結束?[Q] " :
+ "(A)增加 (P)引入其他名單 (Q)結束?[Q] "),
+ uident, 3, LCECHO);
+ if (uident[0] == 'a') {
+ move(1, 0);
+ usercomplete(msg_uid, uident);
+ if (uident[0] && searchuser(uident, uident) && !InNameList(uident)) {
+ friend_add(uident, type, NULL);
+ dirty = 1;
+ }
+ } else if (uident[0] == 'p') {
+ friend_append(type, count);
+ dirty = 1;
+ } else if (uident[0] == 'e' && count) {
+ move(1, 0);
+ namecomplete(msg_uid, uident);
+ if (uident[0] && InNameList(uident)) {
+ friend_editdesc(uident, type);
+ }
+ } else if (uident[0] == 'd' && count) {
+ move(1, 0);
+ namecomplete(msg_uid, uident);
+ if (uident[0] && InNameList(uident)) {
+ friend_delete(uident, type);
+ dirty = 1;
+ }
+ } else if (uident[0] == 'l' && count)
+ more(fpath, YEA);
+ else if (uident[0] == 'k' && count) {
+ getdata(2, 0, "刪除整份名單,確定嗎 (a/N)?", uident, 3,
+ LCECHO);
+ if (uident[0] == 'a')
+ unlink(fpath);
+ dirty = 1;
+ } else if (uident[0] == 'w' && count) {
+ char wall[60];
+ if (!getdata(0, 0, "群體水球:", wall, sizeof(wall), DOECHO))
+ continue;
+ if (getdata(0, 0, "確定丟出群體水球? [Y]", line, 4, LCECHO) &&
+ *line == 'n')
+ continue;
+ friend_water(wall, type);
+ } else
+ break;
+ }
+ if (dirty) {
+ move(2, 0);
+ outs("更新資料中..請稍候.....");
+ refresh();
+ if (type == FRIEND_ALOHA || type == FRIEND_POST) {
+ snprintf(genbuf, sizeof(genbuf), "%s.old", fpath);
+ if ((fp = fopen(genbuf, "r"))) {
+ while (fgets(line, 80, fp)) {
+ sscanf(line, "%" toSTR(IDLEN) "s", uident);
+ sethomefile(genbuf, uident,
+ type == FRIEND_ALOHA ? "aloha" : "postnotify");
+ del_distinct(genbuf, cuser.userid, 0);
+ }
+ fclose(fp);
+ }
+ strlcpy(genbuf, fpath, sizeof(genbuf));
+ if ((fp = fopen(genbuf, "r"))) {
+ while (fgets(line, 80, fp)) {
+ sscanf(line, "%" toSTR(IDLEN) "s", uident);
+ sethomefile(genbuf, uident,
+ type == FRIEND_ALOHA ? "aloha" : "postnotify");
+ add_distinct(genbuf, cuser.userid);
+ }
+ fclose(fp);
+ }
+ } else if (type == FRIEND_SPECIAL) {
+ genbuf[0] = 0;
+ setuserfile(line, special_des);
+ if ((fp = fopen(line, "r"))) {
+ fgets(genbuf, 30, fp);
+ fclose(fp);
+ }
+ getdata_buf(2, 0, " 請為此特別名單取一個簡短名稱:", genbuf, 30,
+ DOECHO);
+ if ((fp = fopen(line, "w"))) {
+ fputs(genbuf, fp);
+ fclose(fp);
+ }
+ } else if (type == BOARD_WATER) {
+ boardheader_t *bp = NULL;
+ currbid = getbnum(currboard);
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ bp = getbcache(currbid);
+ bp->perm_reload = now;
+ assert(0<=currbid-1 && currbid-1<MAX_BOARD);
+ substitute_record(fn_board, bp, sizeof(boardheader_t), currbid);
+ // log_usies("SetBoard", bp->brdname);
+ }
+ friend_load(0);
+ }
+}
+
+int
+t_override(void)
+{
+ friend_edit(FRIEND_OVERRIDE);
+ return 0;
+}
+
+int
+t_reject(void)
+{
+ friend_edit(FRIEND_REJECT);
+ return 0;
+}
diff --git a/pttbbs/mbbsd/gamble.c b/pttbbs/mbbsd/gamble.c
new file mode 100644
index 00000000..049bda74
--- /dev/null
+++ b/pttbbs/mbbsd/gamble.c
@@ -0,0 +1,388 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define MAX_ITEM 8 //最大 賭項(item) 個數
+#define MAX_ITEM_LEN 30 //最大 每一賭項名字長度
+#define MAX_SUBJECT_LEN 650 //8*81 = 648 最大 主題長度
+
+static int
+load_ticket_record(const char *direct, int ticket[])
+{
+ char buf[256];
+ int i, total = 0;
+ FILE *fp;
+ snprintf(buf, sizeof(buf), "%s/" FN_TICKET_RECORD, direct);
+ if (!(fp = fopen(buf, "r")))
+ return 0;
+ for (i = 0; i < MAX_ITEM && fscanf(fp, "%9d ", &ticket[i])==1; i++)
+ total = total + ticket[i];
+ fclose(fp);
+ return total;
+}
+
+static int
+show_ticket_data(char betname[MAX_ITEM][MAX_ITEM_LEN],const char *direct, int *price, const boardheader_t * bh)
+{
+ int i, count, total = 0, end = 0, ticket[MAX_ITEM] = {0, 0, 0, 0, 0, 0, 0, 0};
+ FILE *fp;
+ char genbuf[256], t[25];
+
+ clear();
+ if (bh) {
+ snprintf(genbuf, sizeof(genbuf), "%s 賭盤", bh->brdname);
+ if (bh->endgamble && now < bh->endgamble &&
+ bh->endgamble - now < 3600) {
+ snprintf(t, sizeof(t),
+ "封盤倒數 %d 秒", (int)(bh->endgamble - now));
+ showtitle(genbuf, t);
+ } else
+ showtitle(genbuf, BBSNAME);
+ } else
+ showtitle("Ptt賭盤", BBSNAME);
+ move(2, 0);
+ snprintf(genbuf, sizeof(genbuf), "%s/" FN_TICKET_ITEMS, direct);
+ if (!(fp = fopen(genbuf, "r"))) {
+ outs("\n目前並沒有舉辦賭盤\n");
+ snprintf(genbuf, sizeof(genbuf), "%s/" FN_TICKET_OUTCOME, direct);
+ more(genbuf, NA);
+ return 0;
+ }
+ fgets(genbuf, MAX_ITEM_LEN, fp);
+ *price = atoi(genbuf);
+ for (count = 0; fgets(betname[count], MAX_ITEM_LEN, fp) && count < MAX_ITEM; count++) {
+ chomp(betname[count]);
+ }
+ fclose(fp);
+
+ prints(ANSI_COLOR(32) "站規:" ANSI_RESET " 1.可購買以下不同類型的彩票。每張要花 " ANSI_COLOR(32) "%d" ANSI_RESET " 元。\n"
+ " 2.%s\n"
+ " 3.開獎時只有一種彩票中獎, 有購買該彩票者, 則可依購買的張數均分總賭金。\n"
+ " 4.每筆獎金由系統抽取 5%% 之稅金%s。\n\n"
+ ANSI_COLOR(32) "%s:" ANSI_RESET, *price,
+ bh ? "此賭盤由板主負責舉辦並且決定開獎時間結果, 站長不管, 願賭服輸。" :
+ "系統每天 2:00 11:00 16:00 21:00 開獎。",
+ bh ? ", 其中 2% 分給開獎板主" : "",
+ bh ? "板主自訂規則及說明" : "前幾次開獎結果");
+
+
+ snprintf(genbuf, sizeof(genbuf), "%s/" FN_TICKET, direct);
+ if (!dashf(genbuf)) {
+ snprintf(genbuf, sizeof(genbuf), "%s/" FN_TICKET_END, direct);
+ end = 1;
+ }
+ show_file(genbuf, 8, -1, NO_RELOAD);
+ move(15, 0);
+ outs(ANSI_COLOR(1;32) "目前下注狀況:" ANSI_RESET "\n");
+
+ total = load_ticket_record(direct, ticket);
+
+ outs(ANSI_COLOR(33));
+ for (i = 0; i < count; i++) {
+ prints("%d.%-8s: %-7d", i + 1, betname[i], ticket[i]);
+ if (i == 3)
+ outc('\n');
+ }
+ prints(ANSI_RESET "\n" ANSI_COLOR(42) " 下注總金額:" ANSI_COLOR(31) " %d 元 " ANSI_RESET, total * (*price));
+ if (end) {
+ outs("\n賭盤已經停止下注\n");
+ return -count;
+ }
+ return count;
+}
+
+static int
+append_ticket_record(const char *direct, int ch, int n, int count)
+{
+ FILE *fp;
+ int ticket[8] = {0, 0, 0, 0, 0, 0, 0, 0}, i;
+ char genbuf[256];
+
+ snprintf(genbuf, sizeof(genbuf), "%s/" FN_TICKET, direct);
+ if (!dashf(genbuf))
+ return -1;
+
+ snprintf(genbuf, sizeof(genbuf), "%s/" FN_TICKET_USER, direct);
+ if ((fp = fopen(genbuf, "a"))) {
+ fprintf(fp, "%s %d %d\n", cuser.userid, ch, n);
+ fclose(fp);
+ }
+
+ snprintf(genbuf, sizeof(genbuf), "%s/" FN_TICKET_RECORD, direct);
+
+ if (!dashf(genbuf)) {
+ creat(genbuf, S_IRUSR | S_IWUSR);
+ }
+
+ if ((fp = fopen(genbuf, "r+"))) {
+
+ flock(fileno(fp), LOCK_EX);
+
+ for (i = 0; i < MAX_ITEM; i++)
+ if (fscanf(fp, "%9d ", &ticket[i]) != 1)
+ break;
+ ticket[ch] += n;
+
+ ftruncate(fileno(fp), 0);
+ rewind(fp);
+ for (i = 0; i < count; i++)
+ fprintf(fp, "%d ", ticket[i]);
+ fflush(fp);
+
+ flock(fileno(fp), LOCK_UN);
+ fclose(fp);
+ }
+ return 0;
+}
+
+#define lockreturn0(unmode, state) if(lockutmpmode(unmode, state)) return 0
+int
+ticket(int bid)
+{
+ int ch, end = 0;
+ int n, price, count; /* 購買張數、單價、選項數 */
+ char path[128], fn_ticket[128];
+ char betname[MAX_ITEM][MAX_ITEM_LEN];
+ boardheader_t *bh = NULL;
+
+ STATINC(STAT_GAMBLE);
+ if (bid) {
+ bh = getbcache(bid);
+ setbpath(path, bh->brdname);
+ setbfile(fn_ticket, bh->brdname, FN_TICKET);
+ currbid = bid;
+ } else
+ strcpy(path, "etc/");
+
+ lockreturn0(TICKET, LOCK_MULTI);
+ while (1) {
+ count = show_ticket_data(betname, path, &price, bh);
+ if (count <= 0) {
+ pressanykey();
+ break;
+ }
+ move(20, 0);
+ reload_money();
+ prints(ANSI_COLOR(44) "錢: %-10d " ANSI_RESET "\n" ANSI_COLOR(1) "請選擇要購買的種類(1~%d)"
+ "[Q:離開]" ANSI_RESET ":", cuser.money, count);
+ ch = igetch();
+ /*--
+ Tim011127
+ 為了控制CS問題 但是這邊還不能完全解決這問題,
+ 若user通過檢查下去, 剛好板主開獎, 還是會造成user的這次紀錄
+ 很有可能跑到下次賭盤的紀錄去, 也很有可能被板主新開賭盤時洗掉
+ 不過這邊至少可以做到的是, 頂多只會有一筆資料是錯的
+ --*/
+ if (ch == 'q' || ch == 'Q')
+ break;
+ ch -= '1';
+ if (end || ch >= count || ch < 0)
+ continue;
+ n = 0;
+ ch_buyitem(price, "etc/buyticket", &n, 0);
+
+ if (bid && !dashf(fn_ticket))
+ goto doesnt_catch_up;
+
+ if (n > 0) {
+ if (append_ticket_record(path, ch, n, count) < 0)
+ goto doesnt_catch_up;
+ }
+ }
+ unlockutmpmode();
+ return 0;
+
+doesnt_catch_up:
+
+ price = price * n;
+ if (price > 0)
+ deumoney(currutmp->uid, price);
+ vmsg("板主已經停止下注了 不能賭嚕");
+ unlockutmpmode();
+ return -1;
+}
+
+int
+openticket(int bid)
+{
+ char path[MAXPATHLEN], buf[MAXPATHLEN], outcome[MAXPATHLEN];
+ int i, money = 0, count, bet, price, total = 0,
+ ticket[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+ boardheader_t *bh = getbcache(bid);
+ FILE *fp, *fp1;
+ char betname[MAX_ITEM][MAX_ITEM_LEN];
+
+ setbpath(path, bh->brdname);
+ count = -show_ticket_data(betname, path, &price, bh);
+ if (count == 0) {
+ setbfile(buf, bh->brdname, FN_TICKET_END);
+ unlink(buf);
+//Ptt: 有bug
+ return 0;
+ }
+ lockreturn0(TICKET, LOCK_MULTI);
+ do {
+ do {
+ getdata(20, 0,
+ ANSI_COLOR(1) "選擇中獎的號碼(0:不開獎 99:取消退錢)" ANSI_RESET ":", buf, 3, LCECHO);
+ bet = atoi(buf);
+ move(0, 0);
+ clrtoeol();
+ } while ((bet < 0 || bet > count) && bet != 99);
+ if (bet == 0) {
+ unlockutmpmode();
+ return 0;
+ }
+ getdata(21, 0, ANSI_COLOR(1) "再次確認輸入號碼" ANSI_RESET ":", buf, 3, LCECHO);
+ } while (bet != atoi(buf));
+
+ // before we fork to process,
+ // confirm lock status is correct.
+ setbfile(buf, bh->brdname, FN_TICKET_END);
+ setbfile(outcome, bh->brdname, FN_TICKET_LOCK);
+
+ if(access(outcome, 0) == 0)
+ {
+ unlockutmpmode();
+ vmsg("已另有人開獎,系統稍後將自動公佈中獎結果於看板");
+ return 0;
+ }
+ if(rename(buf, outcome) != 0)
+ {
+ unlockutmpmode();
+ vmsg("無法準備開獎... 請至 PttBug 報告並附上板名。");
+ return 0;
+
+ }
+
+ if (fork()) {
+ /* Ptt: 用 fork() 防止不正常斷線洗錢 */
+ unlockutmpmode();
+ vmsg("系統稍後將自動公佈於中獎結果看板(參加者多時要數分鐘)..");
+ return 0;
+ }
+ close(0);
+ close(1);
+ setproctitle("open ticket");
+#ifdef CPULIMIT
+ {
+ struct rlimit rml;
+ rml.rlim_cur = RLIM_INFINITY;
+ rml.rlim_max = RLIM_INFINITY;
+ setrlimit(RLIMIT_CPU, &rml);
+ }
+#endif
+
+
+ bet--; /* 轉成矩陣的index */
+
+ total = load_ticket_record(path, ticket);
+ setbfile(buf, bh->brdname, FN_TICKET_LOCK);
+ if (!(fp1 = fopen(buf, "r")))
+ exit(1);
+
+ /* 還沒開完獎不能賭博 只要mv一項就好 */
+ if (bet != 98) {
+ money = total * price;
+ demoney(money * 0.02);
+ mail_redenvelop("[賭場抽頭]", cuser.userid, money * 0.02, 'n');
+ money = ticket[bet] ? money * 0.95 / ticket[bet] : 9999999;
+ } else {
+ vice(price * 10, "賭盤退錢手續費");
+ money = price;
+ }
+ setbfile(outcome, bh->brdname, FN_TICKET_OUTCOME);
+ if ((fp = fopen(outcome, "w"))) {
+ fprintf(fp, "賭盤說明\n");
+ while (fgets(buf, sizeof(buf), fp1)) {
+ buf[sizeof(buf)-1] = 0;
+ fputs(buf, fp);
+ }
+ fprintf(fp, "下注情況\n");
+
+ fprintf(fp, ANSI_COLOR(33));
+ for (i = 0; i < count; i++) {
+ fprintf(fp, "%d.%-8s: %-7d", i + 1, betname[i], ticket[i]);
+ if (i == 3)
+ fprintf(fp, "\n");
+ }
+ fprintf(fp, ANSI_RESET "\n");
+
+ if (bet != 98) {
+ fprintf(fp, "\n\n開獎時間: %s \n\n"
+ "開獎結果: %s \n\n"
+ "所有金額: %d 元 \n"
+ "中獎比例: %d張/%d張 (%f)\n"
+ "每張中獎彩票可得 %d 枚P幣 \n\n",
+ Cdatelite(&now), betname[bet], total * price, ticket[bet], total,
+ (float)ticket[bet] / total, money);
+
+ fprintf(fp, "%s 賭盤開出:%s 所有金額:%d 元 獎金/張:%d 元 機率:%1.2f\n\n",
+ Cdatelite(&now), betname[bet], total * price, money,
+ total ? (float)ticket[bet] / total : 0);
+ } else
+ fprintf(fp, "\n\n賭盤取消退錢: %s \n\n", Cdatelite(&now));
+
+ } // XXX somebody may use fp even fp==NULL
+ fclose(fp1);
+ /*
+ * 以下是給錢動作
+ */
+ setbfile(buf, bh->brdname, FN_TICKET_USER);
+ if ((bet == 98 || ticket[bet]) && (fp1 = fopen(buf, "r"))) {
+ int mybet, uid;
+ char userid[IDLEN + 1];
+
+ while (fscanf(fp1, "%s %d %d\n", userid, &mybet, &i) != EOF) {
+ if (bet == 98 && mybet >= 0 && mybet < count) {
+ if (fp)
+ fprintf(fp, "%s 買了 %d 張 %s, 退回 %d 枚P幣\n"
+ ,userid, i, betname[mybet], money * i);
+ snprintf(buf, sizeof(buf),
+ "%s 賭場退錢! $ %d", bh->brdname, money * i);
+ } else if (mybet == bet) {
+ if (fp)
+ fprintf(fp, "恭喜 %s 買了%d 張 %s, 獲得 %d 枚P幣\n"
+ ,userid, i, betname[mybet], money * i);
+ snprintf(buf, sizeof(buf), "%s 中獎咧! $ %d", bh->brdname, money * i);
+ } else
+ continue;
+ if ((uid = searchuser(userid, userid)) == 0)
+ continue;
+ deumoney(uid, money * i);
+ mail_id(userid, buf, "etc/ticket.win", "Ptt賭場");
+ }
+ fclose(fp1);
+ }
+ if (fp) {
+ fprintf(fp, "\n--\n※ 開獎站 :" BBSNAME "(" MYHOSTNAME
+ ") \n◆ From: %s\n", fromhost);
+ fclose(fp);
+ }
+
+ if (bet != 98)
+ snprintf(buf, sizeof(buf), "[公告] %s 賭盤開獎", bh->brdname);
+ else
+ snprintf(buf, sizeof(buf), "[公告] %s 賭盤取消", bh->brdname);
+ post_file(bh->brdname, buf, outcome, "[賭神]");
+ post_file("Record", buf + 7, outcome, "[馬路探子]");
+ post_file("Security", buf + 7, outcome, "[馬路探子]");
+
+ setbfile(buf, bh->brdname, FN_TICKET_RECORD);
+ unlink(buf);
+
+ setbfile(buf, bh->brdname, FN_TICKET_USER);
+ post_file("Security", bh->brdname, buf, "[下注紀錄]");
+ unlink(buf);
+
+ setbfile(buf, bh->brdname, FN_TICKET_LOCK);
+ unlink(buf);
+ exit(1);
+ return 0;
+}
+
+int
+ticket_main(void)
+{
+ ticket(0);
+ return 0;
+}
diff --git a/pttbbs/mbbsd/go.c b/pttbbs/mbbsd/go.c
new file mode 100644
index 00000000..ac117b1a
--- /dev/null
+++ b/pttbbs/mbbsd/go.c
@@ -0,0 +1,1111 @@
+/* $Id$ */
+
+#include "bbs.h"
+#include <sys/socket.h>
+
+#define BBLANK (-1) /* 空白 */
+#define BWHITE (0) /* 白子, 後手 */
+#define BBLACK (1) /* 黑子, 先手 */
+#define LWHITE (2) /* 白空 */
+#define LBLACK (3) /* 黑空 */
+
+/* only used for communicating */
+#define SETHAND (5) /* 讓子 */
+#define CLEAN (6) /* 清除死子 */
+#define UNCLEAN (7) /* 清除錯子,重新來過*/
+#define CLEANDONE (8) /* 開始計地 */
+
+#define MAX_TIME (300)
+
+#define BOARD_LINE_ON_SCREEN(X) ((X) + 2)
+
+#define BRDSIZ (19) /* 棋盤單邊大小 */
+
+static const char* turn_color[] = { ANSI_COLOR(37;43), ANSI_COLOR(30;43) };
+
+static const rc_t SetHandPoints[] =
+{
+ /* 1 */ { 0, 0},
+ /* 2 */ { 3, 3}, {15, 15},
+ /* 3 */ { 3, 3}, { 3, 15}, {15, 15},
+ /* 4 */ { 3, 3}, { 3, 15}, {15, 3}, {15, 15},
+ /* 5 */ { 3, 3}, { 3, 15}, { 9, 9}, {15, 3}, {15, 15},
+ /* 6 */ { 3, 3}, { 3, 15}, { 9, 3}, { 9, 15}, {15, 3}, {15, 15},
+ /* 7 */ { 3, 3}, { 3, 15}, { 9, 3}, { 9, 9}, { 9, 15}, {15, 3},
+ {15, 15},
+ /* 8 */ { 3, 3}, { 3, 9}, { 3, 15}, { 9, 3}, { 9, 15}, {15, 3},
+ {15, 9}, {15, 15},
+ /* 9 */ { 3, 3}, { 3, 9}, { 3, 15}, { 9, 3}, { 9, 9}, { 9, 15},
+ {15, 3}, {15, 9}, {15, 15},
+};
+
+typedef char board_t[BRDSIZ][BRDSIZ];
+typedef char (*board_p)[BRDSIZ];
+
+typedef struct {
+ ChessStepType type; /* necessary one */
+ int color;
+ rc_t loc;
+} go_step_t;
+#define RC_T_EQ(X,Y) ((X).r == (Y).r && (X).c == (Y).c)
+
+typedef struct {
+ board_t backup_board;
+ char game_end;
+ char clean_end; /* bit 1 => I, bit 2 => he */
+ char need_redraw;
+ float feed_back; /* 貼還 */
+ int eaten[2];
+ int backup_eaten[2];
+ rc_t forbidden[2]; /* 打劫之禁手 */
+} go_tag_t;
+#define GET_TAG(INFO) ((go_tag_t*)(INFO)->tag)
+
+static char * const locE = "ABCDEFGHJKLMNOPQRST";
+
+static void go_init_user(const userinfo_t* uinfo, ChessUser* user);
+static void go_init_user_userec(const userec_t* urec, ChessUser* user);
+static void go_init_board(board_t board);
+static void go_drawline(const ChessInfo* info, int line);
+static void go_movecur(int r, int c);
+static int go_prepare_play(ChessInfo* info);
+static int go_process_key(ChessInfo* info, int key, ChessGameResult* result);
+static int go_select(ChessInfo* info, rc_t location,
+ ChessGameResult* result);
+static void go_prepare_step(ChessInfo* info, const go_step_t* step);
+static ChessGameResult go_apply_step(board_t board, const go_step_t* step);
+static void go_drawstep(ChessInfo* info, const go_step_t* step);
+static ChessGameResult go_post_game(ChessInfo* info);
+static void go_gameend(ChessInfo* info, ChessGameResult result);
+static void go_genlog(ChessInfo* info, FILE* fp, ChessGameResult result);
+
+const static ChessActions go_actions = {
+ &go_init_user,
+ &go_init_user_userec,
+ (void (*)(void*)) &go_init_board,
+ &go_drawline,
+ &go_movecur,
+ &go_prepare_play,
+ &go_process_key,
+ &go_select,
+ (void (*)(ChessInfo*, const void*)) &go_prepare_step,
+ (ChessGameResult (*)(void*, const void*)) &go_apply_step,
+ (void (*)(ChessInfo*, const void*)) &go_drawstep,
+ &go_post_game,
+ &go_gameend,
+ &go_genlog
+};
+
+const static ChessConstants go_constants = {
+ sizeof(go_step_t),
+ MAX_TIME,
+ BRDSIZ,
+ BRDSIZ,
+ 1,
+ "圍棋",
+ "photo_go",
+#ifdef GLOBAL_GOCHESS_LOG
+ GLOBAL_GOCHESS_LOG,
+#else
+ NULL,
+#endif
+ { ANSI_COLOR(37;43), ANSI_COLOR(30;43) },
+ { "白棋", "黑棋" },
+};
+
+static void
+go_sethand(board_t board, int n)
+{
+ if (n >= 2 && n <= 9) {
+ const int lower = n * (n - 1) / 2;
+ const int upper = lower + n;
+ int i;
+ for (i = lower; i < upper; ++i)
+ board[SetHandPoints[i].r][SetHandPoints[i].c] = BBLACK;
+ }
+}
+
+/* 計算某子的氣數, recursion part of go_countlib() */
+static int
+go_count(board_t board, board_t mark, int x, int y, int color)
+{
+ const static int diff[][2] = {
+ {1, 0}, {-1, 0}, {0, 1}, {0, -1}
+ };
+ int i;
+ int total = 0;
+
+ mark[x][y] = 0;
+
+ for (i = 0; i < 4; ++i) {
+ int xx = x + diff[i][0];
+ int yy = y + diff[i][1];
+
+ if (xx >= 0 && xx < BRDSIZ && yy >= 0 && yy < BRDSIZ) {
+ if (board[xx][yy] == BBLANK && mark[xx][yy]) {
+ ++total;
+ mark[xx][yy] = 0;
+ } else if (board[xx][yy] == color && mark[xx][yy])
+ total += go_count(board, mark, xx, yy, color);
+ }
+ }
+
+ return total;
+}
+
+/* 計算某子的氣數 */
+static int
+go_countlib(board_t board, int x, int y, char color)
+{
+ int i, j;
+ board_t mark;
+
+ for (i = 0; i < BRDSIZ; i++)
+ for (j = 0; j < BRDSIZ; j++)
+ mark[i][j] = 1;
+
+ return go_count(board, mark, x, y, color);
+}
+
+/* 計算盤面上每個子的氣數 */
+static void
+go_eval(board_t board, int lib[][BRDSIZ], char color)
+{
+ int i, j;
+
+ for (i = 0; i < 19; i++)
+ for (j = 0; j < 19; j++)
+ if (board[i][j] == color)
+ lib[i][j] = go_countlib(board, i, j, color);
+}
+
+/* 檢查一步是否合法 */
+static int
+go_check(ChessInfo* info, const go_step_t* step)
+{
+ board_p board = (board_p) info->board;
+ int lib = go_countlib(board, step->loc.r, step->loc.c, step->color);
+
+ if (lib == 0) {
+ int i, j;
+ int board_lib[BRDSIZ][BRDSIZ];
+ go_tag_t* tag = (go_tag_t*) info->tag;
+
+ board[step->loc.r][step->loc.c] = step->color;
+ go_eval(board, board_lib, !step->color);
+ board[step->loc.r][step->loc.c] = BBLANK; /* restore to open */
+
+ lib = 0;
+ for (i = 0; i < BRDSIZ; i++)
+ for (j = 0; j < BRDSIZ; j++)
+ if (board[i][j] == !step->color && !board_lib[i][j])
+ ++lib;
+
+ if (lib == 0 ||
+ (lib == 1 && RC_T_EQ(step->loc, tag->forbidden[step->color])))
+ return 0;
+ else
+ return 1;
+ } else
+ return 1;
+}
+
+/* Clean up the dead chess of color `color,' summarize number of
+ * eaten chesses and set the forbidden point.
+ *
+ * `info' might be NULL which means no forbidden point check is
+ * needed and don't have to count the number of eaten chesses.
+ *
+ * Return: 1 if any chess of color `color' was eaten; 0 otherwise. */
+static int
+go_examboard(board_t board, int color, ChessInfo* info)
+{
+ int i, j, n;
+ int lib[BRDSIZ][BRDSIZ];
+
+ rc_t dummy_rc;
+ rc_t *forbidden;
+ int dummy_eaten;
+ int *eaten;
+
+ if (info) {
+ go_tag_t* tag = (go_tag_t*) info->tag;
+ forbidden = &tag->forbidden[color];
+ eaten = &tag->eaten[!color];
+ } else {
+ forbidden = &dummy_rc;
+ eaten = &dummy_eaten;
+ }
+
+ go_eval(board, lib, color);
+
+ forbidden->r = -1;
+ forbidden->c = -1;
+
+ n = 0;
+ for (i = 0; i < BRDSIZ; i++)
+ for (j = 0; j < BRDSIZ; j++)
+ if (board[i][j] == color && lib[i][j] == 0) {
+ board[i][j] = BBLANK;
+ forbidden->r = i;
+ forbidden->c = j;
+ ++*eaten;
+ ++n;
+ }
+
+ if ( n != 1 ) {
+ /* No or more than one chess were eaten,
+ * no forbidden points, then. */
+ forbidden->r = -1;
+ forbidden->c = -1;
+ }
+
+ return (n > 0);
+}
+
+static int
+go_clean(board_t board, int mark[][BRDSIZ], int x, int y, int color)
+{
+ const static int diff[][2] = {
+ {1, 0}, {-1, 0}, {0, 1}, {0, -1}
+ };
+ int i;
+ int total = 1;
+
+ mark[x][y] = 0;
+ board[x][y] = BBLANK;
+
+ for (i = 0; i < 4; ++i) {
+ int xx = x + diff[i][0];
+ int yy = y + diff[i][1];
+
+ if (xx >= 0 && xx < BRDSIZ && yy >= 0 && yy < BRDSIZ) {
+ if ((board[xx][yy] == color) && mark[xx][yy])
+ total += go_clean(board, mark, xx, yy, color);
+ }
+ }
+
+ return total;
+}
+
+static int
+go_cleandead(board_t board, int x, int y)
+{
+ int mark[BRDSIZ][BRDSIZ];
+ int i, j;
+
+ if (board[x][y] == BBLANK)
+ return 0;
+
+ for (i = 0; i < BRDSIZ; i++)
+ for (j = 0; j < BRDSIZ; j++)
+ mark[i][j] = 1;
+
+ return go_clean(board, mark, x, y, board[x][y]);
+}
+
+static int
+go_findcolor(board_p board, int x, int y)
+{
+ int k, result = 0, color[4];
+
+ if (board[x][y] != BBLANK)
+ return BBLANK;
+
+ if (x > 0)
+ {
+ k = x;
+ do --k;
+ while ((board[k][y] == BBLANK) && (k > 0));
+ color[0] = board[k][y];
+ }
+ else
+ color[0] = board[x][y];
+
+ if (x < 18)
+ {
+ k = x;
+ do ++k;
+ while ((board[k][y] == BBLANK) && (k < 18));
+ color[1] = board[k][y];
+ }
+ else
+ color[1] = board[x][y];
+
+ if (y > 0)
+ {
+ k = y;
+ do --k;
+ while ((board[x][k] == BBLANK) && (k > 0));
+ color[2] = board[x][k];
+ }
+ else color[2] = board[x][y];
+
+ if (y < 18)
+ {
+ k = y;
+ do ++k;
+ while ((board[x][k] == BBLANK) && (k < 18));
+ color[3] = board[x][k];
+ }
+ else
+ color[3] = board[x][y];
+
+ for (k = 0; k < 4; k++)
+ {
+ if (color[k] == BBLANK)
+ continue;
+ else
+ {
+ result = color[k];
+ break;
+ }
+ }
+ if (k == 4)
+ return BBLANK;
+
+ for (k = 0; k < 4; k++)
+ {
+ if ((color[k] != BBLANK) && (color[k] != result))
+ return BBLANK;
+ }
+
+ return result;
+}
+
+static int
+go_result(ChessInfo* info)
+{
+ int i, j;
+ int count[2];
+ board_p board = (board_p) info->board;
+ go_tag_t *tag = (go_tag_t*) info->tag;
+ board_t result_board;
+
+ memcpy(result_board, board, sizeof(result_board));
+ count[0] = count[1] = 0;
+
+ for (i = 0; i < 19; i++)
+ for (j = 0; j < 19; j++)
+ if (board[i][j] == BBLANK)
+ {
+ int result = go_findcolor(board, i, j);
+ if (result != BBLANK) {
+ count[result]++;
+
+ /* BWHITE => LWHITE, BBLACK => LBLACK */
+ result_board[i][j] = result + 2;
+ }
+ }
+ else
+ count[(int) board[i][j]]++;
+
+ memcpy(board, result_board, sizeof(result_board));
+
+ /* 死子回填 */
+ count[0] -= tag->eaten[1];
+ count[1] -= tag->eaten[0];
+
+ tag->eaten[0] = count[0];
+ tag->eaten[1] = count[1];
+
+ if (tag->feed_back < 0.01 && tag->eaten[0] == tag->eaten[1])
+ return BBLANK; /* tie */
+ else
+ return tag->eaten[0] + tag->feed_back > tag->eaten[1] ?
+ BWHITE : BBLACK;
+}
+
+static char*
+go_getstep(const go_step_t* step, char buf[])
+{
+ const static char* const ColName = "ABCDEFGHJKLMNOPQRST";
+ const static char* const RawName = "19181716151413121110987654321";
+ const static int ansi_length = sizeof(ANSI_COLOR(30;43)) - 1;
+
+ strcpy(buf, turn_color[step->color]);
+ buf[ansi_length ] = ColName[step->loc.c * 2];
+ buf[ansi_length + 1] = ColName[step->loc.c * 2 + 1];
+ buf[ansi_length + 2] = RawName[step->loc.r * 2];
+ buf[ansi_length + 3] = RawName[step->loc.r * 2 + 1];
+ strcpy(buf + ansi_length + 4, ANSI_RESET " ");
+
+ return buf;
+}
+
+static void
+go_init_tag(go_tag_t* tag)
+{
+ tag->game_end = 0;
+ tag->need_redraw = 0;
+ tag->feed_back = 5.5;
+ tag->eaten[0] = 0;
+ tag->eaten[1] = 0;
+ tag->forbidden[0].r = -1;
+ tag->forbidden[0].c = -1;
+ tag->forbidden[1].r = -1;
+ tag->forbidden[1].c = -1;
+}
+
+static void
+go_init_user(const userinfo_t* uinfo, ChessUser* user)
+{
+ strlcpy(user->userid, uinfo->userid, sizeof(user->userid));
+ user->win = uinfo->go_win;
+ user->lose = uinfo->go_lose;
+ user->tie = uinfo->go_tie;
+}
+
+static void
+go_init_user_userec(const userec_t* urec, ChessUser* user)
+{
+ strlcpy(user->userid, urec->userid, sizeof(user->userid));
+ user->win = urec->go_win;
+ user->lose = urec->go_lose;
+ user->tie = urec->go_tie;
+}
+
+static void
+go_init_board(board_t board)
+{
+ memset(board, BBLANK, sizeof(board_t));
+}
+
+static void
+go_drawline(const ChessInfo* info, int line)
+{
+ const static char* const BoardPic[] = {
+ "┌", "┬", "┐",
+ "├", "┼", "┤",
+ "└", "┴", "┘"
+ };
+ const static int BoardPicIndex[] =
+ { 0, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1,
+ 1, 1, 1, 2 };
+
+ board_p board = (board_p) info->board;
+ go_tag_t* tag = (go_tag_t*) info->tag;
+
+ if (line == 0) {
+ prints(ANSI_COLOR(1;46) " 圍棋對戰 " ANSI_COLOR(45)
+ "%30s VS %-20s%10s" ANSI_RESET,
+ info->user1.userid, info->user2.userid,
+ info->mode == CHESS_MODE_WATCH ? "[觀棋模式]" : "");
+ } else if (line == 1) {
+ outs(" A B C D E F G H J K L M N O P Q R S T");
+ } else if (line >= 2 && line <= 20) {
+ const int board_line = line - 2;
+ const char* const* const pics =
+ board_line == 0 ? &BoardPic[0] :
+ board_line == BRDSIZ - 1 ? &BoardPic[6] : &BoardPic[3];
+ int i;
+
+ prints("%2d" ANSI_COLOR(30;43), 21 - line);
+
+ for (i = 0; i < BRDSIZ; ++i)
+ if (board[board_line][i] == BBLANK)
+ outs(pics[BoardPicIndex[i]]);
+ else
+ outs(bw_chess[(int) board[board_line][i]]);
+
+ outs(ANSI_RESET);
+ } else if (line >= 21 && line < b_lines)
+ prints("%40s", "");
+ else if (line == b_lines) {
+ if (info->mode == CHESS_MODE_VERSUS ||
+ info->mode == CHESS_MODE_PERSONAL) {
+ if (tag->game_end)
+ outs(ANSI_COLOR(31;47) "(w)" ANSI_COLOR(30) "計地" ANSI_RESET);
+ else if (info->history.used == 0 && (info->myturn == BWHITE
+ || info->mode == CHESS_MODE_PERSONAL))
+ outs(ANSI_COLOR(31;47) "(x)" ANSI_COLOR(30) "授子" ANSI_RESET);
+ }
+ }
+
+ if (line == 1 || line == 2) {
+ int color = line - 1; /* BWHITE or BBLACK */
+
+ if (tag->game_end && tag->clean_end == 3)
+ prints(" " ANSI_COLOR(30;43) "%s" ANSI_RESET
+ " 方子空:%3.1f", bw_chess[color],
+ tag->eaten[color] +
+ (color == BWHITE ? tag->feed_back : 0.0));
+ else
+ prints(" " ANSI_COLOR(30;43) "%s" ANSI_RESET
+ " 方提子數:%3d", bw_chess[color], tag->eaten[color]);
+ } else
+ ChessDrawExtraInfo(info, line, 3);
+}
+
+static void
+go_movecur(int r, int c)
+{
+ move(r + 2, c * 2 + 3);
+}
+
+static int
+go_prepare_play(ChessInfo* info)
+{
+ if (((go_tag_t*) info->tag)->game_end) {
+ strlcpy(info->warnmsg, "請清除死子,以便計算勝負",
+ sizeof(info->warnmsg));
+ if (info->last_movestr[0] != ' ')
+ strcpy(info->last_movestr, " ");
+ }
+
+ if (info->history.used == 1)
+ ChessDrawLine(info, b_lines); /* clear the 'x' instruction */
+
+ return 0;
+}
+
+static int
+go_process_key(ChessInfo* info, int key, ChessGameResult* result)
+{
+ go_tag_t* tag = (go_tag_t*) info->tag;
+ if (tag->game_end) {
+ if (key == 'w') {
+ if (!(tag->clean_end & 1)) {
+ go_step_t step = { CHESS_STEP_SPECIAL, CLEANDONE };
+ ChessStepSend(info, &step);
+ tag->clean_end |= 1;
+ }
+
+ if (tag->clean_end & 2 || info->mode == CHESS_MODE_PERSONAL) {
+ /* both sides agree */
+ int winner = go_result(info);
+
+ tag->clean_end = 3;
+
+ if (winner == BBLANK)
+ *result = CHESS_RESULT_TIE;
+ else
+ *result = (winner == info->myturn ?
+ CHESS_RESULT_WIN : CHESS_RESULT_LOST);
+
+ ChessRedraw(info);
+ return 1;
+ }
+ } else if (key == 'u') {
+ char buf[4];
+ getdata(b_lines, 0, "是否真的要重新點死子? (y/N)",
+ buf, sizeof(buf), DOECHO);
+ ChessDrawLine(info, b_lines);
+
+ if (buf[0] == 'y' || buf[0] == 'Y') {
+ go_step_t step = { CHESS_STEP_SPECIAL, UNCLEAN };
+ ChessStepSend(info, &step);
+
+ memcpy(info->board, tag->backup_board, sizeof(tag->backup_board));
+ tag->eaten[0] = tag->backup_eaten[0];
+ tag->eaten[1] = tag->backup_eaten[1];
+ }
+ }
+ } else if (key == 'x' && info->history.used == 0 &&
+ ((info->mode == CHESS_MODE_VERSUS && info->myturn == BWHITE) ||
+ info->mode == CHESS_MODE_PERSONAL)) {
+ char buf[4];
+ int n;
+
+ getdata(22, 43, "要授多少子呢(2 - 9)? ", buf, sizeof(buf), DOECHO);
+ n = atoi(buf);
+
+ if (n >= 2 && n <= 9) {
+ go_step_t step = { CHESS_STEP_NORMAL, SETHAND, {n, 0} };
+
+ ChessStepSend(info, &step);
+ ChessHistoryAppend(info, &step);
+
+ go_sethand(info->board, n);
+ ((go_tag_t*)info->tag)->feed_back = 0.0;
+
+ snprintf(info->last_movestr, sizeof(info->last_movestr),
+ ANSI_COLOR(1) "授 %d 子" ANSI_RESET, n);
+ ChessRedraw(info);
+ return 1;
+ } else
+ ChessDrawLine(info, 22);
+ }
+ return 0;
+}
+
+static int
+go_select(ChessInfo* info, rc_t location, ChessGameResult* result)
+{
+ board_p board = (board_p) info->board;
+
+ if (GET_TAG(info)->game_end) {
+ go_step_t step = { CHESS_STEP_SPECIAL, CLEAN, location };
+ if (board[location.r][location.c] == BBLANK)
+ return 0;
+
+ GET_TAG(info)->eaten[!board[location.r][location.c]] +=
+ go_cleandead(board, location.r, location.c);
+
+ ChessStepSend(info, &step);
+ ChessRedraw(info);
+ return 0; /* don't have to return from ChessPlayFuncMy() */
+ } else {
+ go_step_t step = { CHESS_STEP_NORMAL, info->turn, location };
+
+ if (board[location.r][location.c] != BBLANK)
+ return 0;
+
+ if (go_check(info, &step)) {
+ board[location.r][location.c] = info->turn;
+ ChessStepSend(info, &step);
+ ChessHistoryAppend(info, &step);
+
+ go_getstep(&step, info->last_movestr);
+ if (go_examboard(board, !info->myturn, info))
+ ChessRedraw(info);
+ else
+ ChessDrawLine(info, BOARD_LINE_ON_SCREEN(location.r));
+ return 1;
+ } else
+ return 0;
+ }
+}
+
+static void
+go_prepare_step(ChessInfo* info, const go_step_t* step)
+{
+ go_tag_t* tag = GET_TAG(info);
+ if (tag->game_end) {
+ /* some actions need tag so are done here */
+ if (step->color == CLEAN) {
+ board_p board = (board_p) info->board;
+ tag->eaten[!board[step->loc.r][step->loc.c]] +=
+ go_cleandead(board, step->loc.r, step->loc.c);
+ } else if (step->color == UNCLEAN) {
+ memcpy(info->board, tag->backup_board, sizeof(tag->backup_board));
+ tag->eaten[0] = tag->backup_eaten[0];
+ tag->eaten[1] = tag->backup_eaten[1];
+ } else if (step->color == CLEANDONE) {
+ if (tag->clean_end & 1) {
+ /* both sides agree */
+ int winner = go_result(info);
+
+ tag->clean_end = 3;
+
+ if (winner == BBLANK)
+ ((go_step_t*)step)->loc.r = (int) CHESS_RESULT_TIE;
+ else
+ ((go_step_t*)step)->loc.r = (int)
+ (winner == info->myturn ?
+ CHESS_RESULT_WIN : CHESS_RESULT_LOST);
+
+ ChessRedraw(info);
+ } else {
+ ((go_step_t*)step)->color = BBLANK; /* tricks apply */
+ tag->clean_end |= 2;
+ }
+ }
+ } else if (step->type == CHESS_STEP_NORMAL) {
+ if (step->color != SETHAND) {
+ go_getstep(step, info->last_movestr);
+
+ memcpy(tag->backup_board, info->board, sizeof(board_t));
+ tag->backup_board[step->loc.r][step->loc.c] = step->color;
+
+ /* if any chess was eaten, wholely redraw is needed */
+ tag->need_redraw =
+ go_examboard(tag->backup_board, !step->color, info);
+ } else {
+ snprintf(info->last_movestr, sizeof(info->last_movestr),
+ ANSI_COLOR(1) "授 %d 子" ANSI_RESET, step->loc.r);
+ tag->need_redraw = 1;
+ ((go_tag_t*)info->tag)->feed_back = 0.0;
+ }
+ } else if (step->type == CHESS_STEP_PASS)
+ strcpy(info->last_movestr, "虛手");
+}
+
+static ChessGameResult
+go_apply_step(board_t board, const go_step_t* step)
+{
+ if (step->type != CHESS_STEP_NORMAL)
+ return CHESS_RESULT_CONTINUE;
+
+ switch (step->color) {
+ case BWHITE:
+ case BBLACK:
+ board[step->loc.r][step->loc.c] = step->color;
+ go_examboard(board, !step->color, NULL);
+ break;
+
+ case SETHAND:
+ go_sethand(board, step->loc.r);
+ break;
+
+ case CLEAN:
+ go_cleandead(board, step->loc.r, step->loc.c);
+ break;
+
+ case CLEANDONE:
+ /* should be agreed by both sides, [see go_prepare_step()] */
+ return (ChessGameResult) step->loc.r;
+ }
+ return CHESS_RESULT_CONTINUE;
+}
+
+static void
+go_drawstep(ChessInfo* info, const go_step_t* step)
+{
+ go_tag_t* tag = GET_TAG(info);
+ if (tag->game_end || tag->need_redraw)
+ ChessRedraw(info);
+ else
+ ChessDrawLine(info, BOARD_LINE_ON_SCREEN(step->loc.r));
+}
+
+static ChessGameResult
+go_post_game(ChessInfo* info)
+{
+ extern ChessGameResult ChessPlayFuncMy(ChessInfo* info);
+
+ go_tag_t *tag = (go_tag_t*) info->tag;
+ ChessTimeLimit *orig_limit = info->timelimit;
+ ChessGameResult result;
+
+ info->timelimit = NULL;
+ info->turn = info->myturn;
+ strcpy(info->warnmsg, "請點除死子");
+ ChessDrawLine(info, CHESS_DRAWING_WARN_ROW);
+
+ memcpy(tag->backup_board, info->board, sizeof(tag->backup_board));
+ tag->game_end = 1;
+ tag->clean_end = 0;
+ tag->backup_eaten[0] = tag->eaten[0];
+ tag->backup_eaten[1] = tag->eaten[1];
+
+ ChessDrawLine(info, b_lines); /* 'w' instruction */
+
+ while ((result = ChessPlayFuncMy(info)) == CHESS_RESULT_CONTINUE);
+
+ ChessRedraw(info);
+
+ info->timelimit = orig_limit;
+
+ return result;
+}
+
+static void
+go_gameend(ChessInfo* info, ChessGameResult result)
+{
+ if (info->mode == CHESS_MODE_VERSUS) {
+ ChessUser* const user1 = &info->user1;
+ /* ChessUser* const user2 = &info->user2; */
+
+ user1->lose--;
+ if (result == CHESS_RESULT_WIN) {
+ user1->win++;
+ currutmp->go_win++;
+ } else if (result == CHESS_RESULT_LOST) {
+ user1->lose++;
+ currutmp->go_lose++;
+ } else {
+ user1->tie++;
+ currutmp->go_tie++;
+ }
+
+ cuser.go_win = user1->win;
+ cuser.go_lose = user1->lose;
+ cuser.go_tie = user1->tie;
+
+ passwd_update(usernum, &cuser);
+ } else if (info->mode == CHESS_MODE_REPLAY) {
+ free(info->board);
+ free(info->tag);
+ }
+}
+
+static void
+go_genlog(ChessInfo* info, FILE* fp, ChessGameResult result)
+{
+ const static char ColName[] = "ABCDEFGHJKLMNOPQRST";
+ const int nStep = info->history.used;
+ int i;
+ int sethand = 0;
+
+ if (nStep > 0) {
+ const go_step_t* const step =
+ (const go_step_t*) ChessHistoryRetrieve(info, 0);
+ if (step->color == SETHAND)
+ sethand = step->loc.r;
+ }
+
+ for (i = 1; i <= 22; i++)
+ fprintf(fp, "%.*s\n", big_picture[i].len, big_picture[i].data);
+
+ fprintf(fp, "\n");
+ fprintf(fp, "按 z 可進入打譜模式\n");
+ fprintf(fp, "\n");
+
+ if (sethand) {
+ fprintf(fp, "[ 1] 授 %d 子\n ", sethand);
+ i = 1;
+ } else
+ i = 0;
+
+ for (; i < nStep; ++i) {
+ const go_step_t* const step =
+ (const go_step_t*) ChessHistoryRetrieve(info, i);
+ if (step->type == CHESS_STEP_NORMAL)
+ fprintf(fp, "[%3d]%s => %c%-4d", i + 1, bw_chess[step->color],
+ ColName[step->loc.c], 19 - step->loc.r);
+ else if (step->type == CHESS_STEP_PASS)
+ fprintf(fp, "[%3d]%s => 虛手 ", i + 1, bw_chess[(i + 1) % 2]);
+ else
+ break;
+ if (i % 5 == 4)
+ fputc('\n', fp);
+ }
+
+ fprintf(fp,
+ "\n\n《以下為 sgf 格式棋譜》\n<golog>\n(;GM[1]"
+ "GN[%s-%s(W) Ptt]\n"
+ "SZ[19]HA[%d]PB[%s]PW[%s]\n"
+ "PC[FPG BBS/Ptt BBS: ptt.cc]\n",
+ info->user1.userid, info->user2.userid,
+ sethand,
+ info->user1.userid, info->user2.userid);
+
+ if (sethand) {
+ const int lower = sethand * (sethand - 1) / 2;
+ const int upper = lower + sethand;
+ int j;
+ fputs("AB", fp);
+ for (j = lower; j < upper; ++j)
+ fprintf(fp, "[%c%c]",
+ SetHandPoints[j].c + 'a',
+ SetHandPoints[j].r + 'a');
+ fputc('\n', fp);
+ }
+
+ for (i = (sethand ? 1 : 0); i < nStep; ++i) {
+ const go_step_t* const step =
+ (const go_step_t*) ChessHistoryRetrieve(info, i);
+ if (step->type == CHESS_STEP_NORMAL)
+ fprintf(fp, ";%c[%c%c]",
+ step->color == BWHITE ? 'W' : 'B',
+ step->loc.c + 'a',
+ step->loc.r + 'a');
+ else if (step->type == CHESS_STEP_PASS)
+ fprintf(fp, ";%c[] ", i % 2 ? 'W' : 'B');
+ else
+ break;
+ if (i % 10 == 9)
+ fputc('\n', fp);
+ }
+ fprintf(fp, ";)\n<golog>\n\n");
+}
+
+void
+gochess(int s, ChessGameMode mode)
+{
+ ChessInfo* info = NewChessInfo(&go_actions, &go_constants, s, mode);
+ board_t board;
+ go_tag_t tag;
+
+ go_init_board(board);
+ go_init_tag(&tag);
+
+ info->board = board;
+ info->tag = &tag;
+
+ info->cursor.r = 9;
+ info->cursor.c = 9;
+
+ if (mode == CHESS_MODE_WATCH)
+ setutmpmode(CHESSWATCHING);
+ else
+ setutmpmode(UMODE_GO);
+ currutmp->sig = SIG_GO;
+
+ ChessPlay(info);
+
+ DeleteChessInfo(info);
+}
+
+int
+gochess_main(void)
+{
+ return ChessStartGame('g', SIG_GO, "圍棋");
+}
+
+int
+gochess_personal(void)
+{
+ gochess(0, CHESS_MODE_PERSONAL);
+ return 0;
+}
+
+int
+gochess_watch(void)
+{
+ return ChessWatchGame(&gochess, UMODE_GO, "圍棋");
+}
+
+static int
+mygetc(FILE* fp, char* buf, int* idx, int len)
+{
+ for (;;) {
+ while (buf[*idx] && isspace(buf[*idx])) ++*idx;
+
+ if (buf[*idx]) {
+ ++*idx;
+ return buf[*idx - 1];
+ }
+
+ if (fgets(buf, len, fp) == NULL)
+ return EOF;
+
+ if (strcmp(buf, "<golog>\n") == 0)
+ return EOF;
+
+ *idx = 0;
+ }
+}
+
+ChessInfo*
+gochess_replay(FILE* fp)
+{
+ ChessInfo *info;
+ int ch;
+ char userid[2][IDLEN + 1] = { "", "" };
+ char sethand_str[4] = "";
+ char *recording = NULL;
+ char *record_end = NULL;
+ go_step_t step;
+
+ /* for mygetc */
+ char buf[512] = "";
+ int idx = 0;
+
+#define GETC() mygetc(fp, buf, &idx, sizeof(buf))
+
+ /* sgf file started with "(;" */
+ if (GETC() != '(' || GETC() != ';')
+ return NULL;
+
+ /* header info */
+ while ((ch = GETC()) != EOF && ch != ';') {
+ if (ch == '[') {
+ if (recording) {
+ while ((ch = GETC()) != EOF && ch != ']')
+ if (recording < record_end)
+ *recording++ = ch;
+ *recording = 0;
+ recording = NULL;
+ } else
+ while ((ch = GETC()) != EOF && ch != ']')
+ continue;
+
+ if (ch == EOF)
+ break;
+ } else if (ch == ';') /* next stage */
+ break;
+ else {
+ int ch2 = GETC();
+
+ if (ch2 == EOF) {
+ ch = EOF;
+ break;
+ }
+
+ if (ch == 'P') {
+ if (ch2 == 'B') {
+ recording = userid[BBLACK];
+ record_end = userid[BBLACK] + IDLEN;
+ } else if (ch2 == 'W') {
+ recording = userid[BWHITE];
+ record_end = userid[BWHITE] + IDLEN;
+ }
+ } else if (ch == 'H') {
+ if (ch2 == 'A') {
+ recording = sethand_str;
+ record_end = sethand_str + sizeof(sethand_str) - 1;
+ }
+ }
+ }
+ }
+
+ if (ch == EOF)
+ return NULL;
+
+ info = NewChessInfo(&go_actions, &go_constants,
+ 0, CHESS_MODE_REPLAY);
+
+ /* filling header information to info */
+ if (userid[BBLANK][0]) {
+ userec_t rec;
+ if (getuser(userid[BBLANK], &rec))
+ go_init_user_userec(&rec, &info->user1);
+ }
+
+ if (userid[BWHITE][0]) {
+ userec_t rec;
+ if (getuser(userid[BWHITE], &rec))
+ go_init_user_userec(&rec, &info->user2);
+ }
+
+ if (sethand_str[0]) {
+ int sethand = atoi(sethand_str);
+ if (sethand >= 2 && sethand <= 9) {
+ step.type = CHESS_STEP_NORMAL;
+ step.color = SETHAND;
+ step.loc.r = sethand;
+ ChessHistoryAppend(info, &step);
+ }
+ }
+
+ /* steps, ends with ")" */
+ while ((ch = GETC()) != EOF && ch != ')') {
+ if (ch == ';')
+ ChessHistoryAppend(info, &step);
+ else if (ch == 'B')
+ step.color = BBLACK;
+ else if (ch == 'W')
+ step.color = BWHITE;
+ else if (ch == '[') {
+ ch = GETC();
+ if (ch == EOF)
+ break;
+ else if (ch == ']') {
+ step.type = CHESS_STEP_PASS;
+ continue;
+ } else
+ step.loc.c = ch - 'a';
+
+ ch = GETC();
+ if (ch == EOF)
+ break;
+ else if (ch == ']') {
+ step.type = CHESS_STEP_PASS;
+ continue;
+ } else
+ step.loc.r = ch - 'a';
+
+ while ((ch = GETC()) != EOF && ch != ']');
+
+ if (step.loc.r < 0 || step.loc.r >= BRDSIZ ||
+ step.loc.c < 0 || step.loc.c >= BRDSIZ)
+ step.type = CHESS_STEP_PASS;
+ else
+ step.type = CHESS_STEP_NORMAL;
+ }
+ }
+
+ info->board = malloc(sizeof(board_t));
+ info->tag = malloc(sizeof(go_tag_t));
+
+ go_init_board(info->board);
+ go_init_tag(info->tag);
+
+ return info;
+
+#undef GETC
+}
diff --git a/pttbbs/mbbsd/gomo.c b/pttbbs/mbbsd/gomo.c
new file mode 100644
index 00000000..e3664176
--- /dev/null
+++ b/pttbbs/mbbsd/gomo.c
@@ -0,0 +1,583 @@
+/* $Id$ */
+#include "bbs.h"
+#include "gomo.h"
+
+#define QCAST int (*)(const void *, const void *)
+#define BOARD_LINE_ON_SCREEN(X) ((X) + 2)
+
+static const char* turn_color[] = { ANSI_COLOR(37;43), ANSI_COLOR(30;43) };
+
+enum Turn {
+ WHT = 0,
+ BLK
+};
+
+typedef struct {
+ ChessStepType type; /* necessary one */
+ int color;
+ rc_t loc;
+} gomo_step_t;
+
+typedef char board_t[BRDSIZ][BRDSIZ];
+typedef char (*board_p)[BRDSIZ];
+
+static void gomo_init_user(const userinfo_t* uinfo, ChessUser* user);
+static void gomo_init_user_userec(const userec_t* urec, ChessUser* user);
+static void gomo_init_board(board_t board);
+static void gomo_drawline(const ChessInfo* info, int line);
+static void gomo_movecur(int r, int c);
+static int gomo_prepare_play(ChessInfo* info);
+static int gomo_select(ChessInfo* info, rc_t location,
+ ChessGameResult* result);
+static void gomo_prepare_step(ChessInfo* info, const gomo_step_t* step);
+static ChessGameResult gomo_apply_step(board_t board, const gomo_step_t* step);
+static void gomo_drawstep(ChessInfo* info, const gomo_step_t* step);
+static void gomo_gameend(ChessInfo* info, ChessGameResult result);
+static void gomo_genlog(ChessInfo* info, FILE* fp, ChessGameResult result);
+
+const static ChessActions gomo_actions = {
+ &gomo_init_user,
+ &gomo_init_user_userec,
+ (void (*)(void*)) &gomo_init_board,
+ &gomo_drawline,
+ &gomo_movecur,
+ &gomo_prepare_play,
+ NULL, /* process_key */
+ &gomo_select,
+ (void (*)(ChessInfo*, const void*)) &gomo_prepare_step,
+ (ChessGameResult (*)(void*, const void*)) &gomo_apply_step,
+ (void (*)(ChessInfo*, const void*)) &gomo_drawstep,
+ NULL, /* post_game */
+ &gomo_gameend,
+ &gomo_genlog
+};
+
+const static ChessConstants gomo_constants = {
+ sizeof(gomo_step_t),
+ MAX_TIME,
+ BRDSIZ,
+ BRDSIZ,
+ 0,
+ "五子棋",
+ "photo_fivechess",
+#ifdef GLOBAL_FIVECHESS_LOG
+ GLOBAL_FIVECHESS_LOG,
+#else
+ NULL,
+#endif
+ { ANSI_COLOR(37;43), ANSI_COLOR(30;43) },
+ { "白棋", "黑棋, 有禁手" },
+};
+
+/* pattern and advance map */
+
+static int
+intrevcmp(const void *a, const void *b)
+{
+ return (*(int *)b - *(int *)a);
+}
+
+// 以 (x,y) 為起點, 方向 (dx,dy), 傳回以 bit 表示相鄰哪幾格有子
+// 如 10111 表示該方向相鄰 1,2,3 有子, 4 空地
+// 最高位 1 表示對方的子, 或是牆
+/* x,y: 0..BRDSIZ-1 ; color: CBLACK,CWHITE ; dx,dy: -1,0,+1 */
+static int
+gomo_getindex(board_t ku, int x, int y, int color, int dx, int dy)
+{
+ int i, k, n;
+ for (n = -1, i = 0, k = 1; i < 5; i++, k*=2) {
+ x += dx;
+ y += dy;
+
+ if ((x < 0) || (x >= BRDSIZ) || (y < 0) || (y >= BRDSIZ)) {
+ n += k;
+ break;
+ } else if (ku[x][y] != BBLANK) {
+ n += k;
+ if (ku[x][y] != color)
+ break;
+ }
+ }
+
+ if (i >= 5)
+ n += k;
+
+ return n;
+}
+
+ChessGameResult
+chkwin(int style, int limit)
+{
+ if (style == 0x0c)
+ return CHESS_RESULT_WIN;
+ else if (limit == 0) {
+ if (style == 0x0b)
+ return CHESS_RESULT_WIN;
+ return CHESS_RESULT_CONTINUE;
+ }
+ if ((style < 0x0c) && (style > 0x07))
+ return CHESS_RESULT_LOST;
+ return CHESS_RESULT_CONTINUE;
+}
+
+static int getstyle(board_t ku, int x, int y, int color, int limit);
+/* x,y: 0..BRDSIZ-1 ; color: CBLACK,CWHITE ; limit:1,0 ; dx,dy: 0,1 */
+static int
+dirchk(board_t ku, int x, int y, int color, int limit, int dx, int dy)
+{
+ int le, ri, loc, style = 0;
+
+ le = gomo_getindex(ku, x, y, color, -dx, -dy);
+ ri = gomo_getindex(ku, x, y, color, dx, dy);
+
+ loc = (le > ri) ? (((le * (le + 1)) >> 1) + ri) :
+ (((ri * (ri + 1)) >> 1) + le);
+
+ style = pat_gomoku[loc];
+
+ if (limit == 0)
+ return (style & 0x0f);
+
+ style >>= 4;
+
+ if ((style == 3) || (style == 2)) {
+ int i, n = 0, tmp, nx, ny;
+
+ n = adv_gomoku[loc / 2];
+
+ if(loc%2==0)
+ n/=16;
+ else
+ n%=16;
+
+ ku[x][y] = color;
+
+ for (i = 0; i < 2; i++) {
+ if ((tmp = (i == 0) ? (-(n >> 2)) : (n & 3)) != 0) {
+ nx = x + (le > ri ? 1 : -1) * tmp * dx;
+ ny = y + (le > ri ? 1 : -1) * tmp * dy;
+
+ if ((dirchk(ku, nx, ny, color, 0, dx, dy) == 0x06) &&
+ (chkwin(getstyle(ku, nx, ny, color, limit), limit) >= 0))
+ break;
+ }
+ }
+ if (i >= 2)
+ style = 0;
+ ku[x][y] = BBLANK;
+ }
+ return style;
+}
+
+/* 例外=F 錯誤=E 有子=D 連五=C 連六=B 雙四=A 四四=9 三三=8 */
+/* 四三=7 活四=6 斷四=5 死四=4 活三=3 斷三=2 保留=1 無效=0 */
+
+/* x,y: 0..BRDSIZ-1 ; color: CBLACK,CWHITE ; limit: 1,0 */
+static int
+getstyle(board_t ku, int x, int y, int color, int limit)
+{
+ int i, j, dir[4], style;
+
+ if ((x < 0) || (x >= BRDSIZ) || (y < 0) || (y >= BRDSIZ))
+ return 0x0f;
+ if (ku[x][y] != BBLANK)
+ return 0x0d;
+
+ // (-1,1), (0,1), (1,0), (1,1)
+ for (i = 0; i < 4; i++)
+ dir[i] = dirchk(ku, x, y, color, limit, i ? (i >> 1) : -1, i ? (i & 1) : 1);
+
+ qsort(dir, 4, sizeof(int), (QCAST)intrevcmp);
+
+ if ((style = dir[0]) >= 2) {
+ for (i = 1, j = 6 + (limit ? 1 : 0); i < 4; i++) {
+ if ((style > j) || (dir[i] < 2))
+ break;
+ if (dir[i] > 3)
+ style = 9;
+ else if ((style < 7) && (style > 3))
+ style = 7;
+ else
+ style = 8;
+ }
+ }
+ return style;
+}
+
+static char*
+gomo_move_warn(int style, char buf[])
+{
+ char *xtype[] = {
+ ANSI_COLOR(1;31) "跳三" ANSI_RESET,
+ ANSI_COLOR(1;31) "活三" ANSI_RESET,
+ ANSI_COLOR(1;31) "死四" ANSI_RESET,
+ ANSI_COLOR(1;31) "跳四" ANSI_RESET,
+ ANSI_COLOR(1;31) "活四" ANSI_RESET,
+ ANSI_COLOR(1;31) "四三" ANSI_RESET,
+ ANSI_COLOR(1;31) "雙三" ANSI_RESET,
+ ANSI_COLOR(1;31) "雙四" ANSI_RESET,
+ ANSI_COLOR(1;31) "雙四" ANSI_RESET,
+ ANSI_COLOR(1;31) "連六" ANSI_RESET,
+ ANSI_COLOR(1;31) "連五" ANSI_RESET
+ };
+ if (style > 1 && style < 13)
+ return strcpy(buf, xtype[style - 2]);
+ else
+ return NULL;
+}
+
+static void
+gomoku_usr_put(userec_t* userec, const ChessUser* user)
+{
+ userec->five_win = user->win;
+ userec->five_lose = user->lose;
+ userec->five_tie = user->tie;
+}
+
+static char*
+gomo_getstep(const gomo_step_t* step, char buf[])
+{
+ const static char* const ColName = "ABCDEFGHIJKLMN";
+ const static char* const RawName = "151413121110987654321";
+ const static int ansi_length = sizeof(ANSI_COLOR(30;43)) - 1;
+
+ strcpy(buf, turn_color[step->color]);
+ buf[ansi_length ] = ColName[step->loc.c * 2];
+ buf[ansi_length + 1] = ColName[step->loc.c * 2 + 1];
+ buf[ansi_length + 2] = RawName[step->loc.r * 2];
+ buf[ansi_length + 3] = RawName[step->loc.r * 2 + 1];
+ strcpy(buf + ansi_length + 4, ANSI_RESET);
+
+ return buf;
+}
+
+static void
+gomo_init_user(const userinfo_t* uinfo, ChessUser* user)
+{
+ strlcpy(user->userid, uinfo->userid, sizeof(user->userid));
+ user->win = uinfo->five_win;
+ user->lose = uinfo->five_lose;
+ user->tie = uinfo->five_tie;
+}
+
+static void
+gomo_init_user_userec(const userec_t* urec, ChessUser* user)
+{
+ strlcpy(user->userid, urec->userid, sizeof(user->userid));
+ user->win = urec->five_win;
+ user->lose = urec->five_lose;
+ user->tie = urec->five_tie;
+}
+
+static void
+gomo_init_board(board_t board)
+{
+ memset(board, BBLANK, sizeof(board_t));
+}
+
+static void
+gomo_drawline(const ChessInfo* info, int line)
+{
+ const static char* const BoardPic[] = {
+ "┌", "┬", "┐",
+ "├", "┼", "┤",
+ "└", "┴", "┘"
+ };
+ const static int BoardPicIndex[] =
+ { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2 };
+
+ board_p board = (board_p) info->board;
+
+ if (line == 0) {
+ prints(ANSI_COLOR(1;46) " 五子棋對戰 " ANSI_COLOR(45)
+ "%30s VS %-20s%10s" ANSI_RESET,
+ info->user1.userid, info->user2.userid,
+ info->mode == CHESS_MODE_WATCH ? "[觀棋模式]" : "");
+ } else if (line == 1) {
+ outs(" A B C D E F G H I J K L M N");
+ } else if (line >= 2 && line <= 16) {
+ const int board_line = line - 2;
+ const char* const* const pics =
+ board_line == 0 ? &BoardPic[0] :
+ board_line == BRDSIZ - 1 ? &BoardPic[6] : &BoardPic[3];
+ int i;
+
+ prints("%3d" ANSI_COLOR(30;43), 17 - line);
+
+ for (i = 0; i < BRDSIZ; ++i)
+ if (board[board_line][i] == -1)
+ outs(pics[BoardPicIndex[i]]);
+ else
+ outs(bw_chess[(int) board[board_line][i]]);
+
+ outs(ANSI_RESET);
+ } else if (line >= 17 && line < b_lines)
+ prints("%33s", "");
+
+ ChessDrawExtraInfo(info, line, 8);
+}
+
+static void
+gomo_movecur(int r, int c)
+{
+ move(r + 2, c * 2 + 3);
+}
+
+static int
+gomo_prepare_play(ChessInfo* info)
+{
+ if (!gomo_move_warn(*(int*) info->tag, info->warnmsg))
+ info->warnmsg[0] = 0;
+
+ return 0;
+}
+
+static int
+gomo_select(ChessInfo* info, rc_t location, ChessGameResult* result)
+{
+ board_p board = (board_p) info->board;
+ gomo_step_t step;
+
+ if(board[location.r][location.c] != BBLANK)
+ return 0;
+
+ *(int*) info->tag = getstyle(board, location.r, location.c,
+ info->turn, info->turn == BLK);
+ *result = chkwin(*(int*) info->tag, info->turn == BLK);
+
+ board[location.r][location.c] = info->turn;
+
+ step.type = CHESS_STEP_NORMAL;
+ step.color = info->turn;
+ step.loc = location;
+ gomo_getstep(&step, info->last_movestr);
+
+ ChessHistoryAppend(info, &step);
+ ChessStepSend(info, &step);
+ gomo_drawstep(info, &step);
+
+ return 1;
+}
+
+static void
+gomo_prepare_step(ChessInfo* info, const gomo_step_t* step)
+{
+ if (step->type == CHESS_STEP_NORMAL) {
+ gomo_getstep(step, info->last_movestr);
+ *(int*) info->tag = getstyle(info->board, step->loc.r, step->loc.c,
+ step->color, step->color == BLK);
+ info->cursor = step->loc;
+ }
+}
+
+static ChessGameResult
+gomo_apply_step(board_t board, const gomo_step_t* step)
+{
+ int style;
+
+ style = getstyle(board, step->loc.r, step->loc.c,
+ step->color, step->color == BLK);
+ board[step->loc.r][step->loc.c] = step->color;
+ return chkwin(style, step->color == BLK);
+}
+
+static void
+gomo_drawstep(ChessInfo* info, const gomo_step_t* step)
+{
+ ChessDrawLine(info, BOARD_LINE_ON_SCREEN(step->loc.r));
+}
+
+static void
+gomo_gameend(ChessInfo* info, ChessGameResult result)
+{
+ if (info->mode == CHESS_MODE_VERSUS) {
+ ChessUser* const user1 = &info->user1;
+ /* ChessUser* const user2 = &info->user2; */
+
+ user1->lose--;
+ if (result == CHESS_RESULT_WIN) {
+ user1->win++;
+ currutmp->five_win++;
+ } else if (result == CHESS_RESULT_LOST) {
+ user1->lose++;
+ currutmp->five_lose++;
+ } else {
+ user1->tie++;
+ currutmp->five_tie++;
+ }
+
+ cuser.five_win = user1->win;
+ cuser.five_lose = user1->lose;
+ cuser.five_tie = user1->tie;
+
+ passwd_update(usernum, &cuser);
+ } else if (info->mode == CHESS_MODE_REPLAY) {
+ free(info->board);
+ free(info->tag);
+ }
+}
+
+static void
+gomo_genlog(ChessInfo* info, FILE* fp, ChessGameResult result)
+{
+ const int nStep = info->history.used;
+ int i;
+
+ for (i = 1; i <= 18; i++)
+ fprintf(fp, "%.*s\n", big_picture[i].len, big_picture[i].data);
+
+ fprintf(fp, "\n");
+ fprintf(fp, "按 z 可進入打譜模式\n");
+ fprintf(fp, "\n");
+
+ fprintf(fp, "<gomokulog>\nblack:%s\nwhite:%s\n",
+ info->myturn ? info->user1.userid : info->user2.userid,
+ info->myturn ? info->user2.userid : info->user1.userid);
+
+ for (i = 0; i < nStep; ++i) {
+ const gomo_step_t* const step =
+ (const gomo_step_t*) ChessHistoryRetrieve(info, i);
+ if (step->type == CHESS_STEP_NORMAL)
+ fprintf(fp, "[%2d]%s ==> %c%-5d", i + 1, bw_chess[step->color],
+ 'A' + step->loc.c, 15 - step->loc.r);
+ else
+ break;
+ if (i % 2)
+ fputc('\n', fp);
+ }
+
+ if (i % 2)
+ fputc('\n', fp);
+ fputs("</gomokulog>\n", fp);
+}
+
+static int gomo_loadlog(FILE *fp, ChessInfo *info)
+{
+ char buf[256];
+
+#define INVALID_ROW(R) ((R) < 0 || (R) >= BRDSIZ)
+#define INVALID_COL(C) ((C) < 0 || (C) >= BRDSIZ)
+ while (fgets(buf, sizeof(buf), fp)) {
+ if (strcmp("</gomokulog>\n", buf) == 0)
+ return 1;
+ else if (strncmp("black:", buf, 6) == 0 ||
+ strncmp("white:", buf, 6) == 0) {
+ /* /(black|white):([a-zA-Z0-9]+)/; $2 */
+ userec_t rec;
+ ChessUser *user = (buf[0] == 'b' ? &info->user1 : &info->user2);
+
+ chomp(buf);
+ if (getuser(buf + 6, &rec))
+ gomo_init_user_userec(&rec, user);
+ } else if (buf[0] == '[') {
+ /* "[ 1]● ==> H8 [ 2]○ ==> H9" */
+ gomo_step_t step = { CHESS_STEP_NORMAL };
+ int c, r;
+ const char *p = buf;
+ int i;
+
+ for(i=0; i<2; i++) {
+ p = strchr(p, '>');
+
+ if (p == NULL) break;
+
+ ++p; /* skip '>' */
+ while (*p && isspace(*p)) ++p;
+ if (!*p) break;
+
+ /* i=0, p -> "H8 ..." */
+ /* i=1, p -> "H9\n" */
+ c = p[0] - 'A';
+ r = BRDSIZ - atoi(p + 1);
+
+ if (INVALID_COL(c) || INVALID_ROW(r))
+ break;
+
+ step.color = i==0? BLK : WHT;
+ step.loc.r = r;
+ step.loc.c = c;
+ ChessHistoryAppend(info, &step);
+ }
+ }
+ }
+#undef INVALID_ROW
+#undef INVALID_COL
+ return 0;
+}
+
+
+void
+gomoku(int s, ChessGameMode mode)
+{
+ ChessInfo* info = NewChessInfo(&gomo_actions, &gomo_constants, s, mode);
+ board_t board;
+ int tag;
+
+ gomo_init_board(board);
+ tag = 0;
+
+ info->board = board;
+ info->tag = &tag;
+
+ info->cursor.r = 7;
+ info->cursor.c = 7;
+
+ if (info->mode == CHESS_MODE_VERSUS) {
+ /* Assume that info->user1 is me. */
+ info->user1.lose++;
+ passwd_query(usernum, &cuser);
+ gomoku_usr_put(&cuser, &info->user1);
+ passwd_update(usernum, &cuser);
+ }
+
+ if (mode == CHESS_MODE_WATCH)
+ setutmpmode(CHESSWATCHING);
+ else
+ setutmpmode(M_FIVE);
+ currutmp->sig = SIG_GOMO;
+
+ ChessPlay(info);
+
+ DeleteChessInfo(info);
+}
+
+int
+gomoku_main(void)
+{
+ return ChessStartGame('f', SIG_GOMO, "五子棋");
+}
+
+int
+gomoku_personal(void)
+{
+ gomoku(0, CHESS_MODE_PERSONAL);
+ return 0;
+}
+
+int
+gomoku_watch(void)
+{
+ return ChessWatchGame(&gomoku, M_FIVE, "五子棋");
+}
+
+ChessInfo*
+gomoku_replay(FILE* fp)
+{
+ ChessInfo *info;
+
+ info = NewChessInfo(&gomo_actions, &gomo_constants,
+ 0, CHESS_MODE_REPLAY);
+
+ if(!gomo_loadlog(fp, info)) {
+ DeleteChessInfo(info);
+ return NULL;
+ }
+
+ info->board = malloc(sizeof(board_t));
+ info->tag = malloc(sizeof(int));
+
+ gomo_init_board(info->board);
+ *(int*)(info->tag) = 0;
+
+ return info;
+}
diff --git a/pttbbs/mbbsd/guess.c b/pttbbs/mbbsd/guess.c
new file mode 100644
index 00000000..766cbd16
--- /dev/null
+++ b/pttbbs/mbbsd/guess.c
@@ -0,0 +1,369 @@
+/* $Id$ */
+#include "bbs.h"
+#define LOGPASS BBSHOME "/etc/winguess.log"
+
+static void
+show_table(char TABLE[], char ifcomputer)
+{
+ int i;
+
+ move(0, 35);
+ outs(ANSI_COLOR(1;44;33) " 【 猜數字 】 " ANSI_RESET);
+ move(8, 1);
+ outs(ANSI_COLOR(1;44;36) "目 前 倍 率" ANSI_RESET "\n");
+ outs(ANSI_COLOR(1;33) "=================" ANSI_RESET "\n");
+ if (ifcomputer) {
+ outs("贏電腦: 2 倍\n");
+ outs("輸電腦: 0 倍\n");
+ } else {
+ for (i = 1; i <= 6; i++)
+ prints("第%d次, %02d倍\n", i, TABLE[i]);
+ }
+ outs(ANSI_COLOR(33) "=================" ANSI_RESET);
+}
+
+static int
+get_money(void)
+{
+ int money, i;
+ char data[20];
+
+ move(1, 0);
+ prints("您目前有:%d Ptt$", cuser.money);
+ do {
+ getdata(2, 0, "要賭多少(5-10或按q離開): ", data, 9, LCECHO);
+ money = 0;
+ if (data[0] == 'q' || data[0] == 'Q') {
+ unlockutmpmode();
+ return 0;
+ }
+ for (i = 0; data[i]; i++)
+ if (data[i] < '0' || data[i] > '9') {
+ money = -1;
+ break;
+ }
+ if (money != -1) {
+ money = atoi(data);
+ reload_money();
+ if (money > cuser.money || money <= 4 || money > 10 ||
+ money < 1)
+ money = -1;
+ }
+ } while (money == -1);
+ move(1, 0);
+ clrtoeol();
+ reload_money();
+ prints("您目前有:%d Ptt$", cuser.money - money);
+ return money;
+}
+
+static int
+check_data(const char *str)
+{
+ int i, j;
+
+ if (strlen(str) != 4)
+ return -1;
+ for (i = 0; i < 4; i++)
+ if (str[i] < '0' || str[i] > '9')
+ return -1;
+ for (i = 0; i < 4; i++)
+ for (j = i + 1; j < 4; j++)
+ if (str[i] == str[j])
+ return -1;
+ return 1;
+}
+
+static char *
+get_data(char data[5], int count)
+{
+ while (1) {
+ getdata(6, 0, "輸入四位數字(不重複): ", data, 5, LCECHO);
+ if (check_data(data) == 1)
+ break;
+ }
+ return data;
+}
+
+static int
+guess_play(const char *data, const char *answer, int count)
+{
+ int A_num = 0, B_num = 0;
+ int i, j;
+
+ for (i = 0; i < 4; i++) {
+ if (data[i] == answer[i])
+ A_num++;
+ for (j = 0; j < 4; j++)
+ if (i == j)
+ continue;
+ else if (data[i] == answer[j]) {
+ B_num++;
+ break;
+ }
+ }
+ if (A_num == 4)
+ return 1;
+ move(count + 8, 55);
+ prints("%s => " ANSI_COLOR(1;32) "%dA %dB" ANSI_RESET, data, A_num, B_num);
+ return 0;
+}
+
+static int
+result(int correct, int number)
+{
+ char a = 0, b = 0, i, j;
+ char n1[5], n2[5];
+
+ snprintf(n1, sizeof(n1), "%04d", correct);
+ snprintf(n2, sizeof(n2), "%04d", number);
+ for (i = 0; i < 4; i++)
+ for (j = 0; j < 4; j++)
+ if (n1[(int)i] == n2[(int)j])
+ b++;
+ for (i = 0; i < 4; i++)
+ if (n1[(int)i] == n2[(int)i]) {
+ b--;
+ a++;
+ }
+ return 10 * a + b;
+}
+
+static int
+legal(int number)
+{
+ char i, j;
+ char temp[5];
+
+ snprintf(temp, sizeof(temp), "%04d", number);
+ for (i = 0; i < 4; i++)
+ for (j = i + 1; j < 4; j++)
+ if (temp[(int)i] == temp[(int)j])
+ return 0;
+ return 1;
+}
+
+static void
+initcomputer(char flag[])
+{
+ int i;
+
+ for (i = 0; i < 10000; i++)
+ if (legal(i))
+ flag[i] = 1;
+ else
+ flag[i] = 0;
+}
+
+static int
+computer(int correct, int total, char flag[], int n[])
+{
+ int guess;
+ static int j;
+ int k, i;
+ char data[5];
+
+ if (total == 1) {
+ do {
+ guess = random() % 10000;
+ } while (!legal(guess));
+ } else
+ guess = n[random() % j];
+ k = result(correct, guess);
+ if (k == 40) {
+ move(total + 8, 25);
+ snprintf(data, sizeof(data), "%04d", guess);
+ prints("%s => 猜中了!!", data);
+ return 1;
+ } else {
+ move(total + 8, 25);
+ snprintf(data, sizeof(data), "%04d", guess);
+ prints("%s => " ANSI_COLOR(1;32) "%dA %dB" ANSI_RESET, data, k / 10, k % 10);
+ }
+ j = 0;
+ for (i = 0; i < 10000; i++)
+ if (flag[i]) {
+ if (result(i, guess) != k)
+ flag[i] = 0;
+ else
+ n[j++] = i;
+ }
+ return 0;
+}
+
+static void
+Diff_Random(char *answer)
+{
+ register int i = 0, j, k;
+
+ while (i < 4) {
+ k = random() % 10 + '0';
+ for (j = 0; j < i; j++)
+ if (k == answer[j])
+ break;
+ if (j == i) {
+ answer[j] = k;
+ i++;
+ }
+ }
+ answer[4] = 0;
+}
+
+#define lockreturn0(unmode, state) if(lockutmpmode(unmode, state)) return 0
+
+int
+guess_main(void)
+{
+ char data[5];
+ int money;
+ char computerwin = 0, youwin = 0;
+ int count = 0, c_count = 0;
+ char ifcomputer[2];
+ char answer[5];
+ int *n = NULL;
+ char yournum[5];
+ char *flag = NULL;
+ char TABLE[] = {0, 10, 8, 4, 2, 1, 0, 0, 0, 0, 0};
+ FILE *file;
+
+ clear();
+ showtitle("猜數字", BBSName);
+ lockreturn0(GUESSNUM, LOCK_MULTI);
+
+ reload_money();
+ if (cuser.money < 5) {
+ clear();
+ move(12, 35);
+ unlockutmpmode();
+ vmsg("錢不夠啦 至少要 5 Ptt$");
+ return 1;
+ }
+ if ((money = get_money()) == 0)
+ return 1;
+ vice(money, "猜數字");
+
+ Diff_Random(answer);
+ move(2, 0);
+ clrtoeol();
+ prints("您下注 :%d Ptt$", money);
+
+ getdata_str(4, 0, "您要和電腦比賽嗎? <y/n>[y]:",
+ ifcomputer, sizeof(ifcomputer), LCECHO, "y");
+ if (ifcomputer[0] == 'y') {
+ ifcomputer[0] = 1;
+ show_table(TABLE, 1);
+ } else {
+ ifcomputer[0] = 0;
+ show_table(TABLE, 0);
+ }
+ if (ifcomputer[0]) {
+ do {
+ getdata(5, 0, "請輸入您要讓電腦猜的數字: ",
+ yournum, sizeof(yournum), LCECHO);
+ } while (!legal(atoi(yournum)));
+ move(8, 25);
+ outs("電腦猜");
+ flag = malloc(sizeof(char) * 10000);
+ n = malloc(sizeof(int) * 1500);
+ initcomputer(flag);
+ }
+ move(8, 55);
+ outs("你猜");
+ while (((!computerwin || !youwin) && count < 10 && (ifcomputer[0])) ||
+ (!ifcomputer[0] && count < 10 && !youwin)) {
+ if (!computerwin && ifcomputer[0]) {
+ ++c_count;
+ if (computer(atoi(yournum), c_count, flag, n))
+ computerwin = 1;
+ }
+ move(20, 55);
+ prints("第 %d 次機會 ", count + 1);
+ if (!youwin) {
+ ++count;
+ if (guess_play(get_data(data, count), answer, count))
+ youwin = 1;
+ }
+ }
+ move(17, 35);
+ free(flag);
+ free(n);
+ if (ifcomputer[0]) {
+ if (count > c_count) {
+ outs("你輸給電腦了");
+ move(18, 35);
+ prints("你賠了 %d ", money);
+ if ((file = fopen(LOGPASS, "a"))) {
+ fprintf(file, "電腦第%d次猜中, ", c_count);
+ if (youwin)
+ fprintf(file, "%s 第%d次猜中, ", cuser.userid, count);
+ else
+ fprintf(file, "%s 沒猜中, ", cuser.userid);
+ fprintf(file, "電腦賺走了%s %d Ptt$\n", cuser.userid, money);
+ fclose(file);
+ }
+ } else if (count < c_count) {
+ outs("真厲害, 讓你賺到囉");
+ move(18, 35);
+ prints("你賺走了 %d ", money * 2);
+ demoney(money * 2);
+ if ((file = fopen(LOGPASS, "a"))) {
+ fprintf(file, "id: %s, 第%d次猜中, 電腦第%d次猜中, "
+ "贏了電腦 %d Ptt$\n", cuser.userid, count,
+ c_count, money * 2);
+ fclose(file);
+ }
+ } else {
+ prints("真厲害, 和電腦打成平手了, 拿回本錢%d\n", money);
+ demoney(money);
+ if ((file = fopen(LOGPASS, "a"))) {
+ fprintf(file, "id: %s 和電腦打成了平手\n", cuser.userid);
+ fclose(file);
+ }
+ }
+ unlockutmpmode();
+ pressanykey();
+ return 1;
+ }
+ if (youwin) {
+ demoney(TABLE[count] * money);
+ if (count < 5) {
+ outs("真厲害, 錢被你賺走了");
+ if ((file = fopen(LOGPASS, "a"))) {
+ fprintf(file, "id: %s, 第%d次猜中, 贏了 %d Ptt$\n",
+ cuser.userid, count, TABLE[count] * money);
+ fclose(file);
+ }
+ } else if (count > 5) {
+ outs("唉, 太多次才猜出來了");
+ if ((file = fopen(LOGPASS, "a"))) {
+ fprintf(file, "id: %s, 第%d次才猜中, 賠了 %d Ptt$\n",
+ cuser.userid, count, money);
+ fclose(file);
+ }
+ } else {
+ outs("五次猜出來, 還你本錢吧");
+ move(18, 35);
+ clrtoeol();
+ prints("你拿回了%d Ptt$\n", money);
+ if ((file = fopen(LOGPASS, "a"))) {
+ fprintf(file, "id: %s, 第%d次猜中, 拿回了本錢 %d Ptt$\n",
+ cuser.userid, count, money);
+ fclose(file);
+ }
+ }
+ unlockutmpmode();
+ pressanykey();
+ return 1;
+ }
+ move(17, 35);
+ prints("嘿嘿 標準答案是 %s ", answer);
+ move(18, 35);
+ outs("下次再來吧");
+ if ((file = fopen(BBSHOME "/etc/loseguess.log", "a"))) {
+ fprintf(file, "id: %s 賭了 %d Ptt$\n", cuser.userid, money);
+ fclose(file);
+ }
+ unlockutmpmode();
+ pressanykey();
+ return 1;
+}
diff --git a/pttbbs/mbbsd/indict.c b/pttbbs/mbbsd/indict.c
new file mode 100644
index 00000000..a0504d61
--- /dev/null
+++ b/pttbbs/mbbsd/indict.c
@@ -0,0 +1,171 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define REFER "etc/dicts"
+
+static void
+addword(const char *database,char word[])
+{
+ char buf[150], a[3];
+ FILE *fp = fopen(database, "r+");
+
+ if (fp == NULL) {
+ vmsg("database error");
+ return;
+ }
+ fgets(buf, 130, fp);
+ fseek(fp, 0, 2);
+ if (HasUserPerm(PERM_LOGINOK)) {
+ clear();
+ move(4, 0);
+ outs(" " ANSI_COLOR(31) "警告" ANSI_RESET ":若蓄意填寫假資料將" ANSI_COLOR(36) "砍id" ANSI_RESET "處份\n");
+ prints("\n輸入範例\n:" ANSI_COLOR(33) "%s" ANSI_RESET, buf);
+ outs("\n請依上列範例輸入一行資料(直接enter放棄)\n");
+ getdata(10, 0, ":", buf, 65, DOECHO);
+ if (buf[0]) {
+ getdata(13, 0, "確定新增?(Y/n)", a, sizeof(a), LCECHO);
+ if (a[0] != 'n')
+ fprintf(fp, "%-65s[%s]\n", buf, cuser.userid);
+ }
+ }
+ fclose(fp);
+ clear();
+}
+
+static int
+choose_dict(char *dict,int dictlen,char *database,int databaselen)
+{
+#define MAX_DICT 10
+ int n,c;
+ FILE *fp;
+ char buf[MAX_DICT][21], data[MAX_DICT][21], cho[10];
+
+ move(12, 0);
+ clrtobot();
+ outs(" "
+ "● " ANSI_COLOR(45;33) "字典唷 ◇ 要查哪一本?" ANSI_RESET " ●");
+
+ if ((fp = fopen(REFER, "r"))) {
+ for(n=0; n<MAX_DICT && fscanf(fp,"%s %s",buf[n],data[n])==2; n++) { // XXX check buffer size
+ prints("\n "
+ "(" ANSI_COLOR(36) "%d" ANSI_RESET ") %-20s大字典", n + 1, buf[n]);
+ }
+ fclose(fp);
+
+ getdata(22, 14, " ★ 請選擇,[Enter]離開:", cho, 3, LCECHO);
+ c=atoi(cho);
+
+ if (c >= 1 && c <= n) {
+ strlcpy(dict, buf[c-1], dictlen);
+ strlcpy(database, data[c-1], databaselen);
+ return 1;
+ } else
+ return 0;
+ }
+ return 0;
+}
+
+int
+use_dict(char *dict,char *database)
+{
+ FILE *fp;
+ char lang[150], word[80] = "";
+ char j, f, buf[120], sys[] = "|" ANSI_COLOR(31) "e" ANSI_RESET ":編輯字典";
+ int i = 0;
+
+ setutmpmode(DICT);
+ if (!HasUserPerm(PERM_SYSOP))
+ sys[0] = 0;
+
+ clear();
+
+ snprintf(buf, sizeof(buf),
+ ANSI_COLOR(45) " ●" ANSI_COLOR(1;44;33) ""
+ " %-14s" ANSI_COLOR(3;45) " ● ", dict);
+ strlcpy(&buf[100], ANSI_RESET "\n", sizeof(buf) - 100);
+ for (;;) {
+ move(0, 0);
+ prints(" 請輸入關鍵字串(%s) 或指令(h,t,a)\n", dict);
+ prints("[" ANSI_COLOR(32) "<關鍵字>" ANSI_RESET "|" ANSI_COLOR(32) "h" ANSI_RESET ":help|" ANSI_COLOR(32) ""
+ "t" ANSI_RESET ":所有資料|" ANSI_COLOR(32) "a" ANSI_RESET ":新增資料%s]\n:", sys);
+ getdata(2, 0, ":", word, 18, DOECHO);
+ outs("資料搜尋中請稍候....");
+ str_lower(word, word);
+ if (word[0] == 0)
+ return 0;
+ clear();
+ move(4, 0);
+ outs(buf);
+ if (strlen(word) == 1) {
+ if (word[0] == 'a') {
+ clear();
+ move(4, 0);
+ outs(buf);
+ addword(database,word);
+ continue;
+ } else if (word[0] == 't')
+ word[0] = 0;
+ else if (word[0] == 'h') {
+ more("etc/dict.hlp", YEA);
+ clear();
+ continue;
+ } else if (word[0] == 'e' && HasUserPerm(PERM_SYSOP)) {
+ vedit(database, NA, NULL);
+ clear();
+ continue;
+ } else {
+ outs("字串太短,請輸入多一點關鍵字");
+ continue;
+ }
+ }
+ i = 0;
+ if ((fp = fopen(database, "r"))) {
+ while (fgets(lang, sizeof(lang), fp) != NULL) {
+ if (lang[65] == '[') {
+ lang[65] = 0;
+ f = 1;
+ } else
+ f = 0;
+ if (strcasestr(lang, word)) {
+ if (f == 1)
+ lang[65] = '[';
+ outs(lang);
+ i++;
+ if (!((i + 1) % 17)) {
+ move(23, 0);
+ outs(ANSI_COLOR(45) " "
+ "任意鍵繼續 Q:離開 "
+ ANSI_RESET " ");
+ j = igetch();
+ if (j == 'q')
+ break;
+ else {
+ clear();
+ move(4, 0);
+ outs(buf);
+ }
+ }
+ }
+ }
+ fclose(fp);
+ }
+ if (i == 0) {
+ getdata(5, 0, "沒這個資料耶,新增嗎?(y/N)", lang, 3, LCECHO);
+ if (lang[0] == 'y') {
+ clear();
+ move(4, 0);
+ outs(buf);
+ addword(database,word);
+ }
+ }
+ }
+}
+
+int
+x_dict(void)
+{
+ char dict[41], database[41];
+ if (choose_dict(dict,sizeof(dict),database,sizeof(database)))
+ use_dict(dict,database);
+ return 0;
+}
diff --git a/pttbbs/mbbsd/io.c b/pttbbs/mbbsd/io.c
new file mode 100644
index 00000000..0f36177b
--- /dev/null
+++ b/pttbbs/mbbsd/io.c
@@ -0,0 +1,1058 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define OBUFSIZE 2048
+#define IBUFSIZE 128
+
+#ifdef DEBUG
+#define register
+#endif
+
+/* realXbuf is Xbuf+3 because hz convert library requires buf[-2]. */
+static unsigned char real_outbuf[OBUFSIZE+6] = " ", real_inbuf[IBUFSIZE+6] = " ";
+static unsigned char *outbuf = real_outbuf + 3, *inbuf = real_inbuf + 3;
+
+static int obufsize = 0, ibufsize = 0;
+static int icurrchar = 0;
+
+/* ----------------------------------------------------- */
+/* convert routines */
+/* ----------------------------------------------------- */
+#ifdef CONVERT
+
+extern read_write_type write_type;
+extern read_write_type read_type;
+extern convert_type input_type;
+
+inline static ssize_t input_wrapper(void *buf, ssize_t count) {
+ /* input_wrapper is a special case.
+ * because we may do nothing,
+ * a if-branch is better than a function-pointer call.
+ */
+ if(input_type) return (*input_type)(buf, count);
+ else return count;
+}
+
+inline static int read_wrapper(int fd, void *buf, size_t count) {
+ return (*read_type)(fd, buf, count);
+}
+
+inline static int write_wrapper(int fd, void *buf, size_t count) {
+ return (*write_type)(fd, buf, count);
+}
+#endif
+
+/* ----------------------------------------------------- */
+/* output routines */
+/* ----------------------------------------------------- */
+void
+oflush(void)
+{
+ if (obufsize) {
+ STATINC(STAT_SYSWRITESOCKET);
+#ifdef CONVERT
+ write_wrapper(1, outbuf, obufsize);
+#else
+ write(1, outbuf, obufsize);
+#endif
+ obufsize = 0;
+ }
+}
+
+void
+init_buf(void)
+{
+
+ memset(inbuf, 0, IBUFSIZE);
+}
+void
+output(const char *s, int len)
+{
+ /* Invalid if len >= OBUFSIZE */
+
+ assert(len<OBUFSIZE);
+ if (obufsize + len > OBUFSIZE) {
+ STATINC(STAT_SYSWRITESOCKET);
+#ifdef CONVERT
+ write_wrapper(1, outbuf, obufsize);
+#else
+ write(1, outbuf, obufsize);
+#endif
+ obufsize = 0;
+ }
+ memcpy(outbuf + obufsize, s, len);
+ obufsize += len;
+}
+
+int
+ochar(int c)
+{
+ if (obufsize > OBUFSIZE - 1) {
+ STATINC(STAT_SYSWRITESOCKET);
+ /* suppose one byte data doesn't need to be converted. */
+ write(1, outbuf, obufsize);
+ obufsize = 0;
+ }
+ outbuf[obufsize++] = c;
+ return 0;
+}
+
+/* ----------------------------------------------------- */
+/* input routines */
+/* ----------------------------------------------------- */
+
+static int i_newfd = 0;
+static struct timeval i_to, *i_top = NULL;
+static int (*flushf) () = NULL;
+
+void
+add_io(int fd, int timeout)
+{
+ i_newfd = fd;
+ if (timeout) {
+ i_to.tv_sec = timeout;
+ i_to.tv_usec = 16384; /* Ptt: 改成16384 避免不按時for loop吃cpu
+ * time 16384 約每秒64次 */
+ i_top = &i_to;
+ } else
+ i_top = NULL;
+}
+
+int
+num_in_buf(void)
+{
+ return ibufsize - icurrchar;
+}
+
+/*
+ * dogetch() is not reentrant-safe. SIGUSR[12] might happen at any time, and
+ * dogetch() might be called again, and then ibufsize/icurrchar/inbuf might
+ * be inconsistent. We try to not segfault here...
+ */
+
+static int
+dogetch(void)
+{
+ ssize_t len;
+ static time4_t lastact;
+ if (ibufsize <= icurrchar) {
+
+ if (flushf)
+ (*flushf) ();
+
+ refresh();
+
+ if (i_newfd) {
+
+ struct timeval timeout;
+ fd_set readfds;
+
+ if (i_top)
+ timeout = *i_top; /* copy it because select() might
+ * change it */
+
+ FD_ZERO(&readfds);
+ FD_SET(0, &readfds);
+ FD_SET(i_newfd, &readfds);
+
+ /* jochang: modify first argument of select from FD_SETSIZE */
+ /* since we are only waiting input from fd 0 and i_newfd(>0) */
+
+ STATINC(STAT_SYSSELECT);
+ while ((len = select(i_newfd + 1, &readfds, NULL, NULL,
+ i_top ? &timeout : NULL)) < 0) {
+ if (errno != EINTR)
+ abort_bbs(0);
+ /* raise(SIGHUP); */
+ }
+
+ if (len == 0){
+#ifdef OUTTA_TIMER
+ now = SHM->GV2.e.now;
+#else
+ now = time(0);
+#endif
+ return I_TIMEOUT;
+ }
+
+ if (i_newfd && FD_ISSET(i_newfd, &readfds)){
+#ifdef OUTTA_TIMER
+ now = SHM->GV2.e.now;
+#else
+ now = time(0);
+#endif
+ return I_OTHERDATA;
+ }
+ }
+
+#ifdef NOKILLWATERBALL
+ if( currutmp && currutmp->msgcount && !reentrant_write_request )
+ write_request(1);
+#endif
+
+ STATINC(STAT_SYSREADSOCKET);
+
+ do {
+ len = tty_read(inbuf, IBUFSIZE);
+ /* tty_read will handle abort_bbs.
+ * len <= 0: read more */
+#ifdef CONVERT
+ if(len > 0)
+ len = input_wrapper(inbuf, len);
+#endif
+ } while (len <= 0);
+
+ ibufsize = len;
+ icurrchar = 0;
+ }
+
+ if (currutmp) {
+#ifdef OUTTA_TIMER
+ now = SHM->GV2.e.now;
+#else
+ now = time(0);
+#endif
+ /* 3 秒內超過兩 byte 才算 active, anti-antiidle.
+ * 不過方向鍵等組合鍵不止 1 byte */
+ if (now - lastact < 3)
+ currutmp->lastact = now;
+ lastact = now;
+ }
+ return (unsigned char)inbuf[icurrchar++];
+}
+
+#ifdef DEBUG
+/*
+ * These are for terminal keys debug
+ */
+void
+_debug_print_ibuffer()
+{
+ static int y = 0;
+ int i = 0;
+
+ move(y % b_lines, 0);
+ for (i = 0; i < t_columns; i++)
+ outc(' ');
+ move(y % b_lines, 0);
+ prints("%d. Current Buffer: %d/%d, ", y+1, icurrchar, ibufsize);
+ outs(ANSI_COLOR(1) "[" ANSI_RESET);
+ for (i = 0; i < ibufsize; i++)
+ {
+ int c = (unsigned char)inbuf[i];
+ if(c < ' ')
+ {
+ prints(ANSI_COLOR(1;33) "0x%02x" ANSI_RESET, c);
+ } else {
+ outc(c);
+ }
+ }
+ outs(ANSI_COLOR(1) "]" ANSI_RESET);
+ y++;
+ move(y % b_lines, 0);
+ for (i = 0; i < t_columns; i++)
+ outc(' ');
+}
+
+int
+_debug_check_keyinput()
+{
+ int dbcsaware = 0;
+ int flExit = 0;
+
+ clear();
+ while(!flExit)
+ {
+ int i = 0;
+ move(b_lines, 0);
+ for(i=0; i<t_columns; i++)
+ outc(' ');
+ move(b_lines, 0);
+ if(dbcsaware)
+ {
+ prints( ANSI_COLOR(7) "游標在此" ANSI_RESET
+ " 測試中文模式會不會亂送鍵。 'q' 離開, 'd' 回英文模式 ");
+ move(b_lines, 4);
+ } else {
+ outs("Waiting for key input. 'q' to exit, 'd' to try dbcs-aware");
+ }
+ wait_input(-1, 1);
+ switch(dogetch())
+ {
+ case 'd':
+ dbcsaware = !dbcsaware;
+ break;
+ case 'q':
+ flExit = 1;
+ break;
+ }
+ _debug_print_ibuffer();
+ while(num_in_buf() > 0)
+ dogetch();
+ }
+ return 0;
+}
+
+#endif
+
+static int water_which_flag = 0;
+
+int
+igetch(void)
+{
+ register int ch, mode = 0, last = 0;
+ while (1) {
+ ch = dogetch();
+
+ /* for escape codes, check
+ * http://support.dell.com/support/edocs/systems/pe2650/en/ug/5g387ad0.htm
+ */
+ if (mode == 0 && ch == KEY_ESC)
+ mode = 1;
+ else if (mode == 1) {
+
+ /* Escape sequence */
+
+ if (ch == '[' || ch == 'O')
+ { mode = 2; last = ch; }
+#if 0
+ /* some user complained about this since they wanna
+ * do Esc-N paste in vedit.
+ * Before anyone to explain what this is for,
+ * this will be commented.
+ */
+ else if (ch == '1' || ch == '4') /* what is this!? */
+ { mode = 3; last = ch; }
+#endif
+ else {
+ KEY_ESC_arg = ch;
+ return KEY_ESC;
+ }
+
+ }
+ else if (mode == 2)
+ {
+ /* ^[ or ^O,
+ * ordered by frequency */
+
+ if(ch >= 'A' && ch <= 'D') /* Cursor key */
+ {
+ return KEY_UP + (ch - 'A');
+ }
+ else if (ch >= '1' && ch <= '6') /* Ins Del Home End PgUp PgDn */
+ {
+ mode = 3; last = ch;
+ continue;
+ }
+ else if(ch == 'O')
+ {
+ mode = 4;
+ continue;
+ }
+ else if(ch == 'Z')
+ {
+ return KEY_STAB;
+ }
+ else if (ch == '0')
+ {
+ if (dogetch() == 'Z')
+ return KEY_STAB;
+ else
+ return KEY_UNKNOWN;
+ }
+ else if (last == 'O') {
+ /* ^[O ... */
+ if (ch >= 'A' && ch <= 'D')
+ return KEY_UP + (ch - 'A');
+ if (ch >= 'P' && ch <= 'S') // vt100 k1-4
+ return KEY_F1 + (ch - 'P');
+ if (ch >= 'T' && ch <= '[') // putty vt100+ F5-F12
+ return KEY_F5 + (ch - 'T');
+ if (ch >= 't' && ch <= 'z') // vt100 F5-F11
+ return KEY_F5 + (ch - 't');
+ if (ch >= 'p' && ch <= 's') // Old (num or fn)kbd 4 keys
+ return KEY_F1 + (ch - 'p');
+ else if (ch == 'a') // DELL spec
+ return KEY_F12;
+ }
+ else return KEY_UNKNOWN;
+ }
+ else if (mode == 3)
+ {
+ /* ^[[1-6] */
+
+ /* ~: Ins Del Home End PgUp PgDn */
+ if(ch == '~')
+ return KEY_HOME + (last - '1');
+ else if (last == '1')
+ {
+ if (ch >= '1' && ch <= '6')
+ {
+ dogetch(); /* must be '~' */
+ return KEY_F1 + ch - '1';
+ }
+ else if (ch >= '7' && ch <= '9')
+ {
+ dogetch(); /* must be '~' */
+ return KEY_F6 + ch - '7';
+ }
+ else return KEY_UNKNOWN;
+ } else if (last == '2')
+ {
+ if (ch >= '0' && ch <= '4')
+ {
+ dogetch(); /* hope you are '~' */
+ return KEY_F9 + ch - '0';
+ }
+ else return KEY_UNKNOWN;
+ }
+ }
+ else // here is switch for default keys
+ switch (ch) { // XXX: indent error
+#ifdef DEBUG
+ case Ctrl('Q'):{
+ struct rusage ru;
+ getrusage(RUSAGE_SELF, &ru);
+ vmsgf("sbrk: %d KB, idrss: %d KB, isrss: %d KB",
+ ((int)sbrk(0) - 0x8048000) / 1024,
+ (int)ru.ru_idrss, (int)ru.ru_isrss);
+ }
+ continue;
+#endif
+ case Ctrl('L'):
+ redoscr();
+ continue;
+ case Ctrl('U'):
+ if (currutmp != NULL && currutmp->mode != EDITING
+ && currutmp->mode != LUSERS && currutmp->mode) {
+
+ screen_backup_t old_screen;
+ int oldroll = roll;
+ int my_newfd;
+
+ screen_backup(&old_screen);
+ my_newfd = i_newfd;
+ i_newfd = 0;
+
+ t_users();
+
+ i_newfd = my_newfd;
+ roll = oldroll;
+ screen_restore(&old_screen);
+ continue;
+ }
+ return ch;
+ case KEY_TAB:
+ if (WATERMODE(WATER_ORIG) || WATERMODE(WATER_NEW))
+ if (currutmp != NULL && watermode > 0) {
+ check_water_init();
+ watermode = (watermode + water_which->count)
+ % water_which->count + 1;
+ t_display_new();
+ continue;
+ }
+ return ch;
+
+ case Ctrl('R'):
+ if (currutmp == NULL)
+ return (ch);
+
+ if (currutmp->msgs[0].pid &&
+ WATERMODE(WATER_OFO) && wmofo == NOTREPLYING) {
+ int my_newfd;
+ screen_backup_t old_screen;
+
+ screen_backup(&old_screen);
+
+ my_newfd = i_newfd;
+ i_newfd = 0;
+ my_write2();
+ screen_restore(&old_screen);
+ i_newfd = my_newfd;
+ continue;
+ } else if (!WATERMODE(WATER_OFO)) {
+ check_water_init();
+ if (watermode > 0) {
+ watermode = (watermode + water_which->count)
+ % water_which->count + 1;
+ t_display_new();
+ continue;
+ } else if (currutmp->mode == 0 &&
+ (currutmp->chatid[0] == 2 || currutmp->chatid[0] == 3) &&
+ water_which->count != 0 && watermode == 0) {
+ /* 第二次按 Ctrl-R */
+ watermode = 1;
+ t_display_new();
+ continue;
+ } else if (watermode == -1 && currutmp->msgs[0].pid) {
+ /* 第一次按 Ctrl-R (必須先被丟過水球) */
+ screen_backup_t old_screen;
+ int my_newfd;
+ screen_backup(&old_screen);
+
+ /* 如果正在talk的話先不處理對方送過來的封包 (不去select) */
+ my_newfd = i_newfd;
+ i_newfd = 0;
+ show_call_in(0, 0);
+ watermode = 0;
+#ifndef PLAY_ANGEL
+ my_write(currutmp->msgs[0].pid, "水球丟過去: ",
+ currutmp->msgs[0].userid, WATERBALL_GENERAL, NULL);
+#else
+ switch (currutmp->msgs[0].msgmode) {
+ case MSGMODE_TALK:
+ case MSGMODE_WRITE:
+ my_write(currutmp->msgs[0].pid, "水球丟過去: ",
+ currutmp->msgs[0].userid, WATERBALL_GENERAL, NULL);
+ break;
+ case MSGMODE_FROMANGEL:
+ my_write(currutmp->msgs[0].pid, "再問他一次: ",
+ currutmp->msgs[0].userid, WATERBALL_ANGEL, NULL);
+ break;
+ case MSGMODE_TOANGEL:
+ my_write(currutmp->msgs[0].pid, "回答小主人: ",
+ currutmp->msgs[0].userid, WATERBALL_ANSWER, NULL);
+ break;
+ }
+#endif
+ i_newfd = my_newfd;
+
+ /* 還原螢幕 */
+ screen_restore(&old_screen);
+ continue;
+ }
+ }
+ return ch;
+
+ case Ctrl('T'):
+ if (WATERMODE(WATER_ORIG) || WATERMODE(WATER_NEW)) {
+ if (watermode > 0) {
+ check_water_init();
+ if (watermode > 1)
+ watermode--;
+ else
+ watermode = water_which->count;
+ t_display_new();
+ continue;
+ }
+ }
+ return ch;
+
+ case Ctrl('F'):
+ if (WATERMODE(WATER_NEW)) {
+ if (watermode > 0) {
+ check_water_init();
+ if (water_which_flag == (int)water_usies)
+ water_which_flag = 0;
+ else
+ water_which_flag =
+ (water_which_flag + 1) % (int)(water_usies + 1);
+ if (water_which_flag == 0)
+ water_which = &water[0];
+ else
+ water_which = swater[water_which_flag - 1];
+ watermode = 1;
+ t_display_new();
+ continue;
+ }
+ }
+ return ch;
+
+ case Ctrl('G'):
+ if (WATERMODE(WATER_NEW)) {
+ if (watermode > 0) {
+ check_water_init();
+ water_which_flag = (water_which_flag + water_usies) % (water_usies + 1);
+ if (water_which_flag == 0)
+ water_which = &water[0];
+ else
+ water_which = swater[water_which_flag - 1];
+ watermode = 1;
+ t_display_new();
+ continue;
+ }
+ }
+ return ch;
+
+ case Ctrl('J'): /* Ptt 把 \n 拿掉 */
+#ifdef PLAY_ANGEL
+ /* Seams some telnet client still send CR LF when changing lines.
+ CallAngel();
+ */
+#endif
+ continue;
+
+ default:
+ return ch;
+ }
+ }
+ // should not reach here. just to make compiler happy.
+ return 0;
+}
+
+/*
+ * wait user input anything for f seconds.
+ * if f < 0, then wait forever.
+ * Return 1 if anything available.
+ */
+int
+wait_input(float f, int flDoRefresh)
+{
+ int sel = 0;
+ fd_set readfds;
+ struct timeval tv, *ptv = &tv;
+
+ if(num_in_buf() > 0) // for EINTR
+ return 1;
+
+ FD_ZERO(&readfds);
+ FD_SET(0, &readfds);
+
+ if(flDoRefresh)
+ refresh();
+
+ if(f > 0)
+ {
+ tv.tv_sec = (long) f;
+ tv.tv_usec = (f - (long)f) * 1000000L;
+ } else
+ ptv = NULL;
+
+#ifdef STATINC
+ STATINC(STAT_SYSSELECT);
+#endif
+
+ do {
+ if(num_in_buf() > 0)
+ return 1;
+ sel = select(1, &readfds, NULL, NULL, ptv);
+ } while (sel < 0 && errno == EINTR);
+ /* EINTR, interrupted. I don't care! */
+
+ if(sel == 0)
+ return 0;
+
+ return 1;
+}
+
+/**
+ * 根據 mode 來 strip 字串 str,並把結果存到 buf
+ * @param buf
+ * @param str
+ * @param mode enum {STRIP_ALL = 0, ONLY_COLOR, NO_RELOAD};
+ * STRIP_ALL: ??
+ * ONLY_COLOR: ??
+ * NO_RELOAD: 不 strip (?)
+ */
+int
+strip_ansi(char *buf, const char *str, int mode)
+{
+ register int count = 0;
+ static const char EscapeFlag[] = {
+ /* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0, 0, 0, 0, 0,
+ /* 20 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, /* 0~9 ;= */
+ /* 40 */ 0, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, /* ABCDHIJK */
+ /* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 60 */ 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 2, 2, 0, 0, /* fhlm */
+ /* 70 */ 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* su */
+ /* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* A0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* B0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* C0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* D0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* E0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* F0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+#define isEscapeParam(X) (EscapeFlag[(int)(X)] & 1)
+#define isEscapeCommand(X) (EscapeFlag[(int)(X)] & 2)
+
+ for(; *str; ++str)
+ if( *str != ESC_CHR ){
+ if( buf )
+ *buf++ = *str;
+ ++count;
+ }else{
+ const char* p = str + 1;
+ if( *p != '[' ){
+ ++str;
+ if(*str=='\0') break;
+ continue;
+ }
+ while(isEscapeParam(*++p));
+ if( (mode == NO_RELOAD && isEscapeCommand(*p)) ||
+ (mode == ONLY_COLOR && *p == 'm' )){
+ register int len = p - str + 1;
+ if( buf ){
+ strncpy(buf, str, len);
+ buf += len;
+ }
+ count += len;
+ }
+ str = p;
+ if(*str=='\0') break;
+ }
+ if( buf )
+ *buf = 0;
+ return count;
+}
+
+void
+strip_nonebig5(unsigned char *str, int maxlen)
+{
+ int i;
+ int len=0;
+ for(i=0;i<maxlen && str[i];i++) {
+ if(32<=str[i] && str[i]<128)
+ str[len++]=str[i];
+ else if(str[i]==255) {
+ if(i+1<maxlen)
+ if(251<=str[i+1] && str[i+1]<=254) {
+ i++;
+ if(i+1<maxlen && str[i+1])
+ i++;
+ }
+ continue;
+ } else if(str[i]&0x80) {
+ if(i+1<maxlen)
+ if((0x40<=str[i+1] && str[i+1]<=0x7e) ||
+ (0xa1<=str[i+1] && str[i+1]<=0xfe)) {
+ str[len++]=str[i];
+ str[len++]=str[i+1];
+ i++;
+ }
+ }
+ }
+ if(len<maxlen)
+ str[len]='\0';
+}
+
+#ifdef DBCSAWARE
+
+int getDBCSstatus(unsigned char *s, int pos)
+{
+ int sts = DBCS_ASCII;
+ while(pos >= 0)
+ {
+ if(sts == DBCS_LEADING)
+ sts = DBCS_TRAILING;
+ else if (*s >= 0x80)
+ {
+ sts = DBCS_LEADING;
+ } else {
+ sts = DBCS_ASCII;
+ }
+ s++, pos--;
+ }
+ return sts;
+}
+
+#else
+
+#define dbcs_off (1)
+
+#endif
+
+#define MAXLASTCMD 12
+int
+oldgetdata(int line, int col, const char *prompt, char *buf, int len, int echo)
+{
+ register int ch, i;
+ int clen;
+ int x = col, y = line;
+ int dirty_line = 0; /* if this line contains ansi escapes,
+ we have to dirty entire line. */
+ static char lastcmd[MAXLASTCMD][80];
+ unsigned char occupy_msg = 0;
+
+#ifdef DBCSAWARE
+ unsigned int dbcsincomplete = 0;
+#endif
+
+ strip_ansi(buf, buf, STRIP_ALL);
+
+ if(line == b_lines-msg_occupied)
+ occupy_msg=1, msg_occupied ++;
+
+ if (prompt) {
+ x += strip_ansi(NULL, prompt, STRIP_ALL);
+ if(strlen(prompt) + col != x)
+ dirty_line = 1;
+
+ if(!echo || !dirty_line)
+ {
+ move(line, col);
+ clrtoeol();
+ outs(prompt);
+ }
+ }
+
+ if (!echo) {
+ len--;
+ clen = 0;
+ while ((ch = igetch()) != '\r') {
+ if (ch == '\177' || ch == Ctrl('H')) {
+ if (!clen) {
+ bell();
+ continue;
+ }
+ clen--;
+ continue;
+ }
+ if (ch>=0x100 || !isprint(ch)) {
+ continue;
+ }
+ if (clen >= len) {
+ continue;
+ }
+ buf[clen++] = ch;
+ }
+ buf[clen] = '\0';
+ outc('\n');
+ oflush();
+ } else {
+ int cmdpos = 0;
+ int currchar = 0;
+
+ len--;
+ buf[len] = '\0';
+ clen = currchar = strlen(buf);
+
+ if(!dirty_line)
+ {
+ standout();
+ for(i=0; i<=len; i++)
+ outc(' ');
+ standend();
+ move(y, x);
+ edit_outs(buf);
+ }
+
+ while (1) {
+ assert(0<=clen);
+ if(dirty_line) {
+ move(line, col);
+ clrtoeol();
+ outs(prompt);
+ standout();
+ for(i=0; i<=len; i++)
+ {
+ if(i <= clen)
+ outc(buf[i]);
+ else
+ outc(' ');
+ }
+ // edit_outs(buf);
+ standend();
+ }
+ move(y, x + currchar);
+
+ if ((ch = igetch()) == '\r')
+ break;
+ assert(0<=clen);
+ switch (ch) {
+ case KEY_DOWN: case Ctrl('N'):
+ case KEY_UP: case Ctrl('P'):
+ strlcpy(lastcmd[cmdpos], buf, sizeof(lastcmd[0]));
+ if (ch == KEY_UP || ch == Ctrl('P'))
+ cmdpos++;
+ else
+ cmdpos += MAXLASTCMD - 1;
+ cmdpos %= MAXLASTCMD;
+ strlcpy(buf, lastcmd[cmdpos], len+1);
+
+ if(!dirty_line)
+ {
+ move(y, x); /* clrtoeof */
+ for (i = 0; i <= clen; i++)
+ outc(' ');
+ move(y, x);
+ edit_outs(buf);
+ }
+ clen = currchar = strlen(buf);
+ break;
+ case KEY_LEFT:
+ if (currchar > 0)
+ {
+ --currchar;
+#ifdef DBCSAWARE
+ if(currchar > 0 &&
+ ISDBCSAWARE() &&
+ getDBCSstatus((unsigned char*)buf, currchar) == DBCS_TRAILING)
+ currchar --;
+#endif
+ }
+ assert(0<=clen);
+ break;
+ case KEY_RIGHT:
+ if (buf[currchar])
+ {
+ ++currchar;
+#ifdef DBCSAWARE
+ if(buf[currchar] &&
+ ISDBCSAWARE() &&
+ getDBCSstatus((unsigned char*)buf, currchar) == DBCS_TRAILING)
+ currchar++;
+#endif
+ }
+ assert(0<=clen);
+ break;
+ case '\177':
+ case Ctrl('H'):
+ if (currchar) {
+#ifdef DBCSAWARE
+ int dbcs_off = 1;
+ if (ISDBCSAWARE() &&
+ getDBCSstatus((unsigned char*)buf, currchar-1) == DBCS_TRAILING)
+ dbcs_off = 2;
+#endif
+ currchar -= dbcs_off;
+ clen -= dbcs_off;
+ for (i = currchar; i <= clen; i++)
+ buf[i] = buf[i + dbcs_off];
+
+ if(!dirty_line)
+ {
+ move(y, x + clen);
+ outc(' ');
+#ifdef DBCSAWARE
+ while(--dbcs_off > 0) outc(' ');
+#endif
+ move(y, x);
+ edit_outs(buf);
+ }
+ }
+ break;
+ case Ctrl('Y'):
+ currchar = 0;
+ case Ctrl('K'):
+ /* we shoud be able to avoid DBCS issues in ^K mode */
+ buf[currchar] = '\0';
+ if(!dirty_line)
+ {
+ move(y, x + currchar);
+ for (i = currchar; i < clen; i++)
+ outc(' ');
+ }
+ clen = currchar;
+ break;
+ case Ctrl('D'):
+ case KEY_DEL:
+ if (buf[currchar]) {
+#ifdef DBCSAWARE
+ int dbcs_off = 1;
+ if (ISDBCSAWARE() && buf[currchar+1] &&
+ getDBCSstatus((unsigned char*)buf, currchar+1) == DBCS_TRAILING)
+ dbcs_off = 2;
+#endif
+ clen -= dbcs_off;
+ for (i = currchar; i <= clen; i++)
+ buf[i] = buf[i + dbcs_off];
+ if(!dirty_line)
+ {
+ move(y, x + clen);
+ outc(' ');
+#ifdef DBCSAWARE
+ while(--dbcs_off > 0) outc(' ');
+#endif
+ move(y, x);
+ edit_outs(buf);
+ }
+ }
+ break;
+ case Ctrl('A'):
+ case KEY_HOME:
+ currchar = 0;
+ break;
+ case Ctrl('E'):
+ case KEY_END:
+ currchar = clen;
+ break;
+ case KEY_UNKNOWN:
+ break;
+ default:
+ if (isprint2(ch) && clen < len && x + clen < scr_cols) {
+#ifdef DBCSAWARE
+ if(ISDBCSAWARE())
+ {
+ /* to prevent single byte input */
+ if(dbcsincomplete)
+ {
+ dbcsincomplete = 0;
+ }
+ else if (ch >= 0x80)
+ {
+ dbcsincomplete = 1;
+ if(clen + 2 > len)
+ {
+ /* we can't print this. ignore and eat key. */
+ igetch();
+ dbcsincomplete = 0;
+ break;
+ }
+ } else {
+ /* nothing, normal key. */
+ }
+ }
+#endif
+ for (i = clen + 1; i > currchar; i--)
+ buf[i] = buf[i - 1];
+ buf[currchar] = ch;
+ if(!dirty_line)
+ {
+ move(y, x + currchar);
+ edit_outs(buf + currchar);
+ }
+ currchar++;
+ clen++;
+ }
+ break;
+ } /* end case */
+ assert(0<=clen);
+ } /* end while */
+
+ if (clen > 1) {
+ strlcpy(lastcmd[0], buf, sizeof(lastcmd[0]));
+ memmove(lastcmd+1, lastcmd, (MAXLASTCMD-1)*sizeof(lastcmd[0]));
+ }
+ /* why return here? because some code then outs.*/
+ // outc('\n');
+ move(y+1, 0);
+ refresh();
+ assert(0<=currchar && currchar<=clen);
+ assert(0<=clen && clen<=len);
+ }
+ if ((echo == LCECHO) && isupper((int)buf[0]))
+ buf[0] = tolower(buf[0]);
+
+ if(occupy_msg) msg_occupied --;
+ return clen;
+}
+
+/* Ptt */
+int
+getdata_buf(int line, int col, const char *prompt, char *buf, int len, int echo)
+{
+ return oldgetdata(line, col, prompt, buf, len, echo);
+}
+
+
+int
+getdata_str(int line, int col, const char *prompt, char *buf, int len, int echo, const char *defaultstr)
+{
+ strlcpy(buf, defaultstr, len);
+
+ return oldgetdata(line, col, prompt, buf, len, echo);
+}
+
+int
+getdata(int line, int col, const char *prompt, char *buf, int len, int echo)
+{
+ buf[0] = 0;
+ return oldgetdata(line, col, prompt, buf, len, echo);
+}
+
+/* vim:sw=4
+ */
diff --git a/pttbbs/mbbsd/kaede.c b/pttbbs/mbbsd/kaede.c
new file mode 100644
index 00000000..3f284adb
--- /dev/null
+++ b/pttbbs/mbbsd/kaede.c
@@ -0,0 +1,184 @@
+/* $Id$ */
+#include "bbs.h"
+
+char *
+Ptt_prints(char *str, size_t size, int mode)
+{
+ char *strbuf = alloca(size);
+ int r, w;
+ for( r = w = 0 ; str[r] != 0 && w < (size - 1) ; ++r )
+ if( str[r] != ESC_CHR )
+ strbuf[w++] = str[r];
+ else{
+ if( str[++r] != '*' ){
+ if(w+2>=size-1) break;
+ strbuf[w++] = ESC_CHR;
+ strbuf[w++] = str[r];
+ }
+ else{
+ /* Note, w will increased by copied length after */
+ switch( str[++r] ){
+ case 's':
+ strlcpy(strbuf+w, cuser.userid, size-w);
+ w += strlen(strbuf+w);
+ break;
+ case 't':
+ strlcpy(strbuf+w, Cdate(&now), size-w);
+ w += strlen(strbuf+w);
+ break;
+ case 'u':
+ w += snprintf(&strbuf[w], size - w,
+ "%d", SHM->UTMPnumber);
+ break;
+
+ /* disabled for security issue.
+ * we support only entries can be queried by others now.
+ */
+#ifdef LOW_SECURITY
+ case 'b':
+ w += snprintf(&strbuf[w], size - w,
+ "%d/%d", cuser.month, cuser.day);
+ break;
+ case 'm':
+ w += snprintf(&strbuf[w], size - w,
+ "%d", cuser.money);
+ break;
+#else
+
+#if 0
+ case 'm':
+ w += snprintf(&strbuf[w], size - w,
+ "%s", money_level(cuser.money));
+ break;
+#endif
+
+#endif
+
+ case 'l':
+ w += snprintf(&strbuf[w], size - w,
+ "%d", cuser.numlogins);
+ break;
+ case 'p':
+ w += snprintf(&strbuf[w], size - w,
+ "%d", cuser.numposts);
+ break;
+ case 'n':
+ strlcpy(strbuf+w, cuser.nickname, size-w);
+ w += strlen(strbuf+w);
+ break;
+ /* It's saver not to send these undefined escape string.
+ default:
+ strbuf[w++] = ESC_CHR;
+ strbuf[w++] = '*';
+ strbuf[w++] = str[r];
+ */
+ }
+ }
+ }
+ strbuf[w] = 0;
+ strip_ansi(str, strbuf, mode);
+ return str;
+}
+
+int
+Rename(const char *src, const char *dst)
+{
+ char buf[256];
+ if (rename(src, dst) == 0)
+ return 0;
+ if (!strchr(src, ';') && !strchr(dst, ';'))
+ // Ptt 防不正常指令 // XXX 這樣是不夠的
+ {
+ snprintf(buf, sizeof(buf), "/bin/mv %s %s", src, dst);
+ system(buf);
+ }
+ return -1;
+}
+
+int
+Copy(const char *src, const char *dst)
+{
+ int fi, fo, bytes;
+ char buf[8192];
+ fi=open(src, O_RDONLY);
+ if(fi<0) return -1;
+ fo=open(dst, O_WRONLY | O_TRUNC | O_CREAT, 0600);
+ if(fo<0) {close(fi); return -1;}
+ while((bytes=read(fi, buf, sizeof(buf)))>0)
+ write(fo, buf, bytes);
+ close(fo);
+ close(fi);
+ return 0;
+}
+
+int
+CopyN(const char *src, const char *dst, int n)
+{
+ int fi, fo, bytes;
+ char buf[8192];
+
+ fi=open(src, O_RDONLY);
+ if(fi<0) return -1;
+
+ fo=open(dst, O_WRONLY | O_TRUNC | O_CREAT, 0600);
+ if(fo<0) {close(fi); return -1;}
+
+ while(n > 0 && (bytes=read(fi, buf, sizeof(buf)))>0)
+ {
+ n -= bytes;
+ if (n < 0)
+ bytes += n;
+ write(fo, buf, bytes);
+ }
+ close(fo);
+ close(fi);
+ return 0;
+}
+
+/* append data from tail of src (starting point=off) to dst */
+int
+AppendTail(const char *src, const char *dst, int off)
+{
+ int fi, fo, bytes;
+ char buf[8192];
+
+ fi=open(src, O_RDONLY);
+ if(fi<0) return -1;
+
+ fo=open(dst, O_WRONLY | O_APPEND | O_CREAT, 0600);
+ if(fo<0) {close(fi); return -1;}
+
+ if(off > 0)
+ lseek(fi, (off_t)off, SEEK_SET);
+
+ while((bytes=read(fi, buf, sizeof(buf)))>0)
+ {
+ write(fo, buf, bytes);
+ }
+ close(fo);
+ close(fi);
+ return 0;
+}
+
+int
+Link(const char *src, const char *dst)
+{
+ if (strcmp(src, BBSHOME "/home") == 0)
+ return 1;
+ if (symlink(src, dst) == 0)
+ return 0;
+
+ return Copy(src, dst);
+}
+
+char *
+my_ctime(const time4_t * t, char *ans, int len)
+{
+ struct tm *tp;
+
+ tp = localtime4((time4_t*)t);
+ snprintf(ans, len,
+ "%02d/%02d/%02d %02d:%02d:%02d", (tp->tm_year % 100),
+ tp->tm_mon + 1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec);
+ return ans;
+}
diff --git a/pttbbs/mbbsd/lovepaper.c b/pttbbs/mbbsd/lovepaper.c
new file mode 100644
index 00000000..8e9f5267
--- /dev/null
+++ b/pttbbs/mbbsd/lovepaper.c
@@ -0,0 +1,107 @@
+/* $Id$ */
+#include "bbs.h"
+#define DATA "etc/lovepaper.dat"
+
+int
+x_love(void)
+{
+ char buf1[200], save_title[TTLEN + 1];
+ char receiver[61], path[STRLEN] = "home/";
+ int x, y = 0, tline = 0, poem = 0;
+ FILE *fp, *fpo;
+ struct tm *gtime;
+ fileheader_t mhdr;
+
+ setutmpmode(LOVE);
+ gtime = localtime4(&now);
+ snprintf(buf1, sizeof(buf1), "%c/%s/love%d%d",
+ cuser.userid[0], cuser.userid, gtime->tm_sec, gtime->tm_min);
+ strcat(path, buf1);
+ move(1, 0);
+ clrtobot();
+
+ outs("\n歡迎使用情書產生器 v0.00 板 \n");
+ outs("有何難以啟齒的話,交由系統幫你說吧.\n爸爸說 : 濫情不犯法.\n");
+
+ if (!getdata(7, 0, "收信人:", receiver, sizeof(receiver), DOECHO))
+ return 0;
+ if (receiver[0] && !(searchuser(receiver, receiver) &&
+ getdata(8, 0, "主 題:", save_title,
+ sizeof(save_title), DOECHO))) {
+ move(10, 0);
+ vmsg("收信人或主題不正確,情書無法傳遞");
+ return 0;
+ }
+ fpo = fopen(path, "w");
+ assert(fpo);
+ fprintf(fpo, "\n");
+ if ((fp = fopen(DATA, "r"))) {
+ while (fgets(buf1, 100, fp)) {
+ switch (buf1[0]) {
+ case '#':
+ break;
+ case '@':
+ if (!strncmp(buf1, "@begin", 6) || !strncmp(buf1, "@end", 4))
+ tline = 3;
+ else if (!strncmp(buf1, "@poem", 5)) {
+ poem = 1;
+ tline = 1;
+ fprintf(fpo, "\n\n");
+ } else
+ tline = 2;
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ sscanf(buf1, "%d", &x);
+ y = (random() % (x - 1)) * tline;
+ break;
+ default:
+ if (!poem) {
+ if (y > 0)
+ y = y - 1;
+ else {
+ if (tline > 0) {
+ fputs(buf1, fpo);
+ tline--;
+ }
+ }
+ } else {
+ if (buf1[0] == '$')
+ y--;
+ else if (y == 0)
+ fputs(buf1, fpo);
+ }
+ }
+
+ }
+
+ fclose(fp);
+ fclose(fpo);
+ if (vedit(path, YEA, NULL) == -1) {
+ unlink(path);
+ clear();
+ outs("\n\n 放棄寄情書\n");
+ pressanykey();
+ return -2;
+ }
+ sethomepath(buf1, receiver);
+ stampfile(buf1, &mhdr);
+ Rename(path, buf1);
+ strlcpy(mhdr.title, save_title, sizeof(mhdr.title));
+ strlcpy(mhdr.owner, cuser.userid, sizeof(mhdr.owner));
+ sethomedir(path, receiver);
+ if (append_record(path, &mhdr, sizeof(mhdr)) == -1)
+ return -1;
+ hold_mail(buf1, receiver);
+ return 1;
+ }
+ fclose(fpo);
+ return 0;
+}
diff --git a/pttbbs/mbbsd/mail.c b/pttbbs/mbbsd/mail.c
new file mode 100644
index 00000000..2cc3e886
--- /dev/null
+++ b/pttbbs/mbbsd/mail.c
@@ -0,0 +1,1941 @@
+/* $Id$ */
+#include "bbs.h"
+static int mailkeep = 0, mailsum = 0;
+static int mailsumlimit = 0, mailmaxkeep = 0;
+static char currmaildir[32];
+static char msg_cc[] = ANSI_COLOR(32) "[群組名單]" ANSI_RESET "\n";
+static char listfile[] = "list.0";
+
+enum SHOWMAIL_MODES {
+ SHOWMAIL_NORM = 0,
+ SHOWMAIL_SUM,
+ SHOWMAIL_RANGE,
+};
+static int showmail_mode = SHOWMAIL_NORM;
+
+int
+setforward(void)
+{
+ char buf[80], ip[50] = "", yn[4];
+ FILE *fp;
+ int flIdiotSent2Self = 0;
+ int oidlen = strlen(cuser.userid);
+
+ sethomepath(buf, cuser.userid);
+ strcat(buf, "/.forward");
+ if ((fp = fopen(buf, "r"))) {
+ fscanf(fp, "%" toSTR(sizeof(ip)) "s", ip);
+ fclose(fp);
+ }
+ getdata_buf(b_lines - 1, 0, "請輸入自動轉寄的Email: ",
+ ip, sizeof(ip), DOECHO);
+
+ /* anti idiots */
+ if (strncasecmp(ip, cuser.userid, oidlen) == 0)
+ {
+ int addrlen = strlen(ip);
+ if( addrlen == oidlen ||
+ (addrlen > oidlen &&
+ strcasecmp(ip + oidlen, str_mail_address) == 0))
+ flIdiotSent2Self = 1;
+ }
+
+ if (ip[0] && ip[0] != ' ' && !flIdiotSent2Self) {
+ getdata(b_lines, 0, "確定開啟自動轉信功\能?(Y/n)", yn, sizeof(yn),
+ LCECHO);
+ if (yn[0] != 'n' && (fp = fopen(buf, "w"))) {
+ fputs(ip, fp);
+ fclose(fp);
+ vmsg("設定完成!");
+ return 0;
+ }
+ }
+ unlink(buf);
+ if(flIdiotSent2Self)
+ vmsg("自動轉寄是不會設定給自己的,想取消用空白就可以了。");
+ else
+ vmsg("取消自動轉信!");
+ return 0;
+}
+
+int
+toggle_showmail_mode(void)
+{
+ showmail_mode ++;
+ showmail_mode %= SHOWMAIL_RANGE;
+ return FULLUPDATE;
+}
+
+int
+built_mail_index(void)
+{
+ char genbuf[128];
+
+ move(b_lines - 4, 0);
+ outs("本功\能只在信箱檔毀損時使用," ANSI_COLOR(1;33) "無法" ANSI_RESET "救回被刪除的信件。\n"
+ "除非您清楚這個功\能的作用,否則" ANSI_COLOR(1;33) "請不要使用" ANSI_RESET "。\n"
+ "警告:任意的使用將導致" ANSI_COLOR(1;33) "不可預期的結果" ANSI_RESET "!\n");
+ getdata(b_lines - 1, 0,
+ "確定重建信箱?(y/N)", genbuf, 3,
+ LCECHO);
+ if (genbuf[0] != 'y')
+ return 0;
+
+ snprintf(genbuf, sizeof(genbuf),
+ BBSHOME "/bin/buildir " BBSHOME "/home/%c/%s > /dev/null",
+ cuser.userid[0], cuser.userid);
+ mouts(b_lines - 1, 0, ANSI_COLOR(1;31) "已經處理完畢!! 諸多不便 敬請原諒~" ANSI_RESET);
+ system(genbuf);
+ pressanykey();
+ return 0;
+}
+
+int
+sendalert(const char *userid, int alert)
+{
+ userinfo_t *uentp = NULL;
+ int n, tuid, i;
+
+ if ((tuid = searchuser(userid, NULL)) == 0)
+ return -1;
+
+ n = count_logins(tuid, 0);
+ for (i = 1; i <= n; i++)
+ if ((uentp = (userinfo_t *) search_ulistn(tuid, i)))
+ uentp->alerts |= alert;
+ return 0;
+}
+
+int
+mail_muser(userec_t muser, const char *title, const char *filename)
+{
+ return mail_id(muser.userid, title, filename, cuser.userid);
+}
+
+int
+mail_id(const char *id, const char *title, const char *src, const char *owner)
+{
+ fileheader_t mhdr;
+ char dst[128], dirf[128];
+ sethomepath(dst, id);
+ if (stampfile(dst, &mhdr))
+ return 0;
+ strlcpy(mhdr.owner, owner, sizeof(mhdr.owner));
+ strlcpy(mhdr.title, title, sizeof(mhdr.title));
+ mhdr.filemode = 0;
+ Copy(src, dst);
+
+ sethomedir(dirf, id);
+ append_record_forward(dirf, &mhdr, sizeof(mhdr), id);
+ sendalert(id, ALERT_NEW_MAIL);
+ return 0;
+}
+
+int
+invalidaddr(const char *addr)
+{
+#ifdef DEBUG_FWDADDRERR
+ const char *origaddr = addr;
+ char errmsg[PATHLEN];
+#endif
+
+ if (*addr == '\0')
+ return 1; /* blank */
+
+ while (*addr) {
+#ifdef DEBUG_FWDADDRERR
+ if (not_alnum(*addr) && !strchr("[].@-_+", *addr))
+ {
+ int c = (*addr) & 0xff;
+ clear();
+ move(2,0);
+ outs(
+ "您輸入的位址錯誤 (address error)。 \n\n"
+ "由於最近許\多人反應打入正確的位址(id或email)後系統會判斷錯誤\n"
+ "但檢查不出原因,所以我們需要正確的錯誤回報。\n\n"
+ "如果你確實打錯了,請直接略過下面的說明。\n"
+ "如果你認為你輸入的位址確實是對的,請把下面的訊息複製起來\n"
+ "並貼到 SYSOP 或 PttBug 板。本站為造成不便深感抱歉。\n\n"
+ ANSI_COLOR(1;33));
+ sprintf(errmsg, "原始輸入位址: [%s]\n"
+ "錯誤位置: 第 %d 字元: 0x%02X [ %c ]\n",
+ origaddr, (int)(addr - origaddr+1), c, c);
+ outs(errmsg);
+ outs(ANSI_RESET);
+ vmsg("請按任意鍵繼續");
+ clear();
+ return 1;
+ }
+#else
+ if (not_alnum(*addr) && !strchr("[].@-_", *addr))
+ return 1;
+#endif
+ addr++;
+ }
+ return 0;
+}
+
+int
+m_internet(void)
+{
+ char receiver[60];
+
+ getdata(20, 0, "收信人:", receiver, sizeof(receiver), DOECHO);
+ trim(receiver);
+ if (strchr(receiver, '@') && !invalidaddr(receiver) &&
+ getdata(21, 0, "主 題:", save_title, STRLEN, DOECHO))
+ do_send(receiver, save_title);
+ else {
+ vmsg("收信人或主題不正確,請重新選取指令");
+ }
+ return 0;
+}
+
+void
+m_init(void)
+{
+ sethomedir(currmaildir, cuser.userid);
+}
+
+void
+setupmailusage(void)
+{ // Ptt: get_sum_records is a bad function
+ int max_keepmail = MAX_KEEPMAIL;
+#ifdef PLAY_ANGEL
+ if (HasUserPerm(PERM_SYSSUPERSUBOP | PERM_ANGEL))
+#else
+ if (HasUserPerm(PERM_SYSSUPERSUBOP))
+#endif
+ {
+ mailsumlimit = 900;
+ max_keepmail = 700;
+ }
+ else if (HasUserPerm(PERM_SYSSUBOP | PERM_ACCTREG | PERM_PRG |
+ PERM_ACTION | PERM_PAINT)) {
+ mailsumlimit = 700;
+ max_keepmail = 500;
+ } else if (HasUserPerm(PERM_BM)) {
+ mailsumlimit = 500;
+ max_keepmail = 300;
+ } else if (HasUserPerm(PERM_LOGINOK))
+ mailsumlimit = 200;
+ else
+ mailsumlimit = 50;
+ mailsumlimit += (cuser.exmailbox + ADD_EXMAILBOX) * 10;
+ mailmaxkeep = max_keepmail + cuser.exmailbox;
+ mailkeep=get_num_records(currmaildir,sizeof(fileheader_t));
+ mailsum =get_sum_records(currmaildir, sizeof(fileheader_t));
+}
+
+#define MAILBOX_LIM_OK 0
+#define MAILBOX_LIM_KEEP 1
+#define MAILBOX_LIM_SUM 2
+static int
+chk_mailbox_limit(void)
+{
+ if (HasUserPerm(PERM_SYSOP) || HasUserPerm(PERM_MAILLIMIT))
+ return MAILBOX_LIM_OK;
+
+ if (!mailkeep)
+ setupmailusage();
+
+ if (mailkeep > mailmaxkeep)
+ return MAILBOX_LIM_KEEP;
+ if (mailsum > mailsumlimit)
+ return MAILBOX_LIM_SUM;
+ return MAILBOX_LIM_OK;
+}
+
+int
+chkmailbox(void)
+{
+ m_init();
+
+ switch (chk_mailbox_limit()) {
+ case MAILBOX_LIM_KEEP:
+ bell();
+ bell();
+ vmsgf("您保存信件數目 %d 超出上限 %d, 請整理", mailkeep, mailmaxkeep);
+ return mailkeep;
+
+ case MAILBOX_LIM_SUM:
+ bell();
+ bell();
+ vmsgf("信箱容量(大小,非件數) %d 超出上限 %d, "
+ "請砍過長的水球記錄或信件", mailsum, mailsumlimit);
+ if(showmail_mode != SHOWMAIL_SUM)
+ {
+ showmail_mode = SHOWMAIL_SUM;
+ vmsg("信箱顯示模式已自動改為顯示大小,請盡速整理");
+ }
+ return mailsum;
+
+ default:
+ return 0;
+ }
+}
+
+static void
+do_hold_mail(const char *fpath, const char *receiver, const char *holder)
+{
+ char buf[80], title[128];
+
+ fileheader_t mymail;
+
+ sethomepath(buf, holder);
+ stampfile(buf, &mymail);
+
+ mymail.filemode = FILE_READ ;
+ strlcpy(mymail.owner, "[備.忘.錄]", sizeof(mymail.owner));
+ if (receiver) {
+ snprintf(title, sizeof(title), "(%s) %s", receiver, save_title);
+ strlcpy(mymail.title, title, sizeof(mymail.title));
+ } else
+ strlcpy(mymail.title, save_title, sizeof(mymail.title));
+
+ sethomedir(title, holder);
+
+ unlink(buf);
+ Copy(fpath, buf);
+ append_record_forward(title, &mymail, sizeof(mymail), holder);
+}
+
+void
+hold_mail(const char *fpath, const char *receiver)
+{
+ char buf[4];
+
+ getdata(b_lines - 1, 0, "已順利寄出,是否自存底稿(Y/N)?[N] ",
+ buf, sizeof(buf), LCECHO);
+
+ if (buf[0] == 'y')
+ do_hold_mail(fpath, receiver, cuser.userid);
+}
+
+int
+do_send(const char *userid, const char *title)
+{
+ fileheader_t mhdr;
+ char fpath[STRLEN];
+ char receiver[IDLEN + 1];
+ char genbuf[200];
+ int internet_mail, i;
+ userec_t xuser;
+
+ STATINC(STAT_DOSEND);
+ if (strchr(userid, '@'))
+ internet_mail = 1;
+ else {
+ internet_mail = 0;
+ if (!getuser(userid, &xuser))
+ return -1;
+ if (!(xuser.userlevel & PERM_READMAIL))
+ return -3;
+
+ curredit |= EDIT_MAIL;
+ curredit &= ~EDIT_ITEM;
+ }
+ /* process title */
+ if (title)
+ strlcpy(save_title, title, sizeof(save_title));
+ else
+ getdata(2, 0, "主題:", save_title, STRLEN - 20, DOECHO);
+
+ setutmpmode(SMAIL);
+
+ fpath[0] = '\0';
+
+ if (internet_mail) {
+ int res, ch;
+
+ if (vedit(fpath, NA, NULL) == -1) {
+ unlink(fpath);
+ clear();
+ return -2;
+ }
+ clear();
+ prints("信件即將寄給 %s\n標題為:%s\n確定要寄出嗎? (Y/N) [Y]",
+ userid, save_title);
+ ch = igetch();
+ switch (ch) {
+ case 'N':
+ case 'n':
+ outs("N\n信件已取消");
+ res = -2;
+ break;
+ default:
+ outs("Y\n請稍候, 信件傳遞中...\n");
+ res =
+#ifndef USE_BSMTP
+ bbs_sendmail(fpath, save_title, userid);
+#else
+ bsmtp(fpath, save_title, userid);
+#endif
+ hold_mail(fpath, userid);
+ }
+ unlink(fpath);
+ return res;
+ } else {
+ strlcpy(receiver, userid, sizeof(receiver));
+ sethomepath(genbuf, userid);
+ stampfile(genbuf, &mhdr);
+ strlcpy(mhdr.owner, cuser.userid, sizeof(mhdr.owner));
+ if (vedit(genbuf, YEA, NULL) == -1) {
+ unlink(genbuf);
+ clear();
+ return -2;
+ }
+ /* why not make title here? */
+ strlcpy(mhdr.title, save_title, sizeof(mhdr.title));
+ clear();
+ sethomefile(fpath, userid, FN_OVERRIDES);
+ i = belong(fpath, cuser.userid);
+ sethomefile(fpath, userid, FN_REJECT);
+
+ if (i || !belong(fpath, cuser.userid)) {/* Ptt: 用belong有點討厭 */
+ sethomedir(fpath, userid);
+ if (append_record_forward(fpath, &mhdr, sizeof(mhdr), userid) == -1)
+ return -1;
+ sendalert(userid,ALERT_NEW_MAIL);
+ }
+ hold_mail(genbuf, userid);
+ return 0;
+ }
+}
+
+void
+my_send(const char *uident)
+{
+ switch (do_send(uident, NULL)) {
+ case -1:
+ outs(err_uid);
+ break;
+ case -2:
+ outs(msg_cancel);
+ break;
+ case -3:
+ prints("使用者 [%s] 無法收信", uident);
+ break;
+ }
+ pressanykey();
+}
+
+int
+m_send(void)
+{
+ char uident[40];
+
+ stand_title("且聽風的話");
+ usercomplete(msg_uid, uident);
+ showplans(uident);
+ if (uident[0])
+ my_send(uident);
+ return 0;
+}
+
+/* 群組寄信、回信 : multi_send, multi_reply */
+static void
+multi_list(int *reciper)
+{
+ char uid[16];
+ char genbuf[200];
+
+ while (1) {
+ stand_title("群組寄信名單");
+ ShowNameList(3, 0, msg_cc);
+ move(1, 0);
+ outs("(I)引入好友 (O)引入上線通知 (N)引入新文章通知 (0-9)引入其他特別名單");
+ getdata(2, 0,
+ "(A)增加 (D)刪除 (M)確認寄信名單 (Q)取消 ?[M]",
+ genbuf, 4, LCECHO);
+ switch (genbuf[0]) {
+ case 'a':
+ while (1) {
+ move(1, 0);
+ usercomplete("請輸入要增加的代號(只按 ENTER 結束新增): ", uid);
+ if (uid[0] == '\0')
+ break;
+
+ move(2, 0);
+ clrtoeol();
+
+ if (!searchuser(uid, uid))
+ outs(err_uid);
+ else if (!InNameList(uid)) {
+ AddNameList(uid);
+ (*reciper)++;
+ }
+ ShowNameList(3, 0, msg_cc);
+ }
+ break;
+ case 'd':
+ while (*reciper) {
+ move(1, 0);
+ namecomplete("請輸入要刪除的代號(只按 ENTER 結束刪除): ", uid);
+ if (uid[0] == '\0')
+ break;
+ if (RemoveNameList(uid))
+ (*reciper)--;
+ ShowNameList(3, 0, msg_cc);
+ }
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ listfile[5] = genbuf[0];
+ genbuf[0] = '1';
+ case 'i':
+ setuserfile(genbuf, genbuf[0] == '1' ? listfile : fn_overrides);
+ ToggleNameList(reciper, genbuf, msg_cc);
+ break;
+ case 'o':
+ setuserfile(genbuf, "alohaed");
+ ToggleNameList(reciper, genbuf, msg_cc);
+ break;
+ case 'n':
+ setuserfile(genbuf, "postlist");
+ ToggleNameList(reciper, genbuf, msg_cc);
+ break;
+ case 'q':
+ *reciper = 0;
+ return;
+ default:
+ return;
+ }
+ }
+}
+
+static void
+multi_send(char *title)
+{
+ FILE *fp;
+ struct word_t *p = NULL;
+ fileheader_t mymail;
+ char fpath[TTLEN], *ptr;
+ int reciper, listing;
+ char genbuf[256];
+
+ CreateNameList();
+ listing = reciper = 0;
+ if (*quote_file) {
+ AddNameList(quote_user);
+ reciper = 1;
+ fp = fopen(quote_file, "r");
+ assert(fp);
+ while (fgets(genbuf, sizeof(genbuf), fp)) {
+ if (strncmp(genbuf, "※ ", 3)) {
+ if (listing)
+ break;
+ } else {
+ if (listing) {
+ char *strtok_pos;
+ ptr = genbuf + 3;
+ for (ptr = strtok_r(ptr, " \n\r", &strtok_pos);
+ ptr;
+ ptr = strtok_r(NULL, " \n\r", &strtok_pos)) {
+ if (searchuser(ptr, ptr) && !InNameList(ptr) &&
+ strcmp(cuser.userid, ptr)) {
+ AddNameList(ptr);
+ reciper++;
+ }
+ }
+ } else if (!strncmp(genbuf + 3, "[通告]", 6))
+ listing = 1;
+ }
+ }
+ fclose(fp);
+ ShowNameList(3, 0, msg_cc);
+ }
+ multi_list(&reciper);
+ move(1, 0);
+ clrtobot();
+
+ if (reciper) {
+ setutmpmode(SMAIL);
+ if (title)
+ do_reply_title(2, title);
+ else {
+ getdata(2, 0, "主題:", fpath, sizeof(fpath), DOECHO);
+ snprintf(save_title, sizeof(save_title), "[通告] %s", fpath);
+ }
+
+ setuserfile(fpath, fn_notes);
+
+ if ((fp = fopen(fpath, "w"))) {
+ fprintf(fp, "※ [通告] 共 %d 人收件", reciper);
+ listing = 80;
+
+ for (p = toplev; p; p = p->next) {
+ reciper = strlen(p->word) + 1;
+ if (listing + reciper > 75) {
+ listing = reciper;
+ fprintf(fp, "\n※");
+ } else
+ listing += reciper;
+
+ fprintf(fp, " %s", p->word);
+ }
+ memset(genbuf, '-', 75);
+ genbuf[75] = '\0';
+ fprintf(fp, "\n%s\n\n", genbuf);
+ fclose(fp);
+ }
+ curredit |= EDIT_LIST;
+
+ if (vedit(fpath, YEA, NULL) == -1) {
+ unlink(fpath);
+ curredit = 0;
+ vmsg(msg_cancel);
+ return;
+ }
+ listing = 80;
+
+ for (p = toplev; p; p = p->next) {
+ reciper = strlen(p->word) + 1;
+ if (listing + reciper > 75) {
+ listing = reciper;
+ outc('\n');
+ } else {
+ listing += reciper;
+ outc(' ');
+ }
+ outs(p->word);
+ if (searchuser(p->word, p->word) && strcmp(STR_GUEST, p->word)) {
+ sethomefile(genbuf, p->word, FN_OVERRIDES);
+ if (!belong(genbuf, cuser.userid)) { // not friend, check if rejected
+ sethomefile(genbuf, p->word, FN_REJECT);
+ if (belong(genbuf, cuser.userid))
+ continue;
+ }
+ sethomepath(genbuf, p->word);
+ } else
+ continue;
+ stampfile(genbuf, &mymail);
+ unlink(genbuf);
+ Copy(fpath, genbuf);
+
+ strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner));
+ strlcpy(mymail.title, save_title, sizeof(mymail.title));
+ mymail.filemode |= FILE_MULTI; /* multi-send flag */
+ sethomedir(genbuf, p->word);
+ if (append_record_forward(genbuf, &mymail, sizeof(mymail), p->word) == -1)
+ vmsg(err_uid);
+ sendalert(p->word, ALERT_NEW_MAIL);
+ }
+ hold_mail(fpath, NULL);
+ unlink(fpath);
+ curredit = 0;
+ } else
+ vmsg(msg_cancel);
+}
+
+static int
+multi_reply(int ent, fileheader_t * fhdr, const char *direct)
+{
+ if (!(fhdr->filemode & FILE_MULTI))
+ return mail_reply(ent, fhdr, direct);
+
+ stand_title("群組回信");
+ strlcpy(quote_user, fhdr->owner, sizeof(quote_user));
+ setuserfile(quote_file, fhdr->filename);
+ multi_send(fhdr->title);
+ quote_user[0]='\0';
+ quote_file[0]='\0';
+ return FULLUPDATE;
+}
+
+int
+mail_list(void)
+{
+ stand_title("群組作業");
+ multi_send(NULL);
+ return 0;
+}
+
+int
+mail_all(void)
+{
+ FILE *fp;
+ fileheader_t mymail;
+ char fpath[TTLEN];
+ char genbuf[200];
+ int i, unum;
+ char *userid;
+
+ stand_title("給所有使用者的系統通告");
+ setutmpmode(SMAIL);
+ getdata(2, 0, "主題:", fpath, sizeof(fpath), DOECHO);
+ snprintf(save_title, sizeof(save_title),
+ "[系統通告]" ANSI_COLOR(1;32) " %s" ANSI_RESET, fpath);
+
+ setuserfile(fpath, fn_notes);
+
+ if ((fp = fopen(fpath, "w"))) {
+ fprintf(fp, "※ [" ANSI_COLOR(1) "系統通告" ANSI_RESET "] 這是封給所有使用者的信\n");
+ fprintf(fp, "-----------------------------------------------------"
+ "----------------------\n");
+ fclose(fp);
+ }
+ *quote_file = 0;
+
+ curredit |= EDIT_MAIL;
+ curredit &= ~EDIT_ITEM;
+ if (vedit(fpath, YEA, NULL) == -1) {
+ curredit = 0;
+ unlink(fpath);
+ outs(msg_cancel);
+ pressanykey();
+ return 0;
+ }
+ curredit = 0;
+
+ setutmpmode(MAILALL);
+ stand_title("寄信中...");
+
+ sethomepath(genbuf, cuser.userid);
+ stampfile(genbuf, &mymail);
+ unlink(genbuf);
+ Copy(fpath, genbuf);
+ unlink(fpath);
+ strcpy(fpath, genbuf);
+
+ strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner)); /* 站長 ID */
+ strlcpy(mymail.title, save_title, sizeof(mymail.title));
+
+ sethomedir(genbuf, cuser.userid);
+ if (append_record_forward(genbuf, &mymail, sizeof(mymail), cuser.userid) == -1)
+ outs(err_uid);
+
+ for (unum = SHM->number, i = 0; i < unum; i++) {
+ if (bad_user_id(SHM->userid[i]))
+ continue; /* Ptt */
+
+ userid = SHM->userid[i];
+ if (strcmp(userid, STR_GUEST) && strcmp(userid, "new") &&
+ strcmp(userid, cuser.userid)) {
+ sethomepath(genbuf, userid);
+ stampfile(genbuf, &mymail);
+ unlink(genbuf);
+ Copy(fpath, genbuf);
+
+ strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner));
+ strlcpy(mymail.title, save_title, sizeof(mymail.title));
+ /* mymail.filemode |= FILE_MARKED; Ptt 公告改成不會mark */
+ sethomedir(genbuf, userid);
+ if (append_record_forward(genbuf, &mymail, sizeof(mymail), userid) == -1)
+ outs(err_uid);
+ vmsgf("%*s %5d / %5d", IDLEN + 1, userid, i + 1, unum);
+ }
+ }
+ return 0;
+}
+
+int
+mail_mbox(void)
+{
+ char cmd[100];
+ fileheader_t fhdr;
+
+ snprintf(cmd, sizeof(cmd), "/tmp/%s.uu", cuser.userid);
+ snprintf(fhdr.title, sizeof(fhdr.title), "%s 私人資料", cuser.userid);
+ doforward(cmd, &fhdr, 'Z');
+ return 0;
+}
+
+static int
+m_forward(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char uid[STRLEN];
+
+ stand_title("轉達信件");
+ usercomplete(msg_uid, uid);
+ if (uid[0] == '\0')
+ return FULLUPDATE;
+
+ strlcpy(quote_user, fhdr->owner, sizeof(quote_user));
+ setuserfile(quote_file, fhdr->filename);
+ snprintf(save_title, sizeof(save_title), "%.64s (fwd)", fhdr->title);
+ move(1, 0);
+ clrtobot();
+ prints("轉信給: %s\n標 題: %s\n", uid, save_title);
+
+ switch (do_send(uid, save_title)) {
+ case -1:
+ outs(err_uid);
+ break;
+ case -2:
+ outs(msg_cancel);
+ break;
+ case -3:
+ prints("使用者 [%s] 無法收信", uid);
+ break;
+ }
+ pressanykey();
+ quote_user[0]='\0';
+ quote_file[0]='\0';
+ if (strcasecmp(uid, cuser.userid) == 0)
+ return DIRCHANGED;
+ return FULLUPDATE;
+}
+
+struct ReadNewMailArg {
+ int idc;
+ int *delmsgs;
+ int delcnt;
+ int mrd;
+};
+
+static int
+read_new_mail(void * voidfptr, void *optarg)
+{
+ fileheader_t *fptr=(fileheader_t*)voidfptr;
+ struct ReadNewMailArg *arg=(struct ReadNewMailArg*)optarg;
+ char done = NA, delete_it;
+ char fname[PATHLEN];
+ char genbuf[4];
+
+ arg->idc++;
+ if (fptr->filemode)
+ return 0;
+ clear();
+ move(10, 0);
+ prints("您要讀來自[%s]的訊息(%s)嗎?", fptr->owner, fptr->title);
+ getdata(11, 0, "請您確定(Y/N/Q)?[Y] ", genbuf, 3, DOECHO);
+ if (genbuf[0] == 'q')
+ return QUIT;
+ if (genbuf[0] == 'n')
+ return 0;
+
+ setuserfile(fname, fptr->filename);
+ fptr->filemode |= FILE_READ;
+ if (substitute_record(currmaildir, fptr, sizeof(*fptr), arg->idc))
+ return -1;
+
+ arg->mrd = 1;
+ delete_it = NA;
+ while (!done) {
+ int more_result = more(fname, YEA);
+
+ switch (more_result) {
+ case 999:
+ return mail_reply(arg->idc, fptr, currmaildir);
+ case -1:
+ return READ_SKIP;
+ case 0:
+ break;
+ default:
+ return more_result;
+ }
+
+ outmsglr(MSG_MAILER, MSG_MAILER_LEN, "", 0);
+
+ switch (igetch()) {
+ case 'r':
+ case 'R':
+ mail_reply(arg->idc, fptr, currmaildir);
+ break;
+ case 'x':
+ m_forward(arg->idc, fptr, currmaildir);
+ break;
+ case 'y':
+ multi_reply(arg->idc, fptr, currmaildir);
+ break;
+ case 'd':
+ case 'D':
+ delete_it = YEA;
+ default:
+ done = YEA;
+ }
+ }
+ if (delete_it) {
+ if(arg->delcnt==1000) {
+ vmsg("一次最多刪 1000 封信");
+ return 0;
+ }
+ clear();
+ prints("刪除信件《%s》", fptr->title);
+ getdata(1, 0, msg_sure_ny, genbuf, 2, LCECHO);
+ if (genbuf[0] == 'y') {
+ if(arg->delmsgs==NULL) {
+ arg->delmsgs=(int*)malloc(sizeof(int)*1000);
+ if(arg->delmsgs==NULL) {
+ vmsg("失敗, 請洽站長");
+ return 0;
+ }
+ }
+ unlink(fname);
+ arg->delmsgs[arg->delcnt++] = arg->idc;
+ mailsum = mailkeep = 0;
+ }
+ }
+ clear();
+ return 0;
+}
+
+void setmailalert()
+{
+ if(load_mailalert(cuser.userid))
+ currutmp->alerts |= ALERT_NEW_MAIL;
+ else
+ currutmp->alerts &= ~ALERT_NEW_MAIL;
+}
+int
+m_new(void)
+{
+ struct ReadNewMailArg arg;
+ clear();
+ setutmpmode(RMAIL);
+ memset(&arg, 0, sizeof(arg));
+ clear();
+ curredit |= EDIT_MAIL;
+ curredit &= ~EDIT_ITEM;
+ if (apply_record(currmaildir, read_new_mail, sizeof(fileheader_t), &arg) == -1) {
+ if(arg.delmsgs)
+ free(arg.delmsgs);
+ vmsg("沒有新信件了");
+ return -1;
+ }
+ curredit = 0;
+ setmailalert();
+ while (arg.delcnt--)
+ delete_record(currmaildir, sizeof(fileheader_t), arg.delmsgs[arg.delcnt]);
+ if(arg.delmsgs)
+ free(arg.delmsgs);
+ vmsg(arg.mrd ? "信已閱\畢" : "沒有新信件了");
+ return -1;
+}
+
+static void
+mailtitle(void)
+{
+ char buf[STRLEN];
+ int msglen = 0;
+
+ showtitle("郵件選單", BBSName);
+ prints("[←]離開[↑↓]選擇[→]閱\讀信件 [R]回信 [x]轉達 "
+ "[y]群組回信 [O]站外信:%s [h]求助\n"
+ ANSI_COLOR(7) " 編號 %s 作 者 信 件 標 題"
+ "",
+ REJECT_OUTTAMAIL ? ANSI_COLOR(31) "關" ANSI_RESET : "開",
+ (showmail_mode == SHOWMAIL_SUM) ? "大 小":"日 期");
+
+ /* 43 columns in length, used later. */
+ buf[0] = 0;
+
+ if (mailsumlimit)
+ {
+ /* warning: snprintf returns length "if not limited".
+ * however if this case, they should be the same. */
+
+ msglen = snprintf(buf, sizeof(buf),
+ ANSI_COLOR(32)
+ " (容量:%d/%dk %d/%d篇) ",
+ mailsum, mailsumlimit,
+ mailkeep, mailmaxkeep);
+ msglen -= strlen(ANSI_COLOR(32));
+ }
+ outslr("", 44, buf, msglen);
+ outs(ANSI_RESET);
+}
+
+static void
+maildoent(int num, fileheader_t * ent)
+{
+ char *title, *mark, *color = NULL, type = ' ';
+ char datepart[6];
+
+ if (ent->filemode & FILE_MARKED)
+ {
+ type = (ent->filemode & FILE_READ) ?
+ 'm' : 'M';
+ }
+ else if (ent->filemode & FILE_REPLIED)
+ {
+ type = (ent->filemode & FILE_READ) ?
+ 'r' : 'R';
+ }
+ else
+ {
+ type = (ent->filemode & FILE_READ) ?
+ ' ' : '+';
+ }
+
+ if (TagNum && !Tagger(atoi(ent->filename + 2), 0, TAG_NIN))
+ type = 'D';
+
+ title = subject(mark = ent->title);
+ if (title == mark) {
+ color = ANSI_COLOR(1;31);
+ mark = "◇";
+ } else {
+ color = ANSI_COLOR(1;33);
+ mark = "R:";
+ }
+
+ strlcpy(datepart, ent->date, sizeof(datepart));
+
+ switch(showmail_mode)
+ {
+ case SHOWMAIL_SUM:
+ {
+ /* evaluate size */
+ size_t filesz = 0;
+ char ut = 'k';
+ char buf[MAXPATHLEN];
+ struct stat st;
+
+ if( !ent->filename[0] ){
+ filesz = 0;
+ } else {
+ setuserfile(buf, ent->filename);
+ if (stat(buf, &st) >= 0) {
+ filesz = st.st_size;
+ /* find printing unit */
+ filesz = (filesz + 1023) / 1024;
+ if(filesz > 9999){
+ filesz = (filesz+512) / 1024;
+ ut = 'M';
+ }
+ if(filesz > 9999) {
+ filesz = (filesz+512) / 1024;
+ ut = 'G';
+ }
+ }
+ }
+ sprintf(datepart, "%4lu%c", (unsigned long)filesz, ut);
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* print out */
+ if (strncmp(currtitle, title, TTLEN) != 0)
+ {
+ /* is title. */
+ color = "";
+ }
+
+ prints("%6d %c %-6s%-15.14s%s %s%-*.*s%s\n",
+ num, type, datepart, ent->owner, mark, color,
+ t_columns - 34, t_columns - 34,
+ title,
+ *color ? ANSI_RESET : "");
+}
+
+
+static int
+mail_del(int ent, const fileheader_t * fhdr, const char *direct)
+{
+ char genbuf[200];
+
+ if (fhdr->filemode & FILE_MARKED)
+ return DONOTHING;
+
+ if (currmode & MODE_SELECT) {
+ vmsg("請先回到正常模式後再進行刪除...");
+ return READ_REDRAW;
+ }
+
+ if (getans(msg_del_ny) == 'y') {
+ if (!delete_record(direct, sizeof(*fhdr), ent)) {
+ setupmailusage();
+ setdirpath(genbuf, direct, fhdr->filename);
+ unlink(genbuf);
+ mailsum = mailkeep = 0;
+ return DIRCHANGED;
+ }
+ }
+ return READ_REDRAW;
+}
+
+static int
+mail_read(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char buf[PATHLEN];
+ char done, delete_it, replied;
+
+ clear();
+ setdirpath(buf, direct, fhdr->filename);
+ strlcpy(currtitle, subject(fhdr->title), sizeof(currtitle));
+ done = delete_it = replied = NA;
+ while (!done) {
+ int more_result = more(buf, YEA);
+
+ /* whether success or not, update flag.
+ * or users may bug about "black-hole" mails
+ * and blinking notification */
+ if( !(fhdr->filemode & FILE_READ))
+ {
+ fhdr->filemode |= FILE_READ;
+ substitute_ref_record(direct, fhdr, ent);
+ }
+ switch (more_result) {
+ case -1:
+ /* no such file */
+ clear();
+ vmsg("此封信無內容。");
+ return FULLUPDATE;
+ case 999:
+ return mail_reply(ent, fhdr, direct);
+ case 0:
+ break;
+ default:
+ return more_result;
+ }
+ outmsglr(MSG_MAILER, MSG_MAILER_LEN, "", 0);
+
+ switch (igetch()) {
+ case 'r':
+ case 'R':
+ replied = YEA;
+ mail_reply(ent, fhdr, direct);
+ break;
+ case 'x':
+ m_forward(ent, fhdr, direct);
+ break;
+ case 'y':
+ multi_reply(ent, fhdr, direct);
+ break;
+ case 'd':
+ delete_it = YEA;
+ default:
+ done = YEA;
+ }
+ }
+ if (delete_it)
+ mail_del(ent, fhdr, direct);
+ else {
+ fhdr->filemode |= FILE_READ;
+ substitute_ref_record(direct, fhdr, ent);
+ }
+ return FULLUPDATE;
+}
+
+/* in boards/mail 回信給原作者,轉信站亦可 */
+int
+mail_reply(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char uid[STRLEN];
+ FILE *fp;
+ char genbuf[512];
+ int oent = ent;
+
+ stand_title("回 信");
+
+ /* 判斷是 boards 或 mail */
+ if (curredit & EDIT_MAIL)
+ setuserfile(quote_file, fhdr->filename);
+ else
+ setbfile(quote_file, currboard, fhdr->filename);
+
+ /* find the author */
+ strlcpy(quote_user, fhdr->owner, sizeof(quote_user));
+ if (strchr(quote_user, '.')) {
+ char *t;
+ char *strtok_pos;
+ genbuf[0] = '\0';
+ if ((fp = fopen(quote_file, "r"))) {
+ fgets(genbuf, sizeof(genbuf), fp);
+ fclose(fp);
+ }
+ t = strtok_r(genbuf, str_space, &strtok_pos);
+ if (t && (strcmp(t, str_author1)==0 || strcmp(t, str_author2)==0)
+ && (t=strtok_r(NULL, str_space, &strtok_pos)) != NULL)
+ strlcpy(uid, t, sizeof(uid));
+ else {
+ vmsg("錯誤: 找不到作者。");
+ quote_user[0]='\0';
+ quote_file[0]='\0';
+ return FULLUPDATE;
+ }
+ } else
+ strlcpy(uid, quote_user, sizeof(uid));
+
+ /* make the title */
+ do_reply_title(3, fhdr->title);
+ prints("\n收信人: %s\n標 題: %s\n", uid, save_title);
+
+ /* edit, then send the mail */
+ ent = curredit;
+ switch (do_send(uid, save_title)) {
+ case -1:
+ outs(err_uid);
+ break;
+ case -2:
+ outs(msg_cancel);
+ break;
+ case -3:
+ prints("使用者 [%s] 無法收信", uid);
+ break;
+
+ case 0:
+ /* success */
+ if ( direct && /* for board, no direct */
+ (curredit & EDIT_MAIL) &&
+ !(fhdr->filemode & FILE_REPLIED))
+ {
+ fhdr->filemode |= FILE_REPLIED;
+ substitute_ref_record(direct, fhdr, oent);
+ }
+ break;
+ }
+ curredit = ent;
+ pressanykey();
+ quote_user[0]='\0';
+ quote_file[0]='\0';
+ if (strcasecmp(uid, cuser.userid) == 0)
+ return DIRCHANGED;
+ return FULLUPDATE;
+}
+
+static int
+mail_edit(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char genbuf[200];
+
+ if (!HasUserPerm(PERM_SYSOP))
+ return DONOTHING;
+
+ setdirpath(genbuf, direct, fhdr->filename);
+ vedit(genbuf, NA, NULL);
+ return FULLUPDATE;
+}
+
+static int
+mail_nooutmail(int ent, fileheader_t * fhdr, const char *direct)
+{
+ cuser.uflag2 ^= REJ_OUTTAMAIL;
+ passwd_update(usernum, &cuser);
+ return FULLUPDATE;
+
+}
+
+static int
+mail_mark(int ent, fileheader_t * fhdr, const char *direct)
+{
+ fhdr->filemode ^= FILE_MARKED;
+
+ substitute_ref_record(direct, fhdr, ent);
+ return PART_REDRAW;
+}
+
+/* help for mail reading */
+static const char * const mail_help[] = {
+ "\0電子信箱操作說明",
+ "\01基本命令",
+ "(p/↑)(n/↓) 前一篇/下一篇文章",
+ "(P)(PgUp) 前一頁",
+ "(N)(PgDn) 下一頁",
+ "(數字鍵) 跳到第 ## 筆",
+ "($) 跳到最後一筆",
+ "(r)(→) 讀信",
+ "(R)/(y) 回信 / 群組回信",
+ "\01進階命令",
+ "(TAB) 切換顯示模式(目前有一般及顯示大小)",
+ "(O) 關閉/開啟 站外信件轉入",
+ "(c)/(z) 此信件收入私人信件夾/進入私人信件夾",
+ "(x)/(X) 轉信給其它使用者/轉錄文章到其他看板",
+ "(F)/(u) 將信傳送回您的電子信箱/水球整理寄回信箱",
+ "(d) 殺掉此信",
+ "(D) 殺掉指定範圍的信",
+ "(m) 將信標記,以防被清除",
+ "(^G) 立即重建信箱 (信箱毀損時用)",
+ "(t) 標記欲刪除信件",
+ "(^D) 刪除已標記信件",
+ NULL
+};
+
+static int
+m_help(void)
+{
+ show_help(mail_help);
+ return FULLUPDATE;
+}
+
+static int
+mail_cross_post(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char xboard[20], fname[80], xfpath[80], xtitle[80], inputbuf[10];
+ fileheader_t xfile;
+ FILE *xptr;
+ int author = 0;
+ char genbuf[200];
+ char genbuf2[4];
+
+ if (!CheckPostPerm()) {
+ vmsg("對不起,您目前無法轉錄文章!");
+ return FULLUPDATE;
+ }
+ move(2, 0);
+ clrtoeol();
+ move(1, 0);
+ CompleteBoard("轉錄本文章於看板:", xboard);
+ if (*xboard == '\0' || !haspostperm(xboard))
+ return FULLUPDATE;
+
+ /* 借用變數 */
+ ent = StringHash(fhdr->title);
+ /* 同樣 title 不管對哪個板都算 cross post , 所以不用檢查 author */
+
+ if ((ent != 0 && ent == postrecord.checksum[0])) {
+ /* 檢查 cross post 次數 */
+ if (postrecord.times++ > MAX_CROSSNUM)
+ anticrosspost();
+ } else {
+ postrecord.times = 0;
+ postrecord.last_bid = 0;
+ postrecord.checksum[0] = ent;
+ }
+
+ ent = getbnum(xboard);
+ assert(0<=ent-1 && ent-1<MAX_BOARD);
+ if ( !(HasUserPerm(PERM_SYSOP)) &&
+ (cuser.firstlogin > (now - (time4_t)bcache[ent - 1].post_limit_regtime * 2592000) ||
+ cuser.badpost > (255 - (unsigned int)(bcache[ent - 1].post_limit_badpost)) ||
+ cuser.numlogins < ((unsigned int)(bcache[ent - 1].post_limit_logins) * 10) ||
+ cuser.numposts < ((unsigned int)(bcache[ent - 1].post_limit_posts) * 10)) ) {
+ move(5, 10);
+ vmsg("你不夠資深喔! (可在看板內按大寫 I 查看限制)");
+ return FULLUPDATE;
+ }
+
+#ifdef USE_COOLDOWN
+ if(check_cooldown(&bcache[ent - 1]))
+ return READ_REDRAW;
+#endif
+
+ ent = 1;
+ if (HasUserPerm(PERM_SYSOP) || !strcmp(fhdr->owner, cuser.userid)) {
+ getdata(2, 0, "(1)原文轉載 (2)舊轉錄格式?[1] ",
+ genbuf, 3, DOECHO);
+ if (genbuf[0] != '2') {
+ ent = 0;
+ getdata(2, 0, "保留原作者名稱嗎?[Y] ", inputbuf, 3, DOECHO);
+ if (inputbuf[0] != 'n' && inputbuf[0] != 'N')
+ author = 1;
+ }
+ }
+ if (ent)
+ snprintf(xtitle, sizeof(xtitle), "[轉錄]%.66s", fhdr->title);
+ else
+ strlcpy(xtitle, fhdr->title, sizeof(xtitle));
+
+ snprintf(genbuf, sizeof(genbuf), "採用原標題《%.60s》嗎?[Y] ", xtitle);
+ getdata(2, 0, genbuf, genbuf2, sizeof(genbuf2), LCECHO);
+ if (*genbuf2 == 'n')
+ if (getdata(2, 0, "標題:", genbuf, TTLEN, DOECHO))
+ strlcpy(xtitle, genbuf, sizeof(xtitle));
+
+ getdata(2, 0, "(S)存檔 (L)站內 (Q)取消?[Q] ", genbuf, 3, LCECHO);
+ if (genbuf[0] == 'l' || genbuf[0] == 's') {
+ int currmode0 = currmode;
+
+ currmode = 0;
+ setbpath(xfpath, xboard);
+ stampfile(xfpath, &xfile);
+ if (author)
+ strlcpy(xfile.owner, fhdr->owner, sizeof(xfile.owner));
+ else
+ strlcpy(xfile.owner, cuser.userid, sizeof(xfile.owner));
+ strlcpy(xfile.title, xtitle, sizeof(xfile.title));
+ if (genbuf[0] == 'l') {
+ xfile.filemode = FILE_LOCAL;
+ }
+ setuserfile(fname, fhdr->filename);
+ {
+ const char *save_currboard;
+ xptr = fopen(xfpath, "w");
+ assert(xptr);
+
+ strlcpy(save_title, xfile.title, sizeof(save_title));
+ save_currboard = currboard;
+ currboard = xboard;
+ write_header(xptr, save_title);
+ currboard = save_currboard;
+
+ fprintf(xptr, "※ [本文轉錄自 %s 信箱]\n\n", cuser.userid);
+
+ b_suckinfile(xptr, fname);
+ addsignature(xptr, 0);
+ fclose(xptr);
+ }
+
+ setbdir(fname, xboard);
+ append_record(fname, &xfile, sizeof(xfile));
+ setbtotal(getbnum(xboard));
+ if (!xfile.filemode)
+ outgo_post(&xfile, xboard, cuser.userid, cuser.nickname);
+#ifdef USE_COOLDOWN
+ if (bcache[getbnum(xboard) - 1].brdattr & BRD_COOLDOWN)
+ add_cooldowntime(usernum, 5);
+ add_posttimes(usernum, 1);
+#endif
+
+ if (strcmp(xboard, "Test") == 0)
+ outs("測試信件不列入紀錄,敬請包涵。");
+ else
+ cuser.numposts++;
+
+ vmsg("文章轉錄完成");
+ currmode = currmode0;
+ }
+ return FULLUPDATE;
+}
+
+int
+mail_man(void)
+{
+ char buf[PATHLEN], buf1[64];
+ int mode0 = currutmp->mode;
+ int stat0 = currstat;
+
+ sethomeman(buf, cuser.userid);
+ snprintf(buf1, sizeof(buf1), "%s 的信件夾", cuser.userid);
+ a_menu(buf1, buf, HasUserPerm(PERM_MAILLIMIT), NULL);
+ currutmp->mode = mode0;
+ currstat = stat0;
+ return FULLUPDATE;
+}
+
+static int
+mail_cite(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char fpath[PATHLEN];
+ char title[TTLEN + 1];
+ static char xboard[20];
+ char buf[20];
+ int bid;
+
+ setuserfile(fpath, fhdr->filename);
+ strlcpy(title, "◇ ", sizeof(title));
+ strlcpy(title + 3, fhdr->title, sizeof(title) - 3);
+ a_copyitem(fpath, title, 0, 1);
+
+ if (cuser.userlevel >= PERM_BM) {
+ move(2, 0);
+ clrtoeol();
+ move(3, 0);
+ clrtoeol();
+ move(1, 0);
+
+ CompleteBoard("輸入看板名稱 (直接Enter進入私人信件夾):", buf);
+ if (*buf)
+ strlcpy(xboard, buf, sizeof(xboard));
+ if (*xboard && ((bid = getbnum(xboard)) > 0)){ /* XXXbid */
+ setapath(fpath, xboard);
+ setutmpmode(ANNOUNCE);
+ a_menu(xboard, fpath,
+ HasUserPerm(PERM_ALLBOARD) ? 2 : is_BM_cache(bid) ? 1 : 0,
+ NULL);
+ } else {
+ mail_man();
+ }
+ return FULLUPDATE;
+ } else {
+ mail_man();
+ return FULLUPDATE;
+ }
+}
+
+static int
+mail_save(int ent, fileheader_t * fhdr, const char *direct)
+{
+ char fpath[PATHLEN];
+ char title[TTLEN + 1];
+
+ if (HasUserPerm(PERM_MAILLIMIT)) {
+ setuserfile(fpath, fhdr->filename);
+ strlcpy(title, "◇ ", sizeof(title));
+ strlcpy(title + 3, fhdr->title, sizeof(title) - 3);
+ a_copyitem(fpath, title, fhdr->owner, 1);
+ sethomeman(fpath, cuser.userid);
+ a_menu(cuser.userid, fpath, 1, NULL);
+ return FULLUPDATE;
+ }
+ return DONOTHING;
+}
+
+#ifdef OUTJOBSPOOL
+static int
+mail_waterball(int ent, fileheader_t * fhdr, const char *direct)
+{
+ static char address[60], cmode = 1;
+ char fname[500], genbuf[200];
+ FILE *fp;
+
+ if (!(strstr(fhdr->title, "熱線") && strstr(fhdr->title, "記錄"))) {
+ vmsg("必須是 熱線記錄 才能使用水球整理的唷!");
+ return 1;
+ }
+ if (!address[0])
+ strlcpy(address, cuser.email, sizeof(address));
+ move(b_lines - 8, 0);
+ outs("水球整理程式:\n"
+ "系統將會按照和不同人丟的水球各自獨立\n"
+ "於整點的時候 (尖峰時段除外) 將資料整理好寄送給您\n\n\n");
+ if (address[0]) {
+ snprintf(genbuf, sizeof(genbuf), "寄給 [%s] 嗎(Y/N/Q)?[Y] ", address);
+ getdata(b_lines - 5, 0, genbuf, fname, 3, LCECHO);
+ if (fname[0] == 'q') {
+ outmsg("取消處理");
+ return 1;
+ }
+ if (fname[0] == 'n')
+ address[0] = '\0';
+ }
+ if (!address[0]) {
+ getdata(b_lines - 5, 0, "請輸入郵件地址:", fname, 60, DOECHO);
+ if (fname[0] && strchr(fname, '.')) {
+ strlcpy(address, fname, sizeof(address));
+ } else {
+ vmsg("取消處理");
+ return 1;
+ }
+ }
+ trim(address);
+ if (invalidaddr(address))
+ return -2;
+ if( strstr(address, ".bbs") && REJECT_OUTTAMAIL ){
+ move(b_lines - 4, 0);
+ outs("\n您必須要打開接受站外信, 水球整理系統才能寄入結果\n"
+ "請麻煩到【郵件選單】按大寫 O改成接受站外信 (在右上角)\n"
+ "再重新執行本功\能 :)\n");
+ vmsg("請打開站外信, 再重新執行本功\能");
+ return FULLUPDATE;
+ }
+
+ //snprintf(fname, sizeof(fname), "%d\n", cmode);
+ move(b_lines - 4, 0);
+ outs("系統提供兩種模式: \n"
+ "模式 0: 精簡模式, 將不含顏色控制碼, 方便以純文字編輯器整理收藏\n"
+ "模式 1: 華麗模式, 包含顏色控制碼等, 方便在 bbs上直接編輯收藏\n");
+ getdata(b_lines - 1, 0, "使用模式(0/1/Q)? [1]", fname, 3, LCECHO);
+ if (fname[0] == 'Q' || fname[0] == 'q') {
+ outmsg("取消處理");
+ return FULLUPDATE;
+ }
+ cmode = (fname[0] != '0' && fname[0] != '1') ? 1 : fname[0] - '0';
+
+ snprintf(fname, sizeof(fname), BBSHOME "/jobspool/water.src.%s-%d",
+ cuser.userid, (int)now);
+ snprintf(genbuf, sizeof(genbuf), "cp " BBSHOME "/home/%c/%s/%s %s",
+ cuser.userid[0], cuser.userid, fhdr->filename, fname);
+ system(genbuf);
+ /* dirty code ;x */
+ snprintf(fname, sizeof(fname), BBSHOME "/jobspool/water.des.%s-%d",
+ cuser.userid, (int)now);
+ fp = fopen(fname, "wt");
+ assert(fp);
+ fprintf(fp, "%s\n%s\n%d\n", cuser.userid, address, cmode);
+ fclose(fp);
+ vmsg("設定完成, 系統將在下一個整點(尖峰時段除外)將資料寄給您");
+ return FULLUPDATE;
+}
+#endif
+static const onekey_t mail_comms[] = {
+ { 0, NULL }, // Ctrl('A')
+ { 0, NULL }, // Ctrl('B')
+ { 0, NULL }, // Ctrl('C')
+ { 0, NULL }, // Ctrl('D')
+ { 0, NULL }, // Ctrl('E')
+ { 0, NULL }, // Ctrl('F')
+ { 0, built_mail_index }, // Ctrl('G')
+ { 0, NULL }, // Ctrl('H')
+ { 0, toggle_showmail_mode }, // Ctrl('I')
+ { 0, NULL }, // Ctrl('J')
+ { 0, NULL }, // Ctrl('K')
+ { 0, NULL }, // Ctrl('L')
+ { 0, NULL }, // Ctrl('M')
+ { 0, NULL }, // Ctrl('N')
+ { 0, NULL }, // Ctrl('O')
+ { 0, NULL }, // Ctrl('P')
+ { 0, NULL }, // Ctrl('Q')
+ { 0, NULL }, // Ctrl('R')
+ { 0, NULL }, // Ctrl('S')
+ { 0, NULL }, // Ctrl('T')
+ { 0, NULL }, // Ctrl('U')
+ { 0, NULL }, // Ctrl('V')
+ { 0, NULL }, // Ctrl('W')
+ { 0, NULL }, // Ctrl('X')
+ { 0, NULL }, // Ctrl('Y')
+ { 0, NULL }, // Ctrl('Z') 26
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, // 'A' 65
+ { 0, NULL }, // 'B'
+ { 0, NULL }, // 'C'
+ { 1, del_range }, // 'D'
+ { 1, mail_edit }, // 'E'
+ { 0, NULL }, // 'F'
+ { 0, NULL }, // 'G'
+ { 0, NULL }, // 'H'
+ { 0, NULL }, // 'I'
+ { 0, NULL }, // 'J'
+ { 0, NULL }, // 'K'
+ { 0, NULL }, // 'L'
+ { 0, NULL }, // 'M'
+ { 0, NULL }, // 'N'
+ { 1, mail_nooutmail }, // 'O'
+ { 0, NULL }, // 'P'
+ { 0, NULL }, // 'Q'
+ { 1, mail_reply }, // 'R'
+ { 0, NULL }, // 'S'
+ { 1, edit_title }, // 'T'
+ { 0, NULL }, // 'U'
+ { 0, NULL }, // 'V'
+ { 0, NULL }, // 'W'
+ { 1, mail_cross_post }, // 'X'
+ { 0, NULL }, // 'Y'
+ { 0, NULL }, // 'Z' 90
+ { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL },
+ { 0, NULL }, // 'a' 97
+ { 0, NULL }, // 'b'
+ { 1, mail_cite }, // 'c'
+ { 1, mail_del }, // 'd'
+ { 0, NULL }, // 'e'
+ { 0, NULL }, // 'f'
+ { 0, NULL }, // 'g'
+ { 0, m_help }, // 'h'
+ { 0, NULL }, // 'i'
+ { 0, NULL }, // 'j'
+ { 0, NULL }, // 'k'
+ { 0, NULL }, // 'l'
+ { 1, mail_mark }, // 'm'
+ { 0, NULL }, // 'n'
+ { 0, NULL }, // 'o'
+ { 0, NULL }, // 'p'
+ { 0, NULL }, // 'q'
+ { 1, mail_read }, // 'r'
+ { 1, mail_save }, // 's'
+ { 0, NULL }, // 't'
+#ifdef OUTJOBSPOOL
+ { 1, mail_waterball }, // 'u'
+#else
+ { 0, NULL }, // 'u'
+#endif
+ { 0, NULL }, // 'v'
+ { 0, NULL }, // 'w'
+ { 1, m_forward }, // 'x'
+ { 1, multi_reply }, // 'y'
+ { 0, mail_man }, // 'z' 122
+};
+
+int
+m_read(void)
+{
+ int back_bid;
+ if (get_num_records(currmaildir, sizeof(fileheader_t))) {
+ curredit = EDIT_MAIL;
+ curredit &= ~EDIT_ITEM;
+ back_bid = currbid;
+ currbid = 0;
+ i_read(RMAIL, currmaildir, mailtitle, maildoent, mail_comms, -1);
+ currbid = back_bid;
+ curredit = 0;
+ setmailalert();
+ return 0;
+ } else {
+ outs("您沒有來信");
+ return XEASY;
+ }
+}
+
+/* 寄站內信 */
+static int
+send_inner_mail(const char *fpath, const char *title, const char *receiver)
+{
+ char fname[PATHLEN];
+ fileheader_t mymail;
+ char rightid[IDLEN+1];
+
+ if (!searchuser(receiver, rightid))
+ return -2;
+
+ /* to avoid DDOS of disk */
+ sethomedir(fname, rightid);
+ if (strcmp(rightid, cuser.userid) == 0) {
+ if (chk_mailbox_limit())
+ return -4;
+ }
+
+ sethomepath(fname, rightid);
+ stampfile(fname, &mymail);
+ if (!strcmp(rightid, cuser.userid)) {
+ /* Using BBSNAME may be too loooooong. */
+ strlcpy(mymail.owner, "[站內]", sizeof(mymail.owner));
+ mymail.filemode = FILE_READ;
+ } else
+ strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner));
+ strlcpy(mymail.title, title, sizeof(mymail.title));
+ unlink(fname);
+ Copy(fpath, fname);
+ sethomedir(fname, rightid);
+ append_record_forward(fname, &mymail, sizeof(mymail), rightid);
+ sendalert(receiver, ALERT_NEW_MAIL);
+ return 0;
+}
+
+#include <netdb.h>
+#include <pwd.h>
+#include <time.h>
+
+#ifndef USE_BSMTP
+static int
+bbs_sendmail(const char *fpath, const char *title, char *receiver)
+{
+ char *ptr;
+ char genbuf[256];
+ FILE *fin, *fout;
+
+ /* 中途攔截 */
+ if ((ptr = strchr(receiver, ';'))) {
+ *ptr = '\0';
+ }
+ if ((ptr = strstr(receiver, str_mail_address)) || !strchr(receiver, '@')) {
+ char hacker[20];
+ int len;
+
+ if (strchr(receiver, '@')) {
+ len = ptr - receiver;
+ memcpy(hacker, receiver, len);
+ hacker[len] = '\0';
+ } else
+ strlcpy(hacker, receiver, sizeof(hacker));
+ return send_inner_mail(fpath, title, hacker);
+ }
+ /* Running the sendmail */
+ if (fpath == NULL) {
+ snprintf(genbuf, sizeof(genbuf),
+ "/usr/sbin/sendmail %s > /dev/null", receiver);
+ fin = fopen("etc/confirm", "r");
+ } else {
+ snprintf(genbuf, sizeof(genbuf),
+ "/usr/sbin/sendmail -f %s%s %s > /dev/null",
+ cuser.userid, str_mail_address, receiver);
+ fin = fopen(fpath, "r");
+ }
+ if (fin == NULL)
+ return -1;
+ fout = popen(genbuf, "w");
+ if (fout == NULL) {
+ fclose(fin);
+ return -1;
+ }
+
+ if (fpath)
+ fprintf(fout, "Reply-To: %s%s\nFrom: %s <%s%s>\n",
+ cuser.userid, str_mail_address,
+ cuser.nickname,
+ cuser.userid, str_mail_address);
+ fprintf(fout,"To: %s\nSubject: %s\n"
+ "Mime-Version: 1.0\r\n"
+ "Content-Type: text/plain; charset=\"big5\"\r\n"
+ "Content-Transfer-Encoding: 8bit\r\n"
+ "X-Disclaimer: " BBSNAME "對本信內容恕不負責。\n\n",
+ receiver, title);
+
+ while (fgets(genbuf, sizeof(genbuf), fin)) {
+ if (genbuf[0] == '.' && genbuf[1] == '\n')
+ fputs(". \n", fout);
+ else
+ fputs(genbuf, fout);
+ }
+ fclose(fin);
+ fprintf(fout, ".\n");
+ pclose(fout);
+ return 0;
+}
+#else /* USE_BSMTP */
+
+int
+bsmtp(const char *fpath, const char *title, const char *rcpt)
+{
+ char buf[80], *ptr;
+ time4_t chrono;
+ MailQueue mqueue;
+
+ /* check if the mail is a inner mail */
+ if ((ptr = strstr(rcpt, str_mail_address)) || !strchr(rcpt, '@')) {
+ char hacker[20];
+ int len;
+
+ if (strchr(rcpt, '@')) {
+ len = ptr - rcpt;
+ memcpy(hacker, rcpt, len);
+ hacker[len] = '\0';
+ } else
+ strlcpy(hacker, rcpt, sizeof(hacker));
+ return send_inner_mail(fpath, title, hacker);
+ }
+ chrono = now;
+
+ /* stamp the queue file */
+ strlcpy(buf, "out/", sizeof(buf));
+ for (;;) {
+ snprintf(buf + 4, sizeof(buf) - 4, "M.%d.%d.A", (int)++chrono, getpid());
+ if (!dashf(buf)) {
+ Copy(fpath, buf);
+ break;
+ }
+ }
+
+ fpath = buf;
+
+ /* setup mail queue */
+ mqueue.mailtime = chrono;
+ // XXX (unused) mqueue.method = method;
+ strlcpy(mqueue.filepath, fpath, sizeof(mqueue.filepath));
+ strlcpy(mqueue.subject, title, sizeof(mqueue.subject));
+ strlcpy(mqueue.sender, cuser.userid, sizeof(mqueue.sender));
+ strlcpy(mqueue.username, cuser.nickname, sizeof(mqueue.username));
+ strlcpy(mqueue.rcpt, rcpt, sizeof(mqueue.rcpt));
+
+ if (append_record("out/.DIR", (fileheader_t *) & mqueue, sizeof(mqueue)) < 0)
+ return 0;
+ return chrono;
+}
+#endif /* USE_BSMTP */
+
+int
+doforward(const char *direct, const fileheader_t * fh, int mode)
+{
+ static char address[STRLEN] = "";
+ char fname[PATHLEN];
+ char genbuf[PATHLEN];
+ int return_no;
+
+ if (!address[0])
+ strlcpy(address, cuser.email, sizeof(address));
+
+ if( mode == 'U' ){
+ vmsg("將進行 uuencode 。若您不清楚什麼是 uuencode 請改用 F轉寄。");
+ }
+ trim(address);
+
+ if (address[0]) {
+ snprintf(genbuf, sizeof(genbuf),
+ "確定轉寄給 [%s] 嗎(Y/N/Q)?[Y] ", address);
+ getdata(b_lines, 0, genbuf, fname, 3, LCECHO);
+
+ if (fname[0] == 'q') {
+ outmsg("取消轉寄");
+ return 1;
+ }
+ if (fname[0] == 'n')
+ address[0] = '\0';
+ }
+ if (!address[0]) {
+ do {
+ getdata(b_lines - 1, 0, "請輸入轉寄地址:", fname, 60, DOECHO);
+ if (fname[0]) {
+ if (strchr(fname, '.'))
+ strlcpy(address, fname, sizeof(address));
+ else
+ snprintf(address, sizeof(address),
+ "%s.bbs@%s", fname, MYHOSTNAME);
+ } else {
+ vmsg("取消轉寄");
+ return 1;
+ }
+ } while (mode == 'Z' && strstr(address, MYHOSTNAME));
+ }
+ /* according to our experiment, many users leave blanks */
+ trim(address);
+ if (invalidaddr(address))
+ return -2;
+
+ outmsg("正轉寄請稍候...");
+ refresh();
+
+ /* 追蹤使用者 */
+ if (HasUserPerm(PERM_LOGUSER))
+ log_user("mailforward to %s ",address);
+ if (mode == 'Z') {
+ snprintf(fname, sizeof(fname),
+ TAR_PATH " cfz /tmp/home.%s.tgz home/%c/%s; "
+ MUTT_PATH " -a /tmp/home.%s.tgz -s 'home.%s.tgz' '%s' </dev/null;"
+ "rm /tmp/home.%s.tgz",
+ cuser.userid, cuser.userid[0], cuser.userid,
+ cuser.userid, cuser.userid, address, cuser.userid);
+ system(fname);
+ return 0;
+ snprintf(fname, sizeof(fname), TAR_PATH " cfz - home/%c/%s | "
+ "/usr/bin/uuencode %s.tgz > %s",
+ cuser.userid[0], cuser.userid, cuser.userid, direct);
+ system(fname);
+ strlcpy(fname, direct, sizeof(fname));
+ } else if (mode == 'U') {
+ char tmp_buf[128];
+
+ snprintf(fname, sizeof(fname), "/tmp/bbs.uu%05d", (int)currpid);
+ snprintf(tmp_buf, sizeof(tmp_buf),
+ "/usr/bin/uuencode %s/%s uu.%05d > %s",
+ direct, fh->filename, (int)currpid, fname);
+ system(tmp_buf);
+ } else if (mode == 'F') {
+ char tmp_buf[128];
+
+ snprintf(fname, sizeof(fname), "/tmp/bbs.f%05d", (int)currpid);
+ snprintf(tmp_buf, sizeof(tmp_buf), "%s/%s", direct, fh->filename);
+ Copy(tmp_buf, fname);
+ } else
+ return -1;
+
+ return_no =
+#ifndef USE_BSMTP
+ bbs_sendmail(fname, fh->title, address);
+#else
+ bsmtp(fname, fh->title, address);
+#endif
+ unlink(fname);
+ return (return_no);
+}
+
+int
+load_mailalert(const char *userid)
+{
+ struct stat st;
+ char maildir[MAXPATHLEN];
+ int fd;
+ register int num;
+ fileheader_t my_mail;
+
+ sethomedir(maildir, userid);
+ if (!HasUserPerm(PERM_BASIC))
+ return 0;
+ if (stat(maildir, &st) < 0)
+ return 0;
+ num = st.st_size / sizeof(fileheader_t);
+ if (num <= 0)
+ return 0;
+ if (num > 50) num = 50; //check only 50 mails
+
+ /* 看看有沒有信件還沒讀過?從檔尾回頭檢查,效率較高 */
+ if ((fd = open(maildir, O_RDONLY)) > 0) {
+ lseek(fd, st.st_size - sizeof(fileheader_t), SEEK_SET);
+ while (num--) {
+ read(fd, &my_mail, sizeof(fileheader_t));
+ if (!(my_mail.filemode & FILE_READ)) {
+ close(fd);
+ return ALERT_NEW_MAIL;
+ }
+ lseek(fd, -(off_t) 2 * sizeof(fileheader_t), SEEK_CUR);
+ }
+ close(fd);
+ }
+ return 0;
+}
diff --git a/pttbbs/mbbsd/mbbsd.c b/pttbbs/mbbsd/mbbsd.c
new file mode 100644
index 00000000..c2f7d48b
--- /dev/null
+++ b/pttbbs/mbbsd/mbbsd.c
@@ -0,0 +1,2101 @@
+/* $Id$ */
+#ifdef DEBUG
+#define TELOPTS
+#define TELCMDS
+#endif
+#include "bbs.h"
+#include "banip.h"
+
+#ifdef __linux__
+# ifdef CRITICAL_MEMORY
+# include <malloc.h>
+# endif
+# ifdef DEBUG
+# include <mcheck.h>
+# endif
+#endif
+
+
+#define SOCKET_QLEN 4
+
+static void do_aloha(const char *hello);
+static void getremotename(const struct sockaddr_in * from, char *rhost, char *rname);
+
+#ifdef CONVERT
+void big2gb_init(void*);
+void gb2big_init(void*);
+void big2uni_init(void*);
+void uni2big_init(void*);
+#endif
+
+#if 0
+static jmp_buf byebye;
+#endif
+
+static char remoteusername[40] = "?";
+
+static unsigned char enter_uflag;
+static int use_shell_login_mode = 0;
+#ifdef DETECT_CLIENT
+Fnv32_t client_code=FNV1_32_INIT;
+#endif
+
+#ifdef USE_RFORK
+#define fork() rfork(RFFDG | RFPROC | RFNOWAIT)
+#endif
+
+/* set signal handler, which won't be reset once signal comes */
+static void
+signal_restart(int signum, void (*handler) (int))
+{
+ struct sigaction act;
+ act.sa_handler = handler;
+ memset(&(act.sa_mask), 0, sizeof(sigset_t));
+ act.sa_flags = 0;
+ sigaction(signum, &act, NULL);
+}
+
+static void
+start_daemon(void)
+{
+ int n, fd;
+
+ /*
+ * More idiot speed-hacking --- the first time conversion makes the C
+ * library open the files containing the locale definition and time zone.
+ * If this hasn't happened in the parent process, it happens in the
+ * children, once per connection --- and it does add up.
+ */
+ time_t dummy = time(NULL);
+ struct tm *dummy_time = localtime(&dummy);
+ char buf[32];
+
+ strftime(buf, sizeof(buf), "%d/%b/%Y:%H:%M:%S", dummy_time);
+
+#ifndef NO_FORK
+ if ((n = fork())) {
+ exit(0);
+ }
+#endif
+
+ /* rocker.011018: it's a good idea to close all unexcept fd!! */
+#ifndef VALGRIND
+ n = getdtablesize();
+ while (n)
+ close(--n);
+
+ if( ((fd = open("log/stderr", O_WRONLY | O_CREAT | O_APPEND, 0644)) >= 0) && fd != 2 ){
+ dup2(fd, 2);
+ close(fd);
+ }
+#endif
+
+ if(getenv("SSH_CLIENT"))
+ unsetenv("SSH_CLIENT");
+
+ /*
+ * rocker.011018: we don't need to remember original tty, so request a
+ * new session id
+ */
+ setsid();
+
+ /*
+ * rocker.011018: after new session, we should insure the process is
+ * clean daemon
+ */
+#ifndef NO_FORK
+ if ((n = fork())) {
+ exit(0);
+ }
+#endif
+}
+
+static void
+reapchild(int sig)
+{
+ int state, pid;
+
+ while ((pid = waitpid(-1, &state, WNOHANG | WUNTRACED)) > 0);
+}
+
+void
+log_usies(const char *mode, const char *mesg)
+{
+ now = time(NULL);
+ if (!mesg)
+ log_file(FN_USIES, LOG_CREAT | LOG_VF,
+ "%s %s %-12s Stay:%d (%s)\n",
+ Cdate(&now), mode, cuser.userid ,
+ (int)(now - login_start_time) / 60, cuser.nickname);
+ else
+ log_file(FN_USIES, LOG_CREAT | LOG_VF,
+ "%s %s %-12s %s\n",
+ Cdate(&now), mode, cuser.userid, mesg);
+
+ /* 追蹤使用者 */
+ if (HasUserPerm(PERM_LOGUSER))
+ log_user("logout");
+}
+
+
+static void
+setflags(int mask, int value)
+{
+ if (value)
+ cuser.uflag |= mask;
+ else
+ cuser.uflag &= ~mask;
+}
+
+void
+u_exit(const char *mode)
+{
+ int diff = (time(0) - login_start_time) / 60;
+ int dirty = currmode & MODE_DIRTY;
+
+ currmode = 0;
+
+ /* close fd 0 & 1 to terminate network */
+ close(0);
+ close(1);
+
+ assert(strncmp(currutmp->userid,cuser.userid, IDLEN)==0);
+ if(strncmp(currutmp->userid,cuser.userid, IDLEN)!=0)
+ return;
+
+ reload_money();
+ /*
+ cuser.goodpost = currutmp->goodpost;
+ cuser.badpost = currutmp->badpost;
+ cuser.goodsale = currutmp->goodsale;
+ cuser.badsale = currutmp->badsale;
+ */
+
+ auto_backup();
+ setflags(PAGER_FLAG, currutmp->pager != PAGER_ON);
+ setflags(CLOAK_FLAG, currutmp->invisible);
+ save_brdbuf();
+ brc_finalize();
+
+ cuser.invisible = currutmp->invisible;
+ cuser.withme = currutmp->withme;
+ cuser.pager = currutmp->pager;
+ memcpy(cuser.mind, currutmp->mind, 4);
+ setutmpbid(0);
+
+ if (!SHM->GV2.e.shutdown) {
+ if (!(HasUserPerm(PERM_SYSOP) && HasUserPerm(PERM_SYSOPHIDE)) &&
+ !currutmp->invisible)
+ do_aloha("<<下站通知>> -- 我走囉!");
+ }
+
+
+ if ((cuser.uflag != enter_uflag) || dirty || diff) {
+ if (!diff && cuser.numlogins)
+ cuser.numlogins = --cuser.numlogins;
+ /* Leeym 上站停留時間限制式 */
+ }
+ passwd_update(usernum, &cuser);
+ purge_utmp(currutmp);
+ log_usies(mode, NULL);
+}
+
+void
+abort_bbs(int sig)
+{
+ /* ignore normal signals */
+ Signal(SIGALRM, SIG_IGN);
+ Signal(SIGUSR1, SIG_IGN);
+ Signal(SIGUSR2, SIG_IGN);
+ Signal(SIGHUP, SIG_IGN);
+ Signal(SIGTERM, SIG_IGN);
+ Signal(SIGPIPE, SIG_IGN);
+ if (currmode)
+ u_exit("ABORTED");
+ exit(0);
+}
+
+#ifdef GCC_NORETURN
+static void abort_bbs_debug(int sig) GCC_NORETURN;
+#endif
+
+/* NOTE: It's better to use signal-safe functions. Avoid to call
+ * functions with global/static variable -- data may be corrupted */
+static void
+abort_bbs_debug(int sig)
+{
+ int i;
+ sigset_t sigset;
+
+ switch(sig) {
+ case SIGINT: STATINC(STAT_SIGINT); break;
+ case SIGQUIT: STATINC(STAT_SIGQUIT); break;
+ case SIGILL: STATINC(STAT_SIGILL); break;
+ case SIGABRT: STATINC(STAT_SIGABRT); break;
+ case SIGFPE: STATINC(STAT_SIGFPE); break;
+ case SIGBUS: STATINC(STAT_SIGBUS); break;
+ case SIGSEGV: STATINC(STAT_SIGSEGV); break;
+ case SIGXCPU: STATINC(STAT_SIGXCPU); break;
+ }
+ /* ignore normal signals */
+ Signal(SIGALRM, SIG_IGN);
+ Signal(SIGUSR1, SIG_IGN);
+ Signal(SIGUSR2, SIG_IGN);
+ Signal(SIGHUP, SIG_IGN);
+ Signal(SIGTERM, SIG_IGN);
+ Signal(SIGPIPE, SIG_IGN);
+
+ /* unblock */
+ sigemptyset(&sigset);
+ sigaddset(&sigset, SIGINT);
+ sigaddset(&sigset, SIGQUIT);
+ sigaddset(&sigset, SIGILL);
+ sigaddset(&sigset, SIGABRT);
+ sigaddset(&sigset, SIGFPE);
+ sigaddset(&sigset, SIGBUS);
+ sigaddset(&sigset, SIGSEGV);
+ sigaddset(&sigset, SIGXCPU);
+ sigprocmask(SIG_UNBLOCK, &sigset, NULL);
+
+#define CRASH_MSG ANSI_COLOR(0) "\r\n程式異常, 立刻斷線. 請洽 PttBug 板詳述你發生的問題.\r\n"
+#define XCPU_MSG ANSI_COLOR(0) "\r\n程式耗用過多計算資源, 立刻斷線. 可能是 (a)執行太多耗用資源的動作 或 (b)程式掉入無窮迴圈. 請洽 PttBug 板詳述你發生的問題.\r\n"
+ if(sig==SIGXCPU)
+ write(1, XCPU_MSG, sizeof(XCPU_MSG));
+ else
+ write(1, CRASH_MSG, sizeof(CRASH_MSG));
+
+ /* close all file descriptors (including the network connection) */
+ for (i = 0; i < 256; ++i)
+ close(i);
+
+ /* log */
+ /* assume vsnprintf() in log_file() is signal-safe, is it? */
+ log_file("log/crash.log", LOG_VF|LOG_CREAT,
+ "%ld %d %d %.12s\n", time4(NULL), getpid(), sig, cuser.userid);
+
+ /* try logout... not a good idea, maybe crash again. now disabled */
+ /*
+ if (currmode) {
+ currmode = 0;
+ u_exit("AXXED");
+ }
+ */
+
+#ifdef DEBUGSLEEP
+
+#ifndef VALGRIND
+ setproctitle("debug me!(%d)(%s,%d)", sig, cuser.userid, currstat);
+#endif
+ /* do this manually to prevent broken stuff */
+ /* will broken currutmp cause problems here? hope not... */
+ if(currutmp && strncmp(cuser.userid, currutmp->userid, IDLEN) == EQUSTR)
+ currutmp->mode = DEBUGSLEEPING;
+
+ sleep(3600); /* wait 60 mins for debug */
+#endif
+
+ exit(0);
+}
+
+/* 登錄 BBS 程式 */
+static void
+mysrand(void)
+{
+ srandom(time(NULL) + getpid()); /* 時間跟 pid 當 rand 的 seed */
+}
+
+void
+talk_request(int sig)
+{
+ STATINC(STAT_TALKREQUEST);
+ bell();
+ bell();
+ if (currutmp->msgcount) {
+ char timebuf[100];
+#ifdef OUTTA_TIMER
+ now = SHM->GV2.e.now;
+#else
+ now = time(0);
+#endif
+
+ move(0, 0);
+ clrtoeol();
+ prints(ANSI_COLOR(33;41) "★%s" ANSI_COLOR(34;47) " [%s] %s " ANSI_COLOR(0) "",
+ SHM->uinfo[currutmp->destuip].userid, my_ctime(&now,timebuf,sizeof(timebuf)),
+ (currutmp->sig == 2) ? "重要消息廣播!(請Ctrl-U,l查看熱訊記錄)"
+ : "呼叫、呼叫,聽到請回答");
+ refresh();
+ } else {
+ unsigned char mode0 = currutmp->mode;
+ char c0 = currutmp->chatid[0];
+ screen_backup_t old_screen;
+
+ currutmp->mode = 0;
+ currutmp->chatid[0] = 1;
+ screen_backup(&old_screen);
+ talkreply();
+ currutmp->mode = mode0;
+ currutmp->chatid[0] = c0;
+ screen_restore(&old_screen);
+ }
+}
+
+void
+show_call_in(int save, int which)
+{
+ char buf[200];
+#ifdef PLAY_ANGEL
+ if (currutmp->msgs[which].msgmode == MSGMODE_TOANGEL)
+ snprintf(buf, sizeof(buf), ANSI_COLOR(1;37;46) "★%s" ANSI_COLOR(37;45) " %s " ANSI_RESET,
+ currutmp->msgs[which].userid, currutmp->msgs[which].last_call_in);
+ else
+#endif
+ snprintf(buf, sizeof(buf), ANSI_COLOR(1;33;46) "★%s" ANSI_COLOR(37;45) " %s " ANSI_RESET,
+ currutmp->msgs[which].userid, currutmp->msgs[which].last_call_in);
+ outmsg(buf);
+
+ if (save) {
+ char genbuf[200];
+ if (!fp_writelog) {
+ sethomefile(genbuf, cuser.userid, fn_writelog);
+ fp_writelog = fopen(genbuf, "a");
+ }
+ if (fp_writelog) {
+ fprintf(fp_writelog, "%s [%s]\n", buf, Cdatelite(&now));
+ }
+ }
+}
+
+static int
+add_history_water(water_t * w, const msgque_t * msg)
+{
+ memcpy(&w->msg[w->top], msg, sizeof(msgque_t));
+ w->top++;
+ w->top %= WATERMODE(WATER_OFO) ? 5 : MAX_REVIEW;
+
+ if (w->count < MAX_REVIEW)
+ w->count++;
+
+ return w->count;
+}
+
+static int
+add_history(const msgque_t * msg)
+{
+ int i = 0, j, waterinit = 0;
+ water_t *tmp;
+ check_water_init();
+ if (WATERMODE(WATER_ORIG) || WATERMODE(WATER_NEW))
+ add_history_water(&water[0], msg);
+ if (WATERMODE(WATER_NEW) || WATERMODE(WATER_OFO)) {
+ for (i = 0; i < 5 && swater[i]; i++)
+ if (swater[i]->pid == msg->pid
+#ifdef PLAY_ANGEL
+ && swater[i]->msg[0].msgmode == msg->msgmode
+ /* When throwing waterball to angel directly */
+#endif
+ )
+ break;
+ if (i == 5) {
+ waterinit = 1;
+ i = 4;
+ memset(swater[4], 0, sizeof(water_t));
+ } else if (!swater[i]) {
+ water_usies = i + 1;
+ swater[i] = &water[i + 1];
+ waterinit = 1;
+ }
+ tmp = swater[i];
+
+ if (waterinit) {
+ memcpy(swater[i]->userid, msg->userid, sizeof(swater[i]->userid));
+ swater[i]->pid = msg->pid;
+ }
+ if (!swater[i]->uin)
+ swater[i]->uin = currutmp;
+
+ for (j = i; j > 0; j--)
+ swater[j] = swater[j - 1];
+ swater[0] = tmp;
+ add_history_water(swater[0], msg);
+ }
+ if (WATERMODE(WATER_ORIG) || WATERMODE(WATER_NEW)) {
+ if (watermode > 0 &&
+ (water_which == swater[0] || water_which == &water[0])) {
+ if (watermode < water_which->count)
+ watermode++;
+ t_display_new();
+ }
+ }
+ return i;
+}
+
+void
+write_request(int sig)
+{
+ int i, msgcount;
+
+ STATINC(STAT_WRITEREQUEST);
+#ifdef NOKILLWATERBALL
+ if( reentrant_write_request ) /* kill again by shmctl */
+ return;
+ reentrant_write_request = 1;
+#endif
+#ifdef OUTTA_TIMER
+ now = SHM->GV2.e.now;
+#else
+ now = time(0);
+#endif
+ check_water_init();
+ if (WATERMODE(WATER_OFO)) {
+ /* 如果目前正在回水球模式的話, 就不能進行 add_history() ,
+ 因為會改寫 water[], 而使回水球目的爛掉, 所以分成幾種情況考慮.
+ sig != 0表真的有水球進來, 故顯示.
+ sig == 0表示沒有水球進來, 不過之前尚有水球還沒寫到 water[].
+ */
+ static int alreadyshow = 0;
+
+ if( sig ){ /* 真的有水球進來 */
+
+ /* 若原來正在 REPLYING , 則改成 RECVINREPLYING,
+ 這樣在回水球結束後, 會再呼叫一次 write_request(0) */
+ if( wmofo == REPLYING )
+ wmofo = RECVINREPLYING;
+
+ /* 顯示 */
+ for( ; alreadyshow < currutmp->msgcount && alreadyshow < MAX_MSGS
+ ; ++alreadyshow ){
+ bell();
+ show_call_in(1, alreadyshow);
+ refresh();
+ }
+ }
+
+ /* 看看是不是要把 currutmp->msg 拿回 water[] (by add_history())
+ 須要是不在回水球中 (NOTREPLYING) */
+ if( wmofo == NOTREPLYING &&
+ (msgcount = currutmp->msgcount) > 0 ){
+ for( i = 0 ; i < msgcount ; ++i )
+ add_history(&currutmp->msgs[i]);
+ if( (currutmp->msgcount -= msgcount) < 0 )
+ currutmp->msgcount = 0;
+ alreadyshow = 0;
+ }
+ } else {
+ if (currutmp->mode != 0 &&
+ currutmp->pager != PAGER_OFF &&
+ cuser.userlevel != 0 &&
+ currutmp->msgcount != 0 &&
+ currutmp->mode != TALK &&
+ currutmp->mode != EDITING &&
+ currutmp->mode != CHATING &&
+ currutmp->mode != PAGE &&
+ currutmp->mode != IDLE &&
+ currutmp->mode != MAILALL && currutmp->mode != MONITOR) {
+ char c0 = currutmp->chatid[0];
+ int currstat0 = currstat;
+ unsigned char mode0 = currutmp->mode;
+
+ currutmp->mode = 0;
+ currutmp->chatid[0] = 2;
+ currstat = HIT;
+
+#ifdef NOKILLWATERBALL
+ currutmp->wbtime = 0;
+#endif
+ if( (msgcount = currutmp->msgcount) > 0 ){
+ for( i = 0 ; i < msgcount ; ++i ){
+ bell();
+ show_call_in(1, 0);
+ add_history(&currutmp->msgs[0]);
+
+ if( (--currutmp->msgcount) < 0 )
+ i = msgcount; /* force to exit for() */
+ else if( currutmp->msgcount > 0 )
+ memmove(&currutmp->msgs[0],
+ &currutmp->msgs[1],
+ sizeof(msgque_t) * currutmp->msgcount);
+ igetch();
+ }
+ }
+
+ currutmp->chatid[0] = c0;
+ currutmp->mode = mode0;
+ currstat = currstat0;
+ } else {
+ bell();
+ show_call_in(1, 0);
+ add_history(&currutmp->msgs[0]);
+
+ refresh();
+ currutmp->msgcount = 0;
+ }
+ }
+#ifdef NOKILLWATERBALL
+ reentrant_write_request = 0;
+ currutmp->wbtime = 0; /* race */
+#endif
+}
+
+static userinfo_t*
+getotherlogin(int num)
+{
+ userinfo_t *ui;
+ do {
+ if (!(ui = (userinfo_t *) search_ulistn(usernum, num)))
+ return NULL; /* user isn't logged in */
+
+ /* skip sleeping process, this is slow if lots */
+ if(ui->mode == DEBUGSLEEPING)
+ num++;
+ else if(ui->pid <= 0)
+ num++;
+ else if(kill(ui->pid, 0) < 0)
+ num++;
+ else
+ break;
+ } while (1);
+
+ return ui;
+}
+
+static void
+multi_user_check(void)
+{
+ register userinfo_t *ui;
+ char genbuf[3];
+
+ if (HasUserPerm(PERM_SYSOP))
+ return; /* don't check sysops */
+
+ srandom(getpid());
+ // race condition here, sleep may help..?
+ if (cuser.userlevel) {
+ usleep(random()%1000000); // 0~1s
+ ui = getotherlogin(1);
+ if(ui == NULL)
+ return;
+
+ getdata(b_lines - 1, 0, "您想刪除其他重複的 login (Y/N)嗎?[Y] ",
+ genbuf, 3, LCECHO);
+
+ usleep(random()%1000000);
+ if (genbuf[0] != 'n') {
+ do {
+ // scan again, old ui may be invalid
+ ui = getotherlogin(1);
+ if(ui==NULL)
+ return;
+ if (ui->pid > 0) {
+ if(kill(ui->pid, SIGHUP)<0) {
+ perror("kill SIGHUP fail");
+ break;
+ }
+ log_usies("KICK ", cuser.nickname);
+ } else {
+ fprintf(stderr, "id=%s ui->pid=0\n", cuser.userid);
+ }
+ usleep(random()%2000000+1000000); // 1~3s
+ } while(getotherlogin(3) != NULL);
+ } else {
+ /* deny login if still have 3 */
+ if (getotherlogin(3) != NULL)
+ abort_bbs(0); /* Goodbye(); */
+ }
+ } else {
+ /* allow multiple guest user */
+ if (search_ulistn(usernum, MAX_GUEST) != NULL) {
+ vmsg("抱歉,目前已有太多 guest 在站上, 請用new註冊。");
+ exit(1);
+ }
+ }
+}
+
+/* bad login */
+static char * const str_badlogin = "logins.bad";
+
+static void
+logattempt(const char *uid, char type)
+{
+ char fname[40];
+ int fd, len;
+ char genbuf[200];
+
+ snprintf(genbuf, sizeof(genbuf), "%c%-12s[%s] %s@%s\n", type, uid,
+ Cdate(&login_start_time), remoteusername, fromhost);
+ len = strlen(genbuf);
+ if ((fd = open(str_badlogin, O_WRONLY | O_CREAT | O_APPEND, 0644)) > 0) {
+ write(fd, genbuf, len);
+ close(fd);
+ }
+ if (type == '-') {
+ snprintf(genbuf, sizeof(genbuf),
+ "[%s] %s\n", Cdate(&login_start_time), fromhost);
+ len = strlen(genbuf);
+ sethomefile(fname, uid, str_badlogin);
+ if ((fd = open(fname, O_WRONLY | O_CREAT | O_APPEND, 0644)) > 0) {
+ write(fd, genbuf, len);
+ close(fd);
+ }
+ }
+}
+
+void mkuserdir(const char *userid)
+{
+ char genbuf[200];
+ sethomepath(genbuf, userid);
+ // assume it is a dir, so just check if it is exist
+ if (access(genbuf, F_OK) != 0)
+ mkdir(genbuf, 0755);
+}
+
+static void
+login_query(void)
+{
+#ifdef CONVERT
+ /* uid 加一位, for gb login */
+ char uid[IDLEN + 2], passbuf[PASSLEN];
+ int attempts, len;
+#else
+ char uid[IDLEN + 1], passbuf[PASSLEN];
+ int attempts;
+#endif
+ resolve_garbage();
+ now = time(0);
+
+#ifdef DEBUG
+ move(1, 0);
+ prints("debugging mode\ncurrent pid: %d\n", getpid());
+#else
+ show_file("etc/Welcome", 1, -1, NO_RELOAD);
+#endif
+ output("1", 1);
+
+
+ attempts = 0;
+ while (1) {
+ if (attempts++ >= LOGINATTEMPTS) {
+ more("etc/goodbye", NA);
+ pressanykey();
+ exit(1);
+ }
+ bzero(&cuser, sizeof(cuser));
+#ifdef DEBUG
+ move(19, 0);
+ prints("current pid: %d ", getpid());
+#endif
+ getdata(20, 0, "請輸入代號,或以[guest]參觀,以[new]註冊:",
+ uid, sizeof(uid), DOECHO);
+#ifdef CONVERT
+ /* switch to gb mode if uid end with '.' */
+ len = strlen(uid);
+ if (uid[0] && uid[len - 1] == '.') {
+ set_converting_type(CONV_GB);
+ uid[len - 1] = 0;
+ }
+ else if (uid[0] && uid[len - 1] == ',') {
+ set_converting_type(CONV_UTF8);
+ uid[len - 1] = 0;
+ }
+ else if (len >= IDLEN + 1)
+ uid[IDLEN] = 0;
+#endif
+
+ if (strcasecmp(uid, str_new) == 0) {
+#ifdef LOGINASNEW
+ new_register();
+ mkuserdir(cuser.userid);
+ break;
+#else
+ outs("本系統目前無法以 new 註冊, 請用 guest 進入\n");
+ continue;
+#endif
+ } else if (!is_validuserid(uid)) {
+
+ outs(err_uid);
+
+ } else if (strcasecmp(uid, STR_GUEST) == 0) { /* guest */
+
+ if (initcuser(uid)< 1) exit (0) ;
+ cuser.userlevel = 0;
+ cuser.uflag = PAGER_FLAG | BRDSORT_FLAG | MOVIE_FLAG;
+ mkuserdir(cuser.userid);
+ break;
+
+ } else {
+
+ /* normal user */
+ getdata(21, 0, MSG_PASSWD,
+ passbuf, sizeof(passbuf), NOECHO);
+ passbuf[8] = '\0';
+
+ move (22, 0); clrtoeol();
+ outs("正在檢查密碼...");
+ move(22, 0); refresh();
+ /* prepare for later */
+ clrtoeol();
+
+ if( initcuser(uid) < 1 || !cuser.userid[0] ||
+ !checkpasswd(cuser.passwd, passbuf) ){
+
+ if(is_validuserid(cuser.userid))
+ logattempt(cuser.userid , '-');
+ outs(ERR_PASSWD);
+
+ } else {
+
+ logattempt(cuser.userid, ' ');
+ outs("密碼正確! 開始登入系統...");
+ move(22, 0); refresh();
+ clrtoeol();
+
+ if (strcasecmp(str_sysop, cuser.userid) == 0){
+#ifdef NO_SYSOP_ACCOUNT
+ exit(0);
+#else /* 自動加上各個主要權限 */
+ cuser.userlevel = PERM_BASIC | PERM_CHAT | PERM_PAGE |
+ PERM_POST | PERM_LOGINOK | PERM_MAILLIMIT |
+ PERM_CLOAK | PERM_SEECLOAK | PERM_XEMPT |
+ PERM_SYSOPHIDE | PERM_BM | PERM_ACCOUNTS |
+ PERM_CHATROOM | PERM_BOARD | PERM_SYSOP | PERM_BBSADM;
+ mkuserdir(cuser.userid);
+#endif
+ }
+ break;
+ }
+ }
+ }
+ multi_user_check();
+#ifdef DETECT_CLIENT
+ {
+ int fd = open("log/client_code",O_WRONLY | O_CREAT | O_APPEND, 0644);
+ if(fd>=0) {
+ write(fd, &client_code, sizeof(client_code));
+ close(fd);
+ }
+ }
+#endif
+}
+
+void
+add_distinct(const char *fname, const char *line)
+{
+ FILE *fp;
+ int n = 0;
+
+ if ((fp = fopen(fname, "a+"))) {
+ char buffer[80];
+ char tmpname[100];
+ FILE *fptmp;
+
+ strlcpy(tmpname, fname, sizeof(tmpname));
+ strcat(tmpname, "_tmp");
+ if (!(fptmp = fopen(tmpname, "w"))) {
+ fclose(fp);
+ return;
+ }
+ rewind(fp);
+ while (fgets(buffer, 80, fp)) {
+ char *p = buffer + strlen(buffer) - 1;
+
+ if (p[-1] == '\n' || p[-1] == '\r')
+ p[-1] = 0;
+ if (!strcmp(buffer, line))
+ break;
+ sscanf(buffer + strlen(buffer) + 2, "%d", &n);
+ fprintf(fptmp, "%s%c#%d\n", buffer, 0, n);
+ }
+
+ if (feof(fp))
+ fprintf(fptmp, "%s%c#1\n", line, 0);
+ else {
+ sscanf(buffer + strlen(buffer) + 2, "%d", &n);
+ fprintf(fptmp, "%s%c#%d\n", buffer, 0, n + 1);
+ while (fgets(buffer, 80, fp)) {
+ sscanf(buffer + strlen(buffer) + 2, "%d", &n);
+ fprintf(fptmp, "%s%c#%d\n", buffer, 0, n);
+ }
+ }
+ fclose(fp);
+ fclose(fptmp);
+ rename(tmpname, fname);
+ }
+}
+
+void
+del_distinct(const char *fname, const char *line, int casesensitive)
+{
+ FILE *fp;
+ int n = 0;
+
+ if ((fp = fopen(fname, "r"))) {
+ char buffer[80];
+ char tmpname[100];
+ FILE *fptmp;
+
+ strlcpy(tmpname, fname, sizeof(tmpname));
+ strcat(tmpname, "_tmp");
+ if (!(fptmp = fopen(tmpname, "w"))) {
+ fclose(fp);
+ return;
+ }
+ rewind(fp);
+ while (fgets(buffer, 80, fp)) {
+ char *p = buffer + strlen(buffer) - 1;
+
+ if (p[-1] == '\n' || p[-1] == '\r')
+ p[-1] = 0;
+ if(casesensitive)
+ {
+ if (!strcmp(buffer, line))
+ break;
+ } else {
+ if (!strcasecmp(buffer, line))
+ break;
+ }
+ sscanf(buffer + strlen(buffer) + 2, "%d", &n);
+ fprintf(fptmp, "%s%c#%d\n", buffer, 0, n);
+ }
+
+ if (!feof(fp))
+ while (fgets(buffer, 80, fp)) {
+ sscanf(buffer + strlen(buffer) + 2, "%d", &n);
+ fprintf(fptmp, "%s%c#%d\n", buffer, 0, n);
+ }
+ fclose(fp);
+ fclose(fptmp);
+ rename(tmpname, fname);
+ }
+}
+
+#ifdef WHERE
+static int
+where(const char *from)
+{
+ int i;
+
+ for (i = 0; i < SHM->home_num; i++) {
+ if ((SHM->home_ip[i] & SHM->home_mask[i]) == (ipstr2int(from) & SHM->home_mask[i])) {
+ return i;
+ }
+ }
+ return 0;
+}
+#endif
+
+static void
+check_BM(void)
+{
+ /* XXX: -_- */
+ int i;
+
+ cuser.userlevel &= ~PERM_BM;
+ for( i = 0 ; i < numboards ; ++i )
+ if( is_BM_cache(i + 1) ) /* XXXbid */
+ return;
+ //for (i = 0, bhdr = bcache; i < numboards && !is_BM(bhdr->BM); i++, bhdr++);
+}
+
+static void
+setup_utmp(int mode)
+{
+ /* NOTE, 在 getnewutmpent 之前不應該有任何 slow/blocking function */
+ userinfo_t uinfo;
+ memset(&uinfo, 0, sizeof(uinfo));
+ uinfo.pid = currpid = getpid();
+ uinfo.uid = usernum;
+ uinfo.mode = currstat = mode;
+
+ uinfo.userlevel = cuser.userlevel;
+ uinfo.sex = cuser.sex % 8;
+ uinfo.lastact = time(NULL);
+ strlcpy(uinfo.userid, cuser.userid, sizeof(uinfo.userid));
+ //strlcpy(uinfo.realname, cuser.realname, sizeof(uinfo.realname));
+ strlcpy(uinfo.nickname, cuser.nickname, sizeof(uinfo.nickname));
+ strip_nonebig5((unsigned char *)uinfo.nickname, sizeof(uinfo.nickname));
+ strlcpy(uinfo.from, fromhost, sizeof(uinfo.from));
+ uinfo.five_win = cuser.five_win;
+ uinfo.five_lose = cuser.five_lose;
+ uinfo.five_tie = cuser.five_tie;
+ uinfo.chc_win = cuser.chc_win;
+ uinfo.chc_lose = cuser.chc_lose;
+ uinfo.chc_tie = cuser.chc_tie;
+ uinfo.chess_elo_rating = cuser.chess_elo_rating;
+ uinfo.go_win = cuser.go_win;
+ uinfo.go_lose = cuser.go_lose;
+ uinfo.go_tie = cuser.go_tie;
+ uinfo.invisible = cuser.invisible % 2;
+ uinfo.pager = cuser.pager % PAGER_MODES;
+ /*
+ uinfo.goodpost = cuser.goodpost;
+ uinfo.badpost = cuser.badpost;
+ uinfo.goodsale = cuser.goodsale;
+ uinfo.badsale = cuser.badsale;
+ */
+ if(cuser.withme & (cuser.withme<<1) & (WITHME_ALLFLAG<<1))
+ cuser.withme = 0; /* unset all if contradict */
+ uinfo.withme = cuser.withme & ~WITHME_ALLFLAG;
+ memcpy(uinfo.mind, cuser.mind, 4);
+ strip_nonebig5((unsigned char *)uinfo.mind, 4);
+#ifdef WHERE
+ uinfo.from_alias = where(fromhost);
+#endif
+#ifndef FAST_LOGIN
+ setuserfile(buf, "remoteuser");
+
+ strlcpy(remotebuf, fromhost, sizeof(fromhost));
+ strcat(remotebuf, ctime4(&now));
+ chomp(remotebuf);
+ add_distinct(buf, remotebuf);
+#endif
+ if (enter_uflag & CLOAK_FLAG)
+ uinfo.invisible = YEA;
+
+#ifdef PLAY_ANGEL
+ if (REJECT_QUESTION)
+ uinfo.angel = 1;
+ uinfo.angel |= ANGEL_STATUS() << 1;
+#endif
+
+ getnewutmpent(&uinfo);
+ currmode = MODE_STARTED;
+ SHM->UTMPneedsort = 1;
+ // XXX 不用每 20 才檢查吧
+ if (!(cuser.numlogins % 20) && cuser.userlevel & PERM_BM)
+ check_BM(); /* Ptt 自動取下離職板主權力 */
+
+#ifndef _BBS_UTIL_C_
+ /* Very, very slow friend_load. */
+ if( strcmp(cuser.userid, STR_GUEST) != 0 ) // guest 不處理好友
+ friend_load(0);
+ nice(3);
+#endif
+}
+
+inline static void welcome_msg(void)
+{
+ prints(ANSI_RESET " 歡迎您第 "
+ ANSI_COLOR(1;33) "%d" ANSI_COLOR(0;37) " 度拜訪本站,上次您是從 "
+ ANSI_COLOR(1;33) "%s" ANSI_COLOR(0;37) " 連往本站,"
+ ANSI_CLRTOEND "\n"
+ " 我記得那天是 " ANSI_COLOR(1;33) "%s" ANSI_COLOR(0;37) "。"
+ ANSI_CLRTOEND "\n"
+ ANSI_CLRTOEND "\n"
+ ,
+ ++cuser.numlogins, cuser.lasthost, Cdate(&(cuser.lastlogin)));
+ pressanykey();
+}
+
+inline static void check_bad_login(void)
+{
+ char genbuf[200];
+ setuserfile(genbuf, str_badlogin);
+ if (more(genbuf, NA) != -1) {
+ move(b_lines - 3, 0);
+ outs("通常並沒有辦法知道該ip是誰所有, "
+ "以及其意圖(是不小心按錯或有意測您密碼)\n"
+ "若您有帳號被盜用疑慮, 請經常更改您的密碼或使用加密連線");
+ if (getans("您要刪除以上錯誤嘗試的記錄嗎(Y/N)?[N]") == 'y')
+ unlink(genbuf);
+ }
+}
+
+inline static void birthday_make_a_wish(const struct tm *ptime, const struct tm *tmp)
+{
+ if (tmp->tm_mday != ptime->tm_mday) {
+ more("etc/birth.post", YEA);
+ brc_initial_board("WhoAmI");
+ set_board();
+ do_post();
+ }
+}
+
+inline static void record_lasthost(const char *fromhost)
+{
+ strlcpy(cuser.lasthost, fromhost, sizeof(cuser.lasthost));
+}
+
+inline static void check_mailbox_quota(void)
+{
+ if (chkmailbox())
+ m_read();
+}
+
+static void init_guest_info(void)
+{
+ int i;
+ char *nick[13] = {
+ "椰子", "貝殼", "內衣", "寶特瓶", "翻車魚",
+ "樹葉", "浮萍", "鞋子", "潛水艇", "魔王",
+ "鐵罐", "考卷", "大美女"
+ };
+ char *name[13] = {
+ "大王椰子", "鸚鵡螺", "比基尼", "可口可樂", "仰泳的魚",
+ "憶", "高岡屋", "AIR Jordon", "紅色十月號", "批踢踢",
+ "SASAYA椰奶", "鴨蛋", "布魯克鱈魚香絲"
+ };
+ char *addr[13] = {
+ "天堂樂園", "大海", "綠島小夜曲", "美國", "綠色珊瑚礁",
+ "遠方", "原本海", "NIKE", "蘇聯", "男八618室",
+ "愛之味", "天上", "藍色珊瑚礁"
+ };
+ i = login_start_time % 13;
+ snprintf(cuser.nickname, sizeof(cuser.nickname),
+ "海邊漂來的%s", nick[(int)i]);
+ strlcpy(currutmp->nickname, cuser.nickname,
+ sizeof(currutmp->nickname));
+ strlcpy(cuser.realname, name[(int)i], sizeof(cuser.realname));
+ strlcpy(cuser.address, addr[(int)i], sizeof(cuser.address));
+ cuser.sex = i % 8;
+ currutmp->pager = PAGER_DISABLE;
+}
+
+#if FOREIGN_REG_DAY > 0
+inline static void foreign_warning(void){
+ if ((cuser.uflag2 & FOREIGN) && !(cuser.uflag2 & LIVERIGHT)){
+ if (login_start_time - cuser.firstlogin > (FOREIGN_REG_DAY - 5) * 24 * 3600){
+ mail_muser(cuser, "[出入境管理局]", "etc/foreign_expired_warn");
+ }
+ else if (login_start_time - cuser.firstlogin > FOREIGN_REG_DAY * 24 * 3600){
+ cuser.userlevel &= ~(PERM_LOGINOK | PERM_POST);
+ vmsg("警告:請至出入境管理局申請永久居留");
+ }
+ }
+}
+#endif
+
+
+static void
+user_login(void)
+{
+ struct tm ptime, lasttime;
+ int nowusers, ifbirth = 0, i;
+
+ /* NOTE! 在 setup_utmp 之前, 不應該有任何 blocking/slow function,
+ * 否則可藉機 race condition 達到 multi-login */
+
+ /* get local time */
+ ptime = *localtime4(&now);
+
+ /* 初始化: random number 增加user跟時間的差異 */
+ mysrand();
+
+ /* check if over18 */
+ if( (ptime.tm_year - cuser.year) >= 18 ||
+ (ptime.tm_year - cuser.year == 17 &&
+ ((ptime.tm_mon+1) > cuser.month ||
+ ((ptime.tm_mon+1) == cuser.month && ptime.tm_mday > cuser.day))) )
+ over18 = 1;
+
+ log_usies("ENTER", fromhost);
+#ifndef VALGRIND
+ setproctitle("%s: %s", margs, cuser.userid);
+#endif
+ resolve_fcache();
+ /* resolve_boards(); */
+ numboards = SHM->Bnumber;
+
+ if(getenv("SSH_CLIENT") != NULL){
+ struct sockaddr_in xsin;
+ char frombuf[50];
+ sscanf(getenv("SSH_CLIENT"), "%s", frombuf);
+ xsin.sin_family = AF_INET;
+ xsin.sin_port = htons(23);
+ if (strrchr(frombuf, ':'))
+ inet_pton(AF_INET, strrchr(frombuf, ':') + 1, &xsin.sin_addr);
+ else
+ inet_pton(AF_INET, frombuf, &xsin.sin_addr);
+ getremotename(&xsin, fromhost, remoteusername); /* RFC931 */
+ }
+
+ /* 初始化 uinfo、flag、mode */
+ setup_utmp(LOGIN);
+ enter_uflag = cuser.uflag;
+ lasttime = *localtime4(&cuser.lastlogin);
+
+ /* show welcome_login */
+ if( (ifbirth = (ptime.tm_mday == cuser.day &&
+ ptime.tm_mon + 1 == cuser.month)) ){
+ char buf[PATHLEN];
+ snprintf(buf, sizeof(buf), "etc/Welcome_birth.%d", getHoroscope(cuser.month, cuser.day));
+ more(buf, NA);
+ }
+ else {
+#ifndef MULTI_WELCOME_LOGIN
+ more("etc/Welcome_login", NA);
+#else
+ if( SHM->GV2.e.nWelcomes ){
+ char buf[80];
+ snprintf(buf, sizeof(buf), "etc/Welcome_login.%d",
+ (int)login_start_time % SHM->GV2.e.nWelcomes);
+ more(buf, NA);
+ }
+#endif
+ }
+ refresh();
+ currutmp->alerts |= load_mailalert(cuser.userid);
+
+ if ((nowusers = SHM->UTMPnumber) > SHM->max_user) {
+ SHM->max_user = nowusers;
+ SHM->max_time = now;
+ }
+
+ if (!(HasUserPerm(PERM_SYSOP) && HasUserPerm(PERM_SYSOPHIDE)) &&
+ !currutmp->invisible)
+ {
+ /* do_aloha is costly. do it later? */
+ do_aloha("<<上站通知>> -- 我來啦!");
+ }
+
+ if (SHM->loginmsg.pid){
+ if(search_ulist_pid(SHM->loginmsg.pid))
+ getmessage(SHM->loginmsg);
+ else
+ SHM->loginmsg.pid=0;
+ }
+
+ if (cuser.userlevel) { /* not guest */
+ move(t_lines - 4, 0);
+ clrtobot();
+ welcome_msg();
+
+ if( ifbirth ){
+ birthday_make_a_wish(&ptime, &lasttime);
+ if( getans("是否要顯示「壽星」於使用者名單上?(y/N)") == 'y' )
+ currutmp->birth = 1;
+ }
+ check_bad_login();
+ check_mailbox_quota();
+ check_register();
+ record_lasthost(fromhost);
+ restore_backup();
+
+ } else if (strcmp(cuser.userid, STR_GUEST) == 0) { /* guest */
+
+ init_guest_info();
+#if 0 // def DBCSAWARE
+ u_detectDBCSAwareEvilClient();
+#else
+ pressanykey();
+#endif
+ } else {
+ pressanykey();
+ check_mailbox_quota();
+ }
+
+ if(ptime.tm_yday!=lasttime.tm_yday)
+ STATINC(STAT_TODAYLOGIN_MAX);
+
+ if (!PERM_HIDE(currutmp)) {
+ /* If you wanna do incremental upgrade
+ * (like, added a function/flag that wants user to confirm againe)
+ * put it here.
+ */
+
+#if defined(DBCSAWARE) && defined(DBCSAWARE_UPGRADE_STARTTIME)
+ // define the real time you upgraded in your pttbbs.conf
+ if(cuser.lastlogin < DBCSAWARE_UPGRADE_STARTTIME)
+ {
+ if (u_detectDBCSAwareEvilClient())
+ cuser.uflag &= ~DBCSAWARE_FLAG;
+ else
+ cuser.uflag |= DBCSAWARE_FLAG;
+ }
+#endif
+ /* login time update */
+
+ if(ptime.tm_yday!=lasttime.tm_yday)
+ STATINC(STAT_TODAYLOGIN_MIN);
+
+
+ cuser.lastlogin = login_start_time;
+
+ }
+
+#if FOREIGN_REG_DAY > 0
+ foreign_warning();
+#endif
+
+ passwd_update(usernum, &cuser);
+
+ if(cuser.uflag2 & FAVNEW_FLAG) {
+ fav_load();
+ if (get_fav_root() != NULL) {
+ int num;
+ num = updatenewfav(1);
+ if (num > NEW_FAV_THRESHOLD &&
+ getans("找到 %d 個新看板,確定要加入我的最愛嗎?[Y/n]", num) == 'n') {
+ fav_free();
+ fav_load();
+ }
+ }
+ }
+
+ for (i = 0; i < NUMVIEWFILE; i++)
+ if ((cuser.loginview >> i) & 1)
+ more(loginview_file[(int)i][0], YEA);
+}
+
+static void
+do_aloha(const char *hello)
+{
+ FILE *fp;
+ char userid[80];
+ char genbuf[200];
+
+ setuserfile(genbuf, "aloha");
+ if ((fp = fopen(genbuf, "r"))) {
+ while (fgets(userid, 80, fp)) {
+ userinfo_t *uentp;
+ if ((uentp = (userinfo_t *) search_ulist_userid(userid)) &&
+ isvisible(uentp, currutmp)) {
+ my_write(uentp->pid, hello, uentp->userid, WATERBALL_ALOHA, uentp);
+ }
+ }
+ fclose(fp);
+ }
+}
+
+static void
+do_term_init(void)
+{
+ term_init();
+ initscr();
+ if(use_shell_login_mode)
+ raise(SIGWINCH);
+}
+
+inline static void
+start_client(void)
+{
+#ifdef CPULIMIT
+ struct rlimit rml;
+ rml.rlim_cur = CPULIMIT * 60 - 5;
+ rml.rlim_max = CPULIMIT * 60;
+ setrlimit(RLIMIT_CPU, &rml);
+#endif
+
+ STATINC(STAT_LOGIN);
+ /* system init */
+ nice(2); /* Ptt: lower priority */
+ login_start_time = time(0);
+ currmode = 0;
+
+ Signal(SIGHUP, abort_bbs);
+ Signal(SIGTERM, abort_bbs);
+ Signal(SIGPIPE, abort_bbs);
+
+ Signal(SIGINT, abort_bbs_debug);
+ Signal(SIGQUIT, abort_bbs_debug);
+ Signal(SIGILL, abort_bbs_debug);
+ Signal(SIGABRT, abort_bbs_debug);
+ Signal(SIGFPE, abort_bbs_debug);
+ Signal(SIGBUS, abort_bbs_debug);
+ Signal(SIGSEGV, abort_bbs_debug);
+ Signal(SIGXCPU, abort_bbs_debug);
+
+ signal_restart(SIGUSR1, talk_request);
+ signal_restart(SIGUSR2, write_request);
+
+ dup2(0, 1);
+
+ do_term_init();
+ Signal(SIGALRM, abort_bbs);
+ alarm(600);
+
+ login_query(); /* Ptt 加上login time out */
+ m_init(); /* init the user mail path */
+ user_login();
+ auto_close_polls(); /* 自動開票 */
+
+ Signal(SIGALRM, SIG_IGN);
+
+ main_menu();
+}
+
+/* 取得 remote user name 以判定身份 */
+/*
+ * rfc931() speaks a common subset of the RFC 931, AUTH, TAP, IDENT and RFC
+ * 1413 protocols. It queries an RFC 931 etc. compatible daemon on a remote
+ * host to look up the owner of a connection. The information should not be
+ * used for authentication purposes. This routine intercepts alarm signals.
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ */
+
+#define RFC931_TIMEOUT 10
+#define RFC931_PORT 113 /* Semi-well-known port */
+#define ANY_PORT 0 /* Any old port will do */
+
+#if 0
+/* timeout - handle timeouts */
+static void
+timeout(int sig)
+{
+ longjmp(byebye, sig);
+}
+#endif
+
+static void
+getremotename(const struct sockaddr_in * from, char *rhost, char *rname)
+{
+
+ /* get remote host name */
+
+#ifdef FAST_LOGIN
+ strcpy(rhost, (char *)inet_ntoa(from->sin_addr));
+#else
+ struct sockaddr_in our_sin;
+ struct sockaddr_in rmt_sin;
+ unsigned rmt_port, rmt_pt;
+ unsigned our_port, our_pt;
+ FILE *fp;
+ char buffer[512], user[80], *cp;
+ int s;
+ static struct hostent *hp;
+
+
+ hp = NULL;
+ if (setjmp(byebye) == 0) {
+ Signal(SIGALRM, timeout);
+ alarm(3);
+ hp = gethostbyaddr((char *)&from->sin_addr, sizeof(struct in_addr),
+ from->sin_family);
+ alarm(0);
+ }
+ strcpy(rhost, hp ? hp->h_name : (char *)inet_ntoa(from->sin_addr));
+
+ /*
+ * Use one unbuffered stdio stream for writing to and for reading from
+ * the RFC931 etc. server. This is done because of a bug in the SunOS
+ * 4.1.x stdio library. The bug may live in other stdio implementations,
+ * too. When we use a single, buffered, bidirectional stdio stream ("r+"
+ * or "w+" mode) we read our own output. Such behaviour would make sense
+ * with resources that support random-access operations, but not with
+ * sockets.
+ */
+
+ s = sizeof(our_sin);
+ if (getsockname(0, (struct sockaddr *) & our_sin, &s) < 0)
+ return;
+
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
+ return;
+
+ if (!(fp = fdopen(s, "r+"))) {
+ close(s);
+ return;
+ }
+ /* Set up a timer so we won't get stuck while waiting for the server. */
+ if (setjmp(byebye) == 0) {
+ Signal(SIGALRM, timeout);
+ alarm(RFC931_TIMEOUT);
+
+ /*
+ * Bind the local and remote ends of the query socket to the same IP
+ * addresses as the connection under investigation. We go through all
+ * this trouble because the local or remote system might have more
+ * than one network address. The RFC931 etc. client sends only port
+ * numbers; the server takes the IP addresses from the query socket.
+ */
+ our_pt = ntohs(our_sin.sin_port);
+ our_sin.sin_port = htons(ANY_PORT);
+
+ rmt_sin = *from;
+ rmt_pt = ntohs(rmt_sin.sin_port);
+ rmt_sin.sin_port = htons(RFC931_PORT);
+
+ setbuf(fp, (char *)0);
+ s = fileno(fp);
+
+ if (bind(s, (struct sockaddr *) & our_sin, sizeof(our_sin)) >= 0 &&
+ connect(s, (struct sockaddr *) & rmt_sin, sizeof(rmt_sin)) >= 0) {
+ /*
+ * Send query to server. Neglect the risk that a 13-byte write
+ * would have to be fragmented by the local system and cause
+ * trouble with buggy System V stdio libraries.
+ */
+ fprintf(fp, "%u,%u\r\n", rmt_pt, our_pt);
+ fflush(fp);
+ /*
+ * Read response from server. Use fgets()/sscanf() so we can work
+ * around System V stdio libraries that incorrectly assume EOF
+ * when a read from a socket returns less than requested.
+ */
+ if (fgets(buffer, sizeof(buffer), fp) && !ferror(fp)
+ && !feof(fp)
+ && sscanf(buffer, "%u , %u : USERID :%*[^:]:%79s", &rmt_port,
+ &our_port, user) == 3 && rmt_pt == rmt_port
+ && our_pt == our_port) {
+
+ /*
+ * Strip trailing carriage return. It is part of the
+ * protocol, not part of the data.
+ */
+ if ((cp = (char *)strchr(user, '\r')))
+ *cp = 0;
+ strlcpy(rname, user, sizeof(user));
+ }
+ }
+ alarm(0);
+ }
+ fclose(fp);
+#endif
+}
+
+static int
+bind_port(int port)
+{
+ int sock, on, sz;
+ struct linger lin;
+ struct sockaddr_in xsin;
+
+ sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
+ on = 1;
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
+ setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof(on));
+
+ lin.l_onoff = 0;
+ setsockopt(sock, SOL_SOCKET, SO_LINGER, &lin, sizeof(lin));
+
+ sz = 1024;
+ setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&sz, sizeof(sz));
+ sz = 4096;
+ setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&sz, sizeof(sz));
+
+ xsin.sin_family = AF_INET;
+ xsin.sin_addr.s_addr = htonl(INADDR_ANY);
+ xsin.sin_port = htons(port);
+ if (bind(sock, (struct sockaddr *) & xsin, sizeof xsin) < 0) {
+ syslog(LOG_INFO, "bbsd bind_port can't bind to %d", port);
+ exit(1);
+ }
+ if (listen(sock, SOCKET_QLEN) < 0) {
+ syslog(LOG_INFO, "bbsd bind_port can't listen to %d", port);
+ exit(1);
+ }
+ return sock;
+}
+
+
+/*******************************************************/
+
+
+static int shell_login(int argc, char *argv[], char *envp[]);
+static int daemon_login(int argc, char *argv[], char *envp[]);
+static int check_ban_and_load(int fd);
+static int check_banip(char *host);
+
+int
+main(int argc, char *argv[], char *envp[])
+{
+ start_time = time(NULL);
+
+ /* avoid SIGPIPE */
+ Signal(SIGPIPE, SIG_IGN);
+
+ /* avoid erroneous signal from other mbbsd */
+ Signal(SIGUSR1, SIG_IGN);
+ Signal(SIGUSR2, SIG_IGN);
+
+#if defined(__GLIBC__) && defined(CRITICAL_MEMORY)
+ #define MY__MMAP_THRESHOLD (1024 * 8)
+ #define MY__MMAP_MAX (0)
+ #define MY__TRIM_THRESHOLD (1024 * 8)
+ #define MY__TOP_PAD (0)
+
+ mallopt (M_MMAP_THRESHOLD, MY__MMAP_THRESHOLD);
+ mallopt (M_MMAP_MAX, MY__MMAP_MAX);
+ mallopt (M_TRIM_THRESHOLD, MY__TRIM_THRESHOLD);
+ mallopt (M_TOP_PAD, MY__TOP_PAD);
+#endif
+
+ attach_SHM();
+ if( (argc == 3 && shell_login(argc, argv, envp)) ||
+ (argc != 3 && daemon_login(argc, argv, envp)) )
+ start_client();
+
+ return 0;
+}
+
+static int
+shell_login(int argc, char *argv[], char *envp[])
+{
+ int fd;
+
+ STATINC(STAT_SHELLLOGIN);
+ /* Give up root privileges: no way back from here */
+ setgid(BBSGID);
+ setuid(BBSUID);
+ chdir(BBSHOME);
+
+#if defined(linux) && defined(DEBUG)
+// mtrace();
+#endif
+
+ use_shell_login_mode = 1;
+ initsetproctitle(argc, argv, envp);
+
+ snprintf(margs, sizeof(margs), "%s ssh ", argv[0]);
+ /*
+ * copy fromindent: Standard input:1138: Error:Unexpected end of file the
+ * original "bbs"
+ */
+ if (argc > 1) {
+ strcpy(fromhost, argv[1]);
+ if (argc > 3)
+ strlcpy(remoteusername, argv[3], sizeof(remoteusername));
+ }
+ close(2);
+ /* don't close fd 1, at least init_tty need it */
+ if( ((fd = open("log/stderr", O_WRONLY | O_CREAT | O_APPEND, 0644)) >= 0) && fd != 2 ){
+ dup2(fd, 2);
+ close(fd);
+ }
+
+ init_tty();
+ if (check_ban_and_load(0)) {
+ sleep(10);
+ return 0;
+ }
+#ifdef DETECT_CLIENT
+ FNV1A_CHAR(123, client_code);
+#endif
+ return 1;
+}
+
+void telnet_init(void);
+unsigned int telnet_handler(unsigned char c) ;
+
+static int
+daemon_login(int argc, char *argv[], char *envp[])
+{
+ int msock, csock; /* socket for Master and Child */
+ FILE *fp;
+ int listen_port = 23;
+ int len_of_sock_addr, overloading = 0, i;
+ char buf[256];
+#if OVERLOADBLOCKFDS
+ int blockfd[OVERLOADBLOCKFDS];
+ int nblocked = 0;
+#endif
+ struct sockaddr_in xsin;
+ xsin.sin_family = AF_INET;
+
+ /* setup standalone */
+ start_daemon();
+ signal_restart(SIGCHLD, reapchild);
+
+ /* choose port */
+ if( argc < 2 )
+ listen_port = 3006;
+ else{
+#ifdef NO_FORK
+ listen_port = atoi(argv[1]);
+#else
+ for( i = 1 ; i < (argc - 1) ; ++i )
+ switch( fork() ){
+ case -1:
+ perror("fork()");
+ break;
+ case 0:
+ goto out;
+ default:
+ break;
+ }
+ out:
+ listen_port = atoi(argv[i]);
+#endif
+ }
+
+ /* port binding */
+ if( (msock = bind_port(listen_port)) < 0 ){
+ syslog(LOG_INFO, "mbbsd bind_port failed.\n");
+ exit(1);
+ }
+
+ /* Give up root privileges: no way back from here */
+ setgid(BBSGID);
+ setuid(BBSUID);
+ chdir(BBSHOME);
+
+ /* proctitle */
+ initsetproctitle(argc, argv, envp);
+#ifndef VALGRIND
+ snprintf(margs, sizeof(margs), "%s %d ", argv[0], listen_port);
+ setproctitle("%s: listening ", margs);
+#endif
+
+ /* It's better to do something before fork */
+#ifdef CONVERT
+ big2gb_init(NULL);
+ gb2big_init(NULL);
+ big2uni_init(NULL);
+ uni2big_init(NULL);
+#endif
+
+#ifndef NO_FORK
+#ifdef PRE_FORK
+ if( listen_port == 23 ){ // only pre-fork in port 23
+ for( i = 0 ; i < PRE_FORK ; ++i )
+ if( fork() <= 0 )
+ break;
+ }
+#endif
+#endif
+
+ snprintf(buf, sizeof(buf),
+ "run/mbbsd.%d.%d.pid", listen_port, (int)getpid());
+ if ((fp = fopen(buf, "w"))) {
+ fprintf(fp, "%d\n", (int)getpid());
+ fclose(fp);
+ }
+
+ /* main loop */
+ while( 1 ){
+ len_of_sock_addr = sizeof(xsin);
+ if(
+#if defined(Solaris) && __OS_MAJOR_VERSION__ == 5 && __OS_MINOR_VERSION__ < 7
+ (csock = accept(msock, (struct sockaddr *)&xsin,
+ &len_of_sock_addr)) < 0
+#else
+ (csock = accept(msock, (struct sockaddr *)&xsin,
+ (socklen_t *)&len_of_sock_addr)) < 0
+#endif
+ ) {
+ if (errno != EINTR)
+ sleep(1);
+ continue;
+ }
+
+ overloading = check_ban_and_load(csock);
+#if OVERLOADBLOCKFDS
+ if( (!overloading && nblocked) ||
+ (overloading && nblocked == OVERLOADBLOCKFDS) ){
+ for( i = 0 ; i < OVERLOADBLOCKFDS ; ++i )
+ if( blockfd[i] != csock && blockfd[i] != msock )
+ /* blockfd[i] should not be msock, but it happened */
+ close(blockfd[i]);
+ nblocked = 0;
+ }
+#endif
+
+ if( overloading ){
+#if OVERLOADBLOCKFDS
+ blockfd[nblocked++] = csock;
+#else
+ close(csock);
+#endif
+ continue;
+ }
+
+#ifdef NO_FORK
+ break;
+#else
+ if (fork() == 0)
+ break;
+ else
+ close(csock);
+#endif
+ }
+ /* here is only child running */
+
+#ifndef VALGRIND
+ setproctitle("%s: ...login wait... ", margs);
+#endif
+ close(msock);
+ dup2(csock, 0);
+ close(csock);
+
+ getremotename(&xsin, fromhost, remoteusername);
+ if( check_banip(fromhost) ){
+ sleep(10);
+ exit(0);
+ }
+ telnet_init();
+ return 1;
+}
+
+/*
+ * check if we're banning login and if the load is too high. if login is
+ * permitted, return 0; else return -1; approriate message is output to fd.
+ */
+static int
+check_ban_and_load(int fd)
+{
+ FILE *fp;
+ static time4_t chkload_time = 0;
+ static int overload = 0; /* overload or banned, update every 1
+ * sec */
+ static int banned = 0;
+
+#ifdef INSCREEN
+ write(fd, INSCREEN, sizeof(INSCREEN));
+#else
+#define BANNER \
+"【" BBSNAME "】◎ 台大流行網 ◎(" MYHOSTNAME ") 調幅(" MYIP ") \r\n"
+ write(fd, BANNER, sizeof(BANNER));
+#endif
+
+ if ((time(0) - chkload_time) > 1) {
+ overload = 0;
+ banned = 0;
+
+ if(cpuload(NULL) > MAX_CPULOAD)
+ overload = 1;
+ else if (SHM->UTMPnumber >= MAX_ACTIVE
+#ifdef DYMAX_ACTIVE
+ || (SHM->GV2.e.dymaxactive > 2000 &&
+ SHM->UTMPnumber >= SHM->GV2.e.dymaxactive)
+#endif
+ ) {
+ ++SHM->GV2.e.toomanyusers;
+ overload = 2;
+ } else if(!access(BBSHOME "/" BAN_FILE, R_OK))
+ banned = 1;
+
+ chkload_time = time(0);
+ }
+
+ if(overload == 1)
+ write(fd, "系統過載, 請稍後再來\r\n", 22);
+ else if(overload == 2)
+ write(fd, "由於人數過多,請您稍後再來。", 28);
+ else if (banned && (fp = fopen(BBSHOME "/" BAN_FILE, "r"))) {
+ char buf[256];
+ while (fgets(buf, sizeof(buf), fp))
+ write(fd, buf, strlen(buf));
+ fclose(fp);
+ }
+
+ if (banned || overload)
+ return -1;
+
+ return 0;
+}
+
+static int check_banip(char *host)
+{
+ unsigned int thisip = 0;
+ char *ptr, *myhost = strdup(host);
+ char *strtok_pos;
+
+ for( ptr = strtok_r(myhost, ".", &strtok_pos) ; ptr != NULL ; ptr = strtok_r(NULL, ".", &strtok_pos) )
+ thisip = thisip * 256 + atoi(ptr);
+ free(myhost);
+
+ return uintbsearch(thisip, &banip[1], banip[0]) ? 1 : 0;
+}
+
+/* ------- piaip's implementation of TELNET protocol ------- */
+
+enum TELNET_IAC_STATES {
+ IAC_NONE,
+ IAC_COMMAND,
+ IAC_WAIT_OPT,
+ IAC_WAIT_SE,
+ IAC_PROCESS_OPT,
+ IAC_ERROR
+};
+
+static unsigned char iac_state = 0; /* as byte to reduce memory */
+
+#define TELNET_IAC_MAXLEN (16)
+/* We don't reply to most commands, so this maxlen can be minimal.
+ * Warning: if you want to support ENV passing or other long commands,
+ * remember to increase this value. Howver, some poorly implemented
+ * terminals like xxMan may not follow the protocols and user will hang
+ * on those terminals when IACs were sent.
+ */
+
+void
+telnet_init(void)
+{
+ /* We are the boss. We don't respect to client.
+ * It's client's responsibility to follow us.
+ * Please write these codes in i-dont-care opt handlers.
+ */
+ const char telnet_init_cmds[] = {
+ /* retrieve terminal type and throw away.
+ * why? because without this, clients enter line mode.
+ */
+ IAC, DO, TELOPT_TTYPE,
+ IAC, SB, TELOPT_TTYPE, TELQUAL_SEND, IAC, SE,
+
+ /* i'm a smart term with resize ability. */
+ IAC, DO, TELOPT_NAWS,
+
+ /* i will echo. */
+ IAC, WILL, TELOPT_ECHO,
+ /* supress ga. */
+ IAC, WILL, TELOPT_SGA,
+ /* 8 bit binary. */
+ IAC, WILL, TELOPT_BINARY,
+ IAC, DO, TELOPT_BINARY,
+ };
+
+ raw_connection = 1;
+ write(0, telnet_init_cmds, sizeof(telnet_init_cmds));
+}
+
+/* tty_read
+ * read from tty, process telnet commands if raw connection.
+ * return: >0 = length, <=0 means read more, abort/eof is automatically processed.
+ */
+ssize_t
+tty_read(unsigned char *buf, size_t max)
+{
+ ssize_t l = read(0, buf, max);
+
+ if(l == 0 || (l < 0 && !(errno == EINTR || errno == EAGAIN)))
+ abort_bbs(0);
+
+ if(!raw_connection)
+ return l;
+
+ /* process buffer */
+ if (l > 0) {
+ unsigned char *buf2 = buf;
+ size_t i = 0, i2 = 0;
+
+ /* prescan. because IAC is rare,
+ * this cost is worthy. */
+ if (iac_state == IAC_NONE && memchr(buf, IAC, l) == NULL)
+ return l;
+
+ /* we have to look into the buffer. */
+ for (i = 0; i < l; i++, buf++)
+ if(telnet_handler(*buf) == 0)
+ *(buf2++) = *buf;
+ else
+ i2 ++;
+ l = (i2 == l) ? -1L : l - i2;
+ }
+ return l;
+}
+
+/* input: raw character
+ * output: telnet command if c was handled, otherwise zero.
+ */
+unsigned int
+telnet_handler(unsigned char c)
+{
+ static unsigned char iac_quote = 0; /* as byte to reduce memory */
+ static unsigned char iac_opt_req = 0;
+
+ static unsigned char iac_buf[TELNET_IAC_MAXLEN];
+ static unsigned int iac_buflen = 0;
+
+ /* we have to quote all IACs. */
+ if(c == IAC && !iac_quote) {
+ iac_quote = 1;
+ return NOP;
+ }
+
+#ifdef DETECT_CLIENT
+ /* hash client telnet sequences */
+ if(cuser.userid[0]==0) {
+ if(iac_state == IAC_WAIT_SE) {
+ // skip suboption
+ } else {
+ if(iac_quote)
+ FNV1A_CHAR(IAC, client_code);
+ FNV1A_CHAR(c, client_code);
+ }
+ }
+#endif
+
+ /* a special case is the top level iac. otherwise, iac is just a quote. */
+ if (iac_quote) {
+ if(iac_state == IAC_NONE)
+ iac_state = IAC_COMMAND;
+ if(iac_state == IAC_WAIT_SE && c == SE)
+ iac_state = IAC_PROCESS_OPT;
+ iac_quote = 0;
+ }
+
+ /* now, let's process commands by state */
+ switch(iac_state) {
+
+ case IAC_NONE:
+ return 0;
+
+ case IAC_COMMAND:
+#ifdef DEBUG
+ {
+ int cx = c; /* to make compiler happy */
+ write(0, "-", 1);
+ if(TELCMD_OK(cx))
+ write(0, TELCMD(c), strlen(TELCMD(c)));
+ write(0, " ", 1);
+ }
+#endif
+ iac_state = IAC_NONE; /* by default we restore state. */
+ switch(c) {
+ case IAC:
+ return 0;
+
+ /* we don't want to process these. or maybe in future. */
+ case BREAK: /* break */
+ case ABORT: /* Abort process */
+ case SUSP: /* Suspend process */
+ case AO: /* abort output--but let prog finish */
+ case IP: /* interrupt process--permanently */
+ case EOR: /* end of record (transparent mode) */
+ case DM: /* data mark--for connect. cleaning */
+ case xEOF: /* End of file: EOF is already used... */
+ return NOP;
+
+ case NOP: /* nop */
+ return NOP;
+
+ /* we should process these, but maybe in future. */
+ case GA: /* you may reverse the line */
+ case EL: /* erase the current line */
+ case EC: /* erase the current character */
+ return NOP;
+
+ /* good */
+ case AYT: /* are you there */
+ {
+ const char *alive = "I'm still alive, loading: ";
+ char buf[STRLEN];
+
+ /* respond as fast as we can */
+ write(0, alive, strlen(alive));
+ cpuload(buf);
+ write(0, buf, strlen(buf));
+ write(0, "\r\n", 2);
+ }
+ return NOP;
+
+ case DONT: /* you are not to use option */
+ case DO: /* please, you use option */
+ case WONT: /* I won't use option */
+ case WILL: /* I will use option */
+ iac_opt_req = c;
+ iac_state = IAC_WAIT_OPT;
+ return NOP;
+
+ case SB: /* interpret as subnegotiation */
+ iac_state = IAC_WAIT_SE;
+ iac_buflen = 0;
+ return NOP;
+
+ case SE: /* end sub negotiation */
+ default:
+ return NOP;
+ }
+ return 1;
+
+ case IAC_WAIT_OPT:
+#ifdef DEBUG
+ write(0, "-", 1);
+ if(TELOPT_OK(c))
+ write(0, TELOPT(c), strlen(TELOPT(c)));
+ write(0, " ", 1);
+#endif
+ iac_state = IAC_NONE;
+ /*
+ * According to RFC, there're some tricky steps to prevent loop.
+ * However because we have a poor term which does not allow
+ * most abilities, let's be a strong boss here.
+ *
+ * Although my old imeplementation worked, it's even better to follow this:
+ * http://www.tcpipguide.com/free/t_TelnetOptionsandOptionNegotiation-3.htm
+ */
+ switch(c) {
+ /* i-dont-care: i don't care about what client is.
+ * these should be clamed in init and
+ * client must follow me. */
+ case TELOPT_TTYPE: /* termtype or line. */
+ case TELOPT_NAWS: /* resize terminal */
+ case TELOPT_SGA: /* supress GA */
+ case TELOPT_ECHO: /* echo */
+ case TELOPT_BINARY: /* we are CJK. */
+ break;
+
+ /* i-dont-agree: i don't understand/agree these.
+ * according to RFC, saying NO stopped further
+ * requests so there'll not be endless loop. */
+ case TELOPT_RCP: /* prepare to reconnect */
+ default:
+ if (iac_opt_req == WILL || iac_opt_req == DO)
+ {
+ /* unknown option, reply with won't */
+ unsigned char cmd[3] = { IAC, DONT, 0 };
+ if(iac_opt_req == DO) cmd[1] = WONT;
+ cmd[2] = c;
+ write(0, cmd, sizeof(cmd));
+ }
+ break;
+ }
+ return 1;
+
+ case IAC_WAIT_SE:
+ iac_buf[iac_buflen++] = c;
+ /* no need to convert state because previous quoting will do. */
+
+ if(iac_buflen == TELNET_IAC_MAXLEN) {
+ /* may be broken protocol?
+ * whether finished or not, break for safety
+ * or user may be frozen.
+ */
+ iac_state = IAC_NONE;
+ return 0;
+ }
+ return 1;
+
+ case IAC_PROCESS_OPT:
+ iac_state = IAC_NONE;
+#ifdef DEBUG
+ write(0, "-", 1);
+ if(TELOPT_OK(iac_buf[0]))
+ write(0, TELOPT(iac_buf[0]), strlen(TELOPT(iac_buf[0])));
+ write(0, " ", 1);
+#endif
+ switch(iac_buf[0]) {
+
+ /* resize terminal */
+ case TELOPT_NAWS:
+ {
+ int w = (iac_buf[1] << 8) + (iac_buf[2]);
+ int h = (iac_buf[3] << 8) + (iac_buf[4]);
+ term_resize(w, h);
+#ifdef DETECT_CLIENT
+ if(cuser.userid[0]==0) {
+ FNV1A_CHAR(iac_buf[0], client_code);
+ if(w==80 && h==24)
+ FNV1A_CHAR(1, client_code);
+ else if(w==80)
+ FNV1A_CHAR(2, client_code);
+ else if(h==24)
+ FNV1A_CHAR(3, client_code);
+ else
+ FNV1A_CHAR(4, client_code);
+ FNV1A_CHAR(IAC, client_code);
+ FNV1A_CHAR(SE, client_code);
+ }
+#endif
+ }
+ break;
+
+ default:
+#ifdef DETECT_CLIENT
+ if(cuser.userid[0]==0) {
+ int i;
+ for(i=0;i<iac_buflen;i++)
+ FNV1A_CHAR(iac_buf[i], client_code);
+ FNV1A_CHAR(IAC, client_code);
+ FNV1A_CHAR(SE, client_code);
+ }
+#endif
+ break;
+ }
+ return 1;
+ }
+ return 1; /* never reached */
+}
+/* vim: sw=4
+ */
diff --git a/pttbbs/mbbsd/menu.c b/pttbbs/mbbsd/menu.c
new file mode 100644
index 00000000..33538753
--- /dev/null
+++ b/pttbbs/mbbsd/menu.c
@@ -0,0 +1,708 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define CheckMenuPerm(x) ( (x) ? HasUserPerm(x) : 1)
+
+/* help & menu processring */
+static int refscreen = NA;
+extern char *boardprefix;
+extern struct utmpfile_t *utmpshm;
+extern char board_hidden_status;
+
+static const char *title_tail_msgs[] = {
+ "看板",
+ "文摘",
+ "系列",
+};
+static const char *title_tail_attrs[] = {
+ ANSI_COLOR(37),
+ ANSI_COLOR(32),
+ ANSI_COLOR(36),
+};
+enum {
+ TITLE_TAIL_BOARD = 0,
+ TITLE_TAIL_SELECT,
+ TITLE_TAIL_DIGEST,
+};
+
+void
+showtitle(const char *title, const char *mid)
+{
+ /* we have to...
+ * - display title in left, cannot truncate.
+ * - display mid message, cannot truncate
+ * - display tail (board info), if possible.
+ */
+ int llen = -1, rlen = -1, mlen = -1, mpos = 0;
+ int pos = 0;
+ int tail_type = TITLE_TAIL_BOARD;
+ const char *mid_attr = ANSI_COLOR(33);
+
+ static char lastboard[16] = {0};
+ char buf[64];
+
+ if (currmode & MODE_SELECT)
+ tail_type = TITLE_TAIL_SELECT;
+ else if (currmode & MODE_DIGEST)
+ tail_type = TITLE_TAIL_DIGEST;
+
+ /* check if board was changed. */
+ if (strcmp(currboard, lastboard) != 0 && currboard[0]) {
+ int bid = getbnum(currboard);
+ if(bid > 0)
+ {
+ assert(0<=bid-1 && bid-1<MAX_BOARD);
+ board_hidden_status = ((getbcache(bid)->brdattr & BRD_HIDE) &&
+ (getbcache(bid)->brdattr & BRD_POSTMASK));
+ strlcpy(lastboard, currboard, sizeof(lastboard));
+ }
+ }
+
+ /* next, determine if title was overrided. */
+#ifdef DEBUG
+ {
+ sprintf(buf, " current pid: %6d ", getpid());
+ mid = buf;
+ mid_attr = ANSI_COLOR(41;5);
+ mlen = strlen(mid);
+ }
+#else
+ if (ISNEWMAIL(currutmp)) {
+ mid = " 郵差來按鈴囉 ";
+ mid_attr = ANSI_COLOR(41;5);
+ mlen = strlen(mid);
+ } else if ( HasUserPerm(PERM_ACCTREG) ) {
+ int nreg = dashs((char *)fn_register) / 163;
+ if(nreg > 100)
+ {
+ sprintf(buf, " 有 %03d 未審核 ", nreg);
+ mid_attr = ANSI_COLOR(41;5);
+ mid = buf;
+ mlen = strlen(mid);
+ }
+ }
+#endif
+ /* now, calculate real positioning info */
+ if(llen < 0) llen = strlen(title);
+ if(mlen < 0) mlen = strlen(mid);
+ mpos = (t_columns - mlen)/2;
+
+ /* first, print left. */
+ clear();
+ outs(TITLE_COLOR "【");
+ outs(title);
+ outs("】");
+ pos = llen + 4;
+ /* prepare for mid */
+ while(pos < mpos)
+ outc(' '), pos++;
+ outs(mid_attr);
+ outs(mid), pos+=mlen;
+ outs(TITLE_COLOR);
+ /* try to locate right */
+ rlen = strlen(currboard) + 4 + 4;
+ if(currboard[0] && pos+rlen <= t_columns)
+ {
+ // print right stuff
+ while(++pos < t_columns-rlen)
+ outc(' ');
+ outs(title_tail_attrs[tail_type]);
+ outs(title_tail_msgs[tail_type]);
+ outs("《");
+ if (board_hidden_status)
+ outs(ANSI_COLOR(32));
+ outs(currboard);
+ outs(title_tail_attrs[tail_type]);
+ outs("》" ANSI_RESET "\n");
+ } else {
+ // just pad it.
+ while(++pos < t_columns)
+ outc(' ');
+ outs(ANSI_RESET "\n");
+ }
+
+}
+
+/* 動畫處理 */
+#define FILMROW 11
+static const unsigned char menu_row = 12;
+static const unsigned char menu_column = 20;
+
+static void
+show_status(void)
+{
+ int i;
+ struct tm *ptime = localtime4(&now);
+ char mystatus[160];
+ char *myweek = "天一二三四五六";
+ const char *msgs[] = {"關閉", "打開", "拔掉", "防水", "好友"};
+
+ i = ptime->tm_wday << 1;
+ snprintf(mystatus, sizeof(mystatus),
+ ANSI_COLOR(34;46) "[%d/%d 星期%c%c %d:%02d]"
+ ANSI_COLOR(1;33;45) "%-14s"
+ ANSI_COLOR(30;47) " 目前坊裡有" ANSI_COLOR(31)
+ "%d" ANSI_COLOR(30) "人, 我是" ANSI_COLOR(31) "%s"
+ ANSI_COLOR(30) ,
+ ptime->tm_mon + 1, ptime->tm_mday, myweek[i], myweek[i + 1],
+ ptime->tm_hour, ptime->tm_min, currutmp->birth ?
+ "生日要請客唷" : SHM->today_is,
+ SHM->UTMPnumber, cuser.userid);
+ outmsg(mystatus);
+ i = strlen(mystatus) - (3*7+25);
+ sprintf(mystatus, "[扣機]" ANSI_COLOR(31) "%s ",
+ msgs[currutmp->pager]);
+ outslr("", i, mystatus, strlen(msgs[currutmp->pager]) + 7);
+ outs(ANSI_RESET);
+}
+
+/*
+ * current callee of movie:
+ * board.c: movie(0); // called when IN_CLASSROOT()
+ * // with currstat = CLASS -> don't show movies
+ * xyz.c: movie(999999); // logout
+ * menu.c: movie(cmdmode); // ...
+ */
+void
+movie(int cmdmode)
+{
+ // movie 前幾筆是 Note 板精華區「<系統> 動態看板」(SYS) 目錄下的文章
+ // movie_map 是用來依 cmdmode 挑出特定的動態看板,index 跟 mode_map 一樣。
+ const int movie_map[] = {
+ 2, 10, 11, -1, 3, -1, 12,
+ 7, 9, 8, 4, 5, 6,
+ };
+
+#define N_SYSMOVIE (sizeof(movie_map) / sizeof(movie_map[0]))
+ int i;
+ if ((currstat != CLASS) && (cuser.uflag & MOVIE_FLAG) &&
+ !SHM->Pbusystate && SHM->last_film > 0) {
+ if (cmdmode < N_SYSMOVIE &&
+ 0 < movie_map[cmdmode] && movie_map[cmdmode] <= SHM->last_film) {
+ i = movie_map[cmdmode];
+ } else if (cmdmode == 999999) { /* Goodbye my friend */
+ i = 0;
+ } else {
+ i = N_SYSMOVIE + (int)(((float)SHM->last_film - N_SYSMOVIE + 1) * (random() / (RAND_MAX + 1.0)));
+ }
+#undef N_SYSMOVIE
+
+ move(1, 0);
+ clrtoline(1 + FILMROW); /* 清掉上次的 */
+ out_lines(SHM->notes[i], 11); /* 只印11行就好 */
+ outs(reset_color);
+ }
+ show_status();
+ refresh();
+}
+
+static int
+show_menu(int moviemode, const commands_t * p)
+{
+ register int n = 0;
+ register char *s;
+
+ movie(moviemode);
+
+ move(menu_row, 0);
+ while ((s = p[n].desc)) {
+ if (CheckMenuPerm(p[n].level)) {
+ prints("%*s (" ANSI_COLOR(1;36) "%c" ANSI_COLOR(0) ")%s\n", menu_column, "", s[1],
+ s+2);
+ }
+ n++;
+ }
+ return n - 1;
+}
+
+
+enum {
+ M_ADMIN = 0, M_AMUSE, M_CHC, M_JCEE, M_MAIL, M_MMENU, M_NMENU,
+ M_PMENU, M_PSALE, M_SREG, M_TMENU, M_UMENU, M_XMENU,
+};
+
+static const int mode_map[] = {
+ ADMIN, AMUSE, CHC, JCEE, MAIL, MMENU, NMENU,
+ PMENU, PSALE, SREG, TMENU, UMENU, XMENU,
+};
+
+static void
+domenu(int cmdmode, const char *cmdtitle, int cmd, const commands_t cmdtable[])
+{
+ int lastcmdptr, moviemode;
+ int n, pos, total, i;
+ int err;
+
+ static char cursor_position[sizeof(mode_map) / sizeof(mode_map[0])] = { 0 };
+
+ moviemode = cmdmode;
+ cmdmode = mode_map[cmdmode];
+
+ if (cursor_position[cmdmode])
+ cmd = cursor_position[cmdmode];
+
+ setutmpmode(cmdmode);
+
+ showtitle(cmdtitle, BBSName);
+
+ total = show_menu(moviemode, cmdtable);
+
+ show_status();
+ lastcmdptr = pos = 0;
+
+ do {
+ i = -1;
+ switch (cmd) {
+ case Ctrl('I'):
+ t_idle();
+ refscreen = YEA;
+ i = lastcmdptr;
+ break;
+ case Ctrl('N'):
+ New();
+ refscreen = YEA;
+ i = lastcmdptr;
+ break;
+ case Ctrl('A'):
+ if (mail_man() == FULLUPDATE)
+ refscreen = YEA;
+ i = lastcmdptr;
+ break;
+ case KEY_DOWN:
+ i = lastcmdptr;
+ case KEY_HOME:
+ case KEY_PGUP:
+ do {
+ if (++i > total)
+ i = 0;
+ } while (!CheckMenuPerm(cmdtable[i].level));
+ break;
+ case KEY_END:
+ case KEY_PGDN:
+ i = total;
+ break;
+ case KEY_UP:
+ i = lastcmdptr;
+ do {
+ if (--i < 0)
+ i = total;
+ } while (!CheckMenuPerm(cmdtable[i].level));
+ break;
+ case KEY_LEFT:
+ case 'e':
+ case 'E':
+ if (cmdmode == MMENU)
+ cmd = 'G';
+ else if ((cmdmode == MAIL) && chkmailbox())
+ cmd = 'R';
+ else
+ return;
+ default:
+ if ((cmd == 's' || cmd == 'r') &&
+ (currstat == MMENU || currstat == TMENU || currstat == XMENU)) {
+ if (cmd == 's')
+ ReadSelect();
+ else
+ Read();
+ refscreen = YEA;
+ i = lastcmdptr;
+ break;
+ }
+ if (cmd == '\n' || cmd == '\r' || cmd == KEY_RIGHT) {
+ move(b_lines, 0);
+ clrtoeol();
+
+ currstat = XMODE;
+
+ if ((err = (*cmdtable[lastcmdptr].cmdfunc) ()) == QUIT)
+ return;
+ currutmp->mode = currstat = cmdmode;
+
+ if (err == XEASY) {
+ refresh();
+ safe_sleep(1);
+ } else if (err != XEASY + 1 || err == FULLUPDATE)
+ refscreen = YEA;
+
+ if (err != -1)
+ cmd = cmdtable[lastcmdptr].desc[0];
+ else
+ cmd = cmdtable[lastcmdptr].desc[1];
+ cursor_position[cmdmode] = cmdtable[lastcmdptr].desc[0];
+ }
+ if (cmd >= 'a' && cmd <= 'z')
+ cmd &= ~0x20;
+ while (++i <= total)
+ if (cmdtable[i].desc[1] == cmd)
+ break;
+
+ if (!CheckMenuPerm(cmdtable[i].level)) {
+ for (i = 0; cmdtable[i].desc; i++)
+ if (CheckMenuPerm(cmdtable[i].level))
+ break;
+ if (!cmdtable[i].desc)
+ return;
+ }
+
+ if (cmd == 'H' && i > total){
+ /* TODO: Add menu help */
+ }
+ }
+
+ if (i > total || !CheckMenuPerm(cmdtable[i].level))
+ continue;
+
+ if (refscreen) {
+ showtitle(cmdtitle, BBSName);
+
+ show_menu(moviemode, cmdtable);
+
+ show_status();
+ refscreen = NA;
+ }
+ cursor_clear(menu_row + pos, menu_column);
+ n = pos = -1;
+ while (++n <= (lastcmdptr = i))
+ if (CheckMenuPerm(cmdtable[n].level))
+ pos++;
+
+ cursor_show(menu_row + pos, menu_column);
+ } while (((cmd = igetch()) != EOF) || refscreen);
+
+ abort_bbs(0);
+}
+/* INDENT OFF */
+
+/* administrator's maintain menu */
+static const commands_t adminlist[] = {
+ {m_user, PERM_SYSOP, "UUser 使用者資料"},
+ {search_user_bypwd, PERM_ACCOUNTS|PERM_POLICE_MAN,
+ "SSearch User 特殊搜尋使用者"},
+ {search_user_bybakpwd,PERM_ACCOUNTS,"OOld User data 查閱\備份使用者資料"},
+ {m_board, PERM_SYSOP, "BBoard 設定看板"},
+ {m_register, PERM_ACCOUNTS|PERM_ACCTREG,
+ "RRegister 審核註冊表單"},
+ {cat_register, PERM_SYSOP, "CCatregister 無法審核時用的"},
+ {x_file, PERM_SYSOP|PERM_VIEWSYSOP,
+ "XXfile 編輯系統檔案"},
+ {give_money, PERM_SYSOP|PERM_VIEWSYSOP,
+ "GGivemoney 紅包雞"},
+ {m_loginmsg, PERM_SYSOP, "MMessage Login 進站水球"},
+#ifdef HAVE_MAILCLEAN
+ {m_mclean, PERM_SYSOP, "MMail Clean 清理使用者個人信箱"},
+#endif
+#ifdef HAVE_REPORT
+ {m_trace, PERM_SYSOP, "TTrace 設定是否記錄除錯資訊"},
+#endif
+ {NULL, 0, NULL}
+};
+
+/* mail menu */
+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 " ANSI_COLOR(1;32) "情書產生器" ANSI_RESET " "},
+ {mail_list, PERM_LOGINOK, "RMail List 群組寄信"},
+ {setforward, PERM_LOGINOK, "FForward " ANSI_COLOR(32) "設定信箱自動轉寄" ANSI_RESET},
+ {m_sysop, 0, "YYes, sir! 諂媚站長"},
+ {m_internet, PERM_INTERNET, "RInternet 寄信到 Internet"},
+ {mail_mbox, PERM_INTERNET, "RZip UserHome 把所有私人資料打包回去"},
+ {built_mail_index, PERM_LOGINOK, "SSavemail 重建信箱索引"},
+ {mail_all, PERM_SYSOP, "RAll 寄信給所有使用者"},
+ {NULL, 0, NULL}
+};
+
+/* Talk menu */
+static const commands_t talklist[] = {
+ {t_users, 0, "UUsers 完全聊天手冊"},
+ {t_pager, PERM_BASIC, "PPager 切換呼叫器"},
+ {t_idle, 0, "IIdle 發呆"},
+ {t_query, 0, "QQuery 查詢網友"},
+ {t_qchicken, 0, "WWatch Pet 查詢寵物"},
+ {t_talk, PERM_PAGE, "TTalk 找人聊聊"},
+ {t_chat, PERM_CHAT, "CChat 找家茶坊喫茶去"},
+#ifdef PLAY_ANGEL
+ {t_changeangel, PERM_LOGINOK, "UAChange Angel 更換小天使"},
+ {t_angelmsg, PERM_ANGEL, "LLeave message 留言給小主人"},
+#endif
+ {t_display, 0, "DDisplay 顯示上幾次熱訊"},
+ {NULL, 0, NULL}
+};
+
+/* name menu */
+static int t_aloha() {
+ friend_edit(FRIEND_ALOHA);
+ return 0;
+}
+
+static int t_special() {
+ friend_edit(FRIEND_SPECIAL);
+ return 0;
+}
+
+static const commands_t namelist[] = {
+ {t_override, PERM_LOGINOK,"OOverRide 好友名單"},
+ {t_reject, PERM_LOGINOK, "BBlack 壞人名單"},
+ {t_aloha,PERM_LOGINOK, "AALOHA 上站通知名單"},
+#ifdef POSTNOTIFY
+ {t_post,PERM_LOGINOK, "NNewPost 新文章通知名單"},
+#endif
+ {t_special,PERM_LOGINOK, "SSpecial 其他特別名單"},
+ {NULL, 0, NULL}
+};
+
+void Customize(); // user.c
+int u_customize()
+{
+ Customize();
+ return 0;
+}
+
+/* User menu */
+static const commands_t userlist[] = {
+ {u_info, PERM_LOGINOK, "IInfo 設定個人資料與密碼"},
+ {u_customize, PERM_LOGINOK, "IUCustomize 個人化設定"},
+ {calendar, PERM_LOGINOK, "CCalendar 個人行事曆"},
+ {u_editcalendar, PERM_LOGINOK, "CDEditCalendar 編輯個人行事曆"},
+ {u_loginview, PERM_LOGINOK, "LLogin View 選擇進站畫面"},
+#ifdef HAVE_SUICIDE
+ {u_kill, PERM_BASIC, "IKill 自殺!!"},
+#endif
+ {u_editplan, PERM_LOGINOK, "QQueryEdit 編輯名片檔"},
+ {u_editsig, PERM_LOGINOK, "SSignature 編輯簽名檔"},
+#if HAVE_FREECLOAK
+ {u_cloak, PERM_LOGINOK, "KKCloak 隱身術"},
+#else
+ {u_cloak, PERM_CLOAK, "KKCloak 隱身術"},
+#endif
+ {u_register, PERM_BASIC, "RRegister 填寫《註冊申請單》"},
+ {u_cancelbadpost, PERM_LOGINOK, "BBye BadPost 申請刪除劣文"},
+ {u_list, PERM_SYSOP, "XUsers 列出註冊名單"},
+#ifdef MERGEBBS
+// {m_sob, PERM_LOGUSER|PERM_SYSOP, "SSOB Import 沙灘變身術"},
+ {m_sob, PERM_BASIC, "SSOB Import 沙灘變身術"},
+#endif
+ {NULL, 0, NULL}
+};
+
+#ifdef DEBUG
+int _debug_check_keyinput();
+int _debug_testregcode();
+int _debug_reportstruct()
+{
+ clear();
+ prints("boardheader_t:\t%d\n", sizeof(boardheader_t));
+ prints("fileheader_t:\t%d\n", sizeof(fileheader_t));
+ prints("userinfo_t:\t%d\n", sizeof(userinfo_t));
+ prints("screenline_t:\t%d\n", sizeof(screenline_t));
+ prints("SHM_t:\t%d\n", sizeof(SHM_t));
+ prints("bid_t:\t%d\n", sizeof(bid_t));
+ prints("userec_t:\t%d\n", sizeof(userec_t));
+ pressanykey();
+ return 0;
+}
+
+#endif
+
+/* XYZ tool menu */
+static const commands_t xyzlist[] = {
+#ifndef DEBUG
+ /* All these are useless in debug mode. */
+#ifdef HAVE_LICENSE
+ {x_gpl, 0, "LLicense GNU 使用執照"},
+#endif
+#ifdef HAVE_INFO
+ {x_program, 0, "PProgram 本程式之版本與版權宣告"},
+#endif
+ {x_boardman,0, "MMan Boards 《看板精華區排行榜》"},
+// {x_boards,0, "HHot Boards 《看板人氣排行榜》"},
+ {x_history, 0, "HHistory 《我們的成長》"},
+ {x_note, 0, "NNote 《酸甜苦辣流言板》"},
+ {x_login,0, "SSystem 《系統重要公告》"},
+ {x_week, 0, "WWeek 《本週五十大熱門話題》"},
+ {x_issue, 0, "IIssue 《今日十大熱門話題》"},
+ {x_today, 0, "TToday 《今日上線人次統計》"},
+ {x_yesterday, 0, "YYesterday 《昨日上線人次統計》"},
+ {x_user100 ,0, "UUsers 《使用者百大排行榜》"},
+#else
+ {_debug_check_keyinput, 0,
+ "MMKeycode 檢查按鍵控制碼工具"},
+ {_debug_testregcode, 0,
+ "RRegcode 檢查註冊碼公式"},
+ {_debug_reportstruct, 0,
+ "RReportStruct 報告各種結構的大小"},
+#endif
+
+ {p_sysinfo, 0, "XXinfo 《查看系統資訊》"},
+ {NULL, 0, NULL}
+};
+
+/* Ptt money menu */
+static const commands_t moneylist[] = {
+ {p_give, 0, "00Give 給其他人錢"},
+ {save_violatelaw, 0,"11ViolateLaw 繳罰單"},
+#if !HAVE_FREECLOAK
+ {p_cloak, 0, "22Cloak 切換 隱身/現身 $19 /次"},
+#endif
+ {p_from, 0, "33From 暫時修改故鄉 $49 /次"},
+ {ordersong,0, "44OSong 歐桑動態點歌機 $200 /次"},
+ {p_exmail, 0, "55Exmail 購買信箱 $1000/封"},
+ {NULL, 0, NULL}
+};
+
+int main_menu(void) {
+ domenu(M_MMENU, "主功\能表", (ISNEWMAIL(currutmp) ? 'M' : 'C'), cmdlist);
+ return 0;
+}
+
+static int p_money() {
+ domenu(M_PSALE, "Ptt量販店", '0', moneylist);
+ return 0;
+};
+
+#if 0
+const static commands_t jceelist[] = {
+ {x_90,PERM_LOGINOK, "0090 JCEE 【90學年度大學聯招查榜系統】"},
+ {x_89,PERM_LOGINOK, "1189 JCEE 【89學年度大學聯招查榜系統】"},
+ {x_88,PERM_LOGINOK, "2288 JCEE 【88學年度大學聯招查榜系統】"},
+ {x_87,PERM_LOGINOK, "3387 JCEE 【87學年度大學聯招查榜系統】"},
+ {x_86,PERM_LOGINOK, "4486 JCEE 【86學年度大學聯招查榜系統】"},
+ {NULL, 0, NULL}
+};
+
+static int m_jcee() {
+ domenu(M_JCEE, "Ptt查榜系統", '0', jceelist);
+ return 0;
+}
+#endif
+
+static int forsearch();
+static int playground();
+static int chessroom();
+
+/* Ptt Play menu */
+static const commands_t playlist[] = {
+#if 0
+#if HAVE_JCEE
+ {m_jcee, PERM_LOGINOK, "JJCEE 【 大學聯考查榜系統 】"},
+#endif
+#endif
+ {note, PERM_LOGINOK, "NNote 【 刻刻流言板 】"},
+/* XXX 壞掉了, 或許可以換成 weather.today/weather.tomorrow 但反正沒意義 */
+/* {x_weather,0 , "WWeather 【 氣象預報 】"}, */
+/* XXX 壞掉了 */
+/* {x_stock,0 , "SStock 【 股市行情 】"},*/
+ {forsearch,PERM_LOGINOK, "SSearchEngine【" ANSI_COLOR(1;35) " Ptt搜尋器 " ANSI_RESET "】"},
+ {topsong,PERM_LOGINOK, "TTop Songs 【" ANSI_COLOR(1;32) " 點歌排行榜 " ANSI_RESET "】"},
+ {p_money,PERM_LOGINOK, "PPay 【" ANSI_COLOR(1;31) " Ptt量販店 " ANSI_RESET "】"},
+ {chicken_main,PERM_LOGINOK, "CChicken "
+ "【" ANSI_COLOR(1;34) " Ptt養雞場 " ANSI_RESET "】"},
+ {playground,PERM_LOGINOK, "AAmusement 【" ANSI_COLOR(1;33) " Ptt遊樂場 " ANSI_RESET "】"},
+ {chessroom, PERM_LOGINOK, "BBChess 【" ANSI_COLOR(1;34) " Ptt棋院 " ANSI_RESET "】"},
+ {NULL, 0, NULL}
+};
+
+static const commands_t chesslist[] = {
+ {chc_main, PERM_LOGINOK, "11CChessFight 【" ANSI_COLOR(1;33) " 象棋邀局 " ANSI_RESET "】"},
+ {chc_personal, PERM_LOGINOK, "22CChessSelf 【" ANSI_COLOR(1;34) " 象棋打譜 " ANSI_RESET "】"},
+ {chc_watch, PERM_LOGINOK, "33CChessWatch 【" ANSI_COLOR(1;35) " 象棋觀棋 " ANSI_RESET "】"},
+ {gomoku_main, PERM_LOGINOK, "44GomokuFight 【" ANSI_COLOR(1;33) "五子棋邀局" ANSI_RESET "】"},
+ {gomoku_personal, PERM_LOGINOK, "55GomokuSelf 【" ANSI_COLOR(1;34) "五子棋打譜" ANSI_RESET "】"},
+ {gomoku_watch, PERM_LOGINOK, "66GomokuWatch 【" ANSI_COLOR(1;35) "五子棋觀棋" ANSI_RESET "】"},
+ {gochess_main, PERM_LOGINOK, "77GoChessFight 【" ANSI_COLOR(1;33) " 圍棋邀局 " ANSI_RESET "】"},
+ {gochess_personal, PERM_LOGINOK, "88GoChessSelf 【" ANSI_COLOR(1;34) " 圍棋打譜 " ANSI_RESET "】"},
+ {gochess_watch, PERM_LOGINOK, "99GoChessWatch 【" ANSI_COLOR(1;35) " 圍棋觀棋 " ANSI_RESET "】"},
+ {NULL, 0, NULL}
+};
+
+static int chessroom() {
+ domenu(M_CHC, "Ptt棋院", '1', chesslist);
+ return 0;
+}
+
+static const commands_t plist[] = {
+
+/* {p_ticket_main, PERM_LOGINOK,"00Pre 【 總統機 】"},
+ {alive, PERM_LOGINOK, "00Alive 【 訂票雞 】"},
+*/
+ {ticket_main, PERM_LOGINOK, "11Gamble 【 Ptt賭場 】"},
+ {guess_main, PERM_LOGINOK, "22Guess number【 猜數字 】"},
+ {othello_main, PERM_LOGINOK, "33Othello 【 黑白棋 】"},
+// {dice_main, PERM_LOGINOK, "44Dice 【 玩骰子 】"},
+ {vice_main, PERM_LOGINOK, "44Vice 【 發票對獎 】"},
+ {g_card_jack, PERM_LOGINOK, "55Jack 【 黑傑克 】"},
+ {g_ten_helf, PERM_LOGINOK, "66Tenhalf 【 十點半 】"},
+ {card_99, PERM_LOGINOK, "77Nine 【 九十九 】"},
+ {NULL, 0, NULL}
+};
+
+static int playground() {
+ domenu(M_AMUSE, "Ptt遊樂場",'1',plist);
+ return 0;
+}
+
+static const commands_t slist[] = {
+ {x_dict,0, "11Dictionary "
+ "【" ANSI_COLOR(1;33) " 趣味大字典 " ANSI_RESET "】"},
+ {x_mrtmap, 0, "22MRTmap "
+ "【" ANSI_COLOR(1;34) " 捷運地圖 " ANSI_RESET "】"},
+ {NULL, 0, NULL}
+};
+
+static int forsearch() {
+ domenu(M_SREG, "Ptt搜尋器", '1', slist);
+ return 0;
+}
+
+/* main menu */
+
+int
+admin(void)
+{
+ domenu(M_ADMIN, "系統維護", 'X', adminlist);
+ return 0;
+}
+
+int
+Mail(void)
+{
+ domenu(M_MAIL, "電子郵件", 'R', maillist);
+ return 0;
+}
+
+int
+Talk(void)
+{
+ domenu(M_TMENU, "聊天說話", 'U', talklist);
+ return 0;
+}
+
+int
+User(void)
+{
+ domenu(M_UMENU, "個人設定", 'I', userlist);
+ return 0;
+}
+
+int
+Xyz(void)
+{
+ domenu(M_XMENU, "工具程式", 'M', xyzlist);
+ return 0;
+}
+
+int
+Play_Play(void)
+{
+ domenu(M_PMENU, "網路遊樂場", 'A', playlist);
+ return 0;
+}
+
+int
+Name_Menu(void)
+{
+ domenu(M_NMENU, "白色恐怖", 'O', namelist);
+ return 0;
+}
+
diff --git a/pttbbs/mbbsd/merge.c b/pttbbs/mbbsd/merge.c
new file mode 100644
index 00000000..b3b61b84
--- /dev/null
+++ b/pttbbs/mbbsd/merge.c
@@ -0,0 +1,237 @@
+/* $Id$ */
+#define _XOPEN_SOURCE
+#define _ISOC99_SOURCE
+/* this is a interface provided when we merge BBS */
+#include "bbs.h"
+#include "fpg.h"
+
+int
+m_sob(void)
+{
+ char genbuf[256], buf[256], userid[25], passbuf[24], msg[2048]="";
+ int count=0, i, isimported=0, corrected;
+ FILE *fp;
+ sobuserec man;
+ time4_t d;
+
+ clear();
+ move(1,0);
+
+ outs(
+ " 請注意 這是只給陽光沙灘使用者!\n"
+ " 讓沙灘的使用者轉移個人資產以及重要信用資料, 享有平等安全的環境.\n"
+ " 如果您不需要, 請直離開.\n"
+ " -----------------------------------------------------------------\n"
+ " 特別叮嚀:\n"
+ " 為了帳號安全,您只有連續十次密碼錯誤的機會,請小心輸入.\n"
+ " 連續次錯誤您的變身功\能就會被開罰單並直接通知站長.\n"
+ " 請不要在變身過程中不正常斷線, 刻意斷線變半獸人站長不救唷.\n"
+ );
+
+ if(getkey("是否要繼續?(y/N)")!='y') return 0;
+ if(search_ulistn(usernum,2))
+ {vmsg("請登出其他視窗, 以免變身失敗"); return 0;}
+ do
+ {
+ if(!getdata(10,0, " 沙灘的ID [大小寫要完全正確]:", userid, 20,
+ DOECHO)) return 0;
+ if(bad_user_id(userid)) continue;
+ sprintf(genbuf, "sob/passwd/%c/%s.inf",userid[0], userid);
+ if(!(fp=fopen(genbuf, "r")))
+ {
+ isimported = 1;
+ strcat(genbuf, ".done");
+ if(!(fp=fopen(genbuf, "r")))
+ {
+ vmsg("查無此人或已經匯入過..請注意大小寫 ");
+ isimported = 0;
+ continue;
+ }
+ }
+ count = fread(&man, sizeof(man), 1, fp);
+ fclose(fp);
+ }while(!count);
+ count = 0;
+ do{
+ if(!getdata(11,0, " 沙灘的密碼:", passbuf, sizeof(passbuf),
+ NOECHO)) return 0;
+ if(++count>=10)
+ {
+ cuser.userlevel |= PERM_VIOLATELAW;
+ cuser.vl_count++;
+ passwd_update(usernum, &cuser);
+ post_violatelaw(cuser.userid, "[PTT警察]", "測試帳號錯誤十次",
+ "違法觀察");
+ mail_violatelaw(cuser.userid, "[PTT警察]", "測試帳號錯誤十次",
+ "違法觀察");
+
+ return 0;
+ }
+ if(!(corrected = checkpasswd(man.passwd, passbuf)))
+ vmsg("密碼錯誤");
+ } while(!corrected);
+ move(12,0);
+ clrtobot();
+
+ if(!isimported)
+ {
+ if(!dashf(genbuf)) // avoid multi-login
+ {
+ vmsg("請不要嘗試多重id踹匯入");
+ return 0;
+ }
+ sprintf(buf,"%s.done",genbuf);
+ rename(genbuf,buf);
+#ifdef MERGEMONEY
+
+ reload_money();
+
+ sprintf(buf,
+ "您的沙灘鸚鵡螺 %10d 換算成 Ptt 幣為 %9d (匯率 22:1), \n"
+ " 沙灘貝殼有 %10d 換算為 Ptt 幣為 %9d (匯率 222105:1), \n"
+ " 原有P幣 %10d 匯入後共有 %d\n",
+ (int)man.goldmoney, (int)man.goldmoney/22,
+ (int)man.silvermoney, (int)man.silvermoney/222105,
+ cuser.money,
+ (int)(cuser.money + man.goldmoney/22 + man.silvermoney/222105));
+ demoney(man.goldmoney/22 + man.silvermoney/222105 );
+ strcat(msg, buf);
+#endif
+
+ i = cuser.exmailbox + man.exmailbox + man.exmailboxk/2000;
+ if (i > MAX_EXKEEPMAIL) i = MAX_EXKEEPMAIL;
+ sprintf(buf, "您的沙灘信箱有 %d (%dk), 原有 %d 匯入後共有 %d\n",
+ man.exmailbox, man.exmailboxk, cuser.exmailbox, i);
+ strcat(msg, buf);
+ cuser.exmailbox = i;
+
+ if(man.userlevel & PERM_MAILLIMIT)
+ {
+ sprintf(buf, "開啟信箱無上限\n");
+ strcat(msg, buf);
+ cuser.userlevel |= PERM_MAILLIMIT;
+ }
+
+ if (cuser.firstlogin > man.firstlogin)
+ d = man.firstlogin;
+ else
+ d = cuser.firstlogin;
+ cuser.firstlogin = d;
+
+ if (cuser.numlogins < man.numlogins)
+ i = man.numlogins;
+ else
+ i = cuser.numlogins;
+
+ sprintf(buf, "沙灘進站次數 %d 此帳號 %d 將取 %d \n", man.numlogins,
+ cuser.numlogins, i);
+ strcat(msg,buf);
+ cuser.numlogins = i;
+
+ if (cuser.numposts < man.numposts )
+ i = man.numposts;
+ else
+ i = cuser.numposts;
+ sprintf(buf, "沙灘文章次數 %d 此帳號 %d 將取 %d\n",
+ man.numposts,cuser.numposts,i);
+ strcat(msg,buf);
+ cuser.numposts = i;
+ outs(msg);
+ while (search_ulistn(usernum,2))
+ {vmsg("請將重覆上站其他線關閉! 再繼續");}
+ passwd_update(usernum, &cuser);
+ }
+ sethomeman(genbuf, cuser.userid);
+ mkdir(genbuf, 0600);
+ sprintf(buf, "tar zxvf %c/%s.tar.gz>/dev/null",
+ userid[0], userid);
+ chdir("sob/home");
+ system(buf);
+ chdir(BBSHOME);
+
+ if (getans("是否匯入個人信箱? (Y/n)")!='n')
+ {
+ sethomedir(buf, cuser.userid);
+ sprintf(genbuf, "sob/home/%c/%s/.DIR",
+ userid[0], userid);
+ merge_dir(buf, genbuf, 1);
+ strcat(msg, "匯入個人信箱\n");
+ }
+ if(getans("是否匯入個人信箱精華區(個人作品集)? (會覆蓋\現有設定) (y/N)")=='y')
+ {
+ fileheader_t fh;
+ sprintf(buf,
+ "rm -rd home/%c/%s/man>/dev/null ; "
+ "mv sob/home/%c/%s/man home/%c/%s>/dev/null;"
+ "mv sob/home/%c/%s/gem home/%c/%s/man>/dev/null",
+ cuser.userid[0], cuser.userid,
+ userid[0], userid,
+ cuser.userid[0], cuser.userid,
+ userid[0], userid,
+ cuser.userid[0], cuser.userid);
+ system(buf);
+ strcat(msg, "匯入個人信箱精華區(個人作品集)\n");
+ sprintf(buf,"home/%c/%s/man/gem", cuser.userid[0], cuser.userid);
+ if(dashd(buf))
+ {
+ strcat(fh.title, "◆ 個人作品集");
+ strcat(fh.filename, "gem");
+ sprintf(fh.owner, cuser.userid);
+ sprintf(buf, "home/%c/%s/man/.DIR", cuser.userid[0], cuser.userid);
+ append_record(buf, &fh, sizeof(fh));
+ }
+ }
+ if(getans("是否匯入好友名單? (會覆蓋\現有設定, ID可能是不同人)? (y/N)")=='y')
+ {
+ sethomefile(genbuf, cuser.userid, "overrides");
+ sprintf(buf, "sob/home/%c/%s/overrides",userid[0],userid);
+ Copy(buf, genbuf);
+ strcat(buf, genbuf);
+ friend_load(FRIEND_OVERRIDE);
+ strcat(msg, "匯入好友名單\n");
+ }
+ sprintf(buf, "帳號匯入報告 %s -> %s ", userid, cuser.userid);
+ post_msg("Security", buf, msg, "[系統安全局]");
+
+ vmsg("恭喜您完成帳號變身..");
+ return 0;
+}
+
+void
+m_sob_brd(char *bname, char *fromdir)
+{
+ char fbname[25], buf[256];
+ fileheader_t fh;
+
+ fromdir[0]=0;
+ do{
+
+ if(!getdata(20,0, "SOB的板名 [英文大小寫要完全正確]:", fbname, 20,
+ DOECHO)) return;
+ }
+ while((invalid_brdname(fbname)&1));
+
+ sprintf(buf, "sob/man/%s.tar.gz", fbname);
+ if(!dashf(buf))
+ {
+ vmsg("無此看板");
+ return;
+ }
+ chdir(BBSHOME"/sob/boards");
+ sprintf(buf, "tar zxf %s.tar.gz >/dev/null",fbname);
+ system(buf);
+ chdir(BBSHOME"/sob/man");
+ sprintf(buf, "tar zxf %s.tar.gz >/dev/null", fbname);
+ system(buf);
+ chdir(BBSHOME);
+ sprintf(buf, "mv sob/man/%s man/boards/%c/%s", fbname,
+ bname[0], bname);
+ system(buf);
+ sprintf(fh.title, "◆ %s 精華區", fbname);
+ sprintf(fh.filename, fbname);
+ sprintf(fh.owner, cuser.userid);
+ sprintf(buf, "man/boards/%c/%s/.DIR", bname[0], bname);
+ append_record(buf, &fh, sizeof(fh));
+ sprintf(fromdir, "sob/boards/%s/.DIR", fbname);
+ vmsgf("即將匯入 %s 板資料..按鍵後需要一點時間",fbname);
+}
diff --git a/pttbbs/mbbsd/more.c b/pttbbs/mbbsd/more.c
new file mode 100644
index 00000000..5fe300bf
--- /dev/null
+++ b/pttbbs/mbbsd/more.c
@@ -0,0 +1,591 @@
+/* $Id$ */
+#include "bbs.h"
+
+#ifndef USE_TRADITIONAL_MORE
+
+#define USE_PIAIP_MORE
+
+/* use new pager: piaip's more. */
+int more(char *fpath, int promptend)
+{
+ return pmore(fpath, promptend);
+}
+
+#else
+
+/* 把這兩個 size 調到一頁的範圍是不是能降低不必要的 IO ? */
+#define MORE_BUFSIZE 4096
+#define MORE_WINSIZE 4096
+#define STR_ANSICODE "[0123456789;,"
+
+struct MorePool {
+ int base, size, head;
+ unsigned char more_pool[MORE_BUFSIZE];
+};
+
+static const char * const more_help[] = {
+ "\0閱\讀文章功\能鍵使用說明",
+ "\01游標移動功\能鍵",
+ "(↑) 上捲一行",
+ "(↓)(Enter) 下捲一行",
+ "(^B)(PgUp)(BackSpace) 上捲一頁",
+ "(→)(PgDn)(Space) 下捲一頁",
+ "(0)(g)(Home) 檔案開頭",
+ "($)(G) (End) 檔案結尾",
+ "\01其他功\能鍵",
+ "(/) 搜尋字串",
+ "(n/N) 重複正/反向搜尋",
+ "(TAB) URL連結",
+ "(Ctrl-T) 存到暫存檔",
+ "(:/f/b) 跳至某頁/下/上篇",
+ "(a/A) 跳至同一作者下/上篇",
+ "([-/]+) 主題式閱\讀 上/下",
+ "(t) 主題式循序閱\讀",
+ "(q)(←) 結束",
+ "(h)(H)(?) 輔助說明畫面",
+ NULL
+};
+
+#if DEPRECATED__UNUSED
+int beep = 0;
+#endif
+
+static void
+more_goto(int fd, struct MorePool *mp, off_t off)
+{
+ int base = mp->base;
+
+ if (off < base || off >= base + mp->size) {
+ mp->base = base = off & (-MORE_WINSIZE);
+ lseek(fd, base, SEEK_SET);
+ mp->size = read(fd, mp->more_pool, MORE_BUFSIZE);
+ }
+ mp->head = off - base;
+}
+
+static int
+more_readln(int fd, struct MorePool *mp, unsigned char *buf)
+{
+ int ch;
+
+ unsigned char *data, *tail, *cc;
+ int len, bytes, in_ansi, in_big5;
+ int size, head, ansilen;
+
+ len = bytes = in_ansi = in_big5 = ansilen = 0;
+ tail = buf + ANSILINELEN - 1;
+ size = mp->size;
+ head = mp->head;
+ data = &mp->more_pool[head];
+
+ do {
+ if (head >= size) {
+ mp->base += size;
+ data = mp->more_pool;
+ mp->size = size = read(fd, data, MORE_BUFSIZE);
+ if (size == 0)
+ break;
+ head = 0;
+ }
+ ch = *data++;
+ head++;
+ bytes++;
+ if (ch == '\n') {
+ break;
+ }
+ if (ch == '\t') {
+ do {
+ *buf++ = ' ';
+ }
+ while ((++len & 7) && len < t_columns);
+ } else if (ch == ESC_CHR) {
+ if (atoi((char *)(data + 1)) > 47) {
+ if ((cc = (unsigned char *)strchr((char *)(data + 1), 'm')) != NULL) {
+ ch = cc - data + 1;
+
+ data += ch;
+ head += ch;
+ bytes += ch;
+ }
+ } else {
+ *buf++ = ch;
+ in_ansi = 1;
+ }
+ } else if (in_ansi) {
+ *buf++ = ch;
+ if (!strchr(STR_ANSICODE, ch))
+ in_ansi = 0;
+ } else if (in_big5) {
+ ++len;
+ *buf++ = ch;
+ in_big5 = 0;
+ } else if (isprint2(ch)) {
+ len++;
+ *buf++ = ch;
+ if (ch >= 0xa1 && ch <= 0xf9) /* first byte of big5 encoding */
+ in_big5 = 1;
+ }
+ } while (len < t_columns && buf < tail);
+
+ if (in_big5 && len >= t_columns) {
+ --buf;
+ --head;
+ --data;
+ --bytes;
+ }
+
+ if (len == t_columns && head < size && *data == '\n') {
+ /* XXX: not handle head==size, should read data */
+ /* no extra newline dirty hack for exact 80byte line */
+ data++; bytes++; head++;
+ }
+ *buf = '\0';
+ mp->head = head;
+ return bytes;
+}
+
+int
+more(char *fpath, int promptend)
+{
+ char *head[4] = {"作者", "標題", "時間", "轉信"};
+ char *ptr, *word = NULL, buf[ANSILINELEN + 1];
+ struct stat st;
+
+ int fd, fsize;
+
+ unsigned int pagebreak[MAX_PAGES], pageno, lino = 0;
+ int line, ch, viewed, pos, numbytes;
+ int header = 0;
+ int local = 0;
+ char search_char0 = 0;
+ static char search_str[81] = "";
+ typedef char *(*FPTR) ();
+ static FPTR fptr;
+ int searching = 0;
+ int scrollup = 0;
+ char *printcolor[3] = {"44", "33;45", "0;34;46"}, color = 0;
+ struct MorePool mp;
+ /* Ptt */
+
+ STATINC(STAT_MORE);
+ memset(pagebreak, 0, sizeof(pagebreak));
+ if (*search_str)
+ search_char0 = *search_str;
+ *search_str = 0;
+
+ fd = open(fpath, O_RDONLY, 0600);
+ if (fd < 0)
+ return -1;
+
+ if (fstat(fd, &st) || ((fsize = st.st_size) <= 0) || S_ISDIR(st.st_mode)) {
+ close(fd);
+ //Ptt
+ return -1;
+ }
+ pagebreak[0] = pageno = viewed = line = pos = 0;
+ clear();
+
+ /* rocker */
+
+ mp.base = mp.head = mp.size = 0;
+
+ while ((numbytes = more_readln(fd, &mp, (unsigned char *)buf)) || (line == t_lines)) {
+ if (scrollup) {
+ rscroll();
+ move(0, 0);
+ }
+ if (numbytes) { /* 一般資料處理 */
+ if (!viewed) { /* begin of file */
+ if (!strncmp(buf, str_author1, LEN_AUTHOR1)) {
+ line = 3;
+ word = buf + LEN_AUTHOR1;
+ local = 1;
+ } else if (!strncmp(buf, str_author2, LEN_AUTHOR2)) {
+ line = 4;
+ word = buf + LEN_AUTHOR2;
+ }
+ while (pos < line) {
+ if (!pos && ((ptr = strstr(word, str_post1)) ||
+ (ptr = strstr(word, str_post2)))) {
+ ptr[-1] = '\0';
+ prints(ANSI_COLOR(47;34) " %s " ANSI_COLOR(44;37) "%-53.53s"
+ ANSI_COLOR(47;34) " %.4s " ANSI_COLOR(44;37) "%-13s" ANSI_RESET "\n",
+ head[0], word, ptr, ptr + 5);
+ } else if (pos < line)
+ prints(ANSI_COLOR(47;34) " %s " ANSI_COLOR(44;37) "%-72.72s"
+ ANSI_RESET "\n", head[pos], word);
+
+ viewed += numbytes;
+ numbytes = more_readln(fd, &mp, (unsigned char *)buf);
+
+ /* 第一行太長了 */
+ if (!pos && viewed > 79) {
+ /* 第二行不是 [標....] */
+ if (memcmp(buf, head[1], 2)) {
+ /* 讀下一行進來處理 */
+ viewed += numbytes;
+ numbytes = more_readln(fd, &mp, (unsigned char *)buf);
+ }
+ }
+ pos++;
+ }
+ if (pos) {
+ header = 1;
+
+ prints(ANSI_COLOR(36) "%s" ANSI_RESET "\n", msg_seperator);
+ ++line;
+ ++pos;
+ }
+ lino = pos;
+ word = NULL;
+ }
+ /* ※處理引用者 & 引言 */
+ if ((buf[0] == ':' || buf[0] == '>') && (buf[1] == ' '))
+ word = ANSI_COLOR(36);
+ else if (!strncmp(buf, "※", 2) || !strncmp(buf, "==>", 3))
+ word = ANSI_COLOR(32);
+
+ if (word)
+ outs(word);
+ {
+ char msg[500], *pos;
+
+ if (*search_str && (pos = (*fptr)(buf, search_str))) {
+ char SearchStr[81];
+ char buf1[100], *pos1;
+ int search_str_len = strlen(search_str);
+
+ strlcpy(SearchStr, pos, search_str_len + 1);
+ searching = 0;
+ snprintf(msg, sizeof(msg),
+ "%.*s" ANSI_COLOR(7) "%s" ANSI_RESET, (int)(pos - buf), buf,
+ SearchStr);
+ while ((pos = (*fptr)(pos1 = pos + search_str_len,
+ search_str))) {
+ snprintf(buf1, sizeof(buf1),
+ "%.*s" ANSI_COLOR(7) "%s" ANSI_RESET, (int)(pos - pos1),
+ pos1, SearchStr);
+ strlcat(msg, buf1, sizeof(msg));
+ }
+ strlcat(msg, pos1, sizeof(msg));
+ outs(Ptt_prints(msg, sizeof(msg), NO_RELOAD));
+ } else
+ outs(Ptt_prints(buf, sizeof(buf), NO_RELOAD));
+ }
+ if (word) {
+ outs(ANSI_RESET);
+ word = NULL;
+ }
+ outc('\n');
+
+#if DEPRECATED__UNUSED
+ if (beep) {
+ bell();
+ beep = 0;
+ }
+#endif
+ if (line < b_lines) /* 一般資料讀取 */
+ line++;
+
+ if (line == b_lines && searching == -1) {
+ if (pageno > 0)
+ more_goto(fd, &mp, viewed = pagebreak[--pageno]);
+ else
+ searching = 0;
+ lino = pos = line = 0;
+ clear();
+ continue;
+ }
+ if (scrollup) {
+ move(line = b_lines, 0);
+ clrtoeol();
+ for (pos = 1; pos < b_lines; pos++)
+ viewed += more_readln(fd, &mp, (unsigned char *)buf);
+ } else if (pos == b_lines) /* 捲動螢幕 */
+ scroll();
+ else
+ pos++;
+
+ if (!scrollup &&
+ ++lino >= (unsigned)b_lines &&
+ pageno < MAX_PAGES - 1 ) {
+ pagebreak[++pageno] = viewed;
+ lino = 1;
+ }
+ if (scrollup) {
+ lino = scrollup;
+ scrollup = 0;
+ }
+ viewed += numbytes; /* 累計讀過資料 */
+ } else
+ line = b_lines; /* end of END */
+
+ if (promptend &&
+ ((!searching && line == b_lines) || viewed == fsize)) {
+ /* Kaede 剛好 100% 時不停 */
+ move(b_lines, 0);
+ if (viewed == fsize) {
+ if (searching == 1)
+ searching = 0;
+ color = 0;
+ } else if (pageno == 1 && lino == 1) {
+ if (searching == -1)
+ searching = 0;
+ color = 1;
+ } else
+ color = 2;
+
+ prints(ANSI_RESET ANSI_COLOR(%s) " 瀏覽 P.%d(%d%%) %s %-30.30s%s",
+ printcolor[(int)color],
+ pageno,
+ (int)((viewed * 100) / fsize),
+ ANSI_COLOR(31;47),
+ "(h)" ANSI_COLOR(30) "求助 " ANSI_COLOR(31) "→↓[PgUp][",
+ "PgDn][Home][End]" ANSI_COLOR(30) "游標移動 " ANSI_COLOR(31) "←[q]" ANSI_COLOR(30) "結束 " ANSI_RESET);
+
+
+ while (line == b_lines || (line > 0 && viewed == fsize)) {
+ switch ((ch = igetch())) {
+ case ':':
+ {
+ char buf[10];
+ int i = 0;
+
+ getdata(b_lines - 1, 0, "Goto Page: ", buf, 5, DOECHO);
+ sscanf(buf, "%d", &i);
+ if (0 < i && i < MAX_PAGES && (i == 1 || pagebreak[i - 1]))
+ pageno = i - 1;
+ else if (pageno)
+ pageno--;
+ lino = line = 0;
+ break;
+ }
+ case '/':
+ {
+ char ans[4] = "n";
+
+ *search_str = search_char0;
+ getdata_buf(b_lines - 1, 0, "[搜尋]關鍵字:", search_str,
+ 40, DOECHO);
+ if (*search_str) {
+ searching = 1;
+ if (getdata(b_lines - 1, 0, "區分大小寫(Y/N/Q)? [N] ",
+ ans, sizeof(ans), LCECHO) && *ans == 'y')
+ fptr = &strstr;
+ else
+ fptr = &strcasestr;
+ }
+ if (*ans == 'q')
+ searching = 0;
+ if (pageno)
+ pageno--;
+ lino = line = 0;
+ break;
+ }
+ case 'n':
+ if (*search_str) {
+ searching = 1;
+ if (pageno)
+ pageno--;
+ lino = line = 0;
+ }
+ break;
+ case 'N':
+ if (*search_str) {
+ searching = -1;
+ if (pageno)
+ pageno--;
+ lino = line = 0;
+ }
+ break;
+ case 'r': // Ptt: put all reply/recommend function here
+ case 'R':
+ case 'Y':
+ case 'y':
+ close(fd);
+ return 999;
+ case 'X':
+ close(fd);
+ return 998;
+ case 'A':
+ close(fd);
+ return AUTHOR_PREV;
+ case 'a':
+ close(fd);
+ return AUTHOR_NEXT;
+ case 'F':
+ case 'f':
+ close(fd);
+ return READ_NEXT;
+ case 'B':
+ case 'b':
+ close(fd);
+ return READ_PREV;
+ case KEY_LEFT:
+ case 'q':
+ close(fd);
+ return FULLUPDATE;
+ case ']': /* Kaede 為了主題閱讀方便 */
+ case '+':
+ close(fd);
+ return RELATE_NEXT;
+ case '[': /* Kaede 為了主題閱讀方便 */
+ case '-':
+ close(fd);
+ return RELATE_PREV;
+ case '=': /* Kaede 為了主題閱讀方便 */
+ close(fd);
+ return RELATE_FIRST;
+ case Ctrl('F'):
+ case KEY_PGDN:
+ line = 1;
+ break;
+ case 't':
+ if (viewed == fsize) {
+ close(fd);
+ return RELATE_NEXT;
+ }
+ line = 1;
+ break;
+ case ' ':
+ if (viewed == fsize) {
+ close(fd);
+ return READ_NEXT;
+ }
+ line = 1;
+ break;
+ case KEY_RIGHT:
+ if (viewed == fsize) {
+ close(fd);
+ return 0;
+ }
+ line = 1;
+ break;
+ case '\r':
+ case '\n':
+ case KEY_DOWN:
+ if (viewed == fsize ||
+ (promptend == 2 && (ch == '\r' || ch == '\n'))) {
+ close(fd);
+ return READ_NEXT;
+ }
+ line = t_lines - 2;
+ break;
+ case '$':
+ case 'G':
+ case KEY_END:
+ line = t_lines;
+ break;
+ case '0':
+ case 'g':
+ case KEY_HOME:
+ pageno = line = 0;
+ break;
+ case 'h':
+ case 'H':
+ case '?':
+ /* Kaede Buggy ... */
+ show_help(more_help);
+ if (pageno)
+ pageno--;
+ lino = line = 0;
+ break;
+ case 'E':
+ if (HasUserPerm(PERM_SYSOP) && strcmp(fpath, "etc/ve.hlp")) {
+ close(fd);
+ vedit(fpath, NA, NULL);
+ return 0;
+ }
+ break;
+
+ case Ctrl('T'):
+ getdata(b_lines - 2, 0, "把這篇文章收入到暫存檔?[y/N] ",
+ buf, 4, LCECHO);
+ if (buf[0] == 'y') {
+ setuserfile(buf, ask_tmpbuf(b_lines - 1));
+ Copy(fpath, buf);
+ }
+ if (pageno)
+ pageno--;
+ lino = line = 0;
+ break;
+ case KEY_UP:
+ line = -1;
+ break;
+ case Ctrl('B'):
+ case KEY_PGUP:
+ if (pageno > 1) {
+ if (lino < 2)
+ pageno -= 2;
+ else
+ pageno--;
+ lino = line = 0;
+ } else if (pageno && lino > 1)
+ pageno = line = 0;
+ break;
+ case Ctrl('H'):
+ if (pageno > 1) {
+ if (lino < 2)
+ pageno -= 2;
+ else
+ pageno--;
+ lino = line = 0;
+ } else if (pageno && lino > 1)
+ pageno = line = 0;
+ else {
+ close(fd);
+ return READ_PREV;
+ }
+ }
+ }
+
+ if (line > 0) {
+ move(b_lines, 0);
+ clrtoeol();
+ refresh();
+ } else if (line < 0) { /* Line scroll up */
+ if (pageno <= 1) {
+ if (lino == 1 || !pageno) {
+ close(fd);
+ return READ_PREV;
+ }
+ if (header && lino <= 5) {
+ more_goto(fd, &mp, viewed = pagebreak[scrollup = lino =
+ pageno = 0] = 0);
+ clear();
+ }
+ }
+ if (pageno && (int)lino > 1 + local) {
+ line = (lino - 2) - local;
+ if (pageno > 1 && viewed == fsize)
+ line += local;
+ scrollup = lino - 1;
+ more_goto(fd, &mp, viewed = pagebreak[pageno - 1]);
+ while (line--)
+ viewed += more_readln(fd, &mp, (unsigned char *)buf);
+ } else if (pageno > 1) {
+ scrollup = b_lines - 1;
+ line = (b_lines - 2) - local;
+ more_goto(fd, &mp, viewed = pagebreak[--pageno - 1]);
+ while (line--)
+ viewed += more_readln(fd, &mp, (unsigned char *)buf);
+ }
+ line = pos = 0;
+ } else {
+ pos = 0;
+ more_goto(fd, &mp, viewed = pagebreak[pageno]);
+ move(0, 0);
+ clear();
+ }
+ }
+ }
+
+ close(fd);
+ if (promptend) {
+ pressanykey();
+ clear();
+ } else
+ outs(reset_color);
+ return 0;
+}
+#endif
diff --git a/pttbbs/mbbsd/name.c b/pttbbs/mbbsd/name.c
new file mode 100644
index 00000000..c136e9a0
--- /dev/null
+++ b/pttbbs/mbbsd/name.c
@@ -0,0 +1,1084 @@
+/* $Id$ */
+#include "bbs.h"
+
+static word_t *current = NULL;
+static char * const msg_more = "-- More --";
+
+typedef char (*arrptr)[];
+/* name complete for user ID */
+
+//-----------------------------------------------------------------------
+
+void NameList_init(struct NameList *self)
+{
+ self->size = 0;
+ self->capacity = 0;
+ self->base = NULL;
+}
+
+void NameList_delete(struct NameList *self)
+{
+ self->size = 0;
+ self->capacity = 0;
+ if(self->base)
+ free(self->base);
+ self->base = NULL;
+}
+
+void NameList_clear(struct NameList *self)
+{
+ NameList_delete(self);
+ NameList_init(self);
+}
+
+static void NameList_resizefor(struct NameList *self, int size)
+{
+ int capacity = size * (IDLEN+1);
+#define MIN_CAPACITY 4096
+ if (capacity == 0) {
+ if(self->base) free(self->base);
+ self->base = NULL;
+ self->capacity = 0;
+ } else {
+ int old_capacity = self->capacity;
+ assert(capacity > 0);
+ if (self->capacity == 0)
+ self->capacity = MIN_CAPACITY;
+ if (self->capacity > capacity && self->capacity > MIN_CAPACITY)
+ self->capacity /= 2;
+ if (self->capacity < capacity)
+ self->capacity *= 2;
+
+ if(old_capacity != self->capacity || self->base == NULL) {
+ char (*tmp)[IDLEN+1] = (char(*)[IDLEN+1])malloc((IDLEN+1)*self->capacity);
+ assert(tmp);
+ if (self->size)
+ memcpy(tmp, self->base, (IDLEN+1)*self->size);
+ if (self->base)
+ free(self->base);
+ self->base = tmp;
+ }
+ }
+}
+
+void NameList_add(struct NameList *self, const char *name)
+{
+ NameList_resizefor(self, self->size+1);
+ strlcpy(self->base[self->size], name, IDLEN+1);
+ self->size++;
+}
+
+const char* NameList_get(struct NameList *self, int idx)
+{
+ assert(0<=idx && idx<self->size);
+ return self->base[idx];
+}
+
+static int NameList_MaxLen(const struct NameList *list, int offset, int count)
+{
+ int i;
+ int maxlen = 0;
+
+ for(i=offset; i<list->size; i++) {
+ int len = strlen(list->base[i]);
+ if (len > maxlen)
+ maxlen = len;
+ }
+ assert(maxlen <= IDLEN);
+ return maxlen;
+}
+
+int NameList_match(const struct NameList *src, struct NameList *dst, int key, int pos)
+{
+ int uckey, lckey;
+ int i;
+
+ NameList_clear(dst);
+
+ uckey = chartoupper(key);
+ if (key >= 'A' && key <= 'Z')
+ lckey = key | 0x20;
+ else
+ lckey = key;
+
+ for(i=0; i<src->size; i++) {
+ int ch = src->base[i][pos];
+ if (ch == lckey || ch == uckey)
+ NameList_add(dst, src->base[i]);
+ }
+
+ return dst->size;
+}
+
+int NameList_length(struct NameList *self)
+{
+ return self->size;
+}
+
+void NameList_sublist(struct NameList *src, struct NameList *dst, char *tag)
+{
+ int i;
+ int len;
+ NameList_clear(dst);
+
+ len = strlen(tag);
+ for(i=0; i<src->size; i++)
+ if(len==0 || strncasecmp(src->base[i], tag, len)==0)
+ NameList_add(dst, src->base[i]);
+}
+
+int NameList_remove(struct NameList *self, const char *name)
+{
+ int i;
+ for(i=0; i<self->size; i++)
+ if(strcasecmp(self->base[i], name)==0) {
+ strcpy(self->base[i], self->base[self->size-1]);
+
+ self->size--;
+ NameList_resizefor(self, self->size);
+ return 1;
+ }
+ return 0;
+}
+
+int NameList_search(const struct NameList *self, const char *name)
+{
+ int i;
+ for(i=0; i<self->size; i++)
+ if (strcasecmp(self->base[i], name)==0)
+ return 1;
+ return 0;
+}
+
+//-----------------------------------------------------------------------
+
+static int
+UserMaxLen(char cwlist[][IDLEN + 1], int cwnum, int morenum,
+ int count)
+{
+ int len, max = 0;
+
+ while (count-- > 0 && morenum < cwnum) {
+ len = strlen(cwlist[morenum++]);
+ if (len > max)
+ max = len;
+ }
+ /* assert max IDLEN */
+ if(max > IDLEN)
+ max = IDLEN+1;
+ return max;
+}
+
+static int
+UserSubArray(char cwbuf[][IDLEN + 1], char cwlist[][IDLEN + 1],
+ int cwnum, int key, int pos)
+{
+ int key2, num = 0;
+ int n, ch;
+
+ key = chartoupper(key);
+
+ if (key >= 'A' && key <= 'Z')
+ key2 = key | 0x20;
+ else
+ key2 = key;
+
+ for (n = 0; n < cwnum; n++) {
+ ch = cwlist[n][pos];
+ if (ch == key || ch == key2)
+ strlcpy(cwbuf[num++], cwlist[n], sizeof(cwbuf[num]));
+ }
+ return num;
+}
+
+void
+FreeNameList(void)
+{
+ word_t *p, *temp;
+
+ for (p = toplev; p; p = temp) {
+ temp = p->next;
+ free(p->word);
+ free(p);
+ }
+}
+
+void
+CreateNameList(void)
+{
+ if (toplev)
+ FreeNameList();
+ toplev = current = NULL;
+}
+
+void
+AddNameList(const char *name)
+{
+ word_t *node;
+
+ node = (word_t *) malloc(sizeof(word_t));
+ node->next = NULL;
+ node->word = (char *)malloc(strlen(name) + 1);
+ strcpy(node->word, name);
+
+ if (toplev)
+ current = current->next = node;
+ else
+ current = toplev = node;
+}
+
+int
+RemoveNameList(const char *name)
+{
+ word_t *curr, *prev = NULL;
+
+ for (curr = toplev; curr; curr = curr->next) {
+ if (!strcmp(curr->word, name)) {
+ if (prev == NULL)
+ toplev = curr->next;
+ else
+ prev->next = curr->next;
+
+ if (curr == current)
+ current = prev;
+ free(curr->word);
+ free(curr);
+ return 1;
+ }
+ prev = curr;
+ }
+ return 0;
+}
+
+static inline int
+InList(const word_t * list, const char *name)
+{
+ const word_t *p;
+
+ for (p = list; p; p = p->next)
+ if (!strcasecmp(p->word, name))
+ return 1;
+ return 0;
+}
+
+int
+InNameList(const char *name)
+{
+ return InList(toplev, name);
+}
+
+void
+ShowNameList(int row, int column, const char *prompt)
+{
+ word_t *p;
+
+ move(row, column);
+ clrtobot();
+ outs(prompt);
+
+ column = 80;
+ for (p = toplev; p; p = p->next) {
+ row = strlen(p->word) + 1;
+ if (column + row > 76) {
+ column = row;
+ outc('\n');
+ } else {
+ column += row;
+ outc(' ');
+ }
+ outs(p->word);
+ }
+}
+
+void
+ToggleNameList(int *reciper, const char *listfile, const char *msg)
+{
+ FILE *fp;
+ char genbuf[200];
+
+ if ((fp = fopen(listfile, "r"))) {
+ while (fgets(genbuf, STRLEN, fp)) {
+ char *space = strpbrk(genbuf, str_space);
+ if (space) *space = '\0';
+ if (!genbuf[0])
+ continue;
+ if (!InNameList(genbuf)) {
+ AddNameList(genbuf);
+ (*reciper)++;
+ } else {
+ RemoveNameList(genbuf);
+ (*reciper)--;
+ }
+ }
+ fclose(fp);
+ ShowNameList(3, 0, msg);
+ }
+}
+
+static int
+NumInList(const word_t * list)
+{
+ register int i;
+
+ for (i = 0; list; i++)
+ list = list->next;
+ return i;
+}
+
+int
+chkstr(char *otag, const char *tag, const char *name)
+{
+ char ch;
+ const char *oname = name;
+
+ while (*tag) {
+ ch = *name++;
+ if (*tag != chartoupper(ch))
+ return 0;
+ tag++;
+ }
+ if (*tag && *name == '\0')
+ strcpy(otag, oname);
+ return 1;
+}
+
+static word_t *
+GetSubList(char *tag, word_t * list)
+{
+ word_t *wlist, *wcurr;
+ char tagbuf[STRLEN];
+ int n;
+
+ wlist = wcurr = NULL;
+ for (n = 0; tag[n]; n++)
+ tagbuf[n] = chartoupper(tag[n]);
+ tagbuf[n] = '\0';
+
+ while (list) {
+ if (chkstr(tag, tagbuf, list->word)) {
+ register word_t *node;
+
+ node = (word_t *) malloc(sizeof(word_t));
+ node->word = list->word;
+ node->next = NULL;
+ if (wlist)
+ wcurr->next = node;
+ else
+ wlist = node;
+ wcurr = node;
+ }
+ list = list->next;
+ }
+ return wlist;
+}
+
+static void
+ClearSubList(word_t * list)
+{
+ struct word_t *tmp_list;
+
+ while (list) {
+ tmp_list = list->next;
+ free(list);
+ list = tmp_list;
+ }
+}
+
+static int
+MaxLen(const word_t * list, int count)
+{
+ int len = strlen(list->word);
+ int t;
+
+ while (list && count) {
+ if ((t = strlen(list->word)) > len)
+ len = t;
+ list = list->next;
+ count--;
+ }
+ return len;
+}
+
+/* TODO use namecomplete2() instead */
+void
+namecomplete(const char *prompt, char *data)
+{
+ char *temp;
+ word_t *cwlist, *morelist;
+ int x, y, origx, origy;
+ int ch;
+ int count = 0;
+ int clearbot = NA;
+
+ if (toplev == NULL)
+ AddNameList("");
+ cwlist = GetSubList("", toplev);
+ morelist = NULL;
+ temp = data;
+
+ outs(prompt);
+ clrtoeol();
+ getyx(&y, &x);
+ standout();
+ prints("%*s", IDLEN + 1, "");
+ standend();
+ move(y, x);
+ origy = y; origx = x;
+
+ while ((ch = igetch()) != EOF) {
+ if (ch == '\n' || ch == '\r') {
+ *temp = '\0';
+ // outc('\n');
+ if (NumInList(cwlist) == 1)
+ strcpy(data, cwlist->word);
+ else if (!InList(cwlist, data))
+ data[0] = '\0';
+ ClearSubList(cwlist);
+ break;
+ }
+ if (ch == ' ') {
+ int col, len;
+
+ if (NumInList(cwlist) == 1) {
+ strcpy(data, cwlist->word);
+ move(y, x);
+ outs(data + count);
+ count = strlen(data);
+ temp = data + count;
+ getyx(&y, &x);
+ continue;
+ }
+ clearbot = YEA;
+ col = 0;
+ if (!morelist)
+ morelist = cwlist;
+ len = MaxLen(morelist, p_lines);
+ move(2, 0);
+ clrtobot();
+ printdash("相關資訊一覽表", 0);
+ while (len + col < t_columns) {
+ int i;
+
+ for (i = p_lines; (morelist) && (i > 0); i--) {
+ move(3 + (p_lines - i), col);
+ outs(morelist->word);
+ morelist = morelist->next;
+ }
+ col += len + 2;
+ if (!morelist)
+ break;
+ len = MaxLen(morelist, p_lines);
+ }
+ if (morelist) {
+ vmsg(msg_more);
+ }
+ move(y, x);
+ continue;
+ }
+ if (ch == '\177' || ch == '\010') {
+ if (temp == data)
+ continue;
+ temp--;
+ count--;
+ *temp = '\0';
+ ClearSubList(cwlist);
+ cwlist = GetSubList(data, toplev);
+ morelist = NULL;
+ x--;
+ move(y, x);
+ outc(' ');
+ move(y, x);
+ continue;
+ }
+ if (count < STRLEN && isprint(ch)) {
+ word_t *node;
+
+ *temp++ = ch;
+ count++;
+ *temp = '\0';
+ node = GetSubList(data, cwlist);
+ if (node == NULL) {
+ temp--;
+ *temp = '\0';
+ count--;
+ continue;
+ }
+ ClearSubList(cwlist);
+ cwlist = node;
+ morelist = NULL;
+ move(y, x);
+ outc(ch);
+ x++;
+ }
+ }
+ if (ch == EOF)
+ /* longjmp(byebye, -1); */
+ raise(SIGHUP); /* jochang: don't know if this is
+ * necessary... */
+ outc('\n');
+ if (clearbot) {
+ move(2, 0);
+ clrtobot();
+ }
+ if (*data) {
+ move(origy, origx);
+ outs(data);
+ outc('\n');
+ }
+}
+
+void
+namecomplete2(struct NameList *namelist, const char *prompt, char *data)
+{
+ char *temp;
+ int x, y, origx, origy;
+ int ch;
+ int count = 0;
+ int clearbot = NA;
+ struct NameList sublist;
+ int viewoffset = 0;
+
+ NameList_init(&sublist);
+
+ NameList_sublist(namelist, &sublist, "");
+ temp = data;
+
+ outs(prompt);
+ clrtoeol();
+ getyx(&y, &x);
+ standout();
+ prints("%*s", IDLEN + 1, "");
+ standend();
+ move(y, x);
+ origy = y; origx = x;
+ viewoffset = 0;
+
+ while ((ch = igetch()) != EOF) {
+ if (ch == '\n' || ch == '\r') {
+ *temp = '\0';
+ if (NameList_length(&sublist)==1)
+ strcpy(data, NameList_get(&sublist, 0));
+ else if (!NameList_search(&sublist, data))
+ data[0] = '\0';
+ NameList_delete(&sublist);
+ break;
+ }
+ if (ch == ' ') {
+ int col, len;
+
+ if (NameList_length(&sublist) == 1) {
+ strcpy(data, NameList_get(&sublist, 0));
+ move(y, x);
+ outs(data + count);
+ count = strlen(data);
+ temp = data + count;
+ getyx(&y, &x);
+ continue;
+ }
+ clearbot = YEA;
+ col = 0;
+ len = NameList_MaxLen(&sublist, viewoffset, p_lines);
+ move(2, 0);
+ clrtobot();
+ printdash("相關資訊一覽表", 0);
+ while (len + col < t_columns) {
+ int i;
+
+ for (i = p_lines; viewoffset < NameList_length(&sublist) && (i > 0); i--) {
+ move(3 + (p_lines - i), col);
+ outs(NameList_get(&sublist, viewoffset));
+ viewoffset++;
+ }
+ col += len + 2;
+ if (viewoffset == NameList_length(&sublist)) {
+ viewoffset = 0;
+ break;
+ }
+ len = NameList_MaxLen(&sublist, viewoffset, p_lines);
+ }
+ if (viewoffset < NameList_length(&sublist)) {
+ vmsg(msg_more);
+ }
+ move(y, x);
+ continue;
+ }
+ if (ch == '\177' || ch == '\010') {
+ if (temp == data)
+ continue;
+ temp--;
+ count--;
+ *temp = '\0';
+ NameList_sublist(namelist, &sublist, data);
+ viewoffset = 0;
+ x--;
+ move(y, x);
+ outc(' ');
+ move(y, x);
+ continue;
+ }
+ if (count < STRLEN && isprint(ch)) {
+ struct NameList tmplist;
+ NameList_init(&tmplist);
+
+ *temp++ = ch;
+ count++;
+ *temp = '\0';
+
+ NameList_sublist(&sublist, &tmplist, data);
+ if (NameList_length(&tmplist)==0) {
+ NameList_delete(&tmplist);
+ temp--;
+ *temp = '\0';
+ count--;
+ continue;
+ }
+ NameList_delete(&sublist);
+ sublist = tmplist;
+ viewoffset = 0;
+ move(y, x);
+ outc(ch);
+ x++;
+ }
+ }
+ if (ch == EOF)
+ /* longjmp(byebye, -1); */
+ raise(SIGHUP); /* jochang: don't know if this is
+ * necessary... */
+ outc('\n');
+ if (clearbot) {
+ move(2, 0);
+ clrtobot();
+ }
+ if (*data) {
+ move(origy, origx);
+ outs(data);
+ outc('\n');
+ }
+}
+
+void
+usercomplete(const char *prompt, char *data)
+{
+ char *temp;
+ char *cwbuf, *cwlist;
+ int cwnum, x, y, origx, origy;
+ int clearbot = NA, count = 0, morenum = 0;
+ char ch;
+ int dashdirty = 0;
+
+ /* TODO 節省記憶體. (不過這個 function 不常占記憶體...) */
+ cwbuf = malloc(MAX_USERS * (IDLEN + 1));
+ cwlist = u_namearray((arrptr) cwbuf, &cwnum, "");
+ temp = data;
+
+ outs(prompt);
+ clrtoeol();
+ getyx(&y, &x);
+ standout();
+ prints("%*s", IDLEN + 1, "");
+ standend();
+ move(y, x);
+ origy = y; origx = x;
+
+ while ((ch = igetch()) != EOF) {
+
+ if (ch == '\n' || ch == '\r') {
+ int i;
+ char *ptr;
+
+ *temp = '\0';
+ outc('\n');
+ ptr = cwlist;
+ for (i = 0; i < cwnum; i++) {
+ if (strncasecmp(data, ptr, IDLEN + 1) == 0) {
+ strcpy(data, ptr);
+ break;
+ }
+ ptr += IDLEN + 1;
+ }
+ if (i == cwnum)
+ data[0] = '\0';
+ break;
+
+ } else if (ch == '\177' || ch == '\010') {
+ if (temp == data)
+ continue;
+ temp--;
+ count--;
+ *temp = '\0';
+ cwlist = u_namearray((arrptr) cwbuf, &cwnum, data);
+ morenum = 0;
+ x--;
+ move(y, x);
+ outc(' ');
+ move(y, x);
+ continue;
+
+ } else if (!(count <= IDLEN && isprint((int)ch))) {
+
+ /* invalid input */
+ continue;
+
+ } else if (ch != ' ') {
+
+ int n;
+
+ *temp++ = ch;
+ *temp = '\0';
+ n = UserSubArray((arrptr) cwbuf, (arrptr) cwlist, cwnum, ch, count);
+
+ if (n > 0) {
+ /* found something */
+ cwlist = cwbuf;
+ count++;
+ cwnum = n;
+ morenum = 0;
+ move(y, x);
+ outc(ch);
+ x++;
+
+ continue;
+ }
+ /* no break, no continue, list later. */
+ }
+
+ /* finally, list available users. */
+ {
+ int col, len;
+
+ if (ch == ' ' && cwnum == 1) {
+ if(dashdirty)
+ {
+ move(2,0);
+ clrtoeol();
+ printdash(cwlist, 0);
+ }
+ strcpy(data, cwlist);
+ move(y, x);
+ outs(data + count);
+ count = strlen(data);
+ temp = data + count;
+ getyx(&y, &x);
+ continue;
+ }
+
+ clearbot = YEA;
+ col = 0;
+
+ len = UserMaxLen((arrptr) cwlist, cwnum, morenum, p_lines);
+ move(2, 0);
+ clrtobot();
+ printdash("使用者代號一覽表", 0);
+ dashdirty = 0;
+
+ if(ch != ' ')
+ {
+ /* no such user */
+ move(2,0);
+ outs("- 目前無使用者 ");
+ outs(data);
+ outs(" ");
+ temp--;
+ *temp = '\0';
+ dashdirty = 1;
+ }
+
+ while (len + col < t_columns-1) {
+
+ int i;
+
+ for (i = 0; morenum < cwnum && i < p_lines; i++) {
+ move(3 + i, col);
+ prints("%.*s ", IDLEN,
+ cwlist + (IDLEN + 1) * morenum++);
+ }
+ col += len + 2;
+ if (morenum >= cwnum)
+ break;
+ len = UserMaxLen((arrptr) cwlist, cwnum, morenum, p_lines);
+ }
+ if (morenum < cwnum) {
+ move(b_lines, 0); clrtoeol();
+ outs(msg_more);
+ // vmsg(msg_more);
+ } else
+ morenum = 0;
+
+ move(y, x);
+
+ continue;
+ }
+ }
+ free(cwbuf);
+ if (ch == EOF)
+ /* longjmp(byebye, -1); */
+ raise(SIGHUP); /* jochang: don't know if this is necessary */
+ outc('\n');
+ if (clearbot) {
+ move(2, 0);
+ clrtobot();
+ }
+ if (*data) {
+ move(origy, origx);
+ outs(data);
+ outc('\n');
+ }
+}
+
+static int
+gnc_findbound(char *str, int *START, int *END,
+ size_t nmemb, gnc_comp_func compar)
+{
+ int start, end, mid, cmp, strl;
+ strl = strlen(str);
+
+ start = -1, end = nmemb - 1;
+ /* The first available element is always in the half-open interval
+ * (start, end]. (or `end'-th it self if start == end) */
+ while (end > start + 1) {
+ mid = (start + end) / 2;
+ cmp = (*compar)(mid, str, strl);
+ if (cmp >= 0)
+ end = mid;
+ else
+ start = mid;
+ }
+ if ((*compar)(end, str, strl) != 0) {
+ *START = *END = -1;
+ return -1;
+ }
+ *START = end;
+
+ start = end;
+ end = nmemb;
+ /* The last available element is always in the half-open interval
+ * [start, end). (or `start'-th it self if start == end) */
+ while (end > start + 1) {
+ mid = (start + end) / 2;
+ cmp = (*compar)(mid, str, strl);
+ if (cmp <= 0)
+ start = mid;
+ else
+ end = mid;
+ }
+ *END = start;
+ return 0;
+}
+
+static int
+gnc_complete(char *data, int *start, int *end,
+ gnc_perm_func permission, gnc_getname_func getname)
+{
+ int i, count, first = -1, last = *end;
+ if (*start < 0 || *end < 0)
+ return 0;
+ for (i = *start, count = 0; i <= *end; ++i)
+ if ((*permission)(i)) {
+ if (first == -1)
+ first = i;
+ last = i;
+ ++count;
+ }
+ if (count == 1)
+ strcpy(data, (*getname)(first));
+
+ *start = first;
+ *end = last;
+ return count;
+}
+
+
+int
+generalnamecomplete(const char *prompt, char *data, int len, size_t nmemb,
+ gnc_comp_func compar, gnc_perm_func permission,
+ gnc_getname_func getname)
+{
+ int x, y, origx, origy, ch, i, morelist = -1, col, ret = -1;
+ int start, end, ptr;
+ int clearbot = NA;
+
+ outs(prompt);
+ clrtoeol();
+ getyx(&y, &x);
+ standout();
+ prints("%*s", IDLEN + 1, "");
+ standend();
+ move(y, x);
+ origy = y; origx = x;
+
+ ptr = 0;
+ data[ptr] = 0;
+
+ start = 0; end = nmemb - 1;
+ while ((ch = igetch()) != EOF) {
+ if (ch == '\n' || ch == '\r') {
+ data[ptr] = 0;
+ outc('\n');
+ if (ptr != 0) {
+ gnc_findbound(data, &start, &end, nmemb, compar);
+ if (gnc_complete(data, &start, &end, permission, getname)
+ == 1 || (*compar)(start, data, len) == 0) {
+ strcpy(data, (*getname)(start));
+ ret = start;
+ } else {
+ data[0] = '\n';
+ ret = -1;
+ }
+ } else
+ ptr = -1;
+ break;
+ } else if (ch == ' ') {
+ if (morelist == -1) {
+ if (gnc_findbound(data, &start, &end, nmemb, compar) == -1)
+ continue;
+ i = gnc_complete(data, &start, &end, permission, getname);
+ if (i == 1) {
+ move(origy, origx);
+ outs(data);
+ ptr = strlen(data);
+ getyx(&y, &x);
+ continue;
+ } else {
+ char* first = (*getname)(start);
+ i = ptr;
+ while (first[i] && (*compar)(end, first, i + 1) == 0) {
+ data[i] = first[i];
+ ++i;
+ }
+ data[i] = '\0';
+
+ if (i != ptr) { /* did complete several words */
+ move(y, x);
+ outs(data + ptr);
+ getyx(&y, &x);
+ ptr = i;
+ }
+ }
+ morelist = start;
+ } else if (morelist > end)
+ continue;
+ clearbot = YEA;
+ move(2, 0);
+ clrtobot();
+ printdash("相關資訊一覽表", 0);
+
+ col = 0;
+ while (len + col < 79) {
+ for (i = 0; morelist <= end && i < p_lines; ++morelist) {
+ if ((*permission)(morelist)) {
+ move(3 + i, col);
+ prints("%s ", (*getname)(morelist));
+ ++i;
+ }
+ }
+
+ col += len + 2;
+ }
+ if (morelist != end + 1) {
+ vmsg(msg_more);
+ }
+ move(y, x);
+ continue;
+
+ } else if (ch == '\177' || ch == '\010') { /* backspace */
+ if (ptr == 0)
+ continue;
+ morelist = -1;
+ --ptr;
+ --x;
+ data[ptr] = 0;
+ move(y, x);
+ outc(' ');
+ move(y, x);
+ continue;
+ } else if (isprint(ch) && ptr <= (len - 2)) {
+ morelist = -1;
+ data[ptr] = ch;
+ ++ptr;
+ data[ptr] = 0;
+ if (gnc_findbound(data, &start, &end, nmemb, compar) < 0)
+ data[--ptr] = 0;
+ else {
+ for (i = start; i <= end; ++i)
+ if ((*permission)(i))
+ break;
+ if (i == end + 1)
+ data[--ptr] = 0;
+ else {
+ move(y, x);
+ outc(ch);
+ x++;
+ }
+ }
+ }
+ }
+
+ outc('\n');
+ if (clearbot) {
+ move(2, 0);
+ clrtobot();
+ }
+ if (*data) {
+ move(origy, origx);
+ outs(data);
+ outc('\n');
+ }
+ return ret;
+}
+
+/* general complete functions (brdshm) */
+int
+completeboard_compar(int where, const char *str, int len)
+{
+ boardheader_t *bh = &bcache[SHM->bsorted[0][where]];
+ return strncasecmp(bh->brdname, str, len);
+}
+
+int
+completeboard_permission(int where)
+{
+ boardheader_t *bptr = &bcache[SHM->bsorted[0][where]];
+ return (!(bptr->brdattr & BRD_SYMBOLIC) &&
+ (GROUPOP() || HasBoardPerm(bptr)) &&
+ !(bptr->brdattr & BRD_GROUPBOARD) &&
+ !(bptr->brdattr & BRD_OVER18 && !over18));
+}
+
+int
+complete_board_and_group_permission(int where)
+{
+ boardheader_t *bptr = &bcache[SHM->bsorted[0][where]];
+ return (!(bptr->brdattr & BRD_SYMBOLIC) &&
+ (GROUPOP() || HasBoardPerm(bptr)));
+
+}
+
+char *
+completeboard_getname(int where)
+{
+ return bcache[SHM->bsorted[0][where]].brdname;
+}
+
+/* general complete functions (utmpshm) */
+int
+completeutmp_compar(int where, const char *str, int len)
+{
+ userinfo_t *u = &SHM->uinfo[SHM->sorted[SHM->currsorted][0][where]];
+ return strncasecmp(u->userid, str, len);
+}
+
+int
+completeutmp_permission(int where)
+{
+ userinfo_t *u = &SHM->uinfo[SHM->sorted[SHM->currsorted][0][where]];
+ return (unlikely(HasUserPerm(PERM_SYSOP)) ||
+ unlikely(HasUserPerm(PERM_SEECLOAK)) ||
+// !SHM->sorted[SHM->currsorted][0][where]->invisible);
+ isvisible(currutmp, u));
+}
+
+char *
+completeutmp_getname(int where)
+{
+ return SHM->uinfo[SHM->sorted[SHM->currsorted][0][where]].userid;
+}
diff --git a/pttbbs/mbbsd/osdep.c b/pttbbs/mbbsd/osdep.c
new file mode 100644
index 00000000..f4a9bd85
--- /dev/null
+++ b/pttbbs/mbbsd/osdep.c
@@ -0,0 +1,643 @@
+/* $Id$ */
+#include "bbs.h"
+
+#ifdef NEED_STRLCAT
+
+#include <sys/types.h>
+#include <string.h>
+
+/* size_t
+ * strlcat(char *dst, const char *src, size_t size);
+ */
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(dst, src, siz)
+ char *dst;
+ const char *src;
+ size_t siz;
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return(dlen + (s - src)); /* count does not include NUL */
+}
+
+#endif
+
+#ifdef NEED_TIMEGM
+
+#include <time.h>
+#include <stdlib.h>
+
+time_t timegm (struct tm *tm)
+{
+ time_t ret;
+ char *tz;
+
+ tz = getenv("TZ");
+ putenv("TZ=");
+ tzset();
+ ret = mktime(tm);
+
+ if (tz){
+ char *buff = malloc( strlen(tz) + 10);
+ sprintf( buff, "TZ=%s", tz);
+ putenv(buff);
+ free(buff);
+ }
+ else
+ unsetenv("TZ");
+ tzset();
+
+ return ret;
+}
+
+#endif
+
+#ifdef NEED_STRLCPY
+
+/* ------------------------------------------------------------------------ */
+
+/* size_t
+ * strlcpy(char *dst, const char *src, size_t size);
+ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t strlcpy(dst, src, siz)
+ char *dst;
+ const char *src;
+ size_t siz;
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0 && --n != 0) {
+ do {
+ if ((*d++ = *s++) == 0)
+ break;
+ } while (--n != 0);
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
+
+#endif
+
+#ifdef NEED_STRCASESTR
+
+char *
+strcasestr(const char *big, const char *little)
+{
+ char *ans = (char *)big;
+ int len = strlen(little);
+ char *endptr = (char *)big + strlen(big) - len;
+
+ while (ans <= endptr)
+ if (!strncasecmp(ans, little, len))
+ return ans;
+ else
+ ans++;
+ return 0;
+}
+
+#endif
+
+#ifdef NEED_SCANDIR
+
+/*
+ * Scan the directory dirname calling select to make a list of selected
+ * directory entries then sort using qsort and compare routine dcomp.
+ * Returns the number of entries and a pointer to a list of pointers to
+ * struct dirent (through namelist). Returns -1 if there were any errors.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * The DIRSIZ macro is the minimum record length which will hold the directory
+ * entry. This requires the amount of space in struct dirent without the
+ * d_name field, plus enough space for the name and a terminating nul byte
+ * (dp->d_namlen + 1), rounded up to a 4 byte boundary.
+ */
+#undef DIRSIZ
+#define DIRSIZ(dp) \
+ ((sizeof(struct dirent) - sizeof(dp)->d_name) + \
+ ((strlen((dp)->d_name) + 1 + 3) &~ 3))
+#if 0
+((sizeof(struct dirent) - sizeof(dp)->d_name) + \
+ (((dp)->d_namlen + 1 + 3) &~ 3))
+#endif
+
+int
+scandir(dirname, namelist, select, dcomp)
+ const char *dirname;
+ struct dirent ***namelist;
+ int (*select) (struct dirent *);
+ int (*dcomp) (const void *, const void *);
+{
+ register struct dirent *d, *p, **names;
+ register size_t nitems;
+ struct stat stb;
+ long arraysz;
+ DIR *dirp;
+
+ if ((dirp = opendir(dirname)) == NULL)
+ return(-1);
+ if (fstat(dirp->dd_fd, &stb) < 0)
+ return(-1);
+
+ /*
+ * estimate the array size by taking the size of thedirectory file
+ * and dividing it by a multiple of the minimum sizeentry.
+ */
+ arraysz = (stb.st_size / 24);
+ names = (struct dirent **)malloc(arraysz * sizeof(struct dirent *));
+ if (names == NULL)
+ return(-1);
+
+ nitems = 0;
+ while ((d = readdir(dirp)) != NULL) {
+ if (select != NULL && !(*select)(d))
+ continue; /* just selected names */
+ /*
+ * Make a minimum size copy of the data
+ */
+ p = (struct dirent *)malloc(DIRSIZ(d));
+ if (p == NULL)
+ return(-1);
+ p->d_ino = d->d_ino;
+ p->d_off = d->d_off;
+ p->d_reclen = d->d_reclen;
+ memcpy(p->d_name, d->d_name, strlen(d->d_name) +1);
+#if 0
+ p->d_fileno = d->d_fileno;
+ p->d_type = d->d_type;
+ p->d_reclen = d->d_reclen;
+ p->d_namlen = d->d_namlen;
+ bcopy(d->d_name, p->d_name, p->d_namlen + 1);
+#endif
+ /*
+ * Check to make sure the array has space left and
+ * realloc the maximum size.
+ */
+ if (++nitems >= arraysz) {
+ if (fstat(dirp->dd_fd, &stb) < 0)
+ return(-1); /* just might have grown */
+ arraysz = stb.st_size / 12;
+ names = (struct dirent **)realloc((char*)names,
+ arraysz * sizeof(struct dirent*));
+ if (names == NULL)
+ return(-1);
+ }
+ names[nitems-1] = p;
+ }
+ closedir(dirp);
+ if (nitems && dcomp != NULL)
+ qsort(names, nitems, sizeof(struct dirent *),dcomp);
+ *namelist = names;
+ return(nitems);
+}
+
+/*
+ * Alphabetic order comparison routine for those who want it.
+ */
+int
+alphasort(d1, d2)
+ const void *d1;
+ const void *d2;
+{
+ return(strcmp((*(struct dirent **)d1)->d_name,
+ (*(struct dirent **)d2)->d_name));
+}
+
+#endif
+
+#ifdef NEED_FLOCK
+
+int
+flock (int fd, int f)
+{
+ if( f == LOCK_EX )
+ return lockf(fd, F_LOCK, 0L);
+
+ if( f == LOCK_UN )
+ return lockf(fd, F_ULOCK, 0L);
+
+ return -1;
+}
+
+#endif
+
+#ifdef NEED_UNSETENV
+
+void
+unsetenv(name)
+ char *name;
+{
+ extern char **environ;
+ register char **pp;
+ int len = strlen(name);
+
+ for (pp = environ; *pp != NULL; pp++)
+ {
+ if (strncmp(name, *pp, len) == 0 &&
+ ((*pp)[len] == '=' || (*pp)[len] == '\0'))
+ break;
+ }
+
+ for (; *pp != NULL; pp++)
+ *pp = pp[1];
+}
+
+#endif
+
+#ifdef NEED_INET_PTON
+
+#include <arpa/nameser.h>
+
+/*
+ * Copyright (c) 1996 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+int
+inet_pton(int af, const char *src, void *dst)
+{
+ static const char digits[] = "0123456789";
+ int saw_digit, octets, ch;
+ u_char tmp[INADDRSZ], *tp;
+
+ saw_digit = 0;
+ octets = 0;
+ *(tp = tmp) = 0;
+ while ((ch = *src++) != '\0') {
+ const char *pch;
+
+ if ((pch = strchr(digits, ch)) != NULL) {
+ u_int new = *tp * 10 + (pch - digits);
+
+ if (new > 255)
+ return (0);
+ *tp = new;
+ if (! saw_digit) {
+ if (++octets > 4)
+ return (0);
+ saw_digit = 1;
+ }
+ } else if (ch == '.' && saw_digit) {
+ if (octets == 4)
+ return (0);
+ *++tp = 0;
+ saw_digit = 0;
+ } else
+ return (0);
+ }
+ if (octets < 4)
+ return (0);
+
+ memcpy(dst, tmp, INADDRSZ);
+
+ return (1);
+}
+#endif
+
+#ifdef NEED_BSD_SIGNAL
+
+void (*bsd_signal(int sig, void (*func)(int)))(int)
+{
+ struct sigaction act, oact;
+
+ act.sa_handler = func;
+ act.sa_flags = SA_RESTART;
+ sigemptyset(&act.sa_mask);
+ sigaddset(&act.sa_mask, sig);
+ if (sigaction(sig, &act, &oact) == -1)
+ return(SIG_ERR);
+ return(oact.sa_handler);
+}
+
+
+#endif
+
+
+#ifdef Solaris
+
+#include <sys/stat.h>
+#include <sys/swap.h>
+
+
+double swapused(int *total, int *used)
+{
+ double percent = -1;
+ register int cnt, i;
+ register int free;
+ struct swaptable *swt;
+ struct swapent *ste;
+ static char path[256]; // does it really need 'static' ?
+ cnt = swapctl(SC_GETNSWP, 0);
+ swt = (struct swaptable *)malloc(sizeof(int) +
+ cnt * sizeof(struct swapent));
+ if (swt == NULL)
+ {
+ return 0;
+ }
+ swt->swt_n = cnt;
+
+ /* fill in ste_path pointers: we don't care about the paths, so we point
+ them all to the same buffer */
+ ste = &(swt->swt_ent[0]);
+ i = cnt;
+ while (--i >= 0)
+ {
+ ste++->ste_path = path;
+ }
+ /* grab all swap info */
+ swapctl(SC_LIST, swt);
+
+ /* walk thru the structs and sum up the fields */
+ *total = free = 0;
+ ste = &(swt->swt_ent[0]);
+ i = cnt;
+ while (--i >= 0)
+ {
+ /* dont count slots being deleted */
+ if (!(ste->ste_flags & ST_INDEL) &&
+ !(ste->ste_flags & ST_DOINGDEL))
+ {
+ *total += ste->ste_pages;
+ free += ste->ste_free;
+ }
+ ste++;
+ }
+
+ *used = *total - free;
+ if( total != 0)
+ percent = (double)*used / (double)*total;
+ else
+ percent = 0;
+
+ return percent;
+}
+#endif
+
+#if __FreeBSD__
+
+#include <kvm.h>
+
+
+double
+swapused(int *total, int *used)
+{
+ double percent = -1;
+ kvm_t *kd;
+ struct kvm_swap swapinfo;
+ int pagesize;
+
+ kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL);
+ if (kd) {
+ if (kvm_getswapinfo(kd, &swapinfo, 1, 0) == 0) {
+ pagesize = getpagesize();
+ *total = swapinfo.ksw_total * pagesize;
+ *used = swapinfo.ksw_used * pagesize;
+ if (*total != 0)
+ percent = (double)*used / (double)*total;
+ }
+ kvm_close(kd);
+ }
+ return percent;
+}
+
+#endif
+
+#if __FreeBSD__
+
+int
+cpuload(char *str)
+{
+ double l[3] = {-1, -1, -1};
+ if (getloadavg(l, 3) != 3)
+ l[0] = -1;
+
+ if (str) {
+ if (l[0] != -1)
+ sprintf(str, " %.2f %.2f %.2f", l[0], l[1], l[2]);
+ else
+ strcpy(str, " (unknown) ");
+ }
+ return (int)l[0];
+}
+#endif
+
+
+#ifdef Solaris
+
+#include <kstat.h>
+#include <sys/param.h>
+
+#define loaddouble(la) ((double)(la) / FSCALE)
+
+int
+cpuload(char *str)
+{
+ kstat_ctl_t *kc;
+ kstat_t *ks;
+ kstat_named_t *kn;
+ double l[3] = {-1, -1, -1};
+
+ kc = kstat_open();
+
+ if( !kc ){
+ strcpy(str, "(unknown) ");
+ return -1;
+ }
+
+ ks = kstat_lookup( kc, "unix", 0, "system_misc");
+
+ if( kstat_read( kc, ks, 0) == -1){
+ strcpy( str, "( unknown ");
+ return -1;
+ }
+
+ kn = kstat_data_lookup( ks, "avenrun_1min" );
+
+ if( kn ) {
+ l[0] = loaddouble(kn->value.ui32);
+ }
+
+ kn = kstat_data_lookup( ks, "avenrun_5min" );
+
+ if( kn ) {
+ l[1] = loaddouble(kn->value.ui32);
+ }
+
+ kn = kstat_data_lookup( ks, "avenrun_15min" );
+
+ if( kn ) {
+ l[2] = loaddouble(kn->value.ui32);
+ }
+
+ if (str) {
+
+ if (l[0] != -1)
+ sprintf(str, " %.2f %.2f %.2f", l[0], l[1], l[2]);
+ else
+ strcpy(str, " (unknown) ");
+ }
+
+ kstat_close(kc);
+ return (int)l[0];
+}
+
+#endif
+
+#ifdef __linux__
+int
+cpuload(char *str)
+{
+ double l[3] = {-1, -1, -1};
+ FILE *fp;
+
+ if ((fp = fopen("/proc/loadavg", "r"))) {
+ if (fscanf(fp, "%lf %lf %lf", &l[0], &l[1], &l[2]) != 3)
+ l[0] = -1;
+ fclose(fp);
+ }
+ if (str) {
+ if (l[0] != -1)
+ sprintf(str, " %.2f %.2f %.2f", l[0], l[1], l[2]);
+ else
+ strcpy(str, " (unknown) ");
+ }
+ return (int)l[0];
+}
+
+double
+swapused(int *total, int *used)
+{
+ double percent = -1;
+ char buf[101];
+ FILE *fp;
+
+ if ((fp = fopen("/proc/meminfo", "r"))) {
+ while (fgets(buf, 100, fp) && strstr(buf, "SwapTotal:") == NULL);
+ sscanf(buf, "%*s %d", total);
+ fgets(buf, 100, fp);
+ sscanf(buf, "%*s %d", used);
+ *used = *total - *used;
+ if (*total != 0)
+ percent = (double)*used / (double)*total;
+ fclose(fp);
+ }
+ return percent;
+}
+
+#endif
diff --git a/pttbbs/mbbsd/othello.c b/pttbbs/mbbsd/othello.c
new file mode 100644
index 00000000..9431e7df
--- /dev/null
+++ b/pttbbs/mbbsd/othello.c
@@ -0,0 +1,567 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define LOGFILE "etc/othello.log"
+#define SECRET "etc/othello.secret"
+#define NR_TABLE 2
+
+#define true 1
+#define false 0
+#define STARTX 3
+#define STARTY 20
+#define NONE_CHESS " "
+#define WHITE_CHESS "●"
+#define BLACK_CHESS "○"
+#define HINT_CHESS "#"
+#define NONE 0
+#define HINT 1
+#define BLACK 2
+#define WHITE 3
+
+#define INVERT(COLOR) (((COLOR))==WHITE?BLACK:WHITE)
+
+struct OthelloData {
+ char nowx, nowy;
+ char number[2];
+
+ char pass;
+ char if_hint;
+ int think, which_table;
+
+ char nowboard[10][10];
+ char evaltable[NR_TABLE + 1][10][10];
+};
+
+static const char *CHESS_TYPE[] = {NONE_CHESS, HINT_CHESS, BLACK_CHESS, WHITE_CHESS};
+static const char DIRX[] = {-1, -1, -1, 0, 1, 1, 1, 0};
+static const char DIRY[] = {-1, 0, 1, 1, 1, 0, -1, -1};
+static const char init_table[NR_TABLE + 1][5][5] = {
+ {{0, 0, 0, 0, 0},
+ {0, 30, -3, 2, 2},
+ {0, -3, -3, -1, -1},
+ {0, 2, -1, 1, 1},
+ {0, 2, -1, 1, 0}},
+
+ {{0, 0, 0, 0, 0},
+ {0, 70, 5, 20, 30},
+ {0, 5, -5, 3, 3},
+ {0, 20, 3, 5, 5},
+ {0, 30, 3, 5, 5}},
+
+ {{0, 0, 0, 0, 0},
+ {0, 5, 2, 2, 2},
+ {0, 2, 1, 1, 1},
+ {0, 2, 1, 1, 1},
+ {0, 2, 1, 1, 1}}
+};
+
+static void
+print_chess(struct OthelloData *od, int x, int y, char chess)
+{
+ move(STARTX - 1 + x * 2, STARTY - 2 + y * 4);
+ if (chess != HINT || od->if_hint == 1)
+ outs(CHESS_TYPE[(int)chess]);
+ else
+ outs(CHESS_TYPE[NONE]);
+ refresh();
+}
+
+static void
+printboard(struct OthelloData *od)
+{
+ int i;
+
+ move(STARTX, STARTY);
+ outs("┌─┬─┬─┬─┬─┬─┬─┬─┐");
+ for (i = 0; i < 7; i++) {
+ move(STARTX + 1 + i * 2, STARTY);
+ outs("│ │ │ │ │ │ │ │ │");
+ move(STARTX + 2 + i * 2, STARTY);
+ outs("├─┼─┼─┼─┼─┼─┼─┼─┤");
+ }
+ move(STARTX + 1 + i * 2, STARTY);
+ outs("│ │ │ │ │ │ │ │ │");
+ move(STARTX + 2 + i * 2, STARTY);
+ outs("└─┴─┴─┴─┴─┴─┴─┴─┘");
+ print_chess(od, 4, 4, WHITE);
+ print_chess(od, 5, 5, WHITE);
+ print_chess(od, 4, 5, BLACK);
+ print_chess(od, 5, 4, BLACK);
+ move(3, 56);
+ prints("(黑)%s", cuser.userid);
+ move(3, 72);
+ outs(": 02");
+ move(4, 56);
+ outs("(白)電腦 : 02");
+ move(6, 56);
+ outs("# 可以下之處");
+ move(7, 56);
+ outs("[q] 退出");
+ move(8, 56);
+ outs("[h] 開啟/關閉 提示");
+ move(9, 56);
+ outs("[Enter][Space] 下棋");
+ move(10, 56);
+ outs("上:↑, i");
+ move(11, 56);
+ outs("下:↓, k");
+ move(12, 56);
+ outs("左:←, j");
+ move(13, 56);
+ outs("右:→, l");
+}
+
+static int
+get_key(struct OthelloData *od, int x, int y)
+{
+ int ch;
+
+ move(STARTX - 1 + x * 2, STARTY - 1 + y * 4);
+ ch = igetch();
+ move(STARTX - 1 + x * 2, STARTY - 2 + y * 4);
+ if (od->nowboard[x][y] != HINT || od->if_hint == 1)
+ outs(CHESS_TYPE[(int)od->nowboard[x][y]]);
+ else
+ outs(CHESS_TYPE[NONE]);
+ return ch;
+}
+
+static int
+eatline(int i, int j, char color, int dir, char chessboard[][10])
+{
+ int tmpx, tmpy;
+ char tmpchess;
+
+ tmpx = i + DIRX[dir];
+ tmpy = j + DIRY[dir];
+ tmpchess = chessboard[tmpx][tmpy];
+ if (tmpchess == -1)
+ return false;
+ if (tmpchess != INVERT(color))
+ return false;
+
+ tmpx += DIRX[dir];
+ tmpy += DIRY[dir];
+ tmpchess = chessboard[tmpx][tmpy];
+ while (tmpchess != -1) {
+ if (tmpchess < BLACK)
+ return false;
+ if (tmpchess == color) {
+ while (i != tmpx || j != tmpy) {
+ chessboard[i][j] = color;
+ i += DIRX[dir];
+ j += DIRY[dir];
+ }
+ return true;
+ }
+ tmpx += DIRX[dir];
+ tmpy += DIRY[dir];
+ tmpchess = chessboard[tmpx][tmpy];
+ }
+ return false;
+}
+
+static int
+if_can_put(int x, int y, char color, char chessboard[][10])
+{
+ int i, temp, checkx, checky;
+
+ if (chessboard[x][y] < BLACK)
+ for (i = 0; i < 8; i++) {
+ checkx = x + DIRX[i];
+ checky = y + DIRY[i];
+ temp = chessboard[checkx][checky];
+ if (temp < BLACK)
+ continue;
+ if (temp != color)
+ while (chessboard[checkx += DIRX[i]][checky += DIRY[i]] > HINT)
+ if (chessboard[checkx][checky] == color)
+ return true;
+ }
+ return false;
+}
+
+static int
+get_hint(struct OthelloData *od, char color)
+{
+ int i, j, temp = 0;
+
+ for (i = 1; i <= 8; i++)
+ for (j = 1; j <= 8; j++) {
+ if (od->nowboard[i][j] == HINT)
+ od->nowboard[i][j] = NONE;
+ if (if_can_put(i, j, color, od->nowboard)) {
+ od->nowboard[i][j] = HINT;
+ temp++;
+ }
+ print_chess(od, i, j, od->nowboard[i][j]);
+ }
+ return temp;
+}
+
+static void
+eat(int x, int y, int color, char chessboard[][10])
+{
+ int k;
+
+ for (k = 0; k < 8; k++)
+ eatline(x, y, color, k, chessboard);
+}
+
+static void
+end_of_game(struct OthelloData *od, int quit)
+{
+ FILE *fp, *fp1;
+ char *opponent[] = {"", "CD-65", "", "嬰兒", "小孩", "", "大人", "專家"};
+
+ move(STARTX - 1, 30);
+ outs(" ");
+ move(22, 35);
+ fp = fopen(LOGFILE, "a");
+ if (!quit) {
+ fp1 = fopen(SECRET, "a");
+ if (fp1) {
+ fprintf(fp1, "%d,%d,%s,%02d,%02d\n", od->think, od->which_table,
+ cuser.userid, od->number[0], od->number[1]);
+ fclose(fp1);
+ }
+ }
+ if (quit) {
+ if (od->number[0] == 2 && od->number[1] == 2) {
+ if (fp)
+ fclose(fp);
+ return;
+ }
+ fprintf(fp, "在%s級中, %s臨陣脫逃\n", opponent[od->think], cuser.userid);
+ if (fp)
+ fclose(fp);
+ return;
+ }
+ if (od->number[0] > od->number[1]) {
+ prints("你贏了電腦%02d子", od->number[0] - od->number[1]);
+ if (od->think == 6 && od->number[0] - od->number[1] >= 50)
+ demoney(200);
+ if (od->think == 7 && od->number[0] - od->number[1] >= 40)
+ demoney(200);
+ if (fp)
+ fprintf(fp, "在%s級中, %s以 %02d:%02d 贏了電腦%02d子\n",
+ opponent[od->think], cuser.userid, od->number[0], od->number[1],
+ od->number[0] - od->number[1]);
+ } else if (od->number[1] > od->number[0]) {
+ prints("電腦贏了你%02d子", od->number[1] - od->number[0]);
+ if (fp) {
+ fprintf(fp, "在%s級中, ", opponent[od->think]);
+ if (od->number[1] - od->number[0] > 20)
+ fprintf(fp, "電腦以 %02d:%02d 慘電%s %02d子\n", od->number[1],
+ od->number[0], cuser.userid, od->number[1] - od->number[0]);
+ else
+ fprintf(fp, "電腦以 %02d:%02d 贏了%s %02d子\n", od->number[1],
+ od->number[0], cuser.userid, od->number[1] - od->number[0]);
+ }
+ } else {
+ outs("你和電腦打成平手!!");
+ if (fp)
+ fprintf(fp, "在%s級中, %s和電腦以 %02d:%02d 打成了平手\n",
+ opponent[od->think], cuser.userid, od->number[1], od->number[0]);
+ }
+ if (fp)
+ fclose(fp);
+ move(1, 1);
+ igetch();
+}
+
+static void
+othello_redraw(struct OthelloData *od)
+{
+ int i, j;
+
+ for (i = 1; i <= 8; i++)
+ for (j = 1; j <= 8; j++)
+ print_chess(od, i, j, od->nowboard[i][j]);
+}
+
+static int
+player(struct OthelloData *od, char color)
+{
+ int ch;
+
+ if (get_hint(od, color)) {
+ while (true) {
+ ch = get_key(od, od->nowx, od->nowy);
+ switch (ch) {
+ case 'J':
+ case 'j':
+ case KEY_LEFT:
+ od->nowy--;
+ break;
+ case 'L':
+ case 'l':
+ case KEY_RIGHT:
+ od->nowy++;
+ break;
+ case 'I':
+ case 'i':
+ case KEY_UP:
+ od->nowx--;
+ break;
+ case 'K':
+ case 'k':
+ case KEY_DOWN:
+ od->nowx++;
+ break;
+ case ' ':
+ case '\r':
+ if (od->nowboard[(int)od->nowx][(int)od->nowy] != HINT)
+ break;
+ od->pass = 0;
+ od->nowboard[(int)od->nowx][(int)od->nowy] = color;
+ eat(od->nowx, od->nowy, color, od->nowboard);
+ print_chess(od, od->nowx, od->nowy, color);
+ return true;
+ case 'q':
+ end_of_game(od, 1);
+ return false;
+ case 'H':
+ case 'h':
+ od->if_hint = od->if_hint ^ 1;
+ othello_redraw(od);
+ break;
+ }
+ if (od->nowx == 9)
+ od->nowx = 1;
+ if (od->nowx == 0)
+ od->nowx = 8;
+ if (od->nowy == 9)
+ od->nowy = 1;
+ if (od->nowy == 0)
+ od->nowy = 8;
+ }
+ } else {
+ od->pass++;
+ if (od->pass == 1) {
+ move(23, 34);
+ outs("你必需放棄這一步!!");
+ igetch();
+ move(28, 23);
+ outs(" ");
+ } else {
+ end_of_game(od,0);
+ return false;
+ }
+ }
+ return 0;
+}
+
+static void
+init(struct OthelloData *od)
+{
+ int i, j, i1, j1;
+
+ memset(od, 0, sizeof(struct OthelloData));
+ od->nowx = 4;
+ od->nowy = 4;
+ od->number[0] = od->number[1] = 2;
+ for (i = 1; i <= 8; i++)
+ for (j = 1; j <= 8; j++) {
+ i1 = 4.5 - abs(4.5 - i);
+ j1 = 4.5 - abs(4.5 - j);
+ od->evaltable[0][i][j] = init_table[0][i1][j1];
+ od->evaltable[1][i][j] = init_table[1][i1][j1];
+ }
+ memset(od->nowboard, NONE, sizeof(od->nowboard));
+ for(i=0;i<10;i++)
+ od->nowboard[i][0]=od->nowboard[0][i]=od->nowboard[i][9]=od->nowboard[9][i]=-1;
+ od->nowboard[4][4] = od->nowboard[5][5] = WHITE;
+ od->nowboard[4][5] = od->nowboard[5][4] = BLACK;
+}
+
+static void
+report(struct OthelloData *od)
+{
+ int i, j;
+
+ od->number[0] = od->number[1] = 0;
+ for (i = 1; i <= 8; i++)
+ for (j = 1; j <= 8; j++)
+ if (od->nowboard[i][j] == BLACK)
+ od->number[0]++;
+ else if (od->nowboard[i][j] == WHITE)
+ od->number[1]++;
+ move(3, 60);
+ outs(cuser.userid);
+ move(3, 72);
+ prints(": %02d", od->number[0]);
+ move(4, 60);
+ prints("電腦 : %02d", od->number[1]);
+}
+
+static int
+EVL(struct OthelloData *od, char chessboard[][10], int color, int table_number)
+{
+ int points = 0, a, b;
+ for (a = 1; a <= 8; a++)
+ for (b = 1; b <= 8; b++)
+ if (chessboard[a][b] > HINT) {
+ if (chessboard[a][b] == BLACK)
+ points += od->evaltable[table_number][a][b];
+ else
+ points -= od->evaltable[table_number][a][b];
+ }
+ return ((color == BLACK) ? points : -points);
+}
+
+static int
+alphabeta(struct OthelloData *od, int alpha, int beta, int level, char chessboard[][10],
+ int thinkstep, int color, int table)
+{
+ int i, j, k, flag = 1;
+ char tempboard[10][10];
+ if (level == thinkstep + 1)
+ return EVL(od, chessboard, (level & 1 ? color : ((color - 2) ^ 1) + 2),
+ table);
+ for (i = 1; i <= 8; i++) {
+ for (j = 1; j <= 8; j++) {
+ if (if_can_put(i, j, color, chessboard)) {
+ flag = 0;
+ memcpy(tempboard, chessboard, sizeof(char) * 100);
+ eat(i, j, color, tempboard);
+
+ k = alphabeta(od, alpha, beta, level + 1, tempboard, thinkstep,
+ ((color - 2) ^ 1) + 2, table);
+ if (((level & 1) && k > alpha))
+ alpha = k;
+ else if (!(level & 1) && k < beta)
+ beta = k;
+ if (alpha >= beta)
+ break;
+ }
+ }
+ }
+ if (flag)
+ return EVL(od, chessboard, color, table);
+ return ((level & 1) ? alpha : beta);
+}
+
+static int
+Computer(struct OthelloData *od, int thinkstep, int table)
+{
+ int i, j, maxi = 0, maxj = 0, level = 1;
+ char chessboard[10][10];
+ int alpha = -10000, k;
+ if ((od->number[0] + od->number[1]) > 44)
+ table = NR_TABLE;
+ for (i = 1; i <= 8; i++)
+ for (j = 1; j <= 8; j++) {
+ if (if_can_put(i, j, WHITE, od->nowboard)) {
+ memcpy(chessboard, od->nowboard, sizeof(char) * 100);
+ eat(i, j, WHITE, chessboard);
+ k = alphabeta(od, alpha, 10000, level + 1, chessboard, thinkstep,
+ BLACK, table);
+ if (k > alpha) {
+ alpha = k;
+ maxi = i;
+ maxj = j;
+ }
+ }
+ }
+ if (alpha != -10000) {
+ eat(maxi, maxj, WHITE, od->nowboard);
+ od->pass = 0;
+ od->nowx = maxi;
+ od->nowy = maxj;
+ } else {
+ move(23, 30);
+ outs("電腦放棄這一步棋!!");
+ od->pass++;
+ if (od->pass == 2) {
+ move(23, 24);
+ outs(" ");
+ end_of_game(od, 0);
+ return false;
+ }
+ igetch();
+ move(23, 24);
+ outs(" ");
+ }
+ return true;
+}
+
+static int
+choose(void)
+{
+ char thinkstep[2];
+
+ move(2, 0);
+ outs("請選擇難度:");
+ move(5, 0);
+ outs("(1) CD-65\n"); /* 想 1 步 */
+ outs("(2) 嬰兒\n"); /* 想 3 步 */
+ outs("(3) 小孩\n"); /* 想 4 步 */
+ do {
+ getdata(4, 0, "請選擇一個對象和您對打:(1~3)",
+ thinkstep, sizeof(thinkstep), LCECHO);
+ } while (thinkstep[0] < '1' || thinkstep[0] > '3');
+ clear();
+ switch (thinkstep[0]) {
+ case '2':
+ thinkstep[0] = '3';
+ break;
+ case '3':
+ thinkstep[0] = '4';
+ break;
+ default:
+ thinkstep[0] = '1';
+ break;
+ }
+ return atoi(thinkstep);
+}
+
+#define lockreturn0(unmode, state) if(lockutmpmode(unmode, state)) return 0
+
+int
+othello_main(void)
+{
+ struct OthelloData *od;
+
+ lockreturn0(OTHELLO, LOCK_MULTI);
+
+ od=(struct OthelloData*)malloc(sizeof(struct OthelloData));
+ if(od==NULL) {
+ unlockutmpmode();
+ return 0;
+ }
+
+ clear();
+ init(od);
+ od->think = choose();
+ showtitle("黑白棋", BBSName);
+ printboard(od);
+ od->which_table = random() % NR_TABLE;
+ while (true) {
+ move(STARTX - 1, 30);
+ outs("輪到你下了...");
+ if (!player(od, BLACK))
+ break;
+ report(od);
+ othello_redraw(od);
+ if (od->number[0] + od->number[1] == 64) {
+ end_of_game(od, 0);
+ break;
+ }
+ move(STARTX - 1, 30);
+ outs("電腦思考中...");
+ refresh();
+ if (!Computer(od, od->think, od->which_table))
+ break;
+ report(od);
+ othello_redraw(od);
+ if (od->number[0] + od->number[1] == 64) {
+ end_of_game(od, 0);
+ break;
+ }
+ }
+ more(LOGFILE, YEA);
+ unlockutmpmode();
+ free(od);
+ return 1;
+}
diff --git a/pttbbs/mbbsd/passwd.c b/pttbbs/mbbsd/passwd.c
new file mode 100644
index 00000000..b84ef7b3
--- /dev/null
+++ b/pttbbs/mbbsd/passwd.c
@@ -0,0 +1,154 @@
+/* $Id$ */
+#include "bbs.h"
+
+static int semid = -1;
+
+#ifndef SEM_R
+#define SEM_R 0400
+#endif
+
+#ifndef SEM_A
+#define SEM_A 0200
+#endif
+
+#ifndef __FreeBSD__
+#include <sys/sem.h>
+union semun {
+ int val; /* value for SETVAL */
+ struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */
+ unsigned short *array; /* array for GETALL & SETALL */
+ struct seminfo *__buf; /* buffer for IPC_INFO */
+};
+#endif
+
+int
+passwd_init(void)
+{
+ semid = semget(PASSWDSEM_KEY, 1, SEM_R | SEM_A | IPC_CREAT | IPC_EXCL);
+ if (semid == -1) {
+ if (errno == EEXIST) {
+ semid = semget(PASSWDSEM_KEY, 1, SEM_R | SEM_A);
+ if (semid == -1) {
+ perror("semget");
+ exit(1);
+ }
+ } else {
+ perror("semget");
+ exit(1);
+ }
+ } else {
+ union semun s;
+
+ s.val = 1;
+ if (semctl(semid, 0, SETVAL, s) == -1) {
+ perror("semctl");
+ exit(1);
+ }
+ }
+
+ return 0;
+}
+
+int
+passwd_update_money(int num)
+/* update money only
+ Ptt: don't call it directly, call deumoney() */
+{
+ int pwdfd;
+ int money=moneyof(num);
+ userec_t u;
+ if (num < 1 || num > MAX_USERS)
+ return -1;
+
+ if ((pwdfd = open(fn_passwd, O_WRONLY)) < 0)
+ exit(1);
+ lseek(pwdfd, sizeof(userec_t) * (num - 1) +
+ ((char *)&u.money - (char *)&u), SEEK_SET);
+ write(pwdfd, &money, sizeof(int));
+ close(pwdfd);
+ return 0;
+}
+
+int
+passwd_update(int num, userec_t * buf)
+{
+ int pwdfd;
+ if (num < 1 || num > MAX_USERS)
+ return -1;
+ buf->money = moneyof(num);
+ if(usernum == num && ((pwdfd = currutmp->alerts) & ALERT_PWD))
+ {
+ userec_t u;
+ passwd_query(num, &u);
+ if(pwdfd & ALERT_PWD_BADPOST)
+ cuser.badpost = buf->badpost = u.badpost;
+ if(pwdfd & ALERT_PWD_PERM)
+ cuser.userlevel = buf->userlevel = u.userlevel;
+ currutmp->alerts &= ~ALERT_PWD;
+ }
+ if ((pwdfd = open(fn_passwd, O_WRONLY)) < 0)
+ exit(1);
+ lseek(pwdfd, sizeof(userec_t) * (num - 1), SEEK_SET);
+ write(pwdfd, buf, sizeof(userec_t));
+ close(pwdfd);
+ return 0;
+}
+
+int
+passwd_query(int num, userec_t * buf)
+{
+ int pwdfd;
+ if (num < 1 || num > MAX_USERS)
+ return -1;
+ if ((pwdfd = open(fn_passwd, O_RDONLY)) < 0)
+ exit(1);
+ lseek(pwdfd, sizeof(userec_t) * (num - 1), SEEK_SET);
+ read(pwdfd, buf, sizeof(userec_t));
+ close(pwdfd);
+ return 0;
+}
+
+int initcuser(const char *userid)
+{
+ // Ptt: setup cuser and usernum here
+ if(userid[0]=='\0' ||
+ !(usernum = searchuser(userid, NULL)) || usernum > MAX_USERS)
+ return -1;
+ passwd_query(usernum, &cuser);
+ return usernum;
+}
+
+int
+passwd_apply(int (*fptr) (int, userec_t *))
+{
+ int i;
+ userec_t user;
+ for (i = 0; i < MAX_USERS; i++) {
+ passwd_query(i + 1, &user);
+ if ((*fptr) (i, &user) == QUIT)
+ return QUIT;
+ }
+ return 0;
+}
+
+void
+passwd_lock(void)
+{
+ struct sembuf buf = {0, -1, SEM_UNDO};
+
+ if (semop(semid, &buf, 1)) {
+ perror("semop");
+ exit(1);
+ }
+}
+
+void
+passwd_unlock(void)
+{
+ struct sembuf buf = {0, 1, SEM_UNDO};
+
+ if (semop(semid, &buf, 1)) {
+ perror("semop");
+ exit(1);
+ }
+}
diff --git a/pttbbs/mbbsd/pmore.c b/pttbbs/mbbsd/pmore.c
new file mode 100644
index 00000000..e5ba2161
--- /dev/null
+++ b/pttbbs/mbbsd/pmore.c
@@ -0,0 +1,2528 @@
+/* $Id$ */
+
+/*
+ * pmore: piaip's more, a new replacement for traditional pager
+ *
+ * piaip's new implementation of pager(more) with mmap,
+ * designed for unlimilited length(lines).
+ *
+ * "pmore" is "piaip's more", NOT "PTT's more"!!!
+ * pmore is designed for general BBS systems, not
+ * specific to any branch.
+ *
+ * Author: Hung-Te Lin (piaip), June 2005.
+ * <piaip@csie.ntu.edu.tw>
+ * All Rights Reserved.
+ *
+ * MAJOR IMPROVEMENTS:
+ * - Clean source code, and more readble to mortal
+ * - Correct navigation
+ * - Excellent search ability (for correctness and user behavior)
+ * - Less memory consumption (mmap is not considered anyway)
+ * - Better support for large terminals
+ * - Unlimited file length and line numbers
+ *
+ * TODO ANE DONE:
+ * - Optimized speed up with Scroll supporting [done]
+ * - Support PTT_PRINTS [done]
+ * - Wrap long lines [done]
+ * - DBCS friendly wrap [done]
+ * - ASCII Art movie support [done]
+ * - Left-right wide navigation [done]
+ * - Reenrtance for main procedure [done with little hack]
+ * -
+ * - A new optimized terminal base system (piterm)
+ * - ASCII Art movie navigation keys
+ */
+
+// --------------------------------------------------------------- <FEATURES>
+/* These are default values.
+ * You may override them in your bbs.h or config.h etc etc.
+ */
+#define PMORE_PRELOAD_SIZE (64*1024L) // on busy system set smaller or undef
+
+#define PMORE_USE_PTT_PRINTS // support PTT or special printing
+#define PMORE_USE_OPT_SCROLL // optimized scroll
+#define PMORE_USE_DBCS_WRAP // safe wrap for DBCS.
+#define PMORE_USE_ASCII_MOVIE // support ascii movie
+//#define PMORE_RESTRICT_ANSI_MOVEMENT // user cannot use ANSI escapes to move
+#define PMORE_WORKAROUND_POORTERM // try to work with poor terminal sys
+#define PMORE_ACCURATE_WRAPEND // try more harder to find file end in wrap mode
+
+#define PMORE_TRADITIONAL_PROMPTEND // when prompt=NA, show only page 1
+#define PMORE_TRADITIONAL_FULLCOL // to work with traditional ascii arts
+// -------------------------------------------------------------- </FEATURES>
+
+#include "bbs.h"
+
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <ctype.h>
+#include <string.h>
+
+// Platform Related. NoSync is faster but if we don't have it...
+#ifdef MAP_NOSYNC
+#define MF_MMAP_OPTION (MAP_NOSYNC)
+#else
+#define MF_MMAP_OPTION (MAP_SHARED)
+#endif
+
+/* Developer's Guide
+ *
+ * OVERVIEW
+ * - pmore is designed as a line-oriented pager. After you load (mf_attach)
+ * a file, you can move current display window by lines (mf_forward and
+ * mf_backward) and then display a page(mf_display).
+ * And please remember to delete allocated resources (mf_detach)
+ * when you exit.
+ * - Functions are designed to work with global variables.
+ * However you can overcome re-entrance problem by backuping up variables
+ * or replace all "." to "->" with little modification and add pointer as
+ * argument passed to each function.
+ * (This is really tested and it works, however then using global variables
+ * is considered to be faster and easier to maintain, at lease shorter in
+ * time to key-in and for filelength).
+ * - Basically this file should only export one function, "pmore".
+ * Using any other functions here may be dangerous because they are not
+ * coded for external reentrance rightnow.
+ * - mf_* are operation functions to work with file buffer.
+ * Usually these function assumes "mf" can be accessed.
+ * - pmore_* are utility functions
+ *
+ * DETAILS
+ * - The most tricky part of pmore is the design of "maxdisps" and "maxlinenoS".
+ * What do they mean? "The pointer and its line number of last page".
+ * - Because pmore is designed to work with very large files, it's costly to
+ * calculate the total line numbers (and not necessary). But if we don't
+ * know about how many lines left can we display then when navigating by
+ * pages may result in a page with single line conent (if you set display
+ * starting pointer to the real last line).
+ * - To overcome this issue, maxdisps is introduced. It tries to go backward
+ * one page from end of file (this operation is lighter than visiting
+ * entire file content for line number calculation). Then we can set this
+ * as boundary of forward navigation.
+ * - maxlinenoS is the line number of maxdisps. It's NOT the real number of
+ * total line in current file (You have to add the last page). That's why
+ * it has a strange name of trailing "S", to hint you that it's not
+ * "maxlineno" which is easily considered as "max(total) line number".
+ *
+ * HINTS:
+ * - Remember mmap pointers are NOT null terminated strings.
+ * You have to use strn* APIs and make sure not exceeding mmap buffer.
+ * DO NOT USE strcmp, strstr, strchr, ...
+ * - Scroll handling is painful. If you displayed anything on screen,
+ * remember to MFDISP_DIRTY();
+ * - To be portable between most BBS systems, pmore is designed to
+ * workaround most BBS bugs inside itself.
+ * - Basically pmore considered the 'outc' output system as unlimited buffer.
+ * However on most BBS implementation, outc used a buffer with ANSILINELEN
+ * in length. And for some branches they even used unsigned byte for index.
+ * So if user complained about output truncated or blanked, increase buffer.
+ */
+
+#ifdef DEBUG
+int debug = 0;
+# define MFPROTO
+#else
+# define MFPROTO inline static
+#endif
+
+/* DBCS users tend to write unsigned char. let's make compiler happy */
+#define ustrlen(x) strlen((char*)x)
+#define ustrchr(x,y) (unsigned char*)strchr((char*)x, y)
+#define ustrrchr(x,y) (unsigned char*)strrchr((char*)x, y)
+
+
+// --------------------------------------------- <Defines and constants>
+
+// --------------------------- <Display>
+
+/* ANSI COMMAND SYSTEM */
+/* On some systems with pmore style ANSI system applied,
+ * we don't have to define these again.
+ */
+#ifndef PMORE_STYLE_ANSI
+#define PMORE_STYLE_ANSI
+
+// Escapes. I don't like \033 everywhere.
+#define ESC_NUM (0x1b)
+#define ESC_STR "\x1b"
+#define ESC_CHR '\x1b'
+
+// Common ANSI commands.
+#define ANSI_RESET ESC_STR "[m"
+#define ANSI_COLOR(x) ESC_STR "[" #x "m"
+#define ANSI_MOVETO(y,x) ESC_STR "[" #y ";" #x "H"
+#define ANSI_CLRTOEND ESC_STR "[K"
+
+#define ANSI_IN_ESCAPE(x) (((x) >= '0' && (x) <= '9') || \
+ (x) == ';' || (x) == ',' || (x) == '[')
+
+#endif /* PMORE_STYLE_ANSI */
+
+#define ANSI_IN_MOVECMD(x) (strchr("ABCDfjHJRu", x) != NULL)
+#define PMORE_DBCS_LEADING(c) (c >= 0x80)
+
+// Poor BBS terminal system Workarounds
+// - Most BBS implements clrtoeol() as fake command
+// and usually messed up when output ANSI quoted string.
+// - A workaround is suggested by kcwu:
+// https://opensvn.csie.org/traccgi/pttbbs/trac.cgi/changeset/519
+#define FORCE_CLRTOEOL() outs(ANSI_CLRTOEND)
+
+/* Again, if you have a BBS system which optimized out* without recognizing
+ * ANSI escapes, scrolling with ANSI text may result in melformed text (because
+ * ANSI escapes were "optimized" ). So here we provide a method to overcome
+ * with this situation. However your should increase your I/O buffer to prevent
+ * flickers.
+ */
+MFPROTO void
+pmore_clrtoeol(int y, int x)
+{
+#ifdef PMORE_WORKAROUND_POORTERM
+ int i;
+ move(y, x);
+ for (i = x; i < t_columns; i++)
+ outc(' ');
+ move(y, x);
+#else
+ move(y, x);
+ clrtoeol();
+#endif
+}
+
+// --------------------------- </Display>
+
+// --------------------------- <Main Navigation>
+typedef struct
+{
+ unsigned char
+ *start, *end, // file buffer
+ *disps, *dispe, // displayed content start/end
+ *maxdisps; // a very special pointer,
+ // consider as "disps of last page"
+ off_t len; // file total length
+ long lineno, // lineno of disps
+ oldlineno, // last drawn lineno, < 0 means full update
+ xpos, // starting x position
+ //
+ wraplines, // wrapped lines in last display
+ trunclines, // truncated lines in last display
+ dispedlines, // how many different lines displayed
+ // usually dispedlines = PAGE-wraplines,
+ // but if last line is incomplete(wrapped),
+ // dispedlines = PAGE-wraplines + 1
+ lastpagelines,// lines of last page to show
+ // this indicates how many lines can
+ // maxdisps(maxlinenoS) display.
+ maxlinenoS; // lineno of maxdisps, "S"!
+ // What does the magic "S" mean?
+ // Just trying to notify you that it's
+ // NOT REAL MAX LINENO NOR FILELENGTH!!!
+ // You may consider "S" of "Start" (disps).
+} MmappedFile;
+
+MmappedFile mf = {
+ 0, 0, 0, 0, 0, 0L,
+ 0, -1L, 0, 0, -1L, -1L, -1L,-1L
+}; // current file
+
+/* mf_* navigation commands return value meanings */
+enum MF_NAV_COMMANDS {
+ MFNAV_OK, // navigation ok
+ MFNAV_EXCEED, // request exceeds buffer
+};
+
+/* Navigation units (dynamic, so not in enum const) */
+#define MFNAV_PAGE (t_lines-2) // when navigation, how many lines in a page to move
+
+/* Display system */
+enum MF_DISP_CONST {
+ /* newline method (because of poor BBS implementation) */
+ MFDISP_NEWLINE_CLEAR = 0, // \n and cleartoeol
+ MFDISP_NEWLINE_SKIP,
+ MFDISP_NEWLINE_MOVE, // use move to simulate newline.
+
+ MFDISP_WRAP_TRUNCATE = 0,
+ MFDISP_WRAP_WRAP,
+
+ MFDISP_OPT_CLEAR = 0,
+ MFDISP_OPT_OPTIMIZED,
+ MFDISP_OPT_FORCEDIRTY,
+
+ MFDISP_SEP_NONE = 0x00,
+ MFDISP_SEP_LINE = 0x01,
+ MFDISP_SEP_WRAP = 0x02,
+ MFDISP_SEP_OLD = MFDISP_SEP_LINE | MFDISP_SEP_WRAP,
+
+ MFDISP_RAW_NA = 0x00,
+ MFDISP_RAW_NOANSI,
+ MFDISP_RAW_PLAIN,
+ MFDISP_RAW_MODES,
+ // MFDISP_RAW_NOFMT, // this is rarely used sinde we have ansi and plain
+
+};
+
+#define MFDISP_PAGE (t_lines-1) // the real number of lines to be shown.
+#define MFDISP_DIRTY() { mf.oldlineno = -1; }
+
+/* Indicators */
+#define MFDISP_TRUNC_INDICATOR ANSI_COLOR(0;1;37) ">" ANSI_RESET
+#define MFDISP_WRAP_INDICATOR ANSI_COLOR(0;1;37) "\\" ANSI_RESET
+#define MFDISP_WNAV_INDICATOR ANSI_COLOR(0;1;37) "<" ANSI_RESET
+// --------------------------- </Main Navigation>
+
+// --------------------------- <Aux. Structures>
+/* browsing preference */
+typedef struct
+{
+ /* mode flags */
+ unsigned short int
+ wrapmode, // wrap?
+ seperator, // seperator style
+ indicator, // show wrap indicators
+
+ oldwrapmode, // traditional wrap
+ oldstatusbar, // traditional statusbar
+ rawmode; // show file as-is.
+} MF_BrowsingPrefrence;
+
+MF_BrowsingPrefrence bpref =
+{ MFDISP_WRAP_WRAP, MFDISP_SEP_OLD, 1,
+ 0, 0, 0, };
+
+/* pretty format header */
+#define FH_HEADERS (4) // how many headers do we know?
+#define FH_HEADER_LEN (4) // strlen of each heads
+static const char *_fh_disp_heads[FH_HEADERS] =
+ {"作者", "標題", "時間", "轉信"};
+
+typedef struct
+{
+ int lines; // header lines
+ unsigned char *headers[FH_HEADERS];
+ unsigned char *floats[2]; // right floating, name and val
+} MF_PrettyFormattedHeader;
+
+MF_PrettyFormattedHeader fh = { 0, {0,0,0,0}, {0, 0}};
+
+/* search records */
+typedef struct
+{
+ int len;
+ int (*cmpfunc) (const char *, const char *, size_t);
+ unsigned char *search_str; // maybe we can change to dynamic allocation
+} MF_SearchRecord;
+
+MF_SearchRecord sr = { 0, strncmp, NULL};
+
+enum MFSEARCH_DIRECTION {
+ MFSEARCH_FORWARD,
+ MFSEARCH_BACKWARD,
+};
+
+// Reset structures
+#define RESETMF() { memset(&mf, 0, sizeof(mf)); \
+ mf.lastpagelines = mf.maxlinenoS = mf.oldlineno = -1; }
+#define RESETFH() { memset(&fh, 0, sizeof(fh)); \
+ fh.lines = -1; }
+
+// --------------------------- </Aux. Structures>
+
+// --------------------------------------------- </Defines and constants>
+
+// --------------------------------------------- <Optional Modules>
+#ifdef PMORE_USE_ASCII_MOVIE
+enum _MFDISP_MOVIE_MODES {
+ MFDISP_MOVIE_UNKNOWN= 0,
+ MFDISP_MOVIE_DETECTED,
+ MFDISP_MOVIE_YES,
+ MFDISP_MOVIE_NO,
+ MFDISP_MOVIE_PLAYING,
+ MFDISP_MOVIE_PLAYING_OLD,
+};
+
+typedef struct {
+ struct timeval frameclk;
+ struct timeval synctime;
+ unsigned char mode,
+ compat24;
+} MF_Movie;
+
+MF_Movie mfmovie;
+
+#define RESET_MOVIE() { mfmovie.mode = MFDISP_MOVIE_UNKNOWN; \
+ mfmovie.compat24 = 1; \
+ mfmovie.synctime.tv_sec = mfmovie.synctime.tv_usec = 0; \
+ mfmovie.frameclk.tv_sec = 1; mfmovie.frameclk.tv_usec = 0; }
+
+MFPROTO unsigned char * mf_movieFrameHeader(unsigned char *p);
+int pmore_wait_input(struct timeval *ptv);
+int mf_movieNextFrame();
+int mf_movieSyncFrame();
+
+void float2tv(float f, struct timeval *ptv);
+
+#define MOVIE_MIN_FRAMECLK (0.1f)
+#define MOVIE_SECOND_U (1000000L)
+
+#endif
+// --------------------------------------------- </Optional Modules>
+
+// used by mf_attach
+void mf_parseHeaders();
+void mf_freeHeaders();
+void mf_determinemaxdisps(int, int);
+
+/*
+ * mmap basic operations
+ */
+int
+mf_attach(const char *fn)
+{
+ struct stat st;
+ int fd = open(fn, O_RDONLY, 0600);
+
+ if(fd < 0)
+ return 0;
+
+ if (fstat(fd, &st) || ((mf.len = st.st_size) <= 0) || S_ISDIR(st.st_mode))
+ {
+ mf.len = 0;
+ close(fd);
+ return 0;
+ }
+
+ /*
+ mf.len = lseek(fd, 0L, SEEK_END);
+ lseek(fd, 0, SEEK_SET);
+ */
+
+ mf.start = mmap(NULL, mf.len, PROT_READ,
+ MF_MMAP_OPTION, fd, 0);
+ close(fd);
+
+ if(mf.start == MAP_FAILED)
+ {
+ RESETMF();
+ return 0;
+ }
+
+ mf.end = mf.start + mf.len;
+ mf.disps = mf.dispe = mf.start;
+ mf.lineno = 0;
+
+ mf_determinemaxdisps(MFNAV_PAGE, 0);
+
+ mf.disps = mf.dispe = mf.start;
+ mf.lineno = 0;
+
+ /* reset and parse article header */
+ mf_parseHeaders();
+
+ /* a workaround for wrapped seperators */
+ if(mf.maxlinenoS > 0 &&
+ fh.lines >= mf.maxlinenoS &&
+ bpref.seperator & MFDISP_SEP_WRAP)
+ {
+ mf_determinemaxdisps(+1, 1);
+ }
+
+ return 1;
+}
+
+void
+mf_detach()
+{
+ if(mf.start) {
+ munmap(mf.start, mf.len);
+ RESETMF();
+
+ }
+ mf_freeHeaders();
+}
+
+/*
+ * lineno calculation, and moving
+ */
+void
+mf_sync_lineno()
+{
+ unsigned char *p;
+
+ if(mf.disps == mf.maxdisps && mf.maxlinenoS >= 0)
+ {
+ mf.lineno = mf.maxlinenoS;
+ } else {
+ mf.lineno = 0;
+ for (p = mf.start; p < mf.disps; p++)
+ if(*p == '\n')
+ mf.lineno ++;
+
+ if(mf.disps == mf.maxdisps && mf.maxlinenoS < 0)
+ mf.maxlinenoS = mf.lineno;
+ }
+}
+
+MFPROTO int mf_backward(int); // used by mf_buildmaxdisps
+MFPROTO int mf_forward(int); // used by mf_buildmaxdisps
+
+void
+mf_determinemaxdisps(int backlines, int update_by_offset)
+{
+ unsigned char *pbak = mf.disps, *mbak = mf.maxdisps;
+ long lbak = mf.lineno;
+
+ if(update_by_offset)
+ {
+ if(backlines > 0)
+ {
+ /* tricky way because usually
+ * mf_forward checks maxdisps.
+ */
+ mf.disps = mf.maxdisps;
+ mf.maxdisps = mf.end-1;
+ mf_forward(backlines);
+ mf_backward(0);
+ } else
+ mf_backward(backlines);
+ } else {
+ mf.lineno = backlines;
+ mf.disps = mf.end - 1;
+ backlines = mf_backward(backlines);
+ }
+
+ if(mf.disps != mbak)
+ {
+ mf.maxdisps = mf.disps;
+ if(update_by_offset)
+ mf.lastpagelines -= backlines;
+ else
+ mf.lastpagelines = backlines;
+
+ mf.maxlinenoS = -1;
+#ifdef PMORE_PRELOAD_SIZE
+ if(mf.len <= PMORE_PRELOAD_SIZE)
+ mf_sync_lineno(); // maxlinenoS will be automatically updated
+#endif
+ }
+ mf.disps = pbak;
+ mf.lineno = lbak;
+}
+
+/*
+ * mf_backwards is also used for maxno determination,
+ * so we cannot change anything in mf except these:
+ * mf.disps
+ * mf.lineno
+ */
+MFPROTO int
+mf_backward(int lines)
+{
+ int real_moved = 0;
+
+ /* backward n lines means to find n times of '\n'. */
+
+ /* if we're already in a line break, add one mark. */
+ if (mf.disps < mf.end && *mf.disps == '\n')
+ lines++, real_moved --;
+
+ while (1)
+ {
+ if (mf.disps < mf.start || *mf.disps == '\n')
+ {
+ real_moved ++;
+ if(lines-- <= 0 || mf.disps < mf.start)
+ break;
+ }
+ mf.disps --;
+ }
+
+ /* now disps points to previous 1 byte of new address */
+ mf.disps ++;
+ real_moved --;
+ mf.lineno -= real_moved;
+
+ return real_moved;
+}
+
+MFPROTO int
+mf_forward(int lines)
+{
+ int real_moved = 0;
+
+ while(mf.disps <= mf.maxdisps && lines > 0)
+ {
+ while (mf.disps <= mf.maxdisps && *mf.disps++ != '\n');
+
+ if(mf.disps <= mf.maxdisps)
+ mf.lineno++, lines--, real_moved++;
+ }
+
+ if(mf.disps > mf.maxdisps)
+ mf.disps = mf.maxdisps;
+
+ /* please make sure you have lineno synced. */
+ if(mf.disps == mf.maxdisps && mf.maxlinenoS < 0)
+ mf.maxlinenoS = mf.lineno;
+
+ return real_moved;
+ /*
+ if(lines > 0)
+ return MFNAV_OK;
+ else
+ return MFNAV_EXCEED;
+ */
+}
+
+int
+mf_goTop()
+{
+ if(mf.disps == mf.start && mf.xpos > 0)
+ mf.xpos = 0;
+ mf.disps = mf.start;
+ mf.lineno = 0;
+ return MFNAV_OK;
+}
+
+int
+mf_goBottom()
+{
+ mf.disps = mf.maxdisps;
+ mf_sync_lineno();
+
+ return MFNAV_OK;
+}
+
+MFPROTO int
+mf_goto(int lineno)
+{
+ mf.disps = mf.start;
+ mf.lineno = 0;
+ return mf_forward(lineno);
+}
+
+MFPROTO int
+mf_viewedNone()
+{
+ return (mf.disps <= mf.start);
+}
+
+MFPROTO int
+mf_viewedAll()
+{
+ return (mf.dispe >= mf.end);
+}
+/*
+ * search!
+ */
+int
+mf_search(int direction)
+{
+ unsigned char *s = sr.search_str;
+ int l = sr.len;
+ int flFound = 0;
+
+ if(!s || !*s)
+ return 0;
+
+ if(direction == MFSEARCH_FORWARD)
+ {
+ mf_forward(1);
+ while(mf.disps < mf.end - l)
+ {
+ if(sr.cmpfunc((char*)mf.disps, (char*)s, l) == 0)
+ {
+ flFound = 1;
+ break;
+ } else {
+ /* DBCS check here. */
+ if(PMORE_DBCS_LEADING(*mf.disps++))
+ mf.disps++;
+ }
+ }
+ mf_backward(0);
+ if(mf.disps > mf.maxdisps)
+ mf.disps = mf.maxdisps;
+ mf_sync_lineno();
+ }
+ else if(direction == MFSEARCH_BACKWARD)
+ {
+ mf_backward(1);
+ while (!flFound && mf.disps > mf.start)
+ {
+ while(!flFound && mf.disps < mf.end-l && *mf.disps != '\n')
+ {
+ if(sr.cmpfunc((char*)mf.disps, (char*)s, l) == 0)
+ {
+ flFound = 1;
+ } else
+ {
+ /* DBCS check here. */
+ if(PMORE_DBCS_LEADING(*mf.disps++))
+ mf.disps++;
+ }
+ }
+ if(!flFound)
+ mf_backward(1);
+ }
+ mf_backward(0);
+ if(mf.disps < mf.start)
+ mf.disps = mf.start;
+ mf_sync_lineno();
+ }
+ if(flFound)
+ MFDISP_DIRTY();
+ return flFound;
+}
+
+/* String Processing
+ *
+ * maybe you already have your string processors (or not).
+ * whether yes or no, here we provides some.
+ */
+
+#define ISSPACE(x) (x <= ' ')
+
+MFPROTO void
+pmore_str_strip_ansi(unsigned char *p) // warning: p is NULL terminated
+{
+ unsigned char *pb = p;
+ while (*p != 0)
+ {
+ if (*p == ESC_CHR)
+ {
+ // ansi code sequence, ignore them.
+ pb = p++;
+ while (ANSI_IN_ESCAPE(*p))
+ p++;
+ memmove(pb, p, ustrlen(p)+1);
+ p = pb;
+ }
+ else if (*p < ' ' || *p == 0xff)
+ {
+ // control codes, ignore them.
+ // what is 0xff? old BBS does not handle telnet protocol
+ // so IACs were inserted.
+ memmove(p, p+1, ustrlen(p+1)+1);
+ }
+ else
+ p++;
+ }
+}
+
+/* this chomp is a little different:
+ * it kills starting and trailing spaces.
+ */
+MFPROTO void
+pmore_str_chomp(unsigned char *p)
+{
+ unsigned char *pb = p + ustrlen(p)-1;
+
+ while (pb >= p)
+ if(ISSPACE(*pb))
+ *pb-- = 0;
+ else
+ break;
+ pb = p;
+ while (*pb && ISSPACE(*pb))
+ pb++;
+
+ if(pb != p)
+ memmove(p, pb, ustrlen(pb)+1);
+}
+
+#if 0
+int
+pmore_str_safe_big5len(unsigned char *p)
+{
+ return 0;
+}
+#endif
+
+/*
+ * Format Related
+ */
+
+void
+mf_freeHeaders()
+{
+ if(fh.lines > 0)
+ {
+ int i;
+
+ for (i = 0; i < FH_HEADERS; i++)
+ if(fh.headers[i])
+ free(fh.headers[i]);
+ for (i = 0; i < sizeof(fh.floats) / sizeof(unsigned char*); i++)
+ free(fh.floats[i]);
+ RESETFH();
+ }
+}
+
+void
+mf_parseHeaders()
+{
+ /* file format:
+ * AUTHOR: author BOARD: blah <- headers[0], flaots[0], floats[1]
+ * XXX: xxx <- headers[1]
+ * XXX: xxx <- headers[n]
+ * [blank, fill with seperator] <- lines
+ *
+ * #define STR_AUTHOR1 "作者:"
+ * #define STR_AUTHOR2 "發信人:"
+ */
+ unsigned char *pmf = mf.start;
+ int i = 0;
+
+ RESETFH();
+
+ if(mf.len < LEN_AUTHOR2)
+ return;
+
+ if (strncmp((char*)mf.start, STR_AUTHOR1, LEN_AUTHOR1) == 0)
+ {
+ fh.lines = 3; // local
+ }
+ else if (strncmp((char*)mf.start, STR_AUTHOR2, LEN_AUTHOR2) == 0)
+ {
+ fh.lines = 4;
+ }
+ else
+ return;
+
+ for (i = 0; i < fh.lines; i++)
+ {
+ unsigned char *p = pmf, *pb = pmf;
+ int l;
+
+ /* first, go to line-end */
+ while(pmf < mf.end && *pmf != '\n')
+ pmf++;
+ if(pmf >= mf.end)
+ break;
+ p = pmf;
+ pmf ++; // move to next line.
+
+ // p is pointing at a new line. (\n)
+ l = (int)(p - pb);
+#ifdef CRITICAL_MEMORY
+ // kcwu: dirty hack, avoid 64byte slot. use 128byte slot instead.
+ if (l<100) {
+ p = (unsigned char*) malloc (100+1);
+ } else {
+ p = (unsigned char*) malloc (l+1);
+ }
+#else
+ p = (unsigned char*) malloc (l+1);
+#endif
+ fh.headers[i] = p;
+ memcpy(p, pb, l);
+ p[l] = 0;
+
+ // now, postprocess p.
+ pmore_str_strip_ansi(p);
+
+ // strip to quotes[+1 space]
+ if((pb = ustrchr((char*)p, ':')) != NULL)
+ {
+ if(*(pb+1) == ' ') pb++;
+ memmove(p, pb, ustrlen(pb)+1);
+ }
+
+ // kill staring and trailing spaces
+ pmore_str_chomp(p);
+
+ // special case, floats are in line[0].
+ if(i == 0 && (pb = ustrrchr(p, ':')) != NULL && *(pb+1))
+ {
+ unsigned char *np = (unsigned char*)strdup((char*)(pb+1));
+
+ fh.floats[1] = np;
+ pmore_str_chomp(np);
+ // remove quote and traverse back
+ *pb-- = 0;
+ while (pb > p && *pb != ',' && !(ISSPACE(*pb)))
+ pb--;
+
+ if (pb > p) {
+ fh.floats[0] = (unsigned char*)strdup((char*)(pb+1));
+ pmore_str_chomp(fh.floats[0]);
+ *pb = 0;
+ pmore_str_chomp(fh.headers[0]);
+ } else {
+ fh.floats[0] = (unsigned char*)strdup("");
+ }
+ }
+ }
+}
+
+/*
+ * mf_display utility macros
+ */
+MFPROTO void
+MFDISP_SKIPCURLINE()
+{
+ while (mf.dispe < mf.end && *mf.dispe != '\n')
+ mf.dispe++;
+}
+
+MFPROTO int
+MFDISP_PREDICT_LINEWIDTH(unsigned char *p)
+{
+ /* predict from p to line-end, without ANSI seq.
+ */
+ int off = 0;
+ int inAnsi = 0;
+
+ while (p < mf.end && *p != '\n')
+ {
+ if(inAnsi)
+ {
+ if(!ANSI_IN_ESCAPE(*p))
+ inAnsi = 0;
+ } else {
+ if(*p == ESC_CHR)
+ inAnsi = 1;
+ else
+ off ++;
+ }
+ p++;
+ }
+ return off;
+}
+
+MFPROTO int
+MFDISP_DBCS_HEADERWIDTH(int originalw)
+{
+ return originalw - (originalw %2);
+// return (originalw >> 1) << 1;
+}
+
+#define MFDISP_FORCEUPDATE2TOP() { startline = 0; }
+#define MFDISP_FORCEUPDATE2BOT() { endline = MFDISP_PAGE - 1; }
+#define MFDISP_FORCEDIRTY2BOT() \
+ if(optimized == MFDISP_OPT_OPTIMIZED) { \
+ optimized = MFDISP_OPT_FORCEDIRTY; \
+ MFDISP_FORCEUPDATE2BOT(); \
+ }
+
+static char *override_msg = NULL;
+static char *override_attr = NULL;
+
+/*
+ * display mf content from disps for MFDISP_PAGE
+ */
+
+void
+mf_display()
+{
+ int lines = 0, col = 0, currline = 0, wrapping = 0;
+ int startline, endline;
+
+ int optimized = MFDISP_OPT_CLEAR;
+
+ /* why t_columns-1 here?
+ * because BBS systems usually have a poor terminal system
+ * and many stupid clients behave differently.
+ * So we try to avoid using the last column, leave it for
+ * BBS to place '\n' and CLRTOEOL.
+ */
+ const int headerw = MFDISP_DBCS_HEADERWIDTH(t_columns-1);
+ const int dispw = headerw - (t_columns - headerw < 2);
+ const int maxcol = dispw - 1;
+ int newline_default = MFDISP_NEWLINE_CLEAR;
+
+ if(mf.wraplines || mf.trunclines)
+ MFDISP_DIRTY(); // we can't scroll with wrapped lines.
+
+ mf.wraplines = 0;
+ mf.trunclines = 0;
+ mf.dispedlines = 0;
+
+ MFDISP_FORCEUPDATE2TOP();
+ MFDISP_FORCEUPDATE2BOT();
+
+#ifdef PMORE_USE_OPT_SCROLL
+ /* process scrolling */
+ if (mf.oldlineno >= 0 && mf.oldlineno != mf.lineno)
+ {
+ int scrll = mf.lineno - mf.oldlineno, i;
+ int reverse = (scrll > 0 ? 0 : 1);
+
+ if(reverse)
+ scrll = -scrll;
+ else
+ {
+ /* because bottom status line is also scrolled,
+ * we have to erase it here.
+ */
+ pmore_clrtoeol(b_lines, 0);
+ // move(b_lines, 0);
+ // clrtoeol();
+ }
+
+ if(scrll > MFDISP_PAGE)
+ scrll = MFDISP_PAGE;
+
+ i = scrll;
+ while(i-- > 0)
+ if (reverse)
+ rscroll(); // v
+ else
+ scroll(); // ^
+
+ if(reverse)
+ {
+ endline = scrll-1; // v
+ // clear the line which will be scrolled
+ // to bottom (status line position).
+ pmore_clrtoeol(b_lines, 0);
+ // move(b_lines, 0);
+ // clrtoeol();
+ }
+ else
+ {
+ startline = MFDISP_PAGE - scrll; // ^
+ }
+ move(startline, 0);
+ optimized = MFDISP_OPT_OPTIMIZED;
+ // return; // uncomment if you want to observe scrolling
+ }
+ else
+#endif
+ clear(), move(0, 0);
+
+ mf.dispe = mf.disps;
+ while (lines < MFDISP_PAGE)
+ {
+ int inAnsi = 0;
+ int newline = newline_default;
+ int predicted_linewidth = -1;
+ int xprefix = mf.xpos;
+
+#ifdef PMORE_USE_DBCS_WRAP
+ unsigned char *dbcs_incomplete = NULL;
+#endif
+
+ currline = mf.lineno + lines;
+ col = 0;
+
+ if(!wrapping && mf.dispe < mf.end)
+ mf.dispedlines++;
+
+ if(optimized == MFDISP_OPT_FORCEDIRTY)
+ {
+ /* btw, apparently this line should be visible.
+ * if not, maybe something wrong.
+ */
+ pmore_clrtoeol(lines, 0);
+ }
+
+#ifdef PMORE_USE_ASCII_MOVIE
+ if(mfmovie.mode == MFDISP_MOVIE_PLAYING_OLD &&
+ mfmovie.compat24)
+ {
+ if(mf.dispedlines == 23)
+ return;
+ }
+ else
+ if(mfmovie.mode == MFDISP_MOVIE_UNKNOWN ||
+ mfmovie.mode == MFDISP_MOVIE_PLAYING)
+ {
+ if(mf_movieFrameHeader(mf.dispe))
+ switch(mfmovie.mode)
+ {
+ case MFDISP_MOVIE_UNKNOWN:
+ mfmovie.mode = MFDISP_MOVIE_DETECTED;
+ /* let's remove the first control sequence. */
+ MFDISP_SKIPCURLINE();
+ break;
+ case MFDISP_MOVIE_PLAYING:
+ /*
+ * maybe we should do clrtobot() here,
+ * but it's even better if we do clear()
+ * all time. so we set dirty here for
+ * next frame, and please set dirty before
+ * playing.
+ */
+ MFDISP_DIRTY();
+ return;
+ }
+ }
+#endif
+
+ /* Is currentline visible? */
+ if (lines < startline || lines > endline)
+ {
+ MFDISP_SKIPCURLINE();
+ newline = MFDISP_NEWLINE_SKIP;
+ }
+ /* Now, consider what kind of line
+ * (header, seperator, or normal text)
+ * is current line.
+ */
+ else if (currline == fh.lines && bpref.rawmode == MFDISP_RAW_NA)
+ {
+ /* case 1, header seperator line */
+ if (bpref.seperator & MFDISP_SEP_LINE)
+ {
+ outs(ANSI_COLOR(36));
+ for(col = 0; col < headerw; col+=2)
+ {
+ // prints("%02d", col);
+ outs("─");
+ }
+ outs(ANSI_RESET);
+ }
+
+ /* Traditional 'more' adds seperator as a newline.
+ * This is buggy, however we can support this
+ * by using wrapping features.
+ * Anyway I(piaip) don't like this. And using wrap
+ * leads to slow display (we cannt speed it up with
+ * optimized scrolling.
+ */
+ if(bpref.seperator & MFDISP_SEP_WRAP)
+ {
+ /* we have to do all wrapping stuff
+ * in normal text section.
+ * make sure this is updated.
+ */
+ wrapping = 1;
+ mf.wraplines ++;
+ MFDISP_FORCEDIRTY2BOT();
+ if(mf.dispe > mf.start &&
+ mf.dispe < mf.end &&
+ *mf.dispe == '\n')
+ mf.dispe --;
+ }
+ else
+ MFDISP_SKIPCURLINE();
+ }
+ else if (currline < fh.lines && bpref.rawmode == MFDISP_RAW_NA )
+ {
+ /* case 2, we're printing headers */
+ const char *val = (const char*)fh.headers[currline];
+ const char *name = _fh_disp_heads[currline];
+ int w = headerw - FH_HEADER_LEN - 3;
+
+ outs(ANSI_COLOR(47;34) " ");
+ outs(name);
+ outs(" " ANSI_COLOR(44;37) " ");
+
+ /* right floating stuff? */
+ if (currline == 0 && fh.floats[0])
+ {
+ w -= ustrlen(fh.floats[0]) + ustrlen(fh.floats[1]) + 4;
+ }
+
+ prints("%-*.*s", w, w,
+ (val ? val : ""));
+
+ if (currline == 0 && fh.floats[0])
+ {
+ outs(ANSI_COLOR(47;34) " ");
+ outs((const char*)fh.floats[0]);
+ outs(" " ANSI_COLOR(44;37) " ");
+ outs((const char*)fh.floats[1]);
+ outs(" ");
+ }
+
+ outs(ANSI_RESET);
+ MFDISP_SKIPCURLINE();
+ }
+ else if(mf.dispe < mf.end)
+ {
+ /* case 3, normal text */
+ long dist = mf.end - mf.dispe;
+ long flResetColor = 0;
+ int srlen = -1;
+ int breaknow = 0;
+
+ unsigned char c;
+
+ if(xprefix > 0 && !bpref.oldwrapmode && bpref.indicator)
+ {
+ outs(MFDISP_WNAV_INDICATOR);
+ col++;
+ }
+
+ // first check quote
+ if(bpref.rawmode == MFDISP_RAW_NA)
+ {
+ if(dist > 1 &&
+ (*mf.dispe == ':' || *mf.dispe == '>') &&
+ *(mf.dispe+1) == ' ')
+ {
+ outs(ANSI_COLOR(36));
+ flResetColor = 1;
+ } else if (dist > 2 &&
+ (!strncmp((char*)mf.dispe, "※", 2) ||
+ !strncmp((char*)mf.dispe, "==>", 3)))
+ {
+ outs(ANSI_COLOR(32));
+ flResetColor = 1;
+ }
+ }
+
+ while(!breaknow && mf.dispe < mf.end && (c = *mf.dispe) != '\n')
+ {
+ if(inAnsi)
+ {
+ if (!ANSI_IN_ESCAPE(c))
+ inAnsi = 0;
+ /* whatever this is, output! */
+ mf.dispe ++;
+ switch(bpref.rawmode)
+ {
+ case MFDISP_RAW_NOANSI:
+ /* TODO
+ * col++ here may be buggy. */
+ if(col < t_columns)
+ {
+ /* we tried our best to determine */
+ if(xprefix > 0)
+ xprefix --;
+ else
+ {
+ outc(c);
+ col++;
+ }
+ }
+ if(!inAnsi)
+ outs(ANSI_RESET);
+ break;
+ case MFDISP_RAW_PLAIN:
+ break;
+
+ default:
+ if(ANSI_IN_MOVECMD(c))
+#ifdef PMORE_RESTRICT_ANSI_MOVEMENT
+ c = 's'; // "save cursor pos"
+#else
+ // some user cannot live without this.
+ // make them happy.
+ newline_default = newline = MFDISP_NEWLINE_MOVE;
+#endif
+ outc(c);
+ break;
+ }
+ continue;
+
+ } else {
+
+ if(c == ESC_CHR)
+ {
+ inAnsi = 1;
+ /* we can't output now because maybe
+ * ptt_prints wants to do something.
+ */
+ }
+ else if(sr.search_str && srlen < 0 && // support search
+#ifdef PMORE_USE_DBCS_WRAP
+ dbcs_incomplete == NULL &&
+#endif
+ mf.end - mf.dispe > sr.len &&
+ sr.cmpfunc((char*)mf.dispe, (char*)sr.search_str, sr.len) == 0)
+ {
+ outs(ANSI_COLOR(7));
+ srlen = sr.len-1;
+ flResetColor = 1;
+ }
+
+#ifdef PMORE_USE_PTT_PRINTS
+ /* special case to resolve dirty Ptt_prints */
+ if(inAnsi &&
+ mf.end - mf.dispe > 2 &&
+ *(mf.dispe+1) == '*')
+ {
+ int i;
+ char buf[64]; // make sure ptt_prints will not exceed
+
+ memset(buf, 0, sizeof(buf));
+ strncpy(buf, (char*)mf.dispe, 3); // ^[[*s
+ mf.dispe += 2;
+
+ if(bpref.rawmode)
+ buf[0] = '*';
+ else
+ {
+ if(strchr("sbmlpn", buf[2]) != NULL)
+ {
+ override_attr = ANSI_COLOR(1;37;41);
+ override_msg = " 注意: 此頁有控制碼,"
+ "原內容並不一定有您真實個人資訊";
+ }
+ Ptt_prints(buf, sizeof(buf), NO_RELOAD); // result in buf
+ }
+ i = strlen(buf);
+
+ if (col + i > maxcol)
+ i = maxcol - col;
+ if(i > 0)
+ {
+ buf[i] = 0;
+ col += i;
+ outs(buf);
+ }
+ inAnsi = 0;
+ } else
+#endif
+ if(inAnsi)
+ {
+ switch(bpref.rawmode)
+ {
+ case MFDISP_RAW_NOANSI:
+ /* TODO
+ * col++ here may be buggy. */
+ if(col < t_columns)
+ {
+ /* we tried our best to determine */
+ if(xprefix > 0)
+ xprefix --;
+ else
+ {
+ outs(ANSI_COLOR(1) "*");
+ col++;
+ }
+ }
+ break;
+ case MFDISP_RAW_PLAIN:
+ break;
+ default:
+ outc(ESC_CHR);
+ break;
+ }
+ } else {
+ int canOutput = 0;
+ /* if col > maxcol,
+ * because we have the space for
+ * "indicators" (one byte),
+ * so we can tolerate one more byte.
+ */
+ if(col <= maxcol) // normal case
+ canOutput = 1;
+ else if (bpref.oldwrapmode && // oldwrapmode
+ col < t_columns)
+ {
+ canOutput = 1;
+ newline = MFDISP_NEWLINE_MOVE;
+ } else {
+ int off = 0;
+ // put more efforts to determine
+ // if we can use indicator space
+ // determine real offset between \n
+ if(predicted_linewidth < 0)
+ predicted_linewidth = col + 1 +
+ MFDISP_PREDICT_LINEWIDTH(mf.dispe+1);
+ off = predicted_linewidth - (col + 1);
+
+ if (col + off <= (maxcol+1))
+ {
+ canOutput = 1; // indicator space
+ }
+#ifdef PMORE_TRADITIONAL_FULLCOL
+ else if (col + off < t_columns)
+ {
+ canOutput = 1;
+ newline = MFDISP_NEWLINE_MOVE;
+ }
+#endif
+ }
+
+ if(canOutput)
+ {
+ /* the real place to output text
+ */
+#ifdef PMORE_USE_DBCS_WRAP
+ if(mf.xpos > 0 && dbcs_incomplete && col < 2)
+ {
+ /* col = 0 or 1 only */
+ if(col == 0) /* no indicators */
+ c = ' ';
+ else if(!bpref.oldwrapmode && bpref.indicator)
+ c = ' ';
+ }
+
+ if (dbcs_incomplete)
+ dbcs_incomplete = NULL;
+ else if(PMORE_DBCS_LEADING(c))
+ dbcs_incomplete = mf.dispe;
+#endif
+ if(xprefix > 0)
+ xprefix --;
+ else
+ {
+ outc(c);
+ col++;
+ }
+
+ if (srlen == 0)
+ outs(ANSI_RESET);
+ if(srlen >= 0)
+ srlen --;
+ }
+ else
+ /* wrap modes */
+ if(mf.xpos > 0 || bpref.wrapmode == MFDISP_WRAP_TRUNCATE)
+ {
+ breaknow = 1;
+ mf.trunclines ++;
+ MFDISP_SKIPCURLINE();
+ wrapping = 0;
+ }
+ else if (bpref.wrapmode == MFDISP_WRAP_WRAP)
+ {
+ breaknow = 1;
+ wrapping = 1;
+ mf.wraplines ++;
+#ifdef PMORE_USE_DBCS_WRAP
+ if(dbcs_incomplete)
+ {
+ mf.dispe = dbcs_incomplete;
+ dbcs_incomplete = NULL;
+ /* to be more dbcs safe,
+ * use the followings to
+ * erase printed character.
+ */
+ if(col > 0) {
+ /* TODO BUG BUGGY
+ * using move is maybe actually non-sense
+ * because BBS terminal system cannot
+ * display this when ANSI escapes were used
+ * in same line. However, on most
+ * situation this works.
+ * So we used an alternative, forced ANSI
+ * move command.
+ */
+ // move(lines, col-1);
+ char ansicmd[16];
+ sprintf(ansicmd, ANSI_MOVETO(%d,%d),
+ lines+1, col-1+1);
+ /* to preven ANSI ESCAPE being tranlated as
+ * DBCS trailing byte. */
+ outc(' ');
+ /* move back one column */
+ outs(ansicmd);
+ /* erase it (previous leading byte)*/
+ outc(' ');
+ /* go to correct position */
+ outs(ansicmd);
+ }
+ }
+#endif
+ }
+ }
+ }
+ if(!breaknow)
+ mf.dispe ++;
+ }
+ if(flResetColor)
+ outs(ANSI_RESET);
+
+ /* "wrapping" should be only in normal text section.
+ * We don't support wrap within scrolling,
+ * so if we have to wrap, invalidate all lines.
+ */
+ if(breaknow)
+ {
+ if(wrapping)
+ MFDISP_FORCEDIRTY2BOT();
+
+ if(!bpref.oldwrapmode && bpref.indicator && col < t_columns)
+ {
+ if(wrapping)
+ outs(MFDISP_WRAP_INDICATOR);
+ else
+ outs(MFDISP_TRUNC_INDICATOR);
+ } else {
+ outs(ANSI_RESET);
+ }
+ }
+ else
+ wrapping = 0;
+ }
+
+ if(mf.dispe < mf.end && *mf.dispe == '\n')
+ mf.dispe ++;
+ // else, we're in wrap mode.
+
+ switch(newline)
+ {
+ case MFDISP_NEWLINE_SKIP:
+ break;
+ case MFDISP_NEWLINE_CLEAR:
+ FORCE_CLRTOEOL();
+ outc('\n');
+ break;
+ case MFDISP_NEWLINE_MOVE:
+ move(lines+1, 0);
+ break;
+ }
+ lines ++;
+ }
+ /*
+ * we've displayed the file.
+ * but if we got wrapped lines in last page,
+ * mf.maxdisps may be required to be larger.
+ */
+ if(mf.disps == mf.maxdisps && mf.dispe < mf.end)
+ {
+ /*
+ * never mind if that's caused by seperator
+ * however this code is rarely used now.
+ * only if no preload file.
+ */
+ if (bpref.seperator & MFDISP_SEP_WRAP &&
+ mf.wraplines == 1 &&
+ mf.lineno < fh.lines)
+ {
+ /*
+ * o.k, now we know maxline should be one line forward.
+ */
+ mf_determinemaxdisps(+1, 1);
+ } else
+ {
+ /* not caused by seperator?
+ * ok, then it's by wrapped lines.
+ *
+ * old flavor: go bottom:
+ * mf_determinemaxdisps(0)
+ * however we have "update" method now,
+ * so we can achieve more user friendly behavior.
+ */
+ mf_determinemaxdisps(+mf.wraplines, 1);
+ }
+ }
+ mf.oldlineno = mf.lineno;
+}
+
+/* --------------------- MAIN PROCEDURE ------------------------- */
+
+static const char * const pmore_help[] = {
+ "\0閱\讀文章功\能鍵使用說明",
+ "\01游標移動功\能鍵",
+ "(k/↑) (j/↓/Enter) 上捲/下捲一行",
+ "(^B)(PgUp)(BackSpace) 上捲一頁",
+ "(^F)(PgDn)(Space)(→) 下捲一頁",
+ "(,/</S-TAB)(./>/TAB) 左/右捲動",
+ "(0/g/Home) ($/G/End) 檔案開頭/結尾",
+ "(;/:) 跳至某行/某頁",
+ "數字鍵 1-9 跳至輸入的頁數或行號",
+ "\01其他功\能鍵",
+ "(/" ANSI_COLOR(1;30) "/" ANSI_RESET
+ "s) 搜尋字串",
+ "(n/N) 重複正/反向搜尋",
+ "(Ctrl-T) 存入暫存檔",
+ "(f/b) 跳至下/上篇",
+ "(a/A) 跳至同一作者下/上篇",
+ "(t/[-/]+) 主題式閱\讀:循序/前/後篇",
+ "(\\/|) 切換顯示原始內容", // this IS already aligned!
+ "(w/W/l) 切換自動斷行/斷行符號/分隔線顯示方式",
+ "(p/z/o) 播放動畫/棋局打譜/切換傳統模式(狀態列與斷行方式)",
+ "(q/←) (h/H/?/F1) 結束/本說明畫面",
+#ifdef DEBUG
+ "(d) 切換除錯(debug)模式",
+#endif
+ /* You don't have to delete this copyright line.
+ * It will be located in bottom of screen and overrided by
+ * status line. Only user with big terminals will see this :)
+ */
+ "\01本系統使用 piaip 的新式瀏覽程式: pmore, piaip's more",
+ NULL
+};
+/*
+ * pmore utility macros
+ */
+MFPROTO void
+PMORE_UINAV_FORWARDPAGE()
+{
+ /* Usually, a forward is just mf_forward(MFNAV_PAGE);
+ * but because of wrapped lines...
+ * This function is used when user tries to nagivate
+ * with page request.
+ * If you want to a real page forward, don't use this.
+ * That's why we have this special function.
+ */
+ int i = mf.dispedlines - 1;
+
+ if(mf_viewedAll())
+ return;
+
+ if(i < 1)
+ i = 1;
+ mf_forward(i);
+}
+
+MFPROTO void
+PMORE_UINAV_FORWARDLINE()
+{
+ if(mf_viewedAll())
+ return;
+ mf_forward(1);
+}
+
+#define REENTRANT_RESTORE() { mf = bkmf; fh = bkfh; }
+
+/*
+ * piaip's more, a replacement for old more
+ */
+int
+pmore(char *fpath, int promptend)
+{
+ int flExit = 0, retval = 0;
+ int ch = 0;
+ int invalidate = 1;
+
+ /* simple re-entrant hack
+ * I don't want to write pointers everywhere,
+ * and pmore should be simple enough (inside itself)
+ * so we can do so.
+ */
+
+ MmappedFile bkmf;
+ MF_PrettyFormattedHeader bkfh;
+
+#ifdef PMORE_USE_ASCII_MOVIE
+ RESET_MOVIE();
+#endif
+
+ bkmf = mf; /* simple re-entrant hack */
+ bkfh = fh;
+ RESETMF();
+ RESETFH();
+
+ override_msg = NULL; /* elimiate pending errors */
+
+ STATINC(STAT_MORE);
+ if(!mf_attach(fpath))
+ {
+ REENTRANT_RESTORE();
+ return -1;
+ }
+
+ clear();
+ while(!flExit)
+ {
+ if(invalidate)
+ {
+ mf_display();
+ invalidate = 0;
+ }
+
+ /* in current implementation,
+ * we want to invalidate for each keypress.
+ */
+ invalidate = 1;
+
+#ifdef PMORE_TRADITIONAL_PROMPTEND
+
+ if(promptend == NA)
+ {
+#ifdef PMORE_USE_ASCII_MOVIE
+ if(mfmovie.mode == MFDISP_MOVIE_DETECTED)
+ {
+ /* quick auto play */
+ mfmovie.mode = MFDISP_MOVIE_YES;
+ RESET_MOVIE();
+ mfmovie.mode = MFDISP_MOVIE_PLAYING;
+ mf_determinemaxdisps(0, 0); // display until last line
+ mf_movieNextFrame();
+ MFDISP_DIRTY();
+ continue;
+ } else if (mfmovie.mode != MFDISP_MOVIE_PLAYING)
+#endif
+ break;
+ }
+#else
+ if(promptend == NA && mf_viewedAll())
+ break;
+#endif
+ move(b_lines, 0);
+ // clrtoeol(); // this shall be done in mf_display to speed up.
+
+#ifdef PMORE_USE_ASCII_MOVIE
+ switch (mfmovie.mode)
+ {
+ case MFDISP_MOVIE_UNKNOWN:
+ mfmovie.mode = MFDISP_MOVIE_NO;
+ break;
+
+ case MFDISP_MOVIE_DETECTED:
+ mfmovie.mode = MFDISP_MOVIE_YES;
+ {
+ // query if user wants to play movie.
+
+ int w = t_columns-1;
+ const char *s =
+ " 這份文件是可播放的文字動畫,要開始播放嗎? [Y/n]";
+ outs(ANSI_RESET ANSI_COLOR(1;33;44));
+ w -= strlen(s); outs(s);
+ while(w-- > 0) outc(' '); outs(ANSI_RESET);
+ w = tolower(igetch());
+ if(w != 'n' &&
+ w != KEY_UP && w != KEY_LEFT &&
+ w != 'q')
+ {
+ RESET_MOVIE();
+ mfmovie.mode = MFDISP_MOVIE_PLAYING;
+ mf_determinemaxdisps(0, 0); // display until last line
+ mf_movieNextFrame();
+ MFDISP_DIRTY();
+ continue;
+ }
+ /* else, we have to clean up. */
+ move(b_lines, 0);
+ clrtoeol();
+ }
+ break;
+
+ case MFDISP_MOVIE_PLAYING_OLD:
+ case MFDISP_MOVIE_PLAYING:
+ {
+ int w = t_columns - 1;
+ const char *s = " >>> 播放動畫中... 可按任意鍵停止";
+
+ outs(ANSI_RESET ANSI_COLOR(1;30;47));
+ w -= strlen(s); outs(s);
+ while(w-- > 0) outc(' '); outs(ANSI_RESET);
+ }
+
+ if(mf_movieSyncFrame())
+ {
+ /* user did not hit anything.
+ * play next frame.
+ */
+ if(mfmovie.mode == MFDISP_MOVIE_PLAYING)
+ {
+ if(!mf_movieNextFrame())
+ {
+ mfmovie.mode = MFDISP_MOVIE_YES; // nothing more
+ mf_determinemaxdisps(MFNAV_PAGE, 0);
+ mf_forward(0);
+
+ if(promptend == NA)
+ {
+ /* if we played to end,
+ * no need to prevent pressanykey().
+ */
+ flExit = 1, retval = 0;
+ }
+ }
+ }
+ else if(mfmovie.mode == MFDISP_MOVIE_PLAYING_OLD)
+ {
+ if(mf_viewedAll())
+ {
+ mfmovie.mode = MFDISP_MOVIE_NO;
+ mf_determinemaxdisps(MFNAV_PAGE, 0);
+ mf_forward(0);
+ }
+ else
+ {
+ if(!mfmovie.compat24)
+ PMORE_UINAV_FORWARDPAGE();
+ else
+ mf_forward(22);
+ }
+ }
+ } else {
+ igetch();
+
+ /* TODO simple navigation here? */
+
+ /* stop playing */
+ if(mfmovie.mode == MFDISP_MOVIE_PLAYING)
+ {
+ mfmovie.mode = MFDISP_MOVIE_YES;
+ if(promptend == NA)
+ {
+ flExit = 1, retval = READ_NEXT;
+ }
+ }
+ else if(mfmovie.mode == MFDISP_MOVIE_PLAYING_OLD)
+ mfmovie.mode = MFDISP_MOVIE_NO;
+
+ mf_determinemaxdisps(MFNAV_PAGE, 0);
+ mf_forward(0);
+ }
+ continue;
+ }
+#endif
+
+ /* PRINT BOTTOM STATUS BAR */
+#ifdef DEBUG
+ if(debug)
+ {
+ /* in debug mode don't print ANSI codes
+ * because themselves are buggy.
+ */
+ prints("L#%ld(w%ld,lp%ld) pmt=%d Dsp:%08X/%08X/%08X, "
+ "F:%08X/%08X(%d) tScr(%dx%d)",
+ mf.lineno, mf.wraplines, mf.lastpagelines,
+ promptend,
+ (unsigned int)mf.disps,
+ (unsigned int)mf.maxdisps,
+ (unsigned int)mf.dispe,
+ (unsigned int)mf.start, (unsigned int)mf.end,
+ (int)mf.len,
+ t_columns,
+ t_lines
+ );
+ }
+ else
+#endif
+ {
+ char *printcolor;
+
+ char buf[256]; // orz
+ int prefixlen = 0;
+ int barlen = 0;
+ int postfix1len = 0, postfix2len = 0;
+
+ int progress =
+ (int)((unsigned long)(mf.dispe-mf.start) * 100 / mf.len);
+ /*
+ * page determination is hard.
+ * should we use starting line, or finishing line?
+ */
+ int nowpage =
+ (int)((mf.lineno + mf.dispedlines/2) / MFNAV_PAGE)+1;
+ int allpages = -1; /* unknown yet */
+ if (mf.maxlinenoS >= 0)
+ {
+ allpages =
+ (int)((mf.maxlinenoS + mf.lastpagelines -
+ ((bpref.seperator & MFDISP_SEP_WRAP) &&
+ (fh.lines >= 0) ? 0:1)) / MFNAV_PAGE)+1;
+ if (mf.lineno >= mf.maxlinenoS || nowpage > allpages)
+ nowpage = allpages;
+ /*
+ nowpage =
+ (int)((mf.lineno + mf.dispedlines-2) / MFNAV_PAGE)+1 ;
+ */
+ }
+ /* why -2 and -1?
+ * because we want to determine by nav_page,
+ * and mf.dispedlines is based on disp_page (nav_page+1)
+ * mf.lastpagelines is based on nav_page
+ */
+
+ if(mf_viewedAll())
+ printcolor = ANSI_COLOR(37;44);
+ else if (mf_viewedNone())
+ printcolor = ANSI_COLOR(33;45);
+ else
+ printcolor = ANSI_COLOR(34;46);
+
+ outs(ANSI_RESET);
+ outs(printcolor);
+
+ if(bpref.oldstatusbar)
+ {
+
+ prints(" 瀏覽 P.%d(%d%%) %s %-30.30s%s",
+ nowpage,
+ progress,
+ ANSI_COLOR(31;47),
+ "(h)"
+ ANSI_COLOR(30) "求助 "
+ ANSI_COLOR(31) "→↓[PgUp][",
+ "PgDn][Home][End]"
+ ANSI_COLOR(30) "游標移動 "
+ ANSI_COLOR(31) "←[q]"
+ ANSI_COLOR(30) "結束 ");
+
+ } else {
+
+ if(allpages >= 0)
+ sprintf(buf,
+ " 瀏覽 第 %1d/%1d 頁 (%3d%%) ",
+ nowpage,
+ allpages,
+ progress
+ );
+ else
+ sprintf(buf,
+ " 瀏覽 第 %1d 頁 (%3d%%) ",
+ nowpage,
+ progress
+ );
+ outs(buf); prefixlen += strlen(buf);
+
+ outs(ANSI_COLOR(1;30;47));
+
+ if(override_msg)
+ {
+ buf[0] = 0;
+ if(override_attr) outs(override_attr);
+ snprintf(buf, sizeof(buf), override_msg);
+ override_msg = NULL;
+ }
+ else
+ if(mf.xpos > 0)
+ {
+ snprintf(buf, sizeof(buf),
+ " 顯示範圍: %d~%d 欄位, %02d~%02d 行",
+ (int)mf.xpos+1,
+ (int)(mf.xpos + t_columns-(mf.trunclines ? 2 : 1)),
+ (int)(mf.lineno + 1),
+ (int)(mf.lineno + mf.dispedlines)
+ );
+ } else {
+ snprintf(buf, sizeof(buf),
+ " 目前顯示: 第 %02d~%02d 行",
+ (int)(mf.lineno + 1),
+ (int)(mf.lineno + mf.dispedlines)
+ );
+ }
+
+ outs(buf); prefixlen += strlen(buf);
+
+ postfix1len = 12; // check msg below
+ postfix2len = 10;
+ if(mf_viewedAll()) postfix1len = 15;
+
+ if (prefixlen + postfix1len + postfix2len + 1 > t_columns)
+ {
+ postfix1len = 0;
+ if (prefixlen + postfix1len + postfix2len + 1 > t_columns)
+ postfix2len = 0;
+ }
+ barlen = t_columns - 1 - postfix1len - postfix2len - prefixlen;
+
+ while(barlen-- > 0)
+ outc(' ');
+
+ if(postfix1len > 0)
+ outs(
+ mf_viewedAll() ?
+ ANSI_COLOR(0;31;47)"(y)" ANSI_COLOR(30) "回信"
+ ANSI_COLOR(31) "(X)" ANSI_COLOR(30) "推文 "
+ :
+ ANSI_COLOR(0;31;47) "(h)"
+ ANSI_COLOR(30) "按鍵說明 "
+ );
+ if(postfix2len > 0)
+ outs(
+ ANSI_COLOR(0;31;47) "←[q]"
+ ANSI_COLOR(30) "離開 "
+ );
+ }
+ outs(ANSI_RESET);
+ FORCE_CLRTOEOL();
+ }
+
+ /* igetch() will do refresh(); */
+ ch = igetch();
+ switch (ch) {
+ /* ------------------ EXITING KEYS ------------------ */
+ case 'r': case 'R':
+ case 'Y': case 'y':
+ flExit = 1, retval = 999;
+ break;
+ case 'X':
+ flExit = 1, retval = 998;
+ break;
+ case 'A':
+ flExit = 1, retval = AUTHOR_PREV;
+ break;
+ case 'a':
+ flExit = 1, retval = AUTHOR_NEXT;
+ break;
+ case 'F': case 'f':
+ flExit = 1, retval = READ_NEXT;
+ break;
+ case 'B': case 'b':
+ flExit = 1, retval = READ_PREV;
+ break;
+ case KEY_LEFT:
+ /* because we have other keys to do so,
+ * disable now.
+ */
+ /*
+ if(mf.xpos > 0)
+ {
+ mf.xpos --;
+ break;
+ }
+ */
+ flExit = 1, retval = FULLUPDATE;
+ case 'q':
+ flExit = 1, retval = FULLUPDATE;
+ break;
+
+ /* from Kaede, thread reading */
+ case ']':
+ case '+':
+ flExit = 1, retval = RELATE_NEXT;
+ break;
+ case '[':
+ case '-':
+ flExit = 1, retval = RELATE_PREV;
+ break;
+ case '=':
+ flExit = 1, retval = RELATE_FIRST;
+ break;
+ /* ------------------ NAVIGATION KEYS ------------------ */
+ /* Simple Navigation */
+ case 'k': case 'K':
+ mf_backward(1);
+ break;
+ case 'j': case 'J':
+ PMORE_UINAV_FORWARDLINE();
+ break;
+
+ case Ctrl('F'):
+ case KEY_PGDN:
+ PMORE_UINAV_FORWARDPAGE();
+ break;
+ case Ctrl('B'):
+ case KEY_PGUP:
+ mf_backward(MFNAV_PAGE);
+ break;
+
+ case '0':
+ case 'g':
+ case KEY_HOME:
+ mf_goTop();
+ break;
+ case '$':
+ case 'G':
+ case KEY_END:
+ mf_goBottom();
+
+#ifdef PMORE_ACCURATE_WRAPEND
+ /* allright. in design of pmore,
+ * it's possible that when user navigates to file end,
+ * a wrapped line made nav not 100%.
+ */
+ mf_display();
+ invalidate = 0;
+
+ if(!mf_viewedAll())
+ {
+ /* one more try. */
+ mf_goBottom();
+ invalidate = 1;
+ }
+#endif
+ break;
+
+ /* Compound Navigation */
+ case '.':
+ if(mf.xpos == 0)
+ mf.xpos ++;
+ mf.xpos ++;
+ break;
+ case ',':
+ if(mf.xpos > 0)
+ mf.xpos --;
+ break;
+ case '\t':
+ case '>':
+ //if(mf.xpos == 0 || mf.trunclines)
+ mf.xpos = (mf.xpos/8+1)*8;
+ break;
+ /* acronym form shift-tab, ^[[Z */
+ /* however some terminals does not send that. */
+ case KEY_STAB:
+ case '<':
+ mf.xpos = (mf.xpos/8-1)*8;
+ if(mf.xpos < 0) mf.xpos = 0;
+ break;
+ case '\r':
+ case '\n':
+ case KEY_DOWN:
+ if (mf_viewedAll() ||
+ (promptend == 2 && (ch == '\r' || ch == '\n')))
+ flExit = 1, retval = READ_NEXT;
+ else
+ PMORE_UINAV_FORWARDLINE();
+ break;
+
+ case ' ':
+ if (mf_viewedAll())
+ flExit = 1, retval = READ_NEXT;
+ else
+ PMORE_UINAV_FORWARDPAGE();
+ break;
+ case KEY_RIGHT:
+ if(mf_viewedAll())
+ promptend = 0, flExit = 1, retval = 0;
+ else
+ {
+ /* if mf.xpos > 0, widenav mode. */
+ /* because we have other keys to do so,
+ * disable it now.
+ */
+ /*
+ if(mf.trunclines > 0)
+ {
+ if(mf.xpos == 0)
+ mf.xpos++;
+ mf.xpos++;
+ }
+ else if (mf.xpos == 0)
+ */
+ PMORE_UINAV_FORWARDPAGE();
+ }
+ break;
+
+ case KEY_UP:
+ if(mf_viewedNone())
+ flExit = 1, retval = READ_PREV;
+ else
+ mf_backward(1);
+ break;
+ case Ctrl('H'):
+ if(mf_viewedNone())
+ flExit = 1, retval = READ_PREV;
+ else
+ mf_backward(MFNAV_PAGE);
+ break;
+
+ case 't':
+ if (mf_viewedAll())
+ flExit = 1, retval = RELATE_NEXT;
+ else
+ PMORE_UINAV_FORWARDPAGE();
+ break;
+ /* ------------------ SEARCH KEYS ------------------ */
+ case 's':
+ case '/':
+ {
+ char sbuf[81] = "";
+ char ans[4] = "n";
+
+ if(sr.search_str) {
+ free(sr.search_str);
+ sr.search_str = NULL;
+ }
+
+ getdata(b_lines - 1, 0, "[搜尋]關鍵字:", sbuf,
+ 40, DOECHO);
+
+ if (sbuf[0]) {
+ if (getdata(b_lines - 1, 0, "區分大小寫(Y/N/Q)? [N] ",
+ ans, sizeof(ans), LCECHO) && *ans == 'y')
+ sr.cmpfunc = strncmp;
+ else if (*ans == 'q')
+ sbuf[0] = 0;
+ else
+ sr.cmpfunc = strncasecmp;
+ }
+ sr.len = strlen(sbuf);
+ if(sr.len) sr.search_str = (unsigned char*)strdup(sbuf);
+ mf_search(MFSEARCH_FORWARD);
+ MFDISP_DIRTY();
+ }
+ break;
+ case 'n':
+ mf_search(MFSEARCH_FORWARD);
+ break;
+ case 'N':
+ mf_search(MFSEARCH_BACKWARD);
+ break;
+ /* ------------------ SPECIAL KEYS ------------------ */
+ case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9':
+ case ';': case ':':
+ {
+ char buf[16] = "";
+ int i = 0;
+ int pageMode = (ch != ':');
+ if (ch >= '1' && ch <= '9')
+ buf[0] = ch, buf[1] = 0;
+
+ pmore_clrtoeol(b_lines-1, 0);
+ getdata_buf(b_lines-1, 0,
+ (pageMode ?
+ "跳至此頁(若要改指定行數請在結尾加.): " :
+ "跳至此行: "),
+ buf, 8, DOECHO);
+ if(buf[0]) {
+ i = atoi(buf);
+ if(buf[strlen(buf)-1] == '.')
+ pageMode = 0;
+ if(i-- > 0)
+ mf_goto(i * (pageMode ? MFNAV_PAGE : 1));
+ }
+ MFDISP_DIRTY();
+ }
+ break;
+
+ case Ctrl('T'):
+ {
+ char buf[10];
+ getdata(b_lines - 1, 0, "把這篇文章收入到暫存檔?[y/N] ",
+ buf, 4, LCECHO);
+ if (buf[0] == 'y') {
+ setuserfile(buf, ask_tmpbuf(b_lines - 1));
+ Copy(fpath, buf);
+ }
+ MFDISP_DIRTY();
+ }
+ break;
+
+ case 'h': case 'H': case KEY_F1:
+ case '?':
+ // help
+ show_help(pmore_help);
+ MFDISP_DIRTY();
+ break;
+
+ case 'E':
+ // admin edit any files other than ve help file
+ // and posts in Security board
+ if (HasUserPerm(PERM_SYSOP) && strcmp(fpath, "etc/ve.hlp") &&
+ strcmp(currboard, "Security")){
+ mf_detach();
+ vedit(fpath, NA, NULL);
+ REENTRANT_RESTORE();
+ return 0;
+ }
+ break;
+ case 'w':
+ switch(bpref.wrapmode)
+ {
+ case MFDISP_WRAP_WRAP:
+ bpref.wrapmode = MFDISP_WRAP_TRUNCATE;
+ // override_attr = ANSI_COLOR(31);
+ // override_msg = " 已設定為截行模式(不自動斷行)";
+ vmsg("斷行方式已設定為截行模式(不自動斷行)");
+ break;
+ case MFDISP_WRAP_TRUNCATE:
+ bpref.wrapmode = MFDISP_WRAP_WRAP;
+ // override_attr = ANSI_COLOR(34);
+ // override_msg = " 已設定為自動斷行模式";
+ vmsg("斷行方式已設定為自動斷行(預設)");
+ break;
+ }
+ MFDISP_DIRTY();
+ break;
+ case 'W':
+ bpref.indicator = !bpref.indicator;
+ if(bpref.indicator)
+ // override_attr = ANSI_COLOR(34);
+ // override_msg = " 顯示斷行符號";
+ vmsg("設定為斷行時顯示斷行符號(預設)");
+ else
+ // override_attr = ANSI_COLOR(31);
+ // override_msg = " 不再顯示斷行符號";
+ vmsg("設定為斷行時不顯示斷行符號");
+ MFDISP_DIRTY();
+ break;
+ case 'o':
+ bpref.oldwrapmode = !bpref.oldwrapmode;
+ bpref.oldstatusbar = !bpref.oldstatusbar;
+ MFDISP_DIRTY();
+ break;
+ case 'l':
+ switch(bpref.seperator)
+ {
+ case MFDISP_SEP_OLD:
+ bpref.seperator = MFDISP_SEP_LINE;
+ // override_attr = ANSI_COLOR(31);
+ // override_msg = " 設定為單行分隔線";
+ vmsg("分隔線設定為單行分隔線");
+ break;
+ case MFDISP_SEP_LINE:
+ bpref.seperator = 0;
+ // override_attr = ANSI_COLOR(31);
+ // override_msg = " 設定為無分隔線";
+ vmsg("設定為無分隔線");
+ break;
+ default:
+ bpref.seperator = MFDISP_SEP_OLD;
+ // override_attr = ANSI_COLOR(34);
+ // override_msg = " 傳統分隔線加空行";
+ vmsg("分隔線設定為傳統分隔線加空行(預設)");
+ break;
+ }
+ MFDISP_DIRTY();
+ break;
+ case '\\':
+ case '|':
+ if(ch == '|')
+ bpref.rawmode += MFDISP_RAW_MODES-1;
+ else
+ {
+ /* '\\' */
+
+ /* should we do first time prompt checking here,
+ * or remove hotkey '\\'?
+ */
+ static unsigned char first_prompt = 1;
+ if(first_prompt)
+ {
+ char ans[3] = "";
+ getdata(b_lines - 1, 0,
+ "確定改變預設內容顯示方式嗎? "
+ "(若不懂請直接按 Enter)[y/N]"
+ ,
+ ans, 3, LCECHO);
+ if(ans[0] != 'y')
+ break;
+ first_prompt = 0;
+ }
+ bpref.rawmode ++;
+ }
+ bpref.rawmode %= MFDISP_RAW_MODES;
+ switch(bpref.rawmode)
+ {
+ case MFDISP_RAW_NA:
+ // override_attr = ANSI_COLOR(34);
+ // override_msg = " 顯示預設格式化內容";
+ vmsg("顯示方式設定為預設格式化內容");
+ break;
+ /*
+ case MFDISP_RAW_NOFMT:
+ // override_attr = ANSI_COLOR(31);
+ // override_msg = " 省略自動格式化";
+ break;
+ */
+ case MFDISP_RAW_NOANSI:
+ // override_attr = ANSI_COLOR(33);
+ // override_msg = " 顯示原始 ANSI 控制碼";
+ vmsg("顯示方式設定為顯示原始 ANSI 控制碼");
+ break;
+ case MFDISP_RAW_PLAIN:
+ // override_attr = ANSI_COLOR(37);
+ // override_msg = " 顯示純文字";
+ vmsg("顯示方式設定為純文字");
+ break;
+ }
+ MFDISP_DIRTY();
+ break;
+#ifdef PMORE_USE_ASCII_MOVIE
+ case 'p':
+ /* play ascii movie again
+ */
+ if(mfmovie.mode == MFDISP_MOVIE_YES)
+ {
+ RESET_MOVIE();
+ mfmovie.mode = MFDISP_MOVIE_PLAYING;
+ mf_determinemaxdisps(0, 0); // display until last line
+ /* it is said that it's better not to go top. */
+ // mf_goTop();
+ mf_movieNextFrame();
+ MFDISP_DIRTY();
+ }
+ else if (mfmovie.mode == MFDISP_MOVIE_NO)
+ {
+ static char buf[10]="1";
+ //move(b_lines-1, 0);
+
+ /*
+ * TODO scan current page to confirm if this is a new style movie
+ */
+ pmore_clrtoeol(b_lines-1, 0);
+ getdata_buf(b_lines - 1, 0,
+ "這可能是傳統動畫檔, "
+ "若要直接播放請輸入速度(秒): "
+ ,
+ buf, 8, LCECHO);
+ if(buf[0])
+ {
+ float nf = 0;
+ sscanf(buf, "%f", &nf);
+ RESET_MOVIE();
+
+ mfmovie.mode = MFDISP_MOVIE_PLAYING_OLD;
+ float2tv(nf, &mfmovie.frameclk);
+ mfmovie.compat24 = 0;
+ /* are we really going to start? check termsize! */
+ if (t_lines != 24)
+ {
+ char ans[4];
+ pmore_clrtoeol(b_lines-1, 0);
+ getdata(b_lines - 1, 0,
+ "傳統動畫是以 24 行為單位設計的, "
+ "要模擬 24 行嗎? (否則會用現在的行數)[Yn] "
+ , ans, 3, LCECHO);
+ if(ans[0] == 'n')
+ mfmovie.compat24 = 0;
+ else
+ mfmovie.compat24 = 1;
+ }
+ mf_determinemaxdisps(0, 0); // display until last line
+ MFDISP_DIRTY();
+ }
+ }
+ break;
+#endif
+
+#ifdef DEBUG
+ case 'd':
+ debug = !debug;
+ MFDISP_DIRTY();
+ break;
+#endif
+
+ case 'z':
+ ChessReplayGame(fpath);
+ break;
+ }
+ /* DO NOT DO ANYTHING HERE. NOT SAFE RIGHT NOW. */
+ }
+
+ mf_detach();
+ if (retval == 0 && promptend) {
+ pressanykey();
+ clear();
+ } else
+ outs(reset_color);
+
+ REENTRANT_RESTORE();
+ return retval;
+}
+
+// ---------------------------------------------------- Extra modules
+
+#ifdef PMORE_USE_ASCII_MOVIE
+void
+float2tv(float f, struct timeval *ptv)
+{
+ if(f < MOVIE_MIN_FRAMECLK)
+ f = MOVIE_MIN_FRAMECLK;
+ ptv->tv_sec = (long) f;
+ ptv->tv_usec = (f - (long)f) * MOVIE_SECOND_U;
+}
+
+/*
+ * maybe you can use add_io or you have other APIs in
+ * your I/O system, but we'll do it here.
+ * override if you have better methods.
+ */
+int
+pmore_wait_input(struct timeval *ptv)
+{
+ int sel = 0;
+ fd_set readfds;
+
+ if(num_in_buf() > 0)
+ return 1;
+
+ FD_ZERO(&readfds);
+ FD_SET(0, &readfds);
+
+ refresh();
+
+#ifdef STATINC
+ STATINC(STAT_SYSSELECT);
+#endif
+
+ do {
+ if(num_in_buf() > 0) // for EINTR
+ return 1;
+
+ sel = select(1, &readfds, NULL, NULL, ptv);
+ } while (sel < 0 && errno == EINTR);
+ /* EINTR, interrupted. I don't care! */
+
+ if(sel == 0)
+ return 0;
+
+ return 1;
+}
+
+MFPROTO unsigned char *
+mf_movieFrameHeader(unsigned char *p)
+{
+ if(mf.end - p < 3)
+ return NULL;
+
+ if(*p == 12) // ^L
+ return p+1;
+ if( *p == '^' &&
+ *(p+1) == 'L')
+ return p+2;
+ return NULL;
+}
+
+/*
+ * return meaning:
+ * I've got synchronized.
+ * If no (user breaks), return 0
+ */
+int mf_movieSyncFrame()
+{
+ if (mfmovie.synctime.tv_sec > 0)
+ {
+ /* synchronize world timeline model */
+ struct timeval dv;
+ gettimeofday(&dv, NULL);
+ dv.tv_sec = mfmovie.synctime.tv_sec - dv.tv_sec;
+ if(dv.tv_sec < 0)
+ return 1;
+ dv.tv_usec = mfmovie.synctime.tv_usec - dv.tv_usec;
+ if(dv.tv_usec < 0) {
+ dv.tv_sec --;
+ dv.tv_usec += MOVIE_SECOND_U;
+ }
+ if(dv.tv_sec < 0)
+ return 1;
+ return !pmore_wait_input(&dv);
+ } else {
+ /* synchronize each frame clock model */
+ /* because Linux will change the timeval passed to select,
+ * let's use a temp value here.
+ */
+ struct timeval dv = mfmovie.frameclk;
+ return !pmore_wait_input(&dv);
+ }
+}
+
+int
+mf_movieNextFrame()
+{
+ do
+ {
+ unsigned char *p = mf_movieFrameHeader(mf.disps);
+ if(p)
+ {
+ char buf[16];
+ int cbuf = 0;
+ float nf = 0;
+
+ /* process leading */
+ if (*p == 'S') {
+ gettimeofday(&mfmovie.synctime, NULL);
+ p++;
+ }
+
+ while (p < mf.end &&
+ ((*p >= '0' && *p <= '9') || *p == '.'))
+ buf[cbuf++] = *p++;
+
+ buf[cbuf] = 0;
+ if(cbuf)
+ {
+ sscanf(buf, "%f", &nf);
+ float2tv(nf, &mfmovie.frameclk);
+ }
+
+ if(mfmovie.synctime.tv_sec > 0)
+ {
+ mfmovie.synctime.tv_usec += mfmovie.frameclk.tv_usec;
+ mfmovie.synctime.tv_sec += mfmovie.frameclk.tv_sec;
+ mfmovie.synctime.tv_sec += mfmovie.synctime.tv_usec / MOVIE_SECOND_U;
+ mfmovie.synctime.tv_usec %= MOVIE_SECOND_U;
+ }
+
+ mf_forward(1);
+ return 1;
+ }
+ } while(mf_forward(1) > 0);
+
+ return 0;
+}
+#endif
+
+/* vim:sw=4:ts=8
+ */
diff --git a/pttbbs/mbbsd/random.c b/pttbbs/mbbsd/random.c
new file mode 100644
index 00000000..dd369c41
--- /dev/null
+++ b/pttbbs/mbbsd/random.c
@@ -0,0 +1,711 @@
+#ifdef __dietlibc__
+/*
+ Copyright (C) 1995 Free Software Foundation
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307 USA. */
+
+/*
+ Copyright (C) 1983 Regents of the University of California.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 4. Neither the name of the University nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.*/
+
+/*
+ * This is derived from the Berkeley source:
+ * @(#)random.c 5.5 (Berkeley) 7/6/88
+ * It was reworked for the GNU C Library by Roland McGrath.
+ * Rewritten to be reentrant by Ulrich Drepper, 1995
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+struct random_data
+ {
+ int32_t *fptr; /* Front pointer. */
+ int32_t *rptr; /* Rear pointer. */
+ int32_t *state; /* Array of state values. */
+ int rand_type; /* Type of random number generator. */
+ int rand_deg; /* Degree of random number generator. */
+ int rand_sep; /* Distance between front and rear. */
+ int32_t *end_ptr; /* Pointer behind state table. */
+ };
+int __random_r (struct random_data *buf, int32_t *result);
+
+
+
+/* An improved random number generation package. In addition to the standard
+ rand()/srand() like interface, this package also has a special state info
+ interface. The initstate() routine is called with a seed, an array of
+ bytes, and a count of how many bytes are being passed in; this array is
+ then initialized to contain information for random number generation with
+ that much state information. Good sizes for the amount of state
+ information are 32, 64, 128, and 256 bytes. The state can be switched by
+ calling the setstate() function with the same array as was initialized
+ with initstate(). By default, the package runs with 128 bytes of state
+ information and generates far better random numbers than a linear
+ congruential generator. If the amount of state information is less than
+ 32 bytes, a simple linear congruential R.N.G. is used. Internally, the
+ state information is treated as an array of longs; the zeroth element of
+ the array is the type of R.N.G. being used (small integer); the remainder
+ of the array is the state information for the R.N.G. Thus, 32 bytes of
+ state information will give 7 longs worth of state information, which will
+ allow a degree seven polynomial. (Note: The zeroth word of state
+ information also has some other information stored in it; see setstate
+ for details). The random number generation technique is a linear feedback
+ shift register approach, employing trinomials (since there are fewer terms
+ to sum up that way). In this approach, the least significant bit of all
+ the numbers in the state table will act as a linear feedback shift register,
+ and will have period 2^deg - 1 (where deg is the degree of the polynomial
+ being used, assuming that the polynomial is irreducible and primitive).
+ The higher order bits will have longer periods, since their values are
+ also influenced by pseudo-random carries out of the lower bits. The
+ total period of the generator is approximately deg*(2**deg - 1); thus
+ doubling the amount of state information has a vast influence on the
+ period of the generator. Note: The deg*(2**deg - 1) is an approximation
+ only good for large deg, when the period of the shift register is the
+ dominant factor. With deg equal to seven, the period is actually much
+ longer than the 7*(2**7 - 1) predicted by this formula. */
+
+
+
+/* For each of the currently supported random number generators, we have a
+ break value on the amount of state information (you need at least this many
+ bytes of state info to support this random number generator), a degree for
+ the polynomial (actually a trinomial) that the R.N.G. is based on, and
+ separation between the two lower order coefficients of the trinomial. */
+
+/* Linear congruential. */
+#define TYPE_0 0
+#define BREAK_0 8
+#define DEG_0 0
+#define SEP_0 0
+
+/* x**7 + x**3 + 1. */
+#define TYPE_1 1
+#define BREAK_1 32
+#define DEG_1 7
+#define SEP_1 3
+
+/* x**15 + x + 1. */
+#define TYPE_2 2
+#define BREAK_2 64
+#define DEG_2 15
+#define SEP_2 1
+
+/* x**31 + x**3 + 1. */
+#define TYPE_3 3
+#define BREAK_3 128
+#define DEG_3 31
+#define SEP_3 3
+
+/* x**63 + x + 1. */
+#define TYPE_4 4
+#define BREAK_4 256
+#define DEG_4 63
+#define SEP_4 1
+
+
+/* Array versions of the above information to make code run faster.
+ Relies on fact that TYPE_i == i. */
+
+#define MAX_TYPES 5 /* Max number of types above. */
+
+struct random_poly_info
+{
+ int seps[MAX_TYPES];
+ int degrees[MAX_TYPES];
+};
+
+static const struct random_poly_info random_poly_info =
+{
+ { SEP_0, SEP_1, SEP_2, SEP_3, SEP_4 },
+ { DEG_0, DEG_1, DEG_2, DEG_3, DEG_4 }
+};
+
+
+
+
+/* Initialize the random number generator based on the given seed. If the
+ type is the trivial no-state-information type, just remember the seed.
+ Otherwise, initializes state[] based on the given "seed" via a linear
+ congruential generator. Then, the pointers are set to known locations
+ that are exactly rand_sep places apart. Lastly, it cycles the state
+ information a given number of times to get rid of any initial dependencies
+ introduced by the L.C.R.N.G. Note that the initialization of randtbl[]
+ for default usage relies on values produced by this routine. */
+int
+__srandom_r (seed, buf)
+ unsigned int seed;
+ struct random_data *buf;
+{
+ int type;
+ int32_t *state;
+ long int i;
+ long int word;
+ int32_t *dst;
+ int kc;
+
+ if (buf == NULL)
+ goto fail;
+ type = buf->rand_type;
+ if ((unsigned int) type >= MAX_TYPES)
+ goto fail;
+
+ state = buf->state;
+ /* We must make sure the seed is not 0. Take arbitrarily 1 in this case. */
+ if (seed == 0)
+ seed = 1;
+ state[0] = seed;
+ if (type == TYPE_0)
+ goto done;
+
+ dst = state;
+ word = seed;
+ kc = buf->rand_deg;
+ for (i = 1; i < kc; ++i)
+ {
+ /* This does:
+ state[i] = (16807 * state[i - 1]) % 2147483647;
+ but avoids overflowing 31 bits. */
+ long int hi = word / 127773;
+ long int lo = word % 127773;
+ word = 16807 * lo - 2836 * hi;
+ if (word < 0)
+ word += 2147483647;
+ *++dst = word;
+ }
+
+ buf->fptr = &state[buf->rand_sep];
+ buf->rptr = &state[0];
+ kc *= 10;
+ while (--kc >= 0)
+ {
+ int32_t discard;
+ (void) __random_r (buf, &discard);
+ }
+
+ done:
+ return 0;
+
+ fail:
+ return -1;
+}
+
+
+/* Initialize the state information in the given array of N bytes for
+ future random number generation. Based on the number of bytes we
+ are given, and the break values for the different R.N.G.'s, we choose
+ the best (largest) one we can and set things up for it. srandom is
+ then called to initialize the state information. Note that on return
+ from srandom, we set state[-1] to be the type multiplexed with the current
+ value of the rear pointer; this is so successive calls to initstate won't
+ lose this information and will be able to restart with setstate.
+ Note: The first thing we do is save the current state, if any, just like
+ setstate so that it doesn't matter when initstate is called.
+ Returns a pointer to the old state. */
+int
+__initstate_r (seed, arg_state, n, buf)
+ unsigned int seed;
+ char *arg_state;
+ size_t n;
+ struct random_data *buf;
+{
+ int type;
+ int degree;
+ int separation;
+ int32_t *state;
+
+ if (buf == NULL)
+ goto fail;
+
+ if (n >= BREAK_3)
+ type = n < BREAK_4 ? TYPE_3 : TYPE_4;
+ else if (n < BREAK_1)
+ {
+ if (n < BREAK_0)
+ {
+ __set_errno (EINVAL);
+ goto fail;
+ }
+ type = TYPE_0;
+ }
+ else
+ type = n < BREAK_2 ? TYPE_1 : TYPE_2;
+
+ degree = random_poly_info.degrees[type];
+ separation = random_poly_info.seps[type];
+
+ buf->rand_type = type;
+ buf->rand_sep = separation;
+ buf->rand_deg = degree;
+ state = &((int32_t *) arg_state)[1]; /* First location. */
+ /* Must set END_PTR before srandom. */
+ buf->end_ptr = &state[degree];
+
+ buf->state = state;
+
+ __srandom_r (seed, buf);
+
+ state[-1] = TYPE_0;
+ if (type != TYPE_0)
+ state[-1] = (buf->rptr - state) * MAX_TYPES + type;
+
+ return 0;
+
+ fail:
+ __set_errno (EINVAL);
+ return -1;
+}
+
+
+/* Restore the state from the given state array.
+ Note: It is important that we also remember the locations of the pointers
+ in the current state information, and restore the locations of the pointers
+ from the old state information. This is done by multiplexing the pointer
+ location into the zeroth word of the state information. Note that due
+ to the order in which things are done, it is OK to call setstate with the
+ same state as the current state
+ Returns a pointer to the old state information. */
+int
+__setstate_r (arg_state, buf)
+ char *arg_state;
+ struct random_data *buf;
+{
+ int32_t *new_state = 1 + (int32_t *) arg_state;
+ int type;
+ int old_type;
+ int32_t *old_state;
+ int degree;
+ int separation;
+
+ if (arg_state == NULL || buf == NULL)
+ goto fail;
+
+ old_type = buf->rand_type;
+ old_state = buf->state;
+ if (old_type == TYPE_0)
+ old_state[-1] = TYPE_0;
+ else
+ old_state[-1] = (MAX_TYPES * (buf->rptr - old_state)) + old_type;
+
+ type = new_state[-1] % MAX_TYPES;
+ if (type < TYPE_0 || type > TYPE_4)
+ goto fail;
+
+ buf->rand_deg = degree = random_poly_info.degrees[type];
+ buf->rand_sep = separation = random_poly_info.seps[type];
+ buf->rand_type = type;
+
+ if (type != TYPE_0)
+ {
+ int rear = new_state[-1] / MAX_TYPES;
+ buf->rptr = &new_state[rear];
+ buf->fptr = &new_state[(rear + separation) % degree];
+ }
+ buf->state = new_state;
+ /* Set end_ptr too. */
+ buf->end_ptr = &new_state[degree];
+
+ return 0;
+
+ fail:
+ __set_errno (EINVAL);
+ return -1;
+}
+
+
+/* If we are using the trivial TYPE_0 R.N.G., just do the old linear
+ congruential bit. Otherwise, we do our fancy trinomial stuff, which is the
+ same in all the other cases due to all the global variables that have been
+ set up. The basic operation is to add the number at the rear pointer into
+ the one at the front pointer. Then both pointers are advanced to the next
+ location cyclically in the table. The value returned is the sum generated,
+ reduced to 31 bits by throwing away the "least random" low bit.
+ Note: The code takes advantage of the fact that both the front and
+ rear pointers can't wrap on the same call by not testing the rear
+ pointer if the front one has wrapped. Returns a 31-bit random number. */
+
+int
+__random_r (buf, result)
+ struct random_data *buf;
+ int32_t *result;
+{
+ int32_t *state;
+
+ if (buf == NULL || result == NULL)
+ goto fail;
+
+ state = buf->state;
+
+ if (buf->rand_type == TYPE_0)
+ {
+ int32_t val = state[0];
+ val = ((state[0] * 1103515245) + 12345) & 0x7fffffff;
+ state[0] = val;
+ *result = val;
+ }
+ else
+ {
+ int32_t *fptr = buf->fptr;
+ int32_t *rptr = buf->rptr;
+ int32_t *end_ptr = buf->end_ptr;
+ int32_t val;
+
+ val = *fptr += *rptr;
+ /* Chucking least random bit. */
+ *result = (val >> 1) & 0x7fffffff;
+ ++fptr;
+ if (fptr >= end_ptr)
+ {
+ fptr = state;
+ ++rptr;
+ }
+ else
+ {
+ ++rptr;
+ if (rptr >= end_ptr)
+ rptr = state;
+ }
+ buf->fptr = fptr;
+ buf->rptr = rptr;
+ }
+ return 0;
+
+ fail:
+ __set_errno (EINVAL);
+ return -1;
+}
+
+/* Copyright (C) 1995 Free Software Foundation
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307 USA. */
+
+/*
+ * This is derived from the Berkeley source:
+ * @(#)random.c 5.5 (Berkeley) 7/6/88
+ * It was reworked for the GNU C Library by Roland McGrath.
+ * Rewritten to use reentrant functions by Ulrich Drepper, 1995.
+ */
+
+/*
+ Copyright (C) 1983 Regents of the University of California.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 4. Neither the name of the University nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.*/
+
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+
+/* An improved random number generation package. In addition to the standard
+ rand()/srand() like interface, this package also has a special state info
+ interface. The initstate() routine is called with a seed, an array of
+ bytes, and a count of how many bytes are being passed in; this array is
+ then initialized to contain information for random number generation with
+ that much state information. Good sizes for the amount of state
+ information are 32, 64, 128, and 256 bytes. The state can be switched by
+ calling the setstate() function with the same array as was initialized
+ with initstate(). By default, the package runs with 128 bytes of state
+ information and generates far better random numbers than a linear
+ congruential generator. If the amount of state information is less than
+ 32 bytes, a simple linear congruential R.N.G. is used. Internally, the
+ state information is treated as an array of longs; the zeroth element of
+ the array is the type of R.N.G. being used (small integer); the remainder
+ of the array is the state information for the R.N.G. Thus, 32 bytes of
+ state information will give 7 longs worth of state information, which will
+ allow a degree seven polynomial. (Note: The zeroth word of state
+ information also has some other information stored in it; see setstate
+ for details). The random number generation technique is a linear feedback
+ shift register approach, employing trinomials (since there are fewer terms
+ to sum up that way). In this approach, the least significant bit of all
+ the numbers in the state table will act as a linear feedback shift register,
+ and will have period 2^deg - 1 (where deg is the degree of the polynomial
+ being used, assuming that the polynomial is irreducible and primitive).
+ The higher order bits will have longer periods, since their values are
+ also influenced by pseudo-random carries out of the lower bits. The
+ total period of the generator is approximately deg*(2**deg - 1); thus
+ doubling the amount of state information has a vast influence on the
+ period of the generator. Note: The deg*(2**deg - 1) is an approximation
+ only good for large deg, when the period of the shift register is the
+ dominant factor. With deg equal to seven, the period is actually much
+ longer than the 7*(2**7 - 1) predicted by this formula. */
+
+
+
+/* For each of the currently supported random number generators, we have a
+ break value on the amount of state information (you need at least this many
+ bytes of state info to support this random number generator), a degree for
+ the polynomial (actually a trinomial) that the R.N.G. is based on, and
+ separation between the two lower order coefficients of the trinomial. */
+
+/* Linear congruential. */
+#define TYPE_0 0
+#define BREAK_0 8
+#define DEG_0 0
+#define SEP_0 0
+
+/* x**7 + x**3 + 1. */
+#define TYPE_1 1
+#define BREAK_1 32
+#define DEG_1 7
+#define SEP_1 3
+
+/* x**15 + x + 1. */
+#define TYPE_2 2
+#define BREAK_2 64
+#define DEG_2 15
+#define SEP_2 1
+
+/* x**31 + x**3 + 1. */
+#define TYPE_3 3
+#define BREAK_3 128
+#define DEG_3 31
+#define SEP_3 3
+
+/* x**63 + x + 1. */
+#define TYPE_4 4
+#define BREAK_4 256
+#define DEG_4 63
+#define SEP_4 1
+
+
+/* Array versions of the above information to make code run faster.
+ Relies on fact that TYPE_i == i. */
+
+#define MAX_TYPES 5 /* Max number of types above. */
+
+
+/* Initially, everything is set up as if from:
+ initstate(1, randtbl, 128);
+ Note that this initialization takes advantage of the fact that srandom
+ advances the front and rear pointers 10*rand_deg times, and hence the
+ rear pointer which starts at 0 will also end up at zero; thus the zeroth
+ element of the state information, which contains info about the current
+ position of the rear pointer is just
+ (MAX_TYPES * (rptr - state)) + TYPE_3 == TYPE_3. */
+
+static int32_t randtbl[DEG_3 + 1] =
+ {
+ TYPE_3,
+
+ -1726662223, 379960547, 1735697613, 1040273694, 1313901226,
+ 1627687941, -179304937, -2073333483, 1780058412, -1989503057,
+ -615974602, 344556628, 939512070, -1249116260, 1507946756,
+ -812545463, 154635395, 1388815473, -1926676823, 525320961,
+ -1009028674, 968117788, -123449607, 1284210865, 435012392,
+ -2017506339, -911064859, -370259173, 1132637927, 1398500161,
+ -205601318,
+ };
+
+
+static struct random_data unsafe_state =
+ {
+/* FPTR and RPTR are two pointers into the state info, a front and a rear
+ pointer. These two pointers are always rand_sep places aparts, as they
+ cycle through the state information. (Yes, this does mean we could get
+ away with just one pointer, but the code for random is more efficient
+ this way). The pointers are left positioned as they would be from the call:
+ initstate(1, randtbl, 128);
+ (The position of the rear pointer, rptr, is really 0 (as explained above
+ in the initialization of randtbl) because the state table pointer is set
+ to point to randtbl[1] (as explained below).) */
+
+ .fptr = &randtbl[SEP_3 + 1],
+ .rptr = &randtbl[1],
+
+/* The following things are the pointer to the state information table,
+ the type of the current generator, the degree of the current polynomial
+ being used, and the separation between the two pointers.
+ Note that for efficiency of random, we remember the first location of
+ the state information, not the zeroth. Hence it is valid to access
+ state[-1], which is used to store the type of the R.N.G.
+ Also, we remember the last location, since this is more efficient than
+ indexing every time to find the address of the last element to see if
+ the front and rear pointers have wrapped. */
+
+ .state = &randtbl[1],
+
+ .rand_type = TYPE_3,
+ .rand_deg = DEG_3,
+ .rand_sep = SEP_3,
+
+ .end_ptr = &randtbl[sizeof (randtbl) / sizeof (randtbl[0])]
+};
+
+/* POSIX.1c requires that there is mutual exclusion for the `rand' and
+ `srand' functions to prevent concurrent calls from modifying common
+ data. */
+
+/* Initialize the random number generator based on the given seed. If the
+ type is the trivial no-state-information type, just remember the seed.
+ Otherwise, initializes state[] based on the given "seed" via a linear
+ congruential generator. Then, the pointers are set to known locations
+ that are exactly rand_sep places apart. Lastly, it cycles the state
+ information a given number of times to get rid of any initial dependencies
+ introduced by the L.C.R.N.G. Note that the initialization of randtbl[]
+ for default usage relies on values produced by this routine. */
+void
+__srandom (x)
+ unsigned int x;
+{
+ (void) __srandom_r (x, &unsafe_state);
+}
+
+
+/* Initialize the state information in the given array of N bytes for
+ future random number generation. Based on the number of bytes we
+ are given, and the break values for the different R.N.G.'s, we choose
+ the best (largest) one we can and set things up for it. srandom is
+ then called to initialize the state information. Note that on return
+ from srandom, we set state[-1] to be the type multiplexed with the current
+ value of the rear pointer; this is so successive calls to initstate won't
+ lose this information and will be able to restart with setstate.
+ Note: The first thing we do is save the current state, if any, just like
+ setstate so that it doesn't matter when initstate is called.
+ Returns a pointer to the old state. */
+char *
+__initstate (seed, arg_state, n)
+ unsigned int seed;
+ char *arg_state;
+ size_t n;
+{
+ int32_t *ostate;
+
+
+ ostate = &unsafe_state.state[-1];
+
+ __initstate_r (seed, arg_state, n, &unsafe_state);
+
+
+ return (char *) ostate;
+}
+
+
+/* Restore the state from the given state array.
+ Note: It is important that we also remember the locations of the pointers
+ in the current state information, and restore the locations of the pointers
+ from the old state information. This is done by multiplexing the pointer
+ location into the zeroth word of the state information. Note that due
+ to the order in which things are done, it is OK to call setstate with the
+ same state as the current state
+ Returns a pointer to the old state information. */
+char *
+__setstate (arg_state)
+ char *arg_state;
+{
+ int32_t *ostate;
+
+
+ ostate = &unsafe_state.state[-1];
+
+ if (__setstate_r (arg_state, &unsafe_state) < 0)
+ ostate = NULL;
+
+
+ return (char *) ostate;
+}
+
+
+/* If we are using the trivial TYPE_0 R.N.G., just do the old linear
+ congruential bit. Otherwise, we do our fancy trinomial stuff, which is the
+ same in all the other cases due to all the global variables that have been
+ set up. The basic operation is to add the number at the rear pointer into
+ the one at the front pointer. Then both pointers are advanced to the next
+ location cyclically in the table. The value returned is the sum generated,
+ reduced to 31 bits by throwing away the "least random" low bit.
+ Note: The code takes advantage of the fact that both the front and
+ rear pointers can't wrap on the same call by not testing the rear
+ pointer if the front one has wrapped. Returns a 31-bit random number. */
+
+long int
+__random (void)
+{
+ int32_t retval;
+
+
+ (void) __random_r (&unsafe_state, &retval);
+
+
+ return retval;
+}
+
+long int glibc_random(void) { return __random(); }
+void glibc_srandom(unsigned int seed) { __srandom(seed); }
+char *glibc_initstate(unsigned int seed, char *state, size_t n) { return __initstate(seed,state,n); }
+char *glibc_setstate(char *state) { return __setstate(state); }
+#endif
diff --git a/pttbbs/mbbsd/read.c b/pttbbs/mbbsd/read.c
new file mode 100644
index 00000000..7a3d0d4b
--- /dev/null
+++ b/pttbbs/mbbsd/read.c
@@ -0,0 +1,1138 @@
+/* $Id$ */
+#include "bbs.h"
+#include "fnv_hash.h"
+
+static int headers_size;
+static fileheader_t *headers = NULL;
+static int last_line; // PTT: last_line 游標可指的最後一個
+
+#include <sys/mman.h>
+
+/* ----------------------------------------------------- */
+/* Tag List 標籤 */
+/* ----------------------------------------------------- */
+static TagItem *TagList; /* ascending list */
+
+/**
+ * @param locus
+ * @return void
+ */
+void
+UnTagger(int locus)
+{
+ if (locus > TagNum)
+ return;
+
+ TagNum--;
+
+ if (TagNum > locus)
+ memmove(&TagList[locus], &TagList[locus + 1],
+ (TagNum - locus) * sizeof(TagItem));
+}
+
+int
+Tagger(time4_t chrono, int recno, int mode)
+{
+ int head, tail, posi = 0, comp;
+
+ if(TagList == NULL) {
+ TagList = malloc(sizeof(TagItem)*MAXTAGS);
+ }
+
+ for (head = 0, tail = TagNum - 1, comp = 1; head <= tail;) {
+ posi = (head + tail) >> 1;
+ comp = TagList[posi].chrono - chrono;
+ if (!comp) {
+ break;
+ } else if (comp < 0) {
+ head = posi + 1;
+ } else {
+ tail = posi - 1;
+ }
+ }
+
+ if (mode == TAG_NIN) {
+ if (!comp && recno) /* 絕對嚴謹:連 recno 一起比對 */
+ comp = recno - TagList[posi].recno;
+ return comp;
+
+ }
+ if (!comp) {
+ if (mode != TAG_TOGGLE)
+ return NA;
+
+ TagNum--;
+ memmove(&TagList[posi], &TagList[posi + 1],
+ (TagNum - posi) * sizeof(TagItem));
+ } else if (TagNum < MAXTAGS) {
+ TagItem *tagp;
+
+ memmove(&TagList[head+1], &TagList[head], sizeof(TagItem)*(TagNum-head));
+ tagp = &TagList[head];
+ tagp->chrono = chrono;
+ tagp->recno = recno;
+ TagNum++;
+ } else {
+ bell();
+ return 0; /* full */
+ }
+ return YEA;
+}
+
+#if 0
+static void
+EnumTagName(char *fname, int locus) /* unused */
+{
+ snprintf(fname, sizeof(fname), "M.%d.A", (int)TagList[locus].chrono);
+}
+#endif
+
+void
+EnumTagFhdr(fileheader_t * fhdr, char *direct, int locus)
+{
+ get_record(direct, fhdr, sizeof(fileheader_t), TagList[locus].recno);
+}
+
+/* -1 : 取消 */
+/* 0 : single article */
+/* ow: whole tag list */
+
+int
+AskTag(const char *msg)
+{
+ int num;
+
+ num = TagNum;
+ switch (getans("◆ %s A)文章 T)標記 Q)uit?", msg)) {
+ case 'q':
+ num = -1;
+ break;
+ case 'a':
+ num = 0;
+ }
+ return num;
+}
+
+
+#include <sys/mman.h>
+
+#define BATCH_SIZE 65536
+
+static char *
+f_map(const char *fpath, int *fsize)
+{
+ int fd, size;
+ struct stat st;
+ char *map;
+
+ if ((fd = open(fpath, O_RDONLY)) < 0)
+ return (char *)-1;
+
+ if (fstat(fd, &st) || !S_ISREG(st.st_mode) || (size = st.st_size) <= 0) {
+ close(fd);
+ return (char *)-1;
+ }
+ map = (char *)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+ close(fd);
+ *fsize = size;
+ return map;
+}
+
+
+static int
+TagThread(const char *direct)
+{
+ int fsize, count;
+ char *title, *fimage;
+ fileheader_t *head, *tail;
+
+ fimage = f_map(direct, &fsize);
+ if (fimage == (char *)-1)
+ return DONOTHING;
+
+ head = (fileheader_t *) fimage;
+ tail = (fileheader_t *) (fimage + fsize);
+ count = 0;
+ do {
+ count++;
+ title = subject(head->title);
+ if (!strncmp(currtitle, title, TTLEN)) {
+ if (!Tagger(atoi(head->filename + 2), count, TAG_INSERT))
+ break;
+ }
+ } while (++head < tail);
+
+ munmap(fimage, fsize);
+ return FULLUPDATE;
+}
+
+
+int
+TagPruner(int bid)
+{
+ boardheader_t *bp=NULL;
+ assert(bid >= 0); /* bid == 0 means in mailbox */
+ if (bid){
+ bp = getbcache(bid);
+ if (strcmp(bp->brdname, "Security") == 0)
+ return DONOTHING;
+ }
+ if (TagNum && ((currstat != READING) || (currmode & MODE_BOARD))) {
+ if (getans("刪除所有標記[N]?") != 'y')
+ return READ_REDRAW;
+#ifdef SAFE_ARTICLE_DELETE
+ if(bp && !(currmode & MODE_DIGEST) && bp->nuser>30 )
+ safe_delete_range(currdirect, 0, 0);
+ else
+#endif
+ delete_range(currdirect, 0, 0);
+ TagNum = 0;
+ if (bid)
+ setbtotal(bid);
+ else if(currstat == RMAIL)
+ setupmailusage();
+
+ return NEWDIRECT;
+ }
+ return DONOTHING;
+}
+
+
+/* ----------------------------------------------------- */
+/* cursor & reading record position control */
+/* ----------------------------------------------------- */
+keeploc_t *
+getkeep(const char *s, int def_topline, int def_cursline)
+{
+ /* 為省記憶體, 且避免 malloc/free 不成對, getkeep 最好不要 malloc,
+ * 只記 s 的 hash 值,
+ * fvn1a-32bit collision 機率約小於十萬分之一 */
+ /* 原本使用 link list, 可是一方面會造成 malloc/free 不成對,
+ * 一方面 size 小, malloc space overhead 就高, 因此改成 link block,
+ * 以 KEEPSLOT 為一個 block 的 link list.
+ * 只有第一個 block 可能沒滿. */
+ /* TODO LRU recycle? 麻煩在於別處可能把 keeploc_t pointer 記著... */
+#define KEEPSLOT 10
+ struct keepsome {
+ unsigned char used;
+ keeploc_t arr[KEEPSLOT];
+ struct keepsome *next;
+ };
+ static struct keepsome preserv_keepblock;
+ static struct keepsome *keeplist = &preserv_keepblock;
+ struct keeploc_t *p;
+ unsigned int key=fnv1a_32_str(s, FNV1_32_INIT);
+ int i;
+
+ if (def_cursline >= 0) {
+ struct keepsome *kl=keeplist;
+ while(kl) {
+ for(i=0; i<kl->used; i++)
+ if(key == kl->arr[i].hashkey) {
+ p = &kl->arr[i];
+ if (p->crs_ln < 1)
+ p->crs_ln = 1;
+ return p;
+ }
+ kl=kl->next;
+ }
+ } else
+ def_cursline = -def_cursline;
+
+ if(keeplist->used==KEEPSLOT) {
+ struct keepsome *kl;
+ kl = (struct keepsome*)malloc(sizeof(struct keepsome));
+ memset(kl, 0, sizeof(struct keepsome));
+ kl->next = keeplist;
+ keeplist = kl;
+ }
+ p = &keeplist->arr[keeplist->used];
+ keeplist->used++;
+ p->hashkey = key;
+ p->top_ln = def_topline;
+ p->crs_ln = def_cursline;
+ return p;
+}
+
+void
+fixkeep(const char *s, int first)
+{
+ keeploc_t *k;
+
+ k = getkeep(s, 1, 1);
+ if (k->crs_ln >= first) {
+ k->crs_ln = (first == 1 ? 1 : first - 1);
+ k->top_ln = (first < 11 ? 1 : first - 10);
+ }
+}
+
+/* calc cursor pos and show cursor correctly */
+static int
+cursor_pos(keeploc_t * locmem, int val, int from_top, int isshow)
+{
+ int top=locmem->top_ln;
+ if (!last_line){
+ cursor_show(3 , 0);
+ return DONOTHING;
+ }
+ if (val > last_line)
+ val = last_line;
+ if (val <= 0)
+ val = 1;
+ if (val >= top && val < top + headers_size) {
+ if(isshow){
+ if(locmem->crs_ln >= top)
+ cursor_clear(3 + locmem->crs_ln - top, 0);
+ cursor_show(3 + val - top, 0);
+ }
+ locmem->crs_ln = val;
+ return DONOTHING;
+ }
+ locmem->top_ln = val - from_top;
+ if (locmem->top_ln <= 0)
+ locmem->top_ln = 1;
+ locmem->crs_ln = val;
+ return isshow ? PARTUPDATE : HEADERS_RELOAD;
+}
+
+/**
+ * 根據 stypen 選擇上/下一篇文章
+ *
+ * @param locmem 用來存在某看板游標位置的 structure。
+ * @param stypen 游標移動的方法
+ * CURSOR_FIRST, CURSOR_NEXT, CURSOR_PREV:
+ * 與游標目前位置的文章同標題 的 第一篇/下一篇/前一篇 文章。
+ * RELATE_FIRST, RELATE_NEXT, RELATE_PREV:
+ * 與目前正閱讀的文章同標題 的 第一篇/下一篇/前一篇 文章。
+ * NEWPOST_NEXT, NEWPOST_PREV:
+ * 下一個/前一個 thread 的第一篇。
+ * AUTHOR_NEXT, AUTHOR_PREV:
+ * XXX 這功能目前好像沒用到?
+ *
+ * @return 新的游標位置
+ */
+static int
+thread(const keeploc_t * locmem, int stypen)
+{
+ fileheader_t fh;
+ int pos = locmem->crs_ln, jump = THREAD_SEARCH_RANGE, new_ln;
+ int fd = -1, amatch = -1;
+ int step = (stypen & RS_FORWARD) ? 1 : -1;
+ char *key;
+
+ if(locmem->crs_ln==0)
+ return locmem->crs_ln;
+
+ STATINC(STAT_THREAD);
+ if (stypen & RS_AUTHOR)
+ key = headers[pos - locmem->top_ln].owner;
+ else if (stypen & RS_CURRENT)
+ key = subject(currtitle);
+ else
+ key = subject(headers[pos - locmem->top_ln].title );
+
+ for( new_ln = pos + step ;
+ new_ln > 0 && new_ln <= last_line && --jump > 0;
+ new_ln += step ) {
+
+ int rk =
+ get_record_keep(currdirect, &fh, sizeof(fileheader_t), new_ln, &fd);
+
+ if(fd < 0 || rk < 0)
+ {
+ new_ln = pos;
+ break;
+ }
+
+ if( stypen & RS_TITLE ){
+ if( stypen & RS_FIRST ){
+ if( !strncmp(fh.title, key, PROPER_TITLE_LEN) )
+ break;
+ else if( !strncmp(&fh.title[4], key, PROPER_TITLE_LEN) ) {
+ amatch = new_ln;
+ jump = THREAD_SEARCH_RANGE;
+ /* 當搜尋同主題第一篇, 連續找不到多少篇才停 */
+ }
+ }
+ else if( !strncmp(subject(fh.title), key, PROPER_TITLE_LEN) )
+ break;
+ }
+ else if( stypen & RS_NEWPOST ){
+ if( strncmp(fh.title, "Re:", 3) )
+ break;
+ }
+ else{ // RS_AUTHOR
+ if( strcmp(subject(fh.owner), key) == EQUSTR )
+ break;
+ }
+ }
+
+ if( fd != -1 )
+ close(fd);
+
+ if( jump <= 0 || new_ln <= 0 || new_ln > last_line )
+ new_ln = (amatch == -1 ? pos : amatch); //didn't find
+
+ return new_ln;
+}
+
+#ifdef INTERNET_EMAIL
+static void
+mail_forward(const fileheader_t * fhdr, const char *direct, int mode)
+{
+ int i;
+ char buf[STRLEN];
+ char *p;
+
+ strlcpy(buf, direct, sizeof(buf));
+ if ((p = strrchr(buf, '/')))
+ *p = '\0';
+ switch (i = doforward(buf, fhdr, mode)) {
+ case 0:
+ vmsg(msg_fwd_ok);
+ break;
+ case -1:
+ vmsg(msg_fwd_err1);
+ break;
+ case -2:
+#ifndef DEBUG_FWDADDRERR
+ vmsg(msg_fwd_err2);
+#endif
+ break;
+ case -4:
+ vmsg("信箱已滿");
+ break;
+ default:
+ break;
+ }
+}
+#endif
+
+inline static int
+dbcs_strcasestr(const char* pool, const char *ptr)
+{
+ int len = strlen(ptr);
+
+ while(*pool)
+ {
+ // FIXME 用 strncasecmp 還是會錯
+ if(strncasecmp(pool, ptr, len) == 0)
+ return 1;
+ /* else */
+ if(*pool < 0)
+ {
+ pool ++;
+ if(*pool == 0)
+ return 0;
+ }
+ pool ++;
+ }
+ return 0;
+}
+
+static int
+select_read(const keeploc_t * locmem, int sr_mode)
+{
+#define READSIZE 64 // 8192 / sizeof(fileheader_t)
+ time4_t filetime;
+ fileheader_t fhs[READSIZE];
+ char newdirect[MAXPATHLEN];
+ int first_select;
+ char genbuf[MAXPATHLEN], *p = strstr(currdirect, "SR.");
+ static int _mode = 0;
+ int reload, inc;
+ int len, fd, fr, i, count = 0, reference = 0;
+ int filemode;
+ /* selection condition */
+ char keyword[TTLEN + 1] = "";
+ int n_recommend = 0, n_money = 0;
+
+
+ if(locmem->crs_ln == 0)
+ return locmem->crs_ln;
+
+ first_select = p==NULL;
+
+ STATINC(STAT_SELECTREAD);
+ if(sr_mode & RS_AUTHOR)
+ {
+ if(!getdata(b_lines, 0,
+ currmode & MODE_SELECT ? "增加條件 作者: ":"搜尋作者: ",
+ keyword, IDLEN+1, LCECHO))
+ return READ_REDRAW;
+ }
+ else if(sr_mode & RS_KEYWORD)
+ {
+ if(!getdata(b_lines, 0,
+ currmode & MODE_SELECT ? "增加條件 標題: ":"搜尋標題: ",
+ keyword, TTLEN, DOECHO))
+ return READ_REDRAW;
+#ifdef KEYWORD_LOG
+ log_file("keyword_search_log", LOG_CREAT | LOG_VF,
+ "%s:%s\n", currboard, keyword);
+#endif
+ }
+ else if(sr_mode & RS_KEYWORD_EXCLUDE)
+ {
+ if(!(currmode & MODE_SELECT) ||
+ !getdata(b_lines, 0, "增加條件 排除標題: ",
+ keyword, TTLEN, DOECHO))
+ return READ_REDRAW;
+ }
+ else if (sr_mode & RS_RECOMMEND)
+ {
+ if(currstat == RMAIL || (
+ !getdata(b_lines, 0,
+ (currmode & MODE_SELECT) ?
+ "增加條件 推文數: ":"搜尋推文數高於多少的文章: ",
+ keyword, 7, LCECHO) || (n_recommend = atoi(keyword)) <= 0 ))
+ return READ_REDRAW;
+ }
+ else if (sr_mode & RS_MONEY)
+ {
+ if(currstat == RMAIL || (
+ !getdata(b_lines, 0,
+ (currmode & MODE_SELECT) ?
+ "增加條件 文章價格: ":"搜尋價格高於多少的文章: ",
+ keyword, 7, LCECHO) || (n_money = atoi(keyword)) <= 0 ))
+ return READ_REDRAW;
+ strcat(keyword, "M");
+ }
+ else {
+ // Ptt: only once for these modes.
+ if(!first_select && _mode & sr_mode & (RS_TITLE | RS_NEWPOST | RS_MARK))
+ return DONOTHING;
+
+ if(sr_mode & RS_TITLE) {
+ fileheader_t *fh = &headers[locmem->crs_ln - locmem->top_ln];
+ strcpy(keyword, subject(fh->title));
+ }
+ }
+
+ if(first_select)
+ _mode = sr_mode;
+ else
+ _mode |= sr_mode;
+
+ snprintf(genbuf, sizeof(genbuf), "%s%X.%X.%X",
+ first_select ? "SR.":p,
+ sr_mode, (int)strlen(keyword), StringHash(keyword));
+ if( strlen(genbuf) > MAXPATHLEN - 50 )
+ return READ_REDRAW; // avoid overflow
+
+ if (currstat == RMAIL)
+ sethomefile(newdirect, cuser.userid, genbuf);
+ else
+ setbfile(newdirect, currboard, genbuf);
+
+ filetime = dasht(newdirect);
+ count = dashs(newdirect) / sizeof(fileheader_t);
+
+ if(filetime<0 || now-filetime>60*60) {
+ reload = 1;
+ inc = 0;
+ } else if(now-filetime > 3*60) {
+ reload = 1;
+ inc = 1;
+ } else {
+ /* use cached data */
+ reload = 0;
+ }
+
+ /* mark and recommend shouldn't incremental select */
+ if(sr_mode & (RS_MARK | RS_RECOMMEND))
+ inc = 0;
+
+ if(reload) {
+ if( (fr = open(currdirect, O_RDONLY, 0)) != -1 ) {
+ if(inc) {
+ /* find incremental selection start point */
+ int idx;
+ sprintf(fhs[0].filename, "X.%d", (int)filetime);
+ idx = getindex(currdirect, &fhs[0], 0);
+ if(idx<0) {
+ reference = -idx;
+ } else if(idx==0) {
+ inc = 0;
+ } else {
+ reference = idx;
+ }
+ }
+ if(inc) {
+ filemode = O_APPEND | O_RDWR;
+ } else {
+ filemode = O_CREAT | O_RDWR;
+ count = 0;
+ reference = 0;
+ }
+
+ if( (fd = open(newdirect, filemode, 0600)) == -1 ) {
+ close(fr);
+ return READ_REDRAW;
+ }
+
+ if(reference>0)
+ lseek(fr, reference*sizeof(fileheader_t), SEEK_SET);
+
+#ifdef DEBUG
+ vmsgf("search: %s", currdirect);
+#endif
+ while( (len = read(fr, fhs, sizeof(fhs))) > 0 ){
+ len /= sizeof(fileheader_t);
+ for( i = 0 ; i < len ; ++i ){
+ reference++;
+ if( (sr_mode & RS_MARK) &&
+ !(fhs[i].filemode & FILE_MARKED) )
+ continue;
+ else if((sr_mode & RS_NEWPOST) &&
+ !strncmp(fhs[i].title, "Re:", 3))
+ continue;
+ else if((sr_mode & RS_AUTHOR) &&
+ !dbcs_strcasestr(fhs[i].owner, keyword))
+ continue;
+ else if((sr_mode & RS_KEYWORD) &&
+ !dbcs_strcasestr(fhs[i].title, keyword))
+ continue;
+ else if(sr_mode & RS_KEYWORD_EXCLUDE &&
+ dbcs_strcasestr(fhs[i].title, keyword))
+ continue;
+ else if((sr_mode & RS_TITLE) &&
+ strcasecmp(subject(fhs[i].title), keyword))
+ continue;
+ else if ((sr_mode & RS_RECOMMEND) &&
+ fhs[i].recommend < n_recommend )
+ continue;
+ /* please put money test in last */
+ else if ((sr_mode & RS_MONEY) &&
+ query_file_money(fhs+i) < n_money)
+ continue;
+
+ if(first_select) {
+ fhs[i].multi.refer.flag = 1;
+ fhs[i].multi.refer.ref = reference;
+ }
+ ++count;
+ write(fd, &fhs[i], sizeof(fileheader_t));
+ }
+ } // end while
+ close(fr);
+ }
+ ftruncate(fd, count*sizeof(fileheader_t));
+ close(fd);
+ }
+
+ if(count) {
+ strlcpy(currdirect, newdirect, sizeof(currdirect));
+ currmode |= MODE_SELECT;
+ currsrmode |= sr_mode;
+ return NEWDIRECT;
+ }
+ return READ_REDRAW;
+}
+
+static int
+i_read_key(const onekey_t * rcmdlist, keeploc_t * locmem,
+ int bid, int bottom_line)
+{
+ int mode = DONOTHING, num, new_top=10;
+ int ch, new_ln = locmem->crs_ln, lastmode = DONOTHING;
+ char direct[60];
+ static char default_ch = 0;
+
+ do {
+ if( (mode = cursor_pos(locmem, new_ln, new_top, default_ch ? 0 : 1))
+ != DONOTHING )
+ return mode;
+
+ if( !default_ch )
+ ch = igetch();
+ else{
+ if(new_ln != locmem->crs_ln) {// move fault
+ default_ch=0;
+ return FULLUPDATE;
+ }
+ ch = default_ch;
+ }
+
+ new_top = 10; // default 10
+ switch (ch) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if( (num = search_num(ch, last_line)) != -1 )
+ new_ln = num + 1;
+ break;
+ case 'q':
+ case 'e':
+ case KEY_LEFT:
+ if(currmode & MODE_SELECT && locmem->crs_ln>0){
+ char genbuf[PATHLEN];
+ fileheader_t *fhdr = &headers[locmem->crs_ln - locmem->top_ln];
+ board_select();
+ setbdir(genbuf, currboard);
+ locmem = getkeep(genbuf, 0, 1);
+ locmem->crs_ln = fhdr->multi.refer.ref;
+ num = locmem->crs_ln - p_lines + 1;
+ locmem->top_ln = num < 1 ? 1 : num;
+ mode = NEWDIRECT;
+ }
+ else
+ mode =
+ (currmode & MODE_DIGEST) ? board_digest() : DOQUIT;
+ break;
+ case Ctrl('L'):
+ redoscr();
+ break;
+
+ case Ctrl('H'):
+ mode = select_read(locmem, RS_NEWPOST);
+ break;
+
+ case 'Z':
+ mode = select_read(locmem, RS_RECOMMEND);
+ break;
+
+ case 'a':
+ mode = select_read(locmem, RS_AUTHOR);
+ break;
+
+ case 'A':
+ mode = select_read(locmem, RS_MONEY);
+ break;
+
+ case 'G':
+ mode = select_read(locmem, RS_MARK);
+ break;
+
+ case '/':
+ case '?':
+ mode = select_read(locmem, RS_KEYWORD);
+ break;
+
+ case 'S':
+ mode = select_read(locmem, RS_TITLE);
+ break;
+
+ case '!':
+ mode = select_read(locmem, RS_KEYWORD_EXCLUDE);
+ break;
+
+ case '=':
+ new_ln = thread(locmem, RELATE_FIRST);
+ break;
+
+ case '\\':
+ new_ln = thread(locmem, CURSOR_FIRST);
+ break;
+
+ case ']':
+ new_ln = thread(locmem, RELATE_NEXT);
+ break;
+
+ case '+':
+ new_ln = thread(locmem, CURSOR_NEXT);
+ break;
+
+ case '[':
+ new_ln = thread(locmem, RELATE_PREV);
+ break;
+
+ case '-':
+ new_ln = thread(locmem, CURSOR_PREV);
+ break;
+
+ case '<':
+ case ',':
+ new_ln = thread(locmem, NEWPOST_PREV);
+ break;
+
+ case '.':
+ case '>':
+ new_ln = thread(locmem, NEWPOST_NEXT);
+ break;
+
+ case 'p':
+ case 'k':
+ case KEY_UP:
+ if (locmem->crs_ln <= 1) {
+ new_ln = last_line;
+ new_top = p_lines-1;
+ } else {
+ new_ln = locmem->crs_ln - 1;
+ new_top = p_lines - 2;
+ }
+ break;
+
+ case 'n':
+ case 'j':
+ case KEY_DOWN:
+ new_ln = locmem->crs_ln + 1;
+ new_top = 1;
+ break;
+
+ case ' ':
+ case KEY_PGDN:
+ case 'N':
+ case Ctrl('F'):
+ new_ln = locmem->top_ln + p_lines;
+ new_top = 0;
+ break;
+
+ case KEY_PGUP:
+ case Ctrl('B'):
+ case 'P':
+ new_ln = locmem->top_ln - p_lines;
+ new_top = 0;
+ break;
+
+ /* add home(top entry) support? */
+ case KEY_HOME:
+ new_ln = 0;
+ new_top = 0;
+ break;
+
+ case KEY_END:
+ case '$':
+ new_ln = last_line;
+ new_top = p_lines-1;
+ break;
+
+ case 'F':
+ case 'U':
+ if (HasUserPerm(PERM_FORWARD) && locmem->crs_ln>0) {
+ mail_forward(&headers[locmem->crs_ln - locmem->top_ln],
+ currdirect, ch /* == 'U' */ );
+ /* by CharlieL */
+ // mode = READ_REDRAW;
+ return FULLUPDATE;
+ }
+ break;
+
+ case Ctrl('Q'):
+ if(locmem->crs_ln>0)
+ mode = my_query(headers[locmem->crs_ln - locmem->top_ln].owner);
+ break;
+
+ case Ctrl('S'):
+ if (HasUserPerm(PERM_ACCOUNTS|PERM_SYSOP) && locmem->crs_ln>0) {
+ int id;
+ userec_t muser;
+
+ strlcpy(currauthor,
+ headers[locmem->crs_ln - locmem->top_ln].owner,
+ sizeof(currauthor));
+ stand_title("使用者設定");
+ move(1, 0);
+ if ((id = getuser(headers[locmem->crs_ln - locmem->top_ln].owner, &muser))) {
+ user_display(&muser, 1);
+ if( HasUserPerm(PERM_ACCOUNTS) )
+ uinfo_query(&muser, 1, id);
+ else
+ pressanykey();
+ }
+ mode = FULLUPDATE;
+ }
+ break;
+
+ /* rocker.011018: 採用新的tag模式 */
+ case 't':
+ if(locmem->crs_ln == 0)
+ break;
+ /* 將原本在 Read() 裡面的 "TagNum = 0" 移至此處 */
+ if ((currstat & RMAIL && TagBoard != 0) ||
+ (!(currstat & RMAIL) && TagBoard != bid)) {
+ if (currstat & RMAIL)
+ TagBoard = 0;
+ else
+ TagBoard = bid;
+ TagNum = 0;
+ }
+ /* rocker.011112: 解決再select mode標記文章的問題 */
+ if (Tagger(atoi(headers[locmem->crs_ln - locmem->top_ln].filename + 2),
+ (currmode & MODE_SELECT) ?
+ (headers[locmem->crs_ln - locmem->top_ln].multi.refer.ref) :
+ locmem->crs_ln, TAG_TOGGLE))
+ {
+// (*doentry) (locmem->crs_ln, &headers[locmem->crs_ln-locmem->top_ln]);
+ locmem->crs_ln ++;
+ // new_ln = locmem->crs_ln + 1;
+ // new_top = 1;
+ // mode = FULLUPDATE;
+ // mode = PART_REDRAW;
+ mode = PARTUPDATE;
+ }
+ break;
+
+ case Ctrl('C'):
+ if (TagNum) {
+ TagNum = 0;
+ mode = FULLUPDATE;
+ }
+ break;
+
+ case Ctrl('T'):
+ /* XXX duplicated code, copy from case 't' */
+ if ((currstat & RMAIL && TagBoard != 0) ||
+ (!(currstat & RMAIL) && TagBoard != bid)) {
+ if (currstat & RMAIL)
+ TagBoard = 0;
+ else
+ TagBoard = bid;
+ TagNum = 0;
+ }
+ mode = TagThread(currdirect);
+ break;
+
+ case Ctrl('D'):
+ mode = TagPruner(bid);
+ break;
+
+ case '\n':
+ case '\r':
+ case 'l':
+ case KEY_RIGHT:
+ ch = 'r';
+ default:
+ if( ch == 'h' && currmode & (MODE_DIGEST) )
+ break;
+ if (ch > 0 && ch <= onekey_size) {
+ int (*func)() = rcmdlist[ch - 1].func;
+ if(rcmdlist[ch - 1].needitem && locmem->crs_ln == 0)
+ break;
+ if (func != NULL){
+ num = locmem->crs_ln - bottom_line;
+
+ if(!rcmdlist[ch - 1].needitem)
+ mode = (*func)();
+ else if( num > 0 ){
+ sprintf(direct,"%s.bottom", currdirect);
+ mode= (*func)(num, &headers[locmem->crs_ln-locmem->top_ln],
+ direct);
+ }
+ else
+ mode = (*func)(locmem->crs_ln,
+ &headers[locmem->crs_ln - locmem->top_ln],
+ currdirect);
+ if(mode == READ_SKIP)
+ mode = lastmode;
+
+ // 以下這幾種 mode 要再處理游標
+ if(mode == READ_PREV || mode == READ_NEXT ||
+ mode == RELATE_PREV || mode == RELATE_FIRST ||
+ mode == AUTHOR_NEXT || mode == AUTHOR_PREV ||
+ mode == RELATE_NEXT){
+ lastmode = mode;
+
+ switch(mode){
+ case READ_PREV:
+ new_ln = locmem->crs_ln - 1;
+ break;
+ case READ_NEXT:
+ new_ln = locmem->crs_ln + 1;
+ break;
+ case RELATE_PREV:
+ new_ln = thread(locmem, RELATE_PREV);
+ break;
+ case RELATE_NEXT:
+ new_ln = thread(locmem, RELATE_NEXT);
+ /* XXX: 讀到最後一篇要跳出來 */
+ if( new_ln == locmem->crs_ln ){
+ default_ch = 0;
+ return FULLUPDATE;
+ }
+ break;
+ case RELATE_FIRST:
+ new_ln = thread(locmem, RELATE_FIRST);
+ break;
+ case AUTHOR_PREV:
+ new_ln = thread(locmem, AUTHOR_PREV);
+ break;
+ case AUTHOR_NEXT:
+ new_ln = thread(locmem, AUTHOR_NEXT);
+ break;
+ }
+ mode = DONOTHING; default_ch = 'r';
+ }
+ else {
+ default_ch = 0;
+ lastmode = DONOTHING;
+ }
+ } //end if (func != NULL)
+ } // ch > 0 && ch <= onekey_size
+ break;
+ } // end switch
+ } while (mode == DONOTHING);
+ return mode;
+}
+
+static int
+get_records_and_bottom(char *direct, fileheader_t* headers,
+ int recbase, int headers_size, int last_line, int bottom_line)
+{
+ int n = bottom_line - recbase + 1, rv;
+ char directbottom[60];
+
+ if( !last_line )
+ return 0;
+ if( n >= headers_size || (currmode & (MODE_SELECT | MODE_DIGEST)) )
+ return get_records(direct, headers, sizeof(fileheader_t), recbase,
+ headers_size);
+
+ sprintf(directbottom, "%s.bottom", direct);
+ if( n <= 0 )
+ return get_records(directbottom, headers, sizeof(fileheader_t), 1-n,
+ last_line-recbase + 1);
+
+ rv = get_records(direct, headers, sizeof(fileheader_t), recbase, n);
+
+ /* XXX if entries return -1 */
+ if( bottom_line < last_line )
+ rv += get_records(directbottom, headers+n, sizeof(fileheader_t), 1,
+ headers_size - n );
+ return rv;
+}
+
+void
+i_read(int cmdmode, const char *direct, void (*dotitle) (),
+ void (*doentry) (), const onekey_t * rcmdlist, int bidcache)
+{
+ keeploc_t *locmem = NULL;
+ int recbase = 0, mode;
+ int entries = 0;
+ char currdirect0[PATHLEN];
+ int last_line0 = last_line;
+ int bottom_line = 0;
+ fileheader_t *headers0 = headers;
+ int headers_size0 = headers_size;
+
+ strlcpy(currdirect0, currdirect, sizeof(currdirect0));
+#define FHSZ sizeof(fileheader_t)
+ /* Ptt: 這邊 headers 可以針對看板的最後 60 篇做 cache */
+ headers_size = p_lines;
+ headers = (fileheader_t *) calloc(headers_size, FHSZ);
+ assert(headers != NULL);
+ strlcpy(currdirect, direct, sizeof(currdirect));
+ mode = NEWDIRECT;
+
+ do {
+ /* 依據 mode 顯示 fileheader */
+ setutmpmode(cmdmode);
+ switch (mode) {
+ case NEWDIRECT: /* 第一次載入此目錄 */
+ case DIRCHANGED:
+ if (bidcache > 0 && !(currmode & (MODE_SELECT | MODE_DIGEST))){
+ if( (last_line = getbtotal(currbid)) == 0 ){
+ setbtotal(currbid);
+ setbottomtotal(currbid);
+ last_line = getbtotal(currbid);
+ }
+ bottom_line = last_line;
+ last_line += getbottomtotal(currbid);
+ }
+ else
+ bottom_line = last_line = get_num_records(currdirect, FHSZ);
+
+ if (mode == NEWDIRECT) {
+ int num;
+ num = last_line - p_lines + 1;
+ locmem = getkeep(currdirect, num < 1 ? 1 : num, last_line);
+ }
+ recbase = -1;
+ /* no break */
+
+ case FULLUPDATE:
+ (*dotitle) ();
+ /* no break */
+
+ case PARTUPDATE:
+ if (headers_size != p_lines) {
+ headers_size = p_lines;
+ headers = (fileheader_t *) realloc(headers, headers_size*FHSZ);
+ assert(headers);
+ }
+
+ /* In general, records won't be reloaded in PARTUPDATE state.
+ * But since a board is often changed and cached, it is always
+ * reloaded here. */
+ if (bidcache > 0 && !(currmode & (MODE_SELECT | MODE_DIGEST))) {
+ int rec_num;
+ bottom_line = getbtotal(currbid);
+ rec_num = bottom_line + getbottomtotal(currbid);
+ if (last_line != rec_num) {
+ last_line = rec_num;
+ recbase = -1;
+ }
+ }
+
+ if (recbase != locmem->top_ln) { //headers reload
+ recbase = locmem->top_ln;
+ if (recbase > last_line) {
+ recbase = last_line - headers_size + 1;
+ if (recbase < 1)
+ recbase = 1;
+ locmem->top_ln = recbase;
+ }
+ /* XXX if entries return -1 */
+ entries = get_records_and_bottom(currdirect,
+ headers, recbase, headers_size, last_line, bottom_line);
+ }
+ if (locmem->crs_ln > last_line)
+ locmem->crs_ln = last_line;
+ move(3, 0);
+ clrtobot();
+ /* no break */
+ case PART_REDRAW:
+ move(3, 0);
+ if( last_line == 0 )
+ outs(" 沒有文章...");
+ else {
+ int i;
+ for( i = 0; i < entries ; i++ )
+ (*doentry) (locmem->top_ln + i, &headers[i]);
+ }
+ /* no break */
+ case READ_REDRAW:
+ if(curredit & EDIT_ITEM)
+ outmsglr(ANSI_COLOR(44) " 私人收藏 " ANSI_COLOR(30;47), 10,
+ " 繼續? ", 7);
+ else if (curredit & EDIT_MAIL)
+ outmsglr(MSG_MAILER, MSG_MAILER_LEN, "", 0);
+ else
+ outmsglr(MSG_POSTER, MSG_POSTER_LEN, "", 0);
+ break;
+
+ case TITLE_REDRAW:
+ (*dotitle) ();
+ break;
+
+ case HEADERS_RELOAD:
+ if (recbase != locmem->top_ln) {
+ recbase = locmem->top_ln;
+ if (recbase > last_line) {
+ recbase = last_line - p_lines + 1;
+ if (recbase < 1)
+ recbase = 1;
+ locmem->top_ln = recbase;
+ }
+ if(headers_size != p_lines) {
+ headers_size = p_lines;
+ headers = (fileheader_t *) realloc(headers, headers_size*FHSZ);
+ assert(headers);
+ }
+ /* XXX if entries return -1 */
+ entries =
+ get_records_and_bottom(currdirect, headers, recbase,
+ headers_size, last_line, bottom_line);
+ }
+ break;
+ } //end switch
+ mode = i_read_key(rcmdlist, locmem, currbid, bottom_line);
+ } while (mode != DOQUIT);
+#undef FHSZ
+
+ free(headers);
+ last_line = last_line0;
+ headers = headers0;
+ headers_size = headers_size0;
+ strlcpy(currdirect, currdirect0, sizeof(currdirect));
+ return;
+}
diff --git a/pttbbs/mbbsd/record.c b/pttbbs/mbbsd/record.c
new file mode 100644
index 00000000..c58b7ecc
--- /dev/null
+++ b/pttbbs/mbbsd/record.c
@@ -0,0 +1,643 @@
+/* $Id$ */
+
+#include "bbs.h"
+
+#undef HAVE_MMAP
+#define BUFSIZE 512
+
+static void
+PttLock(int fd, int start, int size, int mode)
+{
+ static struct flock lock_it;
+ int ret;
+
+ lock_it.l_whence = SEEK_CUR;/* from current point */
+ lock_it.l_start = start; /* -"- */
+ lock_it.l_len = size; /* length of data */
+ lock_it.l_type = mode; /* set exclusive/write lock */
+ lock_it.l_pid = 0; /* pid not actually interesting */
+ while ((ret = fcntl(fd, F_SETLKW, &lock_it)) < 0 && errno == EINTR)
+ sleep(1);
+}
+
+#define safewrite write
+
+int
+get_num_records(const char *fpath, int size)
+{
+ struct stat st;
+ if (stat(fpath, &st) == -1)
+ {
+ /* TODO: delete this entry, or mark as read */
+ return 0;
+ }
+ return st.st_size / size;
+}
+
+int
+get_sum_records(const char *fpath, int size)
+{
+ struct stat st;
+ int ans = 0;
+ FILE *fp;
+ fileheader_t fhdr;
+ char buf[200], *p;
+
+ // Ptt : should avoid big loop
+ if ((fp = fopen(fpath, "r"))==NULL)
+ return -1;
+
+ strlcpy(buf, fpath, sizeof(buf));
+ p = strrchr(buf, '/');
+ assert(p);
+ p++;
+
+ while (fread(&fhdr, size, 1, fp)==1) {
+ strlcpy(p, fhdr.filename, sizeof(buf) - (p - buf));
+ if (stat(buf, &st) == 0 && S_ISREG(st.st_mode) && st.st_nlink == 1)
+ ans += st.st_size;
+ }
+ fclose(fp);
+ return ans / 1024;
+}
+
+int
+get_record_keep(const char *fpath, void *rptr, int size, int id, int *fd)
+{
+ /* 和 get_record() 一樣. 不過藉由 *fd, 可使同一個檔案不要一直重複開關 */
+ if (id >= 1 &&
+ (*fd > 0 ||
+ ((*fd = open(fpath, O_RDONLY, 0)) > 0))){ // FIXME leak if *fd==0
+ if (lseek(*fd, (off_t) (size * (id - 1)), SEEK_SET) != -1) {
+ if (read(*fd, rptr, size) == size) {
+ return 0;
+ }
+ }
+ }
+ return -1;
+}
+
+int
+get_record(const char *fpath, void *rptr, int size, int id)
+{
+ int fd = -1;
+ /* TODO merge with get_records() */
+
+ if (id >= 1 && (fd = open(fpath, O_RDONLY, 0)) != -1) {
+ if (lseek(fd, (off_t) (size * (id - 1)), SEEK_SET) != -1) {
+ if (read(fd, rptr, size) == size) {
+ close(fd);
+ return 0;
+ }
+ }
+ close(fd);
+ }
+ return -1;
+}
+
+int
+get_records(const char *fpath, void *rptr, int size, int id, int number)
+{
+ int fd;
+
+ if (id < 1 || (fd = open(fpath, O_RDONLY, 0)) == -1)
+ return -1;
+
+ if (lseek(fd, (off_t) (size * (id - 1)), SEEK_SET) == -1) {
+ close(fd);
+ return 0;
+ }
+ if ((id = read(fd, rptr, size * number)) == -1) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return id / size;
+}
+
+int
+substitute_record(const char *fpath, const void *rptr, int size, int id)
+{
+ int fd;
+ int offset=size * (id - 1);
+ if (id < 1 || (fd = open(fpath, O_WRONLY | O_CREAT, 0644)) == -1)
+ return -1;
+
+ lseek(fd, (off_t) (offset), SEEK_SET);
+ PttLock(fd, offset, size, F_WRLCK);
+ write(fd, rptr, size);
+ PttLock(fd, offset, size, F_UNLCK);
+ close(fd);
+
+ return 0;
+}
+
+/* return index>0 if thisstamp==stamp[index],
+ * return -index<0 if stamp[index-1]<thisstamp<stamp[index+1], XXX thisstamp ?<>? stamp[index]
+ * or XXX filename[index]=""
+ * return 0 if error
+ */
+int
+getindex_m(const char *direct, fileheader_t *fhdr, int end, int isloadmoney)
+{ // Ptt: 從前面找很費力 太暴力
+ int fd = -1, begin = 1, i, s, times, stamp;
+ fileheader_t fh;
+
+ int n = get_num_records(direct, sizeof(fileheader_t));
+ if( end > n || end<=0 )
+ end = n;
+ stamp = atoi(fhdr->filename + 2);
+ for( i = (begin + end ) / 2, times = 0 ;
+ end >= begin && times < 20 ; /* 最多只找 20 次 */
+ i = (begin + end ) / 2, ++times ){
+ if( get_record_keep(direct, &fh, sizeof(fileheader_t), i, &fd)==-1 ||
+ !fh.filename[0] )
+ break;
+ s = atoi(fh.filename + 2);
+ if( s > stamp )
+ end = i - 1;
+ else if( s == stamp ){
+ close(fd);
+ if(isloadmoney)
+ fhdr->multi.money = fh.multi.money;
+ return i;
+ }
+ else
+ begin = i + 1;
+ }
+
+ if( times < 20) // Not forever loop. It any because of deletion.
+ {
+ close(fd);
+ return -i;
+ }
+ if( fd != -1 )
+ close(fd);
+ return 0;
+}
+
+inline int
+getindex(const char *direct, fileheader_t *fhdr, int end)
+{
+ return getindex_m(direct, fhdr, end, 0);
+}
+
+int
+substitute_ref_record(const char *direct, fileheader_t * fhdr, int ent)
+{
+ fileheader_t hdr;
+ char fname[PATHLEN];
+ int num = 0;
+
+ /* rocker.011018: 串接模式用reference增進效率 */
+ if (!(fhdr->filemode & FILE_BOTTOM) && (fhdr->multi.refer.flag) &&
+ (num = fhdr->multi.refer.ref)){
+ setdirpath(fname, direct, ".DIR");
+ get_record(fname, &hdr, sizeof(hdr), num);
+ if (strcmp(hdr.filename, fhdr->filename)) {
+ if((num = getindex_m(fname, fhdr, num, 1))>0) {
+ substitute_record(fname, fhdr, sizeof(*fhdr), num);
+ }
+ }
+ else if(num>0) {
+ fhdr->multi.money = hdr.multi.money;
+ substitute_record(fname, fhdr, sizeof(*fhdr), num);
+ }
+ fhdr->multi.refer.flag = 1;
+ fhdr->multi.refer.ref = num; // Ptt: update now!
+ }
+ substitute_record(direct, fhdr, sizeof(*fhdr), ent);
+ return num;
+}
+
+
+/* rocker.011022: 避免lock檔開啟時不正常斷線,造成永久lock */
+#ifndef _BBS_UTIL_C_
+static int
+force_open(const char *fname)
+{
+ int fd;
+ time4_t expire;
+
+ expire = now - 3600; /* lock 存在超過一個小時就是有問題! */
+
+ if (dasht(fname) < expire)
+ return -1;
+ unlink(fname);
+ fd = open(fname, O_WRONLY | O_TRUNC, 0644);
+
+ return fd;
+}
+#endif
+
+/* new/old/lock file processing */
+typedef struct nol_t {
+ char newfn[256];
+ char oldfn[256];
+ char lockfn[256];
+} nol_t;
+
+#ifndef _BBS_UTIL_C_
+static void
+nolfilename(nol_t * n, const char *fpath)
+{
+ snprintf(n->newfn, sizeof(n->newfn), "%s.new", fpath);
+ snprintf(n->oldfn, sizeof(n->oldfn), "%s.old", fpath);
+ snprintf(n->lockfn, sizeof(n->lockfn), "%s.lock", fpath);
+}
+#endif
+
+int
+delete_records(const char *fpath, int size, int id, int num)
+{
+ char abuf[BUFSIZE];
+ int fi, fo, locksize=0, readsize=0, offset = size * (id - 1), c, d=0;
+ struct stat st;
+
+
+ if ((fi=open(fpath, O_RDONLY, 0)) == -1)
+ return -1;
+
+ if ((fo=open(fpath, O_WRONLY, 0)) == -1)
+ {
+ close(fi);
+ return -1;
+ }
+
+ if(fstat(fi, &st)==-1)
+ { close(fo); close(fi); return -1;}
+
+ locksize = st.st_size - offset;
+ readsize = locksize - size*num;
+ if (locksize < 0 )
+ { close(fo); close(fi); return -1;}
+
+ PttLock(fo, offset, locksize, F_WRLCK);
+ if(readsize>0)
+ {
+ lseek(fi, offset+size, SEEK_SET);
+ lseek(fo, offset, SEEK_SET);
+ while( d<readsize && (c = read(fi, abuf, BUFSIZE))>0)
+ {
+ write(fo, abuf, c);
+ d=d+c;
+ }
+ }
+ close(fi);
+ ftruncate(fo, st.st_size - size*num);
+ PttLock(fo, offset, locksize, F_UNLCK);
+ close(fo);
+ return 0;
+
+}
+
+
+int delete_record(const char *fpath, int size, int id)
+{
+ return delete_records(fpath, size, id, 1);
+}
+
+
+#ifndef _BBS_UTIL_C_
+#ifdef SAFE_ARTICLE_DELETE
+void safe_delete_range(const char *fpath, int id1, int id2)
+{
+ int fd, i;
+ fileheader_t fhdr;
+ char fullpath[STRLEN], *t;
+ strlcpy(fullpath, fpath, sizeof(fullpath));
+ t = strrchr(fullpath, '/');
+ assert(t);
+ t++;
+ if( (fd = open(fpath, O_RDONLY)) == -1 )
+ return;
+ for( i = 1 ; (read(fd, &fhdr, sizeof(fileheader_t)) ==
+ sizeof(fileheader_t)) ; ++i ){
+ strcpy(t, fhdr.filename);
+ /* rocker.011018: add new tag delete */
+ if (!((fhdr.filemode & FILE_MARKED) || /* 標記 */
+ (fhdr.filemode & FILE_DIGEST) || /* 文摘 */
+ (id1 && (i < id1 || i > id2)) || /* range */
+ (!id1 && Tagger(atoi(t + 2), i, TAG_NIN)))) /* TagList */
+ safe_article_delete(i, &fhdr, fpath);
+ }
+ close(fd);
+}
+#endif
+
+int
+delete_range(const char *fpath, int id1, int id2)
+{
+ fileheader_t fhdr;
+ nol_t my;
+ char fullpath[STRLEN], *t;
+ int fdr, fdw, fd;
+ int count, dcount=0;
+
+ nolfilename(&my, fpath);
+
+ if ((fd = open(my.lockfn, O_RDWR | O_CREAT | O_APPEND, 0644)) == -1)
+ return -1;
+
+ flock(fd, LOCK_EX);
+
+ if ((fdr = open(fpath, O_RDONLY, 0)) == -1) {
+ flock(fd, LOCK_UN);
+ close(fd);
+ return -1;
+ }
+ if (
+ ((fdw = open(my.newfn, O_WRONLY | O_CREAT | O_EXCL, 0644)) == -1) &&
+ ((fdw = force_open(my.newfn)) == -1)) {
+ close(fdr);
+ flock(fd, LOCK_UN);
+ close(fd);
+ return -1;
+ }
+ count = 1;
+ strlcpy(fullpath, fpath, sizeof(fullpath));
+ t = strrchr(fullpath, '/');
+ assert(t);
+ t++;
+
+ while (read(fdr, &fhdr, sizeof(fileheader_t)) == sizeof(fileheader_t)) {
+ strcpy(t, fhdr.filename);
+
+ /* rocker.011018: add new tag delete */
+ if (
+ (fhdr.filemode & FILE_MARKED) || /* 標記 */
+ ((fhdr.filemode & FILE_DIGEST) && (currstat != RMAIL) )||
+ /* 文摘 , FILE_DIGEST is used as REPLIED in mail menu.*/
+ (id1 && (count < id1 || count > id2)) || /* range */
+ (!id1 && Tagger(atoi(t + 2), count, TAG_NIN))) { /* TagList */
+ if ((safewrite(fdw, &fhdr, sizeof(fileheader_t)) == -1)) {
+ close(fdr);
+ close(fdw);
+ unlink(my.newfn);
+ flock(fd, LOCK_UN);
+ close(fd);
+ return -1;
+ }
+ } else {
+ //if (dashd(fullpath))
+ unlink(fullpath);
+ dcount++;
+ }
+ ++count;
+ }
+ close(fdr);
+ close(fdw);
+ if (Rename(fpath, my.oldfn) == -1 || Rename(my.newfn, fpath) == -1) {
+ flock(fd, LOCK_UN);
+ close(fd);
+ return -1;
+ }
+ flock(fd, LOCK_UN);
+ close(fd);
+ return dcount;
+}
+#endif
+
+
+#ifdef SAFE_ARTICLE_DELETE
+int
+safe_article_delete(int ent, const fileheader_t *fhdr, const char *direct)
+{
+ fileheader_t newfhdr;
+ memcpy(&newfhdr, fhdr, sizeof(fileheader_t));
+ sprintf(newfhdr.title, "(本文已被刪除)");
+ strcpy(newfhdr.filename, ".deleted");
+ strcpy(newfhdr.owner, "-");
+ substitute_record(direct, &newfhdr, sizeof(newfhdr), ent);
+ return 0;
+}
+
+int
+safe_article_delete_range(const char *direct, int from, int to)
+{
+ fileheader_t newfhdr;
+ int fd;
+ char fn[128], *ptr;
+
+ strlcpy(fn, direct, sizeof(fn));
+ if( (ptr = rindex(fn, '/')) == NULL )
+ return 0;
+
+ ++ptr;
+ if( (fd = open(direct, O_RDWR)) != -1 &&
+ lseek(fd, sizeof(fileheader_t) * (from - 1), SEEK_SET) != -1 ){
+
+ for( ; from <= to ; ++from ){
+ read(fd, &newfhdr, sizeof(fileheader_t));
+ if( newfhdr.filemode & (FILE_MARKED | FILE_DIGEST) )
+ continue;
+ if(newfhdr.filename[0]=='L') newfhdr.filename[0]='M';
+ strlcpy(ptr, newfhdr.filename, sizeof(newfhdr.filename));
+ unlink(fn);
+
+ sprintf(newfhdr.title, "(本文已被刪除)");
+ strcpy(newfhdr.filename, ".deleted");
+ strcpy(newfhdr.owner, "-");
+ // because off_t is unsigned, we could NOT seek backward.
+ lseek(fd, sizeof(fileheader_t) * (from - 1), SEEK_SET);
+ write(fd, &newfhdr, sizeof(fileheader_t));
+ }
+ close(fd);
+ }
+ return 0;
+}
+
+
+#endif
+
+int
+apply_record(const char *fpath, int (*fptr) (void *item, void *optarg), int size, void *arg){
+ char abuf[BUFSIZE];
+ int fp;
+
+ if((fp=open(fpath, O_RDONLY, 0)) == -1)
+ return -1;
+
+ assert(size<=sizeof(abuf));
+ while (read(fp, abuf, size) == (size_t)size)
+ if ((*fptr) (abuf, arg) == QUIT) {
+ close(fp);
+ return QUIT;
+ }
+ close(fp);
+ return 0;
+}
+
+/* mail / post 時,依據時間建立檔案,加上郵戳 */
+int
+stampfile_u(char *fpath, fileheader_t * fh)
+ // Ptt: stampfile_u: won't clear fileheader
+ // stampfile: will clear fileheader
+{
+ register char *ip = fpath;
+ time4_t dtime = COMMON_TIME;
+ struct tm *ptime;
+#ifdef _BBS_UTIL_C_
+ int fp = 0; //Ptt: don't need to check
+ // for utils, the time may be the same between several runs, by scw
+#endif
+
+ if (access(fpath, X_OK | R_OK | W_OK))
+ mkdir(fpath, 0755);
+
+ while (*(++ip));
+ *ip++ = '/';
+#ifdef _BBS_UTIL_C_
+ do {
+#endif
+ sprintf(ip, "M.%d.A.%3.3X", (int)(++dtime), (unsigned int)(random() & 0xFFF));
+#ifdef _BBS_UTIL_C_
+ if (fp == -1 && errno != EEXIST)
+ return -1;
+ } while ((fp = open(fpath, O_CREAT | O_EXCL | O_WRONLY, 0644)) == -1);
+ close(fp);
+#endif
+ strlcpy(fh->filename, ip, sizeof(fh->filename));
+ ptime = localtime4(&dtime);
+ snprintf(fh->date, sizeof(fh->date),
+ "%2d/%02d", ptime->tm_mon + 1, ptime->tm_mday);
+ return 0;
+}
+
+inline int
+stampfile(char *fpath, fileheader_t * fh)
+{
+ memset(fh, 0, sizeof(fileheader_t));
+ return stampfile_u(fpath, fh);
+}
+
+void
+stampdir(char *fpath, fileheader_t * fh)
+{
+ register char *ip = fpath;
+ time4_t dtime = COMMON_TIME;
+ struct tm *ptime;
+
+ if (access(fpath, X_OK | R_OK | W_OK))
+ mkdir(fpath, 0755);
+
+ while (*(++ip));
+ *ip++ = '/';
+ do {
+ sprintf(ip, "D%X", (int)++dtime & 07777);
+ } while (mkdir(fpath, 0755) == -1);
+ memset(fh, 0, sizeof(fileheader_t));
+ strlcpy(fh->filename, ip, sizeof(fh->filename));
+ ptime = localtime4(&dtime);
+ snprintf(fh->date, sizeof(fh->date),
+ "%2d/%02d", ptime->tm_mon + 1, ptime->tm_mday);
+}
+
+void
+stamplink(char *fpath, fileheader_t * fh)
+{
+ register char *ip = fpath;
+ time4_t dtime = COMMON_TIME;
+ struct tm *ptime;
+
+ if (access(fpath, X_OK | R_OK | W_OK))
+ mkdir(fpath, 0755);
+
+ while (*(++ip));
+ *ip++ = '/';
+ do {
+ sprintf(ip, "S%X", (int)++dtime);
+ } while (symlink("temp", fpath) == -1);
+ memset(fh, 0, sizeof(fileheader_t));
+ strlcpy(fh->filename, ip, sizeof(fh->filename));
+ ptime = localtime4(&dtime);
+ snprintf(fh->date, sizeof(fh->date),
+ "%2d/%02d", ptime->tm_mon + 1, ptime->tm_mday);
+}
+
+int
+append_record(const char *fpath, const fileheader_t * record, int size)
+{
+ int fd, fsize=0, index;
+ struct stat st;
+
+ if ((fd = open(fpath, O_WRONLY | O_CREAT, 0644)) == -1) {
+ char buf[STRLEN];
+ assert(errno != EISDIR);
+ sprintf(buf, "id(%s), open(%s)", cuser.userid, fpath);
+ perror(buf);
+ return -1;
+ }
+ flock(fd, LOCK_EX);
+
+ if(fstat(fd, &st)!=-1)
+ fsize = st.st_size;
+
+ index = fsize / size;
+ lseek(fd, index * size, SEEK_SET); // avoid offset
+
+ safewrite(fd, record, size);
+
+ flock(fd, LOCK_UN);
+ close(fd);
+ return index + 1;
+}
+
+int
+append_record_forward(char *fpath, fileheader_t * record, int size, const char *origid)
+{
+#if !defined(_BBS_UTIL_C_)
+ if (get_num_records(fpath, sizeof(fileheader_t)) <= MAX_KEEPMAIL * 2) {
+ FILE *fp;
+ char buf[512];
+ int n;
+
+ for (n = strlen(fpath) - 1; fpath[n] != '/' && n > 0; n--);
+ strncpy(buf, fpath, n + 1);
+ if (n + sizeof(".forward") > sizeof(buf))
+ return -1;
+ strcpy(buf + n + 1, ".forward");
+ if ((fp = fopen(buf, "r"))) {
+
+ char address[64];
+ int flIdiotSent2Self = 0;
+ int oidlen = origid ? strlen(origid) : 0;
+
+ address[0] = 0;
+ fscanf(fp, "%63s", address);
+ fclose(fp);
+ /* some idiots just set forwarding to themselves.
+ * and even after we checked "sameid", some still
+ * set STUPID_ID.bbs@host <- "自以為聰明"
+ * damn it, we have a complex rule now.
+ */
+ if(oidlen > 0) {
+ if (strncasecmp(address, origid, oidlen) == 0)
+ {
+ int addrlen = strlen(address);
+ if( addrlen == oidlen ||
+ (addrlen > oidlen &&
+ strcasecmp(address + oidlen, str_mail_address) == 0))
+ flIdiotSent2Self = 1;
+ }
+ }
+
+ if (buf[0] && buf[0] != ' ' && !flIdiotSent2Self) {
+ buf[n + 1] = 0;
+ strcat(buf, record->filename);
+ append_record(fpath, record, size);
+#ifndef USE_BSMTP
+ bbs_sendmail(buf, record->title, address);
+#else
+ bsmtp(buf, record->title, address);
+#endif
+ return 0;
+ }
+ }
+ }
+#endif
+
+ append_record(fpath, record, size);
+
+ return 0;
+}
diff --git a/pttbbs/mbbsd/register.c b/pttbbs/mbbsd/register.c
new file mode 100644
index 00000000..a9b5e911
--- /dev/null
+++ b/pttbbs/mbbsd/register.c
@@ -0,0 +1,336 @@
+/* $Id$ */
+#include "bbs.h"
+
+char *
+genpasswd(char *pw)
+{
+ if (pw[0]) {
+ char saltc[2], c;
+ int i;
+
+ i = 9 * getpid();
+ saltc[0] = i & 077;
+ saltc[1] = (i >> 6) & 077;
+
+ for (i = 0; i < 2; i++) {
+ c = saltc[i] + '.';
+ if (c > '9')
+ c += 7;
+ if (c > 'Z')
+ c += 6;
+ saltc[i] = c;
+ }
+ return crypt(pw, saltc);
+ }
+ return "";
+}
+
+// NOTE it will clean string in "plain"
+int
+checkpasswd(const char *passwd, char *plain)
+{
+ int ok;
+ char *pw;
+
+ ok = 0;
+ pw = crypt(plain, passwd);
+ if(pw && strcmp(pw, passwd)==0)
+ ok = 1;
+ memset(plain, 0, strlen(plain));
+
+ return ok;
+}
+
+/* 檢查 user 註冊情況 */
+int
+bad_user_id(const char *userid)
+{
+ if(!is_validuserid(userid))
+ return 1;
+
+ if (strcasecmp(userid, str_new) == 0)
+ return 1;
+
+#ifdef NO_GUEST_ACCOUNT_REG
+ if (strcasecmp(userid, STR_GUEST) == 0)
+ return 1;
+#endif
+
+ /* in2: 原本是用strcasestr,
+ 不過有些人中間剛剛好出現這個字應該還算合理吧? */
+ if( strncasecmp(userid, "fuck", 4) == 0 ||
+ strncasecmp(userid, "shit", 4) == 0 )
+ return 1;
+
+ /*
+ * while((ch = *(++userid))) if(not_alnum(ch)) return 1;
+ */
+ return 0;
+}
+
+/* -------------------------------- */
+/* New policy for allocate new user */
+/* (a) is the worst user currently */
+/* (b) is the object to be compared */
+/* -------------------------------- */
+static int
+compute_user_value(const userec_t * urec, time4_t clock)
+{
+ int value;
+
+ /* if (urec) has XEMPT permission, don't kick it */
+ if ((urec->userid[0] == '\0') || (urec->userlevel & PERM_XEMPT)
+ /* || (urec->userlevel & PERM_LOGINOK) */
+ || !strcmp(STR_GUEST, urec->userid))
+ return 999999;
+ value = (clock - urec->lastlogin) / 60; /* minutes */
+
+ /* new user should register in 30 mins */
+ if (strcmp(urec->userid, str_new) == 0)
+ return 30 - value;
+#if 0
+ if (!urec->numlogins) /* 未 login 成功者,不保留 */
+ return -1;
+ if (urec->numlogins <= 3) /* #login 少於三者,保留 20 天 */
+ return 20 * 24 * 60 - value;
+#endif
+ /* 未完成註冊者,保留 15 天 */
+ /* 一般情況,保留 120 天 */
+ return (urec->userlevel & PERM_LOGINOK ? 120 : 15) * 24 * 60 - value;
+}
+
+int
+check_and_expire_account(int uid, const userec_t * urec)
+{
+ char genbuf[200];
+ int val;
+ if ((val = compute_user_value(urec, now)) < 0) {
+ snprintf(genbuf, sizeof(genbuf), "#%d %-12s %15.15s %d %d %d",
+ uid, urec->userid, ctime4(&(urec->lastlogin)) + 4,
+ urec->numlogins, urec->numposts, val);
+ if (val > -1 * 60 * 24 * 365) {
+ log_usies("CLEAN", genbuf);
+ kill_user(uid, urec->userid);
+ } else {
+ val = 0;
+ log_usies("DATED", genbuf);
+ }
+ }
+ return val;
+}
+
+
+int
+setupnewuser(const userec_t *user)
+{
+ char genbuf[50];
+ char *fn_fresh = ".fresh";
+ userec_t utmp;
+ time_t clock;
+ struct stat st;
+ int fd, uid;
+
+ clock = now;
+
+ /* Lazy method : 先找尋已經清除的過期帳號 */
+ if ((uid = dosearchuser("", NULL)) == 0) {
+ /* 每 1 個小時,清理 user 帳號一次 */
+ if ((stat(fn_fresh, &st) == -1) || (st.st_mtime < clock - 3600)) {
+ if ((fd = open(fn_fresh, O_RDWR | O_CREAT, 0600)) == -1)
+ return -1;
+ write(fd, ctime(&clock), 25);
+ close(fd);
+ log_usies("CLEAN", "dated users");
+
+ fprintf(stdout, "尋找新帳號中, 請稍待片刻...\n\r");
+
+ if ((fd = open(fn_passwd, O_RDWR | O_CREAT, 0600)) == -1)
+ return -1;
+
+ /* 不曉得為什麼要從 2 開始... Ptt:因為SYSOP在1 */
+ for (uid = 2; uid <= MAX_USERS; uid++) {
+ passwd_query(uid, &utmp);
+ check_and_expire_account(uid, &utmp);
+ }
+ }
+ }
+
+ /* initialize passwd semaphores */
+ if (passwd_init())
+ exit(1);
+
+ passwd_lock();
+
+ uid = dosearchuser("", NULL);
+ if ((uid <= 0) || (uid > MAX_USERS)) {
+ passwd_unlock();
+ vmsg("抱歉,使用者帳號已經滿了,無法註冊新的帳號");
+ exit(1);
+ }
+
+ setuserid(uid, user->userid);
+ snprintf(genbuf, sizeof(genbuf), "uid %d", uid);
+ log_usies("APPLY", genbuf);
+
+ SHM->money[uid - 1] = user->money;
+
+ if (passwd_update(uid, (userec_t *)user) == -1) {
+ passwd_unlock();
+ vmsg("客滿了,再見!");
+ exit(1);
+ }
+
+ passwd_unlock();
+
+ return uid;
+}
+
+void
+new_register(void)
+{
+ userec_t newuser;
+ char passbuf[STRLEN];
+ int try, id, uid;
+
+#ifdef HAVE_USERAGREEMENT
+ more(HAVE_USERAGREEMENT, YEA);
+ while( 1 ){
+ getdata(b_lines - 1, 0, "請問您接受這份使用者條款嗎? (yes/no) ",
+ passbuf, 4, LCECHO);
+ if( passbuf[0] == 'y' )
+ break;
+ if( passbuf[0] == 'n' ){
+ vmsg("抱歉, 您須要接受使用者條款才能註冊帳號享受我們的服務唷!");
+ exit(1);
+ }
+ vmsg("請輸入 y表示接受, n表示不接受");
+ }
+#endif
+ memset(&newuser, 0, sizeof(newuser));
+ more("etc/register", NA);
+ try = 0;
+ while (1) {
+ userec_t xuser;
+ int minute;
+
+ if (++try >= 6) {
+ vmsg("您嘗試錯誤的輸入太多,請下次再來吧");
+ exit(1);
+ }
+ getdata(17, 0, msg_uid, newuser.userid,
+ sizeof(newuser.userid), DOECHO);
+ strcpy(passbuf, newuser.userid);
+
+ if (bad_user_id(passbuf))
+ outs("無法接受這個代號,請使用英文字母,並且不要包含空格\n");
+ else if ((id = getuser(passbuf, &xuser)) &&
+ (minute = check_and_expire_account(id, &xuser)) >= 0) {
+ if (minute == 999999) // XXX magic number. It should be greater than MAX_USERS at least.
+ outs("此代號已經有人使用 是不死之身");
+ else {
+ prints("此代號已經有人使用 還有%d天才過期 \n", minute / (60 * 24));
+ }
+ } else
+ break;
+ }
+
+ try = 0;
+ while (1) {
+ if (++try >= 6) {
+ vmsg("您嘗試錯誤的輸入太多,請下次再來吧");
+ exit(1);
+ }
+ move(18, 0); clrtoeol();
+ outs(ANSI_COLOR(1;33) "為避免被偷看,您的密碼並不會顯示在畫面上,直接輸入完後按 Enter 鍵即可。" ANSI_RESET);
+ if ((getdata(19, 0, "請設定密碼:", passbuf,
+ sizeof(passbuf), NOECHO) < 3) ||
+ !strcmp(passbuf, newuser.userid)) {
+ outs("密碼太簡單,易遭入侵,至少要 4 個字,請重新輸入\n");
+ continue;
+ }
+ strlcpy(newuser.passwd, passbuf, PASSLEN);
+ getdata(20, 0, "請檢查密碼:", passbuf, sizeof(passbuf), NOECHO);
+ if (strncmp(passbuf, newuser.passwd, PASSLEN)) {
+ outs("密碼輸入錯誤, 請重新輸入密碼.\n");
+ continue;
+ }
+ passbuf[8] = '\0';
+ strlcpy(newuser.passwd, genpasswd(passbuf), PASSLEN);
+ break;
+ }
+ newuser.version = PASSWD_VERSION;
+ newuser.userlevel = PERM_DEFAULT;
+ newuser.uflag = BRDSORT_FLAG | MOVIE_FLAG;
+ newuser.uflag2 = 0;
+ newuser.firstlogin = newuser.lastlogin = now;
+ newuser.money = 0;
+ newuser.pager = PAGER_ON;
+
+#ifdef DBCSAWARE
+ if(u_detectDBCSAwareEvilClient())
+ newuser.uflag &= ~DBCSAWARE_FLAG;
+ else
+ newuser.uflag |= DBCSAWARE_FLAG;
+#endif
+
+ setupnewuser(&newuser);
+
+ if( (uid = initcuser(newuser.userid)) < 0) {
+ vmsg("無法建立帳號");
+ exit(1);
+ }
+ log_usies("REGISTER", fromhost);
+}
+
+
+void
+check_register(void)
+{
+ char *ptr = NULL;
+
+ if (HasUserPerm(PERM_LOGINOK))
+ return;
+
+ /*
+ * 避免使用者被退回註冊單後,在知道退回的原因之前,
+ * 又送出一次註冊單。
+ */
+ if (ISNEWMAIL(currutmp))
+ m_read();
+
+ stand_title("請詳細填寫個人資料");
+
+ while (strlen(cuser.nickname) < 2)
+ getdata(2, 0, "綽號暱稱:", cuser.nickname,
+ sizeof(cuser.nickname), DOECHO);
+
+ for (ptr = cuser.nickname; *ptr; ptr++) {
+ if (*ptr == 9) /* TAB convert */
+ *ptr = ' ';
+ }
+ while (strlen(cuser.realname) < 4)
+ getdata(4, 0, "真實姓名:", cuser.realname,
+ sizeof(cuser.realname), DOECHO);
+
+ while (strlen(cuser.address) < 8)
+ getdata(6, 0, "聯絡地址:", cuser.address,
+ sizeof(cuser.address), DOECHO);
+
+
+ if (!HasUserPerm(PERM_SYSOP)) {
+ /* 回覆過身份認證信函,或曾經 E-mail post 過 */
+ clear();
+ move(9, 3);
+ outs("請詳填寫" ANSI_COLOR(32) "註冊申請單" ANSI_RESET ","
+ "通告站長以獲得進階使用權力。\n\n\n\n");
+ u_register();
+
+#ifdef NEWUSER_LIMIT
+ if (cuser.lastlogin - cuser->firstlogin < 3 * 86400)
+ cuser.userlevel &= ~PERM_POST;
+ more("etc/newuser", YEA);
+#endif
+ }
+}
+/* vim:sw=4
+ */
diff --git a/pttbbs/mbbsd/reversi.c b/pttbbs/mbbsd/reversi.c
new file mode 100644
index 00000000..f32689c5
--- /dev/null
+++ b/pttbbs/mbbsd/reversi.c
@@ -0,0 +1,506 @@
+/* $Id$ */
+
+#include "bbs.h"
+
+#define MAX_TIME (300)
+#define BRDSIZ (8) /* 棋盤單邊大小 */
+
+#define NONE_CHESS " "
+#define WHITE_CHESS "●"
+#define BLACK_CHESS "○"
+#define HINT_CHESS "#"
+#define NONE 0
+#define HINT 1
+#define BLACK 2
+#define WHITE 3
+
+#define STARTY 10
+
+#define INVERT(COLOR) (((COLOR))==WHITE?BLACK:WHITE)
+
+#define IS_BLANK(COLOR) ((COLOR) < BLACK) /* NONE or HINT */
+#define IS_CHESS(COLOR) ((COLOR) >= BLACK)
+#define TURN_TO_COLOR(TURN) (WHITE - (TURN))
+#define COLOR_TO_TURN(COLOR) (WHITE - (COLOR))
+
+typedef char color_t;
+typedef color_t board_t[BRDSIZ + 2][BRDSIZ + 2];
+typedef color_t (*board_p)[BRDSIZ + 2];
+/* [0] & [9] are dummy */
+
+typedef struct {
+ ChessStepType type; /* necessary one */
+ color_t color;
+ rc_t loc;
+} reversi_step_t;
+
+typedef struct {
+ int number[2];
+} reversi_tag_t;
+
+/* chess framework action functions */
+static void reversi_init_user(const userinfo_t *uinfo, ChessUser *user);
+static void reversi_init_user_userec(const userec_t *urec, ChessUser *user);
+static void reversi_init_board(board_t board);
+static void reversi_drawline(const ChessInfo* info, int line);
+static void reversi_movecur(int r, int c);
+static int reversi_prepare_play(ChessInfo* info);
+static int reversi_select(ChessInfo* info, rc_t scrloc, ChessGameResult* result);
+static void reversi_prepare_step(ChessInfo* info, const reversi_step_t* step);
+static ChessGameResult reversi_apply_step(board_t board, const reversi_step_t* step);
+static void reversi_drawstep(ChessInfo* info, const void* move);
+static ChessGameResult reversi_post_game(ChessInfo* info);
+static void reversi_gameend(ChessInfo* info, ChessGameResult result);
+static void reversi_genlog(ChessInfo* info, FILE* fp, ChessGameResult result);
+
+static const char *CHESS_TYPE[] = {NONE_CHESS, HINT_CHESS, BLACK_CHESS, WHITE_CHESS};
+static const char DIRX[] = {-1, -1, -1, 0, 1, 1, 1, 0};
+static const char DIRY[] = {-1, 0, 1, 1, 1, 0, -1, -1};
+
+static const ChessActions reversi_actions = {
+ &reversi_init_user,
+ &reversi_init_user_userec,
+ (void (*) (void*)) &reversi_init_board,
+ &reversi_drawline,
+ &reversi_movecur,
+ &reversi_prepare_play,
+ NULL, /* process_key */
+ &reversi_select,
+ (void (*)(ChessInfo*, const void*)) &reversi_prepare_step,
+ (ChessGameResult (*)(void*, const void*)) &reversi_apply_step,
+ &reversi_drawstep,
+ &reversi_post_game,
+ &reversi_gameend,
+ &reversi_genlog
+};
+
+const static ChessConstants reversi_constants = {
+ sizeof(reversi_step_t),
+ MAX_TIME,
+ BRDSIZ,
+ BRDSIZ,
+ 0,
+ "黑白棋",
+ "photo_reversi",
+#ifdef GLOBAL_REVERSI_LOG
+ GLOBAL_REVERSI_LOG,
+#else
+ NULL,
+#endif
+ { "", "" },
+ { "白棋", "黑棋" },
+};
+
+static int
+can_put(board_t board, color_t who, int x, int y)
+{
+ int i, temp, checkx, checky;
+
+ if (IS_BLANK(board[x][y]))
+ for (i = 0; i < 8; ++i) {
+ checkx = x + DIRX[i];
+ checky = y + DIRY[i];
+ temp = board[checkx][checky];
+ if (IS_BLANK(temp))
+ continue;
+ if (temp != who) {
+ while (board[checkx += DIRX[i]][checky += DIRY[i]] == temp);
+ if (board[checkx][checky] == who)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int
+caculate_hint(board_t board, color_t who)
+{
+ int i, j, count = 0;
+
+ for (i = 1; i <= 8; i++)
+ for (j = 1; j <= 8; j++) {
+ if (board[i][j] == HINT)
+ board[i][j] = NONE;
+ if (can_put(board, who, i, j)) {
+ board[i][j] = HINT;
+ ++count;
+ }
+ }
+ return count;
+}
+
+static void
+reversi_init_user(const userinfo_t* uinfo, ChessUser* user)
+{
+ strlcpy(user->userid, uinfo->userid, sizeof(user->userid));
+ user->win =
+ user->lose =
+ user->tie = 0;
+}
+
+static void
+reversi_init_user_userec(const userec_t* urec, ChessUser* user)
+{
+ strlcpy(user->userid, urec->userid, sizeof(user->userid));
+ user->win =
+ user->lose =
+ user->tie = 0;
+}
+
+static void
+reversi_init_board(board_t board)
+{
+ memset(board, NONE, sizeof(board_t));
+ board[4][4] = board[5][5] = WHITE;
+ board[4][5] = board[5][4] = BLACK;
+
+ caculate_hint(board, BLACK);
+}
+
+static void
+reversi_drawline(const ChessInfo* info, int line){
+ static const char* num_str[] =
+ {"", "1", "2", "3", "4", "5", "6", "7", "8"};
+ if(line)
+ move(line, STARTY);
+
+ if (line == 0) {
+ prints(ANSI_COLOR(1;46) " 黑白棋對戰 " ANSI_COLOR(45)
+ "%30s VS %-20s%10s" ANSI_RESET,
+ info->user1.userid, info->user2.userid,
+ info->mode == CHESS_MODE_WATCH ? "[觀棋模式]" : "");
+ } else if (line == 2)
+ outs(" A B C D E F G H");
+ else if (line == 3)
+ outs("┌─┬─┬─┬─┬─┬─┬─┬─┐");
+ else if (line == 19)
+ outs("└─┴─┴─┴─┴─┴─┴─┴─┘");
+ else if (line == 20)
+ prints(" (" BLACK_CHESS ") %-15s%2d%*s",
+ info->myturn ? info->user1.userid : info->user2.userid,
+ ((reversi_tag_t*)info->tag)->number[COLOR_TO_TURN(BLACK)],
+ 34 - 24, "");
+ else if (line == 21)
+ prints(" (" WHITE_CHESS ") %-15s%2d%*s",
+ info->myturn ? info->user2.userid : info->user1.userid,
+ ((reversi_tag_t*)info->tag)->number[COLOR_TO_TURN(WHITE)],
+ 34 - 24, "");
+ else if (line > 3 && line < 19) {
+ if ((line & 1) == 1)
+ outs("├─┼─┼─┼─┼─┼─┼─┼─┤");
+ else {
+ int x = line / 2 - 1;
+ int y;
+ board_p board = (board_p) info->board;
+
+ move(line, STARTY - 2);
+ prints("%s│", num_str[x]);
+ for(y = 1; y <= 8; ++y)
+ prints("%s│", CHESS_TYPE[(int) board[x][y]]);
+ }
+ }
+
+ ChessDrawExtraInfo(info, line, 4);
+}
+
+static void
+reversi_movecur(int r, int c)
+{
+ move(r * 2 + 4, c * 4 + STARTY + 2);
+}
+
+static int
+reversi_prepare_play(ChessInfo* info)
+{
+ int x, y;
+ int result;
+ board_p board = (board_p) info->board;
+ reversi_tag_t* tag = (reversi_tag_t*) info->tag;
+
+ tag->number[0] = tag->number[1] = 0;
+ for(x = 1; x <= 8; ++x)
+ for(y = 1; y <= 8; ++y)
+ if (IS_CHESS(board[x][y]))
+ ++tag->number[COLOR_TO_TURN(board[x][y])];
+
+ result = !caculate_hint(board, TURN_TO_COLOR(info->turn));
+ if (result) {
+ reversi_step_t step = { CHESS_STEP_SPECIAL, TURN_TO_COLOR(info->turn) };
+ if (info->turn == info->myturn) {
+ ChessStepSend(info, &step);
+ ChessHistoryAppend(info, &step);
+ strcpy(info->last_movestr, "你必須放棄這一步!!");
+ } else {
+ ChessStepReceive(info, &step);
+ strcpy(info->last_movestr, "對方必須放棄這一步!!");
+ }
+ }
+
+ ChessRedraw(info);
+ return result;
+}
+
+static int
+reversi_select(ChessInfo* info, rc_t loc, ChessGameResult* result)
+{
+ board_p board = (board_p) info->board;
+
+ ++loc.r; ++loc.c;
+ if (can_put(board, TURN_TO_COLOR(info->turn), loc.r, loc.c)) {
+ reversi_step_t step = { CHESS_STEP_NORMAL,
+ TURN_TO_COLOR(info->turn), loc };
+ reversi_apply_step(board, &step);
+
+ snprintf(info->last_movestr, sizeof(info->last_movestr),
+ "%c%d", step.loc.c - 1 + 'A', step.loc.r);
+
+ ChessStepSend(info, &step);
+ ChessHistoryAppend(info, &step);
+
+ return 1;
+ } else
+ return 0;
+}
+
+static ChessGameResult
+reversi_apply_step(board_t board, const reversi_step_t* step)
+{
+ int i;
+ color_t opposite = INVERT(step->color);
+
+ if (step->type != CHESS_STEP_NORMAL)
+ return CHESS_RESULT_CONTINUE;
+
+ for (i = 0; i < 8; ++i) {
+ int x = step->loc.r;
+ int y = step->loc.c;
+
+ while (board[x += DIRX[i]][y += DIRY[i]] == opposite);
+
+ if (board[x][y] == step->color) {
+ x = step->loc.r;
+ y = step->loc.c;
+
+ while (board[x += DIRX[i]][y += DIRY[i]] == opposite)
+ board[x][y] = step->color;
+ }
+ }
+ board[step->loc.r][step->loc.c] = step->color;
+
+ return CHESS_RESULT_CONTINUE;
+}
+
+static void
+reversi_prepare_step(ChessInfo* info, const reversi_step_t* step)
+{
+ if (step->type == CHESS_STEP_NORMAL)
+ snprintf(info->last_movestr, sizeof(info->last_movestr),
+ "%c%d", step->loc.c - 1 + 'A', step->loc.r);
+ else if (step->color == TURN_TO_COLOR(info->myturn))
+ strcpy(info->last_movestr, "你必須放棄這一步!!");
+ else
+ strcpy(info->last_movestr, "對方必須放棄這一步!!");
+}
+
+static void
+reversi_drawstep(ChessInfo* info, const void* move)
+{
+ ChessRedraw(info);
+}
+
+static ChessGameResult
+reversi_post_game(ChessInfo* info)
+{
+ int x, y;
+ board_p board = (board_p) info->board;
+ reversi_tag_t* tag = (reversi_tag_t*) info->tag;
+
+ tag->number[0] = tag->number[1] = 0;
+ for(x = 1; x <= 8; ++x)
+ for(y = 1; y <= 8; ++y)
+ if (board[x][y] == HINT)
+ board[x][y] = NONE;
+ else if (IS_CHESS(board[x][y]))
+ ++tag->number[COLOR_TO_TURN(board[x][y])];
+
+ ChessRedraw(info);
+
+ if (tag->number[0] == tag->number[1])
+ return CHESS_RESULT_TIE;
+ else if (tag->number[(int) info->myturn] < tag->number[info->myturn ^ 1])
+ return CHESS_RESULT_LOST;
+ else
+ return CHESS_RESULT_WIN;
+}
+
+static void
+reversi_gameend(ChessInfo* info, ChessGameResult result)
+{
+ /* nothing to do now
+ * TODO game record */
+}
+
+static void
+reversi_genlog(ChessInfo* info, FILE* fp, ChessGameResult result)
+{
+ const int nStep = info->history.used;
+ int i;
+
+ for (i = 2; i <= 21; i++)
+ fprintf(fp, "%.*s\n", big_picture[i].len, big_picture[i].data);
+
+ fprintf(fp, "\n");
+ fprintf(fp, "按 z 可進入打譜模式\n");
+ fprintf(fp, "\n");
+
+ fprintf(fp, "<reversilog>\nblack:%s\nwhite:%s\n",
+ info->myturn ? info->user1.userid : info->user2.userid,
+ info->myturn ? info->user2.userid : info->user1.userid);
+
+ for (i = 0; i < nStep; ++i) {
+ const reversi_step_t* const step =
+ (const reversi_step_t*) ChessHistoryRetrieve(info, i);
+ if (step->type == CHESS_STEP_NORMAL)
+ fprintf(fp, "[%2d]%s ==> %c%-5d", i + 1,
+ CHESS_TYPE[(int) step->color],
+ 'A' + step->loc.c - 1, step->loc.r);
+ else
+ fprintf(fp, "[%2d]%s ==> pass ", i + 1,
+ CHESS_TYPE[(int) step->color]);
+ if (i % 2)
+ fputc('\n', fp);
+ }
+
+ if (i % 2)
+ fputc('\n', fp);
+ fputs("</reversilog>\n", fp);
+}
+
+static int
+reversi_loadlog(FILE *fp, ChessInfo *info)
+{
+ char buf[256];
+
+#define INVALID_ROW(R) ((R) <= 0 || (R) > 8)
+#define INVALID_COL(C) ((C) <= 0 || (C) > 8)
+ while (fgets(buf, sizeof(buf), fp)) {
+ if (strcmp("</reversilog>\n", buf) == 0)
+ return 1;
+ else if (strncmp("black:", buf, 6) == 0 ||
+ strncmp("white:", buf, 6) == 0) {
+ /* /(black|white):([a-zA-Z0-9]+)/; $2 */
+ userec_t rec;
+ ChessUser *user = (buf[0] == 'b' ? &info->user1 : &info->user2);
+
+ chomp(buf);
+ if (getuser(buf + 6, &rec))
+ reversi_init_user_userec(&rec, user);
+ } else if (buf[0] == '[') {
+ /* "[ 1]● ==> C4 [ 2]○ ==> C5" */
+ reversi_step_t step = { CHESS_STEP_NORMAL };
+ int c, r;
+ const char *p = buf;
+ int i;
+
+ for(i=0; i<2; i++) {
+ p = strchr(p, '>');
+
+ if (p == NULL) break;
+
+ ++p; /* skip '>' */
+ while (*p && isspace(*p)) ++p;
+ if (!*p) break;
+
+ /* i=0, p -> "C4 ..." */
+ /* i=1, p -> "C5\n" */
+
+ if (strncmp(p, "pass", 4) == 0)
+ /* [..] .. => pass */
+ step.type = CHESS_STEP_SPECIAL;
+ else {
+ c = p[0] - 'A' + 1;
+ r = atoi(p + 1);
+
+ if (INVALID_COL(c) || INVALID_ROW(r))
+ break;
+
+ step.loc.r = r;
+ step.loc.c = c;
+ }
+
+ step.color = i==0 ? BLACK : WHITE;
+ ChessHistoryAppend(info, &step);
+ }
+ }
+ }
+#undef INVALID_ROW
+#undef INVALID_COL
+ return 0;
+}
+
+void
+reversi(int s, ChessGameMode mode)
+{
+ ChessInfo* info = NewChessInfo(&reversi_actions, &reversi_constants, s, mode);
+ board_t board;
+ reversi_tag_t tag = { { 2, 2 } }; /* will be overridden */
+
+ reversi_init_board(board);
+
+ info->board = board;
+ info->tag = &tag;
+
+ info->cursor.r = 3;
+ info->cursor.c = 3;
+
+ if (mode == CHESS_MODE_WATCH)
+ setutmpmode(CHESSWATCHING);
+ else
+ setutmpmode(REVERSI);
+ currutmp->sig = SIG_REVERSI;
+
+ ChessPlay(info);
+
+ DeleteChessInfo(info);
+}
+
+int
+reversi_main(void)
+{
+ return ChessStartGame('r', SIG_REVERSI, "黑白棋");
+}
+
+int
+reversi_personal(void)
+{
+ reversi(0, CHESS_MODE_PERSONAL);
+ return 0;
+}
+
+int
+reversi_watch(void)
+{
+ return ChessWatchGame(&reversi, REVERSI, "黑白棋");
+}
+
+ChessInfo*
+reversi_replay(FILE* fp)
+{
+ ChessInfo *info;
+
+ info = NewChessInfo(&reversi_actions, &reversi_constants,
+ 0, CHESS_MODE_REPLAY);
+
+ if(!reversi_loadlog(fp, info)) {
+ DeleteChessInfo(info);
+ return NULL;
+ }
+
+ info->board = malloc(sizeof(board_t));
+ info->tag = malloc(sizeof(reversi_tag_t));
+
+ reversi_init_board(info->board);
+ /* tag will be initialized later */
+
+ return info;
+}
diff --git a/pttbbs/mbbsd/screen.c b/pttbbs/mbbsd/screen.c
new file mode 100644
index 00000000..ee3f44f8
--- /dev/null
+++ b/pttbbs/mbbsd/screen.c
@@ -0,0 +1,581 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define o_clear() output(clearbuf,clearbuflen)
+#define o_cleol() output(cleolbuf,cleolbuflen)
+#define o_scrollrev() output(scrollrev,scrollrevlen)
+#define o_standup() output(strtstandout,strtstandoutlen)
+#define o_standdown() output(endstandout,endstandoutlen)
+
+static unsigned short cur_ln = 0, cur_col = 0;
+static unsigned char docls;
+static unsigned char standing = NA;
+static int scrollcnt, tc_col, tc_line;
+
+#define MODIFIED (1) /* if line has been modifed, screen output */
+#define STANDOUT (2) /* if this line has a standout region */
+
+void
+initscr(void)
+{
+ if (!big_picture) {
+ big_picture = (screenline_t *) calloc(scr_lns, sizeof(screenline_t));
+ docls = YEA;
+ }
+}
+
+void
+move(int y, int x)
+{
+ assert(y>=0);
+ assert(x>=0);
+ cur_col = x;
+ cur_ln = y;
+}
+
+void
+getyx(int *y, int *x)
+{
+ *y = cur_ln;
+ *x = cur_col;
+}
+
+static inline
+screenline_t* GetCurrentLine(){
+ register int i = cur_ln + roll;
+ if(i >= scr_lns)
+ i %= scr_lns;
+ return &big_picture[i];
+}
+
+static void
+rel_move(int was_col, int was_ln, int new_col, int new_ln)
+{
+ if (new_ln >= t_lines || new_col >= t_columns)
+ return;
+
+ tc_col = new_col;
+ tc_line = new_ln;
+ if (new_col == 0) {
+ if (new_ln == was_ln) {
+ if (was_col)
+ ochar('\r');
+ return;
+ } else if (new_ln == was_ln + 1) {
+ ochar('\n');
+ if (was_col)
+ ochar('\r');
+ return;
+ }
+ }
+ if (new_ln == was_ln) {
+ if (was_col == new_col)
+ return;
+
+ if (new_col == was_col - 1) {
+ ochar(Ctrl('H'));
+ return;
+ }
+ }
+ do_move(new_col, new_ln);
+}
+
+static void
+standoutput(const char *buf, int ds, int de, int sso, int eso)
+{
+ int st_start, st_end;
+
+ if (eso <= ds || sso >= de) {
+ output(buf + ds, de - ds);
+ } else {
+ st_start = MAX(sso, ds);
+ st_end = MIN(eso, de);
+ if (sso > ds)
+ output(buf + ds, sso - ds);
+ o_standup();
+ output(buf + st_start, st_end - st_start);
+ o_standdown();
+ if (de > eso)
+ output(buf + eso, de - eso);
+ }
+}
+
+void
+redoscr(void)
+{
+ register screenline_t *bp;
+ register int i, j, len;
+
+ o_clear();
+ for (tc_col = tc_line = i = 0, j = roll; i < scr_lns; i++, j++) {
+ if (j >= scr_lns)
+ j = 0;
+ bp = &big_picture[j];
+ if ((len = bp->len)) {
+ rel_move(tc_col, tc_line, 0, i);
+ if (bp->mode & STANDOUT) {
+ standoutput((char *)bp->data, 0, len, bp->sso, bp->eso);
+ }
+ else
+ output((char *)bp->data, len);
+ tc_col += len;
+ if (tc_col >= t_columns) {
+ if (automargins)
+ tc_col = t_columns - 1;
+ else {
+ tc_col -= t_columns;
+ tc_line++;
+ if (tc_line >= t_lines)
+ tc_line = b_lines;
+ }
+ }
+ bp->mode &= ~(MODIFIED);
+ bp->oldlen = len;
+ }
+ }
+ rel_move(tc_col, tc_line, cur_col, cur_ln);
+ docls = scrollcnt = 0;
+ oflush();
+}
+
+void
+redoln(void)
+{
+ screenline_t *slp = GetCurrentLine();
+ int len, mode;
+
+ len = slp->len;
+ rel_move(tc_col, tc_line, 0, cur_ln);
+ if (len)
+ {
+ if ((mode = slp->mode) & STANDOUT)
+ standoutput((char*)slp->data, 0, len, slp->sso, slp->eso);
+ else
+ output((char*)slp->data, len);
+
+ slp->mode = mode & ~(MODIFIED);
+
+ slp->oldlen = tc_col = len;
+ }
+ else
+ clrtoeol();
+ rel_move(tc_col, tc_line, cur_col, cur_ln);
+ oflush();
+}
+
+void
+refresh(void)
+{
+ /* TODO remove unnecessary refresh() call, to save CPU time */
+ register screenline_t *bp = big_picture;
+ register int i, j, len;
+ if (num_in_buf())
+ return;
+
+ if ((docls) || (abs(scrollcnt) >= (scr_lns - 3))) {
+ redoscr();
+ return;
+ }
+ if (scrollcnt < 0) {
+ if (!scrollrevlen) {
+ redoscr();
+ return;
+ }
+ rel_move(tc_col, tc_line, 0, 0);
+ do {
+ o_scrollrev();
+ } while (++scrollcnt);
+ } else if (scrollcnt > 0) {
+ rel_move(tc_col, tc_line, 0, b_lines);
+ do {
+ ochar('\n');
+ } while (--scrollcnt);
+ }
+ for (i = 0, j = roll; i < scr_lns; i++, j++) {
+ if (j >= scr_lns)
+ j = 0;
+ bp = &big_picture[j];
+ len = bp->len;
+ if (bp->mode & MODIFIED && bp->smod < len) {
+ bp->mode &= ~(MODIFIED);
+ if (bp->emod >= len)
+ bp->emod = len - 1;
+ rel_move(tc_col, tc_line, bp->smod, i);
+
+ if (bp->mode & STANDOUT)
+ standoutput((char *)bp->data, bp->smod, bp->emod + 1,
+ bp->sso, bp->eso);
+ else
+ output((char *)&bp->data[bp->smod], bp->emod - bp->smod + 1);
+ tc_col = bp->emod + 1;
+ if (tc_col >= t_columns) {
+ if (automargins)
+ tc_col = t_columns - 1;
+ else {
+ tc_col -= t_columns;
+ tc_line++;
+ if (tc_line >= t_lines)
+ tc_line = b_lines;
+ }
+ }
+ }
+ if (bp->oldlen > len) {
+ /* XXX len/oldlen also count the length of escape sequence,
+ * before we fix it, we must print ANSI_CLRTOEND everywhere */
+ rel_move(tc_col, tc_line, len, i);
+ o_cleol();
+ }
+ bp->oldlen = len;
+
+ }
+
+ rel_move(tc_col, tc_line, cur_col, cur_ln);
+
+ oflush();
+}
+
+void
+clear(void)
+{
+ register screenline_t *slp;
+
+ register int i;
+
+ docls = YEA;
+ cur_col = cur_ln = roll = 0;
+ for(i=0; i<scr_lns; i++) {
+ slp = &big_picture[i];
+ slp->mode = slp->len = slp->oldlen = 0;
+ }
+}
+
+void
+clrtoeol(void)
+{
+ register screenline_t *slp = GetCurrentLine();
+ register int ln;
+
+ standing = NA;
+ if (cur_col <= slp->sso)
+ slp->mode &= ~STANDOUT;
+
+ if (cur_col > slp->oldlen) {
+ for (ln = slp->len; ln <= cur_col; ln++)
+ slp->data[ln] = ' ';
+ }
+ if (cur_col < slp->oldlen) {
+ for (ln = slp->len; ln >= cur_col; ln--)
+ slp->data[ln] = ' ';
+ }
+ slp->len = cur_col;
+}
+
+/**
+ * 從目前的行數(scr_ln) clear 到第 line 行
+ */
+void
+clrtoline(int line)
+{
+ register screenline_t *slp;
+ register int i, j;
+
+ for (i = cur_ln, j = i + roll; i < line; i++, j++) {
+ if (j >= scr_lns)
+ j -= scr_lns;
+ slp = &big_picture[j];
+ slp->mode = slp->len = 0;
+ if (slp->oldlen)
+ slp->oldlen = scr_cols;
+ }
+}
+
+/**
+ * 從目前的行數(scr_ln) clear 到底
+ */
+inline void
+clrtobot(void)
+{
+ clrtoline(scr_lns);
+}
+
+void
+outc(unsigned char c)
+{
+ register screenline_t *slp = GetCurrentLine();
+ register int i;
+
+ if (c == '\n' || c == '\r') {
+ if (standing) {
+ slp->eso = MAX(slp->eso, cur_col);
+ standing = NA;
+ }
+ if ((i = cur_col - slp->len) > 0)
+ memset(&slp->data[slp->len], ' ', i + 1);
+ slp->len = cur_col;
+ cur_col = 0;
+ if (cur_ln < scr_lns)
+ cur_ln++;
+ return;
+ }
+ /*
+ * else if(c != ESC_CHR && !isprint2(c)) { c = '*'; //substitute a '*' for
+ * non-printable }
+ */
+ if (cur_col >= slp->len) {
+ for (i = slp->len; i < cur_col; i++)
+ slp->data[i] = ' ';
+ slp->data[cur_col] = '\0';
+ slp->len = cur_col + 1;
+ }
+ if (slp->data[cur_col] != c) {
+ slp->data[cur_col] = c;
+ if (!(slp->mode & MODIFIED))
+ slp->smod = slp->emod = cur_col;
+ slp->mode |= MODIFIED;
+ if (cur_col > slp->emod)
+ slp->emod = cur_col;
+ if (cur_col < slp->smod)
+ slp->smod = cur_col;
+ }
+#if 1
+ if(cur_col < scr_cols)
+ cur_col++;
+#else
+ /* vvv commented by piaip: but SCR_COLS is 511 > unsigned char! */
+ /* this comparison is always false (cur_col is unsigned char and scr_cols
+ * is 511). */
+ if (++cur_col >= scr_cols) {
+ if (standing && (slp->mode & STANDOUT)) {
+ standing = 0;
+ slp->eso = MAX(slp->eso, cur_col);
+ }
+ cur_col = 0;
+ if (cur_ln < scr_lns)
+ cur_ln++;
+ }
+#endif
+}
+
+void
+outs(const char *str)
+{
+ while (*str) {
+ outc(*str++);
+ }
+}
+
+void
+outs_n(const char *str, int n)
+{
+ while (*str && n--) {
+ outc(*str++);
+ }
+}
+//
+void
+outslr(const char *left, int leftlen, const char *right, int rightlen)
+{
+ if (left == NULL)
+ left = "";
+ if (right == NULL)
+ right = "";
+ if(*left && leftlen < 0)
+ leftlen = strlen(left);
+ if(*right && rightlen < 0)
+ rightlen = strlen(right);
+ // now calculate padding
+ rightlen = t_columns - leftlen - rightlen;
+ outs(left);
+
+ // ignore right msg if we need to.
+ if(rightlen >= 0)
+ {
+ while(--rightlen > 0)
+ outc(' ');
+ outs(right);
+ } else {
+ rightlen = t_columns - leftlen;
+ while(--rightlen > 0)
+ outc(' ');
+ }
+}
+
+
+/* Jaky */
+void
+out_lines(const char *str, int line)
+{
+ while (*str && line) {
+ outc(*str);
+ if (*str == '\n')
+ line--;
+ str++;
+ }
+}
+
+void
+outmsg(const char *msg)
+{
+ move(b_lines - msg_occupied, 0);
+ clrtoeol();
+ outs(msg);
+}
+
+void
+outmsglr(const char *msg, int llen, const char *rmsg, int rlen)
+{
+ move(b_lines - msg_occupied, 0);
+ clrtoeol();
+ outslr(msg, llen, rmsg, rlen);
+ outs(ANSI_RESET ANSI_CLRTOEND);
+}
+
+void
+prints(const char *fmt,...)
+{
+ va_list args;
+ char buff[1024];
+
+ va_start(args, fmt);
+ vsnprintf(buff, sizeof(buff), fmt, args);
+ va_end(args);
+ outs(buff);
+}
+
+void
+mouts(int y, int x, const char *str)
+{
+ move(y, x);
+ clrtoeol();
+ outs(str);
+}
+
+void
+scroll(void)
+{
+ scrollcnt++;
+ if (++roll >= scr_lns)
+ roll = 0;
+ move(b_lines, 0);
+ clrtoeol();
+}
+
+void
+rscroll(void)
+{
+ scrollcnt--;
+ if (--roll < 0)
+ roll = b_lines;
+ move(0, 0);
+ clrtoeol();
+}
+
+void
+region_scroll_up(int top, int bottom)
+{
+ int i;
+
+ if (top > bottom) {
+ i = top;
+ top = bottom;
+ bottom = i;
+ }
+ if (top < 0 || bottom >= scr_lns)
+ return;
+
+ for (i = top; i < bottom; i++)
+ big_picture[i] = big_picture[i + 1];
+ memset(big_picture + i, 0, sizeof(*big_picture));
+ memset(big_picture[i].data, ' ', scr_cols);
+ save_cursor();
+ change_scroll_range(top, bottom);
+ do_move(0, bottom);
+ scroll_forward();
+ change_scroll_range(0, scr_lns - 1);
+ restore_cursor();
+ refresh();
+}
+
+void
+standout(void)
+{
+ if (!standing && strtstandoutlen) {
+ register screenline_t *slp;
+
+ slp = GetCurrentLine();
+ standing = YEA;
+ slp->sso = slp->eso = cur_col;
+ slp->mode |= STANDOUT;
+ }
+}
+
+void
+standend(void)
+{
+ if (standing && strtstandoutlen) {
+ register screenline_t *slp;
+
+ slp = GetCurrentLine();
+ standing = NA;
+ slp->eso = MAX(slp->eso, cur_col);
+ }
+}
+
+static size_t screen_backupsize(int len, const screenline_t *bp)
+{
+ int i;
+ size_t sum = 0;
+ for(i = 0; i < len; i++)
+ sum += ((char*)&bp[i].data - (char*)&bp[i]) + bp[i].len;
+ return sum;
+}
+
+void screen_backup(screen_backup_t *old)
+{
+ int i;
+ size_t offset = 0;
+ void *buf;
+ screenline_t* bp = big_picture;
+
+ buf = old->raw_memory = malloc(screen_backupsize(t_lines, big_picture));
+
+ old->col = t_columns;
+ old->row = t_lines;
+ getyx(&old->y, &old->x);
+
+ for(i = 0; i < t_lines; i++) {
+ /* backup header */
+ memcpy((char*)buf + offset, &bp[i], ((char*)&bp[i].data - (char*)&bp[i]));
+ offset += ((char*)&bp[i].data - (char*)&bp[i]);
+
+ /* backup body */
+ memcpy((char*)buf + offset, &bp[i].data, bp[i].len);
+ offset += bp[i].len;
+ }
+}
+
+void screen_restore(const screen_backup_t *old)
+{
+ int i;
+ size_t offset=0;
+ void *buf = old->raw_memory;
+ screenline_t* bp = big_picture;
+ const int len = MIN(old->row, t_lines);
+
+ for(i = 0; i < len; i++) {
+ /* restore header */
+ memcpy(&bp[i], (char*)buf + offset, ((char*)&bp[i].data - (char*)&bp[i]));
+ offset += ((char*)&bp[i].data - (char*)&bp[i]);
+
+ /* restore body */
+ memcpy(&bp[i].data, (char*)buf + offset, bp[i].len);
+ offset += bp[i].len;
+ }
+
+ free(old->raw_memory);
+ move(old->y, old->x);
+ redoscr();
+}
+
+/* vim:sw=4
+ */
diff --git a/pttbbs/mbbsd/stuff.c b/pttbbs/mbbsd/stuff.c
new file mode 100644
index 00000000..a11bbd83
--- /dev/null
+++ b/pttbbs/mbbsd/stuff.c
@@ -0,0 +1,1121 @@
+/* $Id$ */
+#include "bbs.h"
+#include "fnv_hash.h"
+
+/* ----------------------------------------------------- */
+/* set file path for boards/user home */
+/* ----------------------------------------------------- */
+static const char * const str_home_file = "home/%c/%s/%s";
+static const char * const str_board_file = "boards/%c/%s/%s";
+static const char * const str_board_n_file = "boards/%c/%s/%s.%d";
+
+static char cdate_buffer[32];
+
+#define STR_DOTDIR ".DIR"
+static const char * const str_dotdir = STR_DOTDIR;
+
+/* XXX set*() all assume buffer size = PATHLEN */
+void
+sethomepath(char *buf, const char *userid)
+{
+ assert(is_validuserid(userid));
+ snprintf(buf, PATHLEN, "home/%c/%s", userid[0], userid);
+}
+
+void
+sethomedir(char *buf, const char *userid)
+{
+ assert(is_validuserid(userid));
+ snprintf(buf, PATHLEN, str_home_file, userid[0], userid, str_dotdir);
+}
+
+void
+sethomeman(char *buf, const char *userid)
+{
+ assert(is_validuserid(userid));
+ snprintf(buf, PATHLEN, str_home_file, userid[0], userid, "man");
+}
+
+
+void
+sethomefile(char *buf, const char *userid, const char *fname)
+{
+ assert(is_validuserid(userid));
+ assert(fname[0]);
+ snprintf(buf, PATHLEN, str_home_file, userid[0], userid, fname);
+}
+
+void
+setuserfile(char *buf, const char *fname)
+{
+ assert(is_validuserid(cuser.userid));
+ assert(fname[0]);
+ snprintf(buf, PATHLEN, str_home_file, cuser.userid[0], cuser.userid, fname);
+}
+
+void
+setapath(char *buf, const char *boardname)
+{
+ //assert(boardname[0]);
+ snprintf(buf, PATHLEN, "man/boards/%c/%s", boardname[0], boardname);
+}
+
+void
+setadir(char *buf, const char *path)
+{
+ //assert(path[0]);
+ snprintf(buf, PATHLEN, "%s/%s", path, str_dotdir);
+}
+
+void
+setbpath(char *buf, const char *boardname)
+{
+ //assert(boardname[0]);
+ snprintf(buf, PATHLEN, "boards/%c/%s", boardname[0], boardname);
+}
+
+void
+setbdir(char *buf, const char *boardname)
+{
+ //assert(boardname[0]);
+ snprintf(buf, PATHLEN, str_board_file, boardname[0], boardname,
+ (currmode & MODE_DIGEST ? fn_mandex : str_dotdir));
+}
+
+void
+setbfile(char *buf, const char *boardname, const char *fname)
+{
+ //assert(boardname[0]);
+ assert(fname[0]);
+ snprintf(buf, PATHLEN, str_board_file, boardname[0], boardname, fname);
+}
+
+void
+setbnfile(char *buf, const char *boardname, const char *fname, int n)
+{
+ //assert(boardname[0]);
+ assert(fname[0]);
+ snprintf(buf, PATHLEN, str_board_n_file, boardname[0], boardname, fname, n);
+}
+
+/*
+ * input direct
+ * output buf: copy direct
+ * fname: direct 的檔名部分
+ */
+void
+setdirpath(char *buf, const char *direct, const char *fname)
+{
+ char *p;
+ strcpy(buf, direct);
+ p = strrchr(buf, '/');
+ assert(p);
+ strlcpy(p + 1, fname, PATHLEN-(p+1-buf));
+}
+
+/**
+ * 給定文章標題 title,傳回指到主題的部分的指標。
+ * @param title
+ */
+char *
+subject(char *title)
+{
+ if (!strncasecmp(title, str_reply, 3)) {
+ title += 3;
+ if (*title == ' ')
+ title++;
+ }
+ return title;
+}
+
+/* ----------------------------------------------------- */
+/* 字串轉換檢查函數 */
+/* ----------------------------------------------------- */
+/**
+ * 將字串 s 轉為小寫存回 t
+ * @param t allocated char array
+ * @param s
+ */
+void
+str_lower(char *t, const char *s)
+{
+ register unsigned char ch;
+
+ do {
+ ch = *s++;
+ *t++ = char_lower(ch);
+ } while (ch);
+}
+
+/**
+ * 移除字串 buf 後端多餘的空白。
+ * @param buf
+ */
+void
+trim(char *buf)
+{ /* remove trailing space */
+ char *p = buf;
+
+ while (*p)
+ p++;
+ while (--p >= buf) {
+ if (*p == ' ')
+ *p = '\0';
+ else
+ break;
+ }
+}
+
+/**
+ * 移除 src 的 '\n' 並改成 '\0'
+ * @param src
+ */
+void chomp(char *src)
+{
+ while(*src){
+ if (*src == '\n')
+ *src = 0;
+ else
+ src++;
+ }
+}
+
+/* ----------------------------------------------------- */
+/* 字串檢查函數:英文、數字、檔名、E-mail address */
+/* ----------------------------------------------------- */
+
+int
+invalid_pname(const char *str)
+{
+ const char *p1, *p2, *p3;
+
+ p1 = str;
+ while (*p1) {
+ if (!(p2 = strchr(p1, '/')))
+ p2 = str + strlen(str);
+ if (p1 + 1 > p2 || p1 + strspn(p1, ".") == p2) /* 不允許用 / 開頭, 或是 // 之間只有 . */
+ return 1;
+ for (p3 = p1; p3 < p2; p3++)
+ if (not_alnum(*p3) && !strchr("@[]-._", *p3)) /* 只允許 alnum 或這些符號 */
+ return 1;
+ p1 = p2 + (*p2 ? 1 : 0);
+ }
+ return 0;
+}
+
+int is_validuserid(const char *id)
+{
+ int len, i;
+ if(id==NULL)
+ return 0;
+ len = strlen(id);
+
+ if (len < 2 || len>IDLEN)
+ return 0;
+
+ if (not_alpha(id[0]))
+ return 0;
+ for (i = 1; i < len; i++)
+ if (not_alnum(id[i]))
+ return 0;
+ return 1;
+}
+
+int
+is_uBM(const char *list, const char *id)
+{
+ register int len;
+
+ if (list[0] == '[')
+ list++;
+ if (list[0] > ' ') {
+ len = strlen(id);
+ do {
+ if (!strncasecmp(list, id, len)) {
+ list += len;
+ if ((*list == 0) || (*list == '/') ||
+ (*list == ']') || (*list == ' '))
+ return 1;
+ }
+ if ((list = strchr(list, '/')) != NULL)
+ list++;
+ else
+ break;
+ } while (1);
+ }
+ return 0;
+}
+
+int
+is_BM(const char *list)
+{
+ if (is_uBM(list, cuser.userid)) {
+ cuser.userlevel |= PERM_BM; /* Ptt 自動加上BM的權利 */
+ return 1;
+ }
+ return 0;
+}
+
+int
+userid_is_BM(const char *userid, const char *list)
+{
+ register int ch, len;
+
+ // TODO merge with is_uBM
+ ch = list[0];
+ if ((ch > ' ') && (ch < 128)) {
+ len = strlen(userid);
+ do {
+ if (!strncasecmp(list, userid, len)) {
+ ch = list[len];
+ if ((ch == 0) || (ch == '/') || (ch == ']'))
+ return 1;
+ }
+ while ((ch = *list++)) {
+ if (ch == '/')
+ break;
+ }
+ } while (ch);
+ }
+ return 0;
+}
+
+/* ----------------------------------------------------- */
+/* 檔案檢查函數:檔案、目錄、屬於 */
+/* ----------------------------------------------------- */
+
+/**
+ * 傳回 fname 的檔案大小
+ * @param fname
+ */
+off_t
+dashs(const char *fname)
+{
+ struct stat st;
+
+ if (!stat(fname, &st))
+ return st.st_size;
+ else
+ return -1;
+}
+
+/**
+ * 傳回 fname 的 mtime
+ * @param fname
+ */
+time4_t
+dasht(const char *fname)
+{
+ struct stat st;
+
+ if (!stat(fname, &st))
+ return st.st_mtime;
+ else
+ return -1;
+}
+
+/**
+ * 傳回 fname 是否為 symbolic link
+ * @param fname
+ */
+int
+dashl(const char *fname)
+{
+ struct stat st;
+
+ return (lstat(fname, &st) == 0 && S_ISLNK(st.st_mode));
+}
+
+/**
+ * 傳回 fname 是否為一般的檔案
+ * @param fname
+ */
+int
+dashf(const char *fname)
+{
+ struct stat st;
+
+ return (stat(fname, &st) == 0 && S_ISREG(st.st_mode));
+}
+
+/**
+ * 傳回 fname 是否為目錄
+ * @param fname
+ */
+int
+dashd(const char *fname)
+{
+ struct stat st;
+
+ return (stat(fname, &st) == 0 && S_ISDIR(st.st_mode));
+}
+
+#define BUFFER_SIZE 8192
+static int copy_file_to_file(const char *src, const char *dst)
+{
+ char buf[BUFFER_SIZE];
+ int fdr, fdw, len;
+
+ if ((fdr = open(src, O_RDONLY)) < 0)
+ return -1;
+
+ if ((fdw = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0) {
+ close(fdr);
+ return -1;
+ }
+
+ while (1) {
+ len = read(fdr, buf, sizeof(buf));
+ if (len <= 0)
+ break;
+ write(fdw, buf, len);
+ if (len < BUFFER_SIZE)
+ break;
+ }
+
+ close(fdr);
+ close(fdw);
+ return 0;
+}
+#undef BUFFER_SIZE
+
+static int copy_file_to_dir(const char *src, const char *dst)
+{
+ char buf[PATHLEN];
+ char *slash;
+ if ((slash = rindex(src, '/')) == NULL)
+ snprintf(buf, PATHLEN, "%s/%s", dst, src);
+ else
+ snprintf(buf, PATHLEN, "%s/%s", dst, slash);
+ return copy_file_to_file(src, buf);
+}
+
+static int copy_dir_to_dir(const char *src, const char *dst)
+{
+ DIR *dir;
+ struct dirent *entry;
+ struct stat st;
+ char buf[PATHLEN], buf2[PATHLEN];
+
+ if (stat(dst, &st) < 0)
+ if (mkdir(dst, 0700) < 0)
+ return -1;
+
+ if ((dir = opendir(src)) == NULL)
+ return -1;
+
+ while ((entry = readdir(dir)) != NULL) {
+ if (strcmp(entry->d_name, ".") == 0 ||
+ strcmp(entry->d_name, "..") == 0)
+ continue;
+ snprintf(buf, PATHLEN, "%s/%s", src, entry->d_name);
+ snprintf(buf2, PATHLEN, "%s/%s", dst, entry->d_name);
+ if (stat(buf, &st) < 0)
+ continue;
+ if (S_ISDIR(st.st_mode))
+ mkdir(buf2, 0700);
+ copy_file(buf, buf2);
+ }
+
+ closedir(dir);
+ return 0;
+}
+
+/**
+ * copy src to dst (recursively)
+ * @param src and dst are file or dir
+ * @return -1 if failed
+ */
+int copy_file(const char *src, const char *dst)
+{
+ struct stat st;
+
+ if (stat(dst, &st) == 0 && S_ISDIR(st.st_mode)) {
+ if (stat(src, &st) < 0)
+ return -1;
+
+ if (S_ISDIR(st.st_mode))
+ return copy_dir_to_dir(src, dst);
+ else if (S_ISREG(st.st_mode))
+ return copy_file_to_dir(src, dst);
+ return -1;
+ }
+ else if (stat(src, &st) == 0 && S_ISDIR(st.st_mode))
+ return copy_dir_to_dir(src, dst);
+ return copy_file_to_file(src, dst);
+}
+
+int
+belong(const char *filelist, const char *key)
+{
+ return file_exist_record(filelist, key);
+}
+
+unsigned int
+ipstr2int(const char *ip)
+{
+ unsigned int i, val = 0;
+ char buf[32];
+ char *nil, *p;
+
+ strlcpy(buf, ip, sizeof(buf));
+ p = buf;
+ for (i = 0; i < 4; i++) {
+ nil = strchr(p, '.');
+ if (nil != NULL)
+ *nil = 0;
+ val *= 256;
+ val += atoi(p);
+ if (nil != NULL)
+ p = nil + 1;
+ }
+ return val;
+}
+
+#ifndef _BBS_UTIL_C_ /* getdata_buf */
+time4_t
+gettime(int line, time4_t dt, const char*head)
+{
+ char yn[7];
+ int i;
+ struct tm *ptime = localtime4(&dt), endtime;
+
+ memcpy(&endtime, ptime, sizeof(struct tm));
+ snprintf(yn, sizeof(yn), "%4d", ptime->tm_year + 1900);
+ move(line, 0); outs(head);
+ i=strlen(head);
+ do {
+ getdata_buf(line, i, " 西元年:", yn, 5, LCECHO);
+ } while ((endtime.tm_year = atoi(yn) - 1900) < 0 || endtime.tm_year > 200);
+ snprintf(yn, sizeof(yn), "%d", ptime->tm_mon + 1);
+ do {
+ getdata_buf(line, i+15, "月:", yn, 3, LCECHO);
+ } while ((endtime.tm_mon = atoi(yn) - 1) < 0 || endtime.tm_mon > 11);
+ snprintf(yn, sizeof(yn), "%d", ptime->tm_mday);
+ do {
+ getdata_buf(line, i+24, "日:", yn, 3, LCECHO);
+ } while ((endtime.tm_mday = atoi(yn)) < 1 || endtime.tm_mday > 31);
+ snprintf(yn, sizeof(yn), "%d", ptime->tm_hour);
+ do {
+ getdata_buf(line, i+33, "時(0-23):", yn, 3, LCECHO);
+ } while ((endtime.tm_hour = atoi(yn)) < 0 || endtime.tm_hour > 23);
+ return mktime(&endtime);
+}
+#endif
+
+char *
+Cdate(const time4_t *clock)
+{
+ time_t temp = (time_t)*clock;
+ struct tm *mytm = localtime(&temp);
+
+ strftime(cdate_buffer, sizeof(cdate_buffer), "%m/%d/%Y %T %a", mytm);
+ return cdate_buffer;
+}
+
+char *
+Cdatelite(const time4_t *clock)
+{
+ time_t temp = (time_t)*clock;
+ struct tm *mytm = localtime(&temp);
+
+ strftime(cdate_buffer, sizeof(cdate_buffer), "%m/%d/%Y %T", mytm);
+ return cdate_buffer;
+}
+
+char *
+Cdatedate(const time4_t * clock)
+{
+ time_t temp = (time_t)*clock;
+ struct tm *mytm = localtime(&temp);
+
+ strftime(cdate_buffer, sizeof(cdate_buffer), "%m/%d/%Y", mytm);
+ return cdate_buffer;
+}
+
+#ifndef _BBS_UTIL_C_
+/* 這一區都是有關於畫面處理的, 故 _BBS_UTIL_C_ 不須要 */
+static void
+capture_screen(void)
+{
+ char fname[200];
+ FILE *fp;
+ int i;
+
+ getdata(b_lines - 2, 0, "把這個畫面收入到暫存檔?[y/N] ",
+ fname, 4, LCECHO);
+ if (fname[0] != 'y')
+ return;
+
+ setuserfile(fname, ask_tmpbuf(b_lines - 1));
+ if ((fp = fopen(fname, "w"))) {
+ for (i = 0; i < scr_lns; i++)
+ fprintf(fp, "%.*s\n", big_picture[i].len, big_picture[i].data);
+ fclose(fp);
+ }
+}
+
+#ifdef PLAY_ANGEL
+void
+pressanykey_or_callangel(){
+ int ch;
+
+ outmsg(
+ ANSI_COLOR(1;34;44) " ▄▄▄▄ "
+ ANSI_COLOR(32) "H " ANSI_COLOR(36) "呼叫小天使" ANSI_COLOR(34)
+ " ▄▄▄▄" ANSI_COLOR(37;44) " 請按 " ANSI_COLOR(36) "任意鍵 "
+ ANSI_COLOR(37) "繼續 " ANSI_COLOR(1;34)
+ "▄▄▄▄▄" ANSI_COLOR(36) "^T 收錄暫存檔" ANSI_COLOR(34) "▄▄▄ " ANSI_RESET);
+ do {
+ ch = igetch();
+
+ if (ch == Ctrl('T')) {
+ capture_screen();
+ break;
+ }else if (ch == 'h' || ch == 'H'){
+ CallAngel();
+ break;
+ }
+ } while ((ch != ' ') && (ch != KEY_LEFT) && (ch != '\r') && (ch != '\n'));
+ move(b_lines, 0);
+ clrtoeol();
+ refresh();
+}
+#endif
+
+/**
+ * 給 printf format 的參數,印到最底下一行。
+ * 傳回使用者的選擇(char)。
+ */
+char
+getans(const char *fmt,...)
+{
+ char msg[256];
+ char ans[5];
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(msg , 128, fmt, ap);
+ va_end(ap);
+
+ getdata(b_lines, 0, msg, ans, sizeof(ans), LCECHO);
+ return ans[0];
+}
+
+int
+getkey(const char *fmt,...)
+{
+ char msg[256], i;
+ va_list ap;
+ va_start(ap, fmt);
+ i = vsnprintf(msg , 128, fmt, ap);
+ va_end(ap);
+ return vmsg(msg);
+}
+
+static const char *msg_pressanykey_full =
+ ANSI_COLOR(37;44) " 請按" ANSI_COLOR(36) " 任意鍵 " ANSI_COLOR(37) "繼續 " ANSI_COLOR(34);
+#define msg_pressanykey_full_len (18)
+
+static const char *msg_pressanykey_full_trail =
+ ANSI_COLOR(36)
+ " [^T 收錄暫存檔] " ANSI_RESET;
+#define msg_pressanykey_full_trail_len (18) /* 4 for head */
+
+static const char* msg_pressanykey_trail =
+ ANSI_COLOR(33;46) " " ANSI_COLOR(200) ANSI_COLOR(1431) ANSI_COLOR(506)
+ "[按任意鍵繼續]" ANSI_COLOR(201) " " ANSI_RESET;
+#define msg_pressanykey_trail_len (16+1+4) /* 4 for head */
+
+int
+vmsg(const char *msg)
+{
+ int len = msg ? strlen(msg) : 0;
+ int i = 0;
+
+ if(len == 0) msg = NULL;
+
+ move(b_lines, 0);
+ clrtoeol();
+
+ if(!msg)
+ {
+ /* msg_pressanykey_full */
+ int w = (t_columns - msg_pressanykey_full_len - 8) / 2;
+ int pad = 0;
+
+ outs(ANSI_COLOR(1;34;44) " ");
+ pad += 1;
+ for (i = 0; i < w; i += 2)
+ outs("▄"), pad+=2;
+ outs(msg_pressanykey_full), pad+= msg_pressanykey_full_len;
+ /* pad now points to position of current cursor. */
+ pad = t_columns - msg_pressanykey_full_trail_len - pad;
+ /* pad is now those left . */
+ if (pad > 0)
+ {
+ for (i = 0; i <= pad-2; i += 2)
+ outs("▄");
+ if (i == pad-1)
+ outc(' ');
+ }
+ outs(msg_pressanykey_full_trail);
+ } else {
+ /* msg_pressanykey_trail */
+ outs(ANSI_COLOR(1;36;44) " ◆ ");
+ if(len >= t_columns - msg_pressanykey_trail_len)
+ len = t_columns - msg_pressanykey_trail_len;
+ while (i++ < len)
+ outc(*msg++);
+ i--;
+ while (i++ < t_columns - msg_pressanykey_trail_len)
+ outc(' ');
+ outs(msg_pressanykey_trail);
+ }
+
+ do {
+ if( (i = igetch()) == Ctrl('T') )
+ if(cuser.userid[0]) // if already login
+ capture_screen();
+ } while( i == 0 );
+
+ move(b_lines, 0);
+ clrtoeol();
+ return i;
+}
+
+int
+vmsgf(const char *fmt,...)
+{
+ char msg[256];
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(msg, sizeof(msg)-1, fmt, ap);
+ va_end(ap);
+ msg[sizeof(msg)-1] = 0;
+ return vmsg(msg);
+}
+
+/**
+ * 從第 y 列開始 show 出 filename 檔案中的前 lines 行。
+ * mode 為 output 的模式,參數同 strip_ansi。
+ * @param filename
+ * @param x
+ * @param lines
+ * @param mode
+ * @return 失敗傳回 0,否則為 1。
+ */
+int
+show_file(const char *filename, int y, int lines, int mode)
+{
+ FILE *fp;
+ char buf[1024];
+
+ if (y >= 0)
+ move(y, 0);
+ clrtoline(lines + y);
+ if ((fp = fopen(filename, "r"))) {
+ while (fgets(buf, sizeof(buf), fp) && lines--)
+ outs(Ptt_prints(buf, sizeof(buf), mode));
+ fclose(fp);
+ } else
+ return 0;
+ return 1;
+}
+
+void
+bell(void)
+{
+ char c;
+
+ c = Ctrl('G');
+ write(1, &c, 1);
+}
+
+int
+search_num(int ch, int max)
+{
+ int clen = 1;
+ int x, y;
+ char genbuf[10];
+
+ outmsg(ANSI_COLOR(7) " 跳至第幾項:" ANSI_RESET);
+ outc(ch);
+ genbuf[0] = ch;
+ getyx(&y, &x);
+ x--;
+ while ((ch = igetch()) != '\r') {
+ if (ch == 'q' || ch == 'e')
+ return -1;
+ if (ch == '\n')
+ break;
+ if (ch == '\177' || ch == Ctrl('H')) {
+ if (clen == 0) {
+ bell();
+ continue;
+ }
+ clen--;
+ move(y, x + clen);
+ outc(' ');
+ move(y, x + clen);
+ continue;
+ }
+ if (!isdigit(ch)) {
+ bell();
+ continue;
+ }
+ if (x + clen >= scr_cols || clen >= 6) {
+ bell();
+ continue;
+ }
+ genbuf[clen++] = ch;
+ outc(ch);
+ }
+ genbuf[clen] = '\0';
+ move(b_lines, 0);
+ clrtoeol();
+ if (genbuf[0] == '\0')
+ return -1;
+ clen = atoi(genbuf);
+ if (clen == 0)
+ return 0;
+ if (clen > max)
+ return max;
+ return clen - 1;
+}
+
+/**
+ * 在螢幕左上角 show 出 "【title】"
+ * @param title
+ */
+void
+stand_title(const char *title)
+{
+ clear();
+ prints(ANSI_COLOR(1;37;46) "【 %s 】" ANSI_RESET "\n", title);
+}
+
+void
+cursor_show(int row, int column)
+{
+ move(row, column);
+ outs(STR_CURSOR);
+ move(row, column + 1);
+}
+
+void
+cursor_clear(int row, int column)
+{
+ move(row, column);
+ outs(STR_UNCUR);
+}
+
+int
+cursor_key(int row, int column)
+{
+ int ch;
+
+ cursor_show(row, column);
+ ch = igetch();
+ move(row, column);
+ outs(STR_UNCUR);
+ return ch;
+}
+
+void
+printdash(const char *mesg, int msglen)
+{
+ int head = 0, tail;
+
+ if(msglen <= 0)
+ msglen = strlen(mesg);
+
+ if (mesg)
+ head = (msglen + 1) >> 1;
+
+ tail = head;
+
+ while (head++ < t_columns/2-2)
+ outc('-');
+
+ if (tail) {
+ outc(' ');
+ if(mesg) outs(mesg);
+ outc(' ');
+ }
+ while (tail++ < t_columns/2-2)
+ outc('-');
+
+ outc('\n');
+}
+
+int
+log_user(const char *fmt, ...)
+{
+ char msg[256], filename[256];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(msg , 128, fmt, ap);
+ va_end(ap);
+
+ sethomefile(filename, cuser.userid, "USERLOG");
+ return log_file(filename, LOG_CREAT | LOG_VF,
+ "%s: %s %s", cuser.userid, msg, Cdate(&now));
+}
+
+int
+log_file(const char *fn, int flag, const char *fmt,...)
+{
+ int fd;
+ char msg[256];
+ const char *realmsg;
+ if( !(flag & LOG_VF) ){
+ realmsg = fmt;
+ }
+ else{
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(msg , 128, fmt, ap);
+ va_end(ap);
+ realmsg = msg;
+ }
+
+ if( (fd = open(fn, O_APPEND | O_WRONLY | ((flag & LOG_CREAT)? O_CREAT : 0),
+ ((flag & LOG_CREAT) ? 0664 : 0))) < 0 )
+ return -1;
+ if( write(fd, realmsg, strlen(realmsg)) < 0 ){
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
+
+void
+show_help(const char * const helptext[])
+{
+ const char *str;
+ int i;
+
+ clear();
+ for (i = 0; (str = helptext[i]); i++) {
+ if (*str == '\0')
+ prints(ANSI_COLOR(1) "【 %s 】" ANSI_COLOR(0) "\n", str + 1);
+ else if (*str == '\01')
+ prints("\n" ANSI_COLOR(36) "【 %s 】" ANSI_RESET "\n", str + 1);
+ else
+ prints(" %s\n", str);
+ }
+#ifdef PLAY_ANGEL
+ if (HasUserPerm(PERM_LOGINOK))
+ pressanykey_or_callangel();
+ else
+#endif
+ pressanykey();
+}
+
+void
+show_helpfile(const char *helpfile)
+{
+ clear();
+ show_file((char *)helpfile, 0, b_lines, NO_RELOAD);
+#ifdef PLAY_ANGEL
+ if (HasUserPerm(PERM_LOGINOK))
+ pressanykey_or_callangel();
+ else
+#endif
+ pressanykey();
+}
+
+#endif // _BBS_UTIL_C_
+
+/* ----------------------------------------------------- */
+/* use mmap() to malloc large memory in CRITICAL_MEMORY */
+/* ----------------------------------------------------- */
+#ifdef CRITICAL_MEMORY
+void *MALLOC(int size)
+{
+ int *p;
+ p = (int *)mmap(NULL, (size + 4), PROT_READ | PROT_WRITE,
+ MAP_ANON | MAP_PRIVATE, -1, 0);
+ p[0] = size;
+#if defined(DEBUG) && !defined(_BBS_UTIL_C_)
+ vmsgf("critical malloc %d bytes", size);
+#endif
+ return (void *)&p[1];
+}
+
+void FREE(void *ptr)
+{
+ int size = ((int *)ptr)[-1];
+ munmap((void *)(&(((int *)ptr)[-1])), size);
+#if defined(DEBUG) && !defined(_BBS_UTIL_C_)
+ vmsgf("critical free %d bytes", size);
+#endif
+}
+#endif
+
+unsigned
+StringHash(const char *s)
+{
+ return fnv1a_32_strcase(s, FNV1_32_INIT);
+}
+
+inline int *intbsearch(int key, const int *base0, int nmemb)
+{
+ /* 改自 /usr/src/lib/libc/stdlib/bsearch.c ,
+ 專給搜 int array 用的, 不透過 compar function 故較快些 */
+ const char *base = (const char *)base0;
+ size_t lim;
+ int *p;
+
+ for (lim = nmemb; lim != 0; lim >>= 1) {
+ p = (int *)(base + (lim >> 1) * 4);
+ if( key == *p )
+ return p;
+ if( key > *p ){/* key > p: move right */
+ base = (char *)p + 4;
+ lim--;
+ } /* else move left */
+ }
+ return (NULL);
+}
+
+inline unsigned int *
+uintbsearch(const unsigned int key, const unsigned int *base0, const int nmemb)
+{
+ /* 改自 /usr/src/lib/libc/stdlib/bsearch.c ,
+ 專給搜 int array 用的, 不透過 compar function 故較快些 */
+ const char *base = (const char *)base0;
+ size_t lim;
+ unsigned int *p;
+
+ for (lim = nmemb; lim != 0; lim >>= 1) {
+ p = (unsigned int *)(base + (lim >> 1) * 4);
+ if( key == *p )
+ return p;
+ if( key > *p ){/* key > p: move right */
+ base = (char *)p + 4;
+ lim--;
+ } /* else move left */
+ }
+ return (NULL);
+}
+
+int qsort_intcompar(const void *a, const void *b)
+{
+ return *(int *)a - *(int *)b;
+}
+
+#ifdef TIMET64
+char *
+ctime4(const time4_t *clock)
+{
+ time_t temp = (time_t)*clock;
+
+ return ctime(&temp);
+}
+
+struct tm *localtime4(const time4_t *t)
+{
+ if( t == NULL )
+ return localtime(NULL);
+ else {
+ time_t temp = (time_t)*t;
+ return localtime(&temp);
+ }
+}
+
+time4_t time4(time4_t *ptr)
+{
+ if( ptr == NULL )
+ return time(NULL);
+ else
+ return *ptr = (time4_t)time(NULL);
+}
+#endif
+
+#ifdef OUTTACACHE
+int tobind(const char * host, int port)
+{
+ int sockfd, val = 1;
+ struct sockaddr_in servaddr;
+
+ if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
+ perror("socket()");
+ exit(1);
+ }
+ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&val, sizeof(val));
+ bzero(&servaddr, sizeof(servaddr));
+ servaddr.sin_family = AF_INET;
+ if (host == NULL || host[0] == 0)
+ servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
+ else if (inet_aton(host, &servaddr.sin_addr) == 0) {
+ perror("inet_aton()");
+ exit(1);
+ }
+ servaddr.sin_port = htons(port);
+ if( bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0 ) {
+ perror("bind()");
+ exit(1);
+ }
+ if( listen(sockfd, 5) < 0 ) {
+ perror("listen()");
+ exit(1);
+ }
+
+ return sockfd;
+}
+
+int toconnect(const char *host, int port)
+{
+ int sock;
+ struct sockaddr_in serv_name;
+ if( (sock = socket(AF_INET, SOCK_STREAM, 0)) < 0 ){
+ perror("socket");
+ return -1;
+ }
+
+ serv_name.sin_family = AF_INET;
+ serv_name.sin_addr.s_addr = inet_addr(host);
+ serv_name.sin_port = htons(port);
+ if( connect(sock, (struct sockaddr*)&serv_name, sizeof(serv_name)) < 0 ){
+ close(sock);
+ return -1;
+ }
+ return sock;
+}
+
+/**
+ * same as read(2), but read until exactly size len
+ */
+int toread(int fd, void *buf, int len)
+{
+ int l;
+ for( l = 0 ; len > 0 ; )
+ if( (l = read(fd, buf, len)) <= 0 )
+ return -1;
+ else{
+ buf += l;
+ len -= l;
+ }
+ return l;
+}
+
+/**
+ * same as write(2), but write until exactly size len
+ */
+int towrite(int fd, const void *buf, int len)
+{
+ int l;
+ for( l = 0 ; len > 0 ; )
+ if( (l = write(fd, buf, len)) <= 0 )
+ return -1;
+ else{
+ buf += l;
+ len -= l;
+ }
+ return l;
+}
+#endif
diff --git a/pttbbs/mbbsd/syspost.c b/pttbbs/mbbsd/syspost.c
new file mode 100644
index 00000000..13e71390
--- /dev/null
+++ b/pttbbs/mbbsd/syspost.c
@@ -0,0 +1,154 @@
+/* $Id$ */
+#include "bbs.h"
+
+int
+post_msg(const char *bname, const char *title, const char *msg, const char *author)
+{
+ FILE *fp;
+ int bid;
+ fileheader_t fhdr;
+ char fname[PATHLEN];
+
+ /* 在 bname 板發表新文章 */
+ setbpath(fname, bname);
+ stampfile(fname, &fhdr);
+ fp = fopen(fname, "w");
+
+ if (!fp)
+ return -1;
+
+ fprintf(fp, "作者: %s 看板: %s\n標題: %s \n", author, bname, title);
+ fprintf(fp, "時間: %s\n", ctime4(&now));
+
+ /* 文章的內容 */
+ fputs(msg, fp);
+ fclose(fp);
+
+ /* 將檔案加入列表 */
+ strlcpy(fhdr.title, title, sizeof(fhdr.title));
+ strlcpy(fhdr.owner, author, sizeof(fhdr.owner));
+ setbdir(fname, bname);
+ if (append_record(fname, &fhdr, sizeof(fhdr)) != -1)
+ if ((bid = getbnum(bname)) > 0)
+ setbtotal(bid);
+ return 0;
+}
+
+int
+post_file(const char *bname, const char *title, const char *filename, const char *author)
+{
+ int size = dashs(filename);
+ char *msg;
+ FILE *fp;
+
+ if (size <= 0)
+ return -1;
+ if (!(fp = fopen(filename, "r")))
+ return -1;
+ msg = (char *)malloc(size + 1);
+ size = fread(msg, 1, size, fp);
+ msg[size] = 0;
+ size = post_msg(bname, title, msg, author);
+ fclose(fp);
+ free(msg);
+ return size;
+}
+
+void
+post_change_perm(int oldperm, int newperm, const char *sysopid, const char *userid)
+{
+ FILE *fp;
+ fileheader_t fhdr;
+ char genbuf[200], reason[30];
+ int i, flag = 0;
+
+ setbpath(genbuf, "Security");
+ stampfile(genbuf, &fhdr);
+ if (!(fp = fopen(genbuf, "w")))
+ return;
+
+ fprintf(fp, "作者: [系統安全局] 看板: Security\n"
+ "標題: [公安報告] 站長修改權限報告\n"
+ "時間: %s\n", ctime4(&now));
+ for (i = 0; i < NUMPERMS; i++) {
+ if (((oldperm >> i) & 1) != ((newperm >> i) & 1)) {
+ fprintf(fp, " 站長" ANSI_COLOR(1;32) "%s%s%s%s" ANSI_RESET "的權限\n",
+ sysopid,
+ (((oldperm >> i) & 1) ? ANSI_COLOR(1;33) "關閉" : ANSI_COLOR(1;33) "開啟"),
+ userid, str_permid[i]);
+ flag++;
+ }
+ }
+
+ if (flag) {
+ clrtobot();
+ clear();
+ while (!getdata(5, 0, "請輸入理由以示負責:",
+ reason, sizeof(reason), DOECHO));
+ fprintf(fp, "\n " ANSI_COLOR(1;37) "站長%s修改權限理由是:%s" ANSI_RESET,
+ cuser.userid, reason);
+ fclose(fp);
+
+ snprintf(fhdr.title, sizeof(fhdr.title),
+ "[公安報告] 站長%s修改%s權限報告",
+ cuser.userid, userid);
+ strlcpy(fhdr.owner, "[系統安全局]", sizeof(fhdr.owner));
+ append_record("boards/S/Security/.DIR", &fhdr, sizeof(fhdr));
+ } else
+ fclose(fp);
+}
+
+void
+post_violatelaw(const char *crime, const char *police, const char *reason, const char *result)
+{
+ char title[TTLEN+1];
+ char msg[200];
+
+ snprintf(title, sizeof(title), "[報告] %s:%-*s 判決", crime,
+ (int)(37 - strlen(reason) - strlen(crime)), reason);
+
+ snprintf(msg, sizeof(msg),
+ ANSI_COLOR(1;32) "%s" ANSI_RESET "判決:\n"
+ " " ANSI_COLOR(1;32) "%s" ANSI_RESET "因" ANSI_COLOR(1;35) "%s" ANSI_RESET "行為,\n"
+ "違反本站站規,處以" ANSI_COLOR(1;35) "%s" ANSI_RESET ",特此公告\n",
+ police, crime, reason, result);
+
+ if (!strstr(police, "警察")) {
+ post_msg("PoliceLog", title, msg, "[Ptt法院]");
+
+ snprintf(msg, sizeof(msg),
+ ANSI_COLOR(1;32) "%s" ANSI_RESET "判決:\n"
+ " " ANSI_COLOR(1;32) "%s" ANSI_RESET "因" ANSI_COLOR(1;35) "%s" ANSI_RESET "行為,\n"
+ "違反本站站規,處以" ANSI_COLOR(1;35) "%s" ANSI_RESET ",特此公告\n",
+ "站務警察", crime, reason, result);
+ }
+
+ post_msg("ViolateLaw", title, msg, "[Ptt法院]");
+}
+
+void
+post_newboard(const char *bgroup, const char *bname, const char *bms)
+{
+ char genbuf[256], title[TTLEN+1];
+
+ snprintf(title, sizeof(title), "[新板成立] %s", bname);
+ snprintf(genbuf, sizeof(genbuf),
+ "%s 開了一個新板 %s : %s\n\n新任板主為 %s\n\n恭喜*^_^*\n",
+ cuser.userid, bname, bgroup, bms);
+
+ post_msg("Record", title, genbuf, "[系統]");
+}
+
+void
+post_policelog(const char *bname, const char *atitle, const char *action, const char *reason, const int toggle)
+{
+ char genbuf[256], title[TTLEN+1];
+
+ snprintf(title, sizeof(title), "[%s][%s] %s by %s", action, toggle ? "開啟" : "關閉", bname, cuser.userid);
+ snprintf(genbuf, sizeof(genbuf),
+ "%s (%s) %s %s 看板 %s 功\能\n原因 : %s\n%s%s\n",
+ cuser.userid, fromhost, toggle ? "開啟" : "關閉", bname, action,
+ reason, atitle ? "文章標題 : " : "", atitle ? atitle : "");
+
+ post_msg("PoliceLog", title, genbuf, "[系統]");
+}
diff --git a/pttbbs/mbbsd/talk.c b/pttbbs/mbbsd/talk.c
new file mode 100644
index 00000000..e2a5ba30
--- /dev/null
+++ b/pttbbs/mbbsd/talk.c
@@ -0,0 +1,3555 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define QCAST int (*)(const void *, const void *)
+
+static char * const IdleTypeTable[] = {
+ "偶在花呆啦", "情人來電", "覓食中", "拜見周公", "假死狀態", "我在思考"
+};
+static char * const sig_des[] = {
+ "鬥雞", "聊天", "", "下棋", "象棋", "暗棋", "下圍棋", "下黑白棋",
+};
+static char * const withme_str[] = {
+ "談天", "下五子棋", "鬥寵物", "下象棋", "下暗棋", "下圍棋", "下黑白棋", NULL
+};
+
+#define MAX_SHOW_MODE 6
+#define M_INT 15 /* monitor mode update interval */
+#define P_INT 20 /* interval to check for page req. in
+ * talk/chat */
+#define BOARDFRI 1
+
+typedef struct talkwin_t {
+ int curcol, curln;
+ int sline, eline;
+} talkwin_t;
+
+typedef struct pickup_t {
+ userinfo_t *ui;
+ int friend, uoffset;
+} pickup_t;
+
+/* 記錄 friend 的 user number */
+//
+#define PICKUP_WAYS 8
+
+static char * const fcolor[11] = {
+ "", ANSI_COLOR(36), ANSI_COLOR(32), ANSI_COLOR(1;32),
+ ANSI_COLOR(33), ANSI_COLOR(1;33), ANSI_COLOR(1;37), ANSI_COLOR(1;37),
+ ANSI_COLOR(31), ANSI_COLOR(1;35), ANSI_COLOR(1;36)
+};
+static char save_page_requestor[40];
+static char page_requestor[40];
+
+userinfo_t *uip;
+
+int
+iswritable_stat(const userinfo_t * uentp, int fri_stat)
+{
+ if (uentp == currutmp)
+ return 0;
+
+ if (HasUserPerm(PERM_SYSOP))
+ return 1;
+
+ if (!HasUserPerm(PERM_LOGINOK) || HasUserPerm(PERM_VIOLATELAW))
+ return 0;
+
+ return (uentp->pager != PAGER_ANTIWB &&
+ (fri_stat & HFM || uentp->pager != PAGER_FRIENDONLY));
+}
+
+int
+isvisible_stat(const userinfo_t * me, const userinfo_t * uentp, int fri_stat)
+{
+ if (!uentp || uentp->userid[0] == 0)
+ return 0;
+
+ /* to avoid paranoid users get crazy*/
+ if (uentp->mode == DEBUGSLEEPING)
+ return 0;
+
+ if (PERM_HIDE(uentp) && !(PERM_HIDE(me))) /* 對方紫色隱形而你沒有 */
+ return 0;
+ else if ((me->userlevel & PERM_SYSOP) ||
+ ((fri_stat & HRM) && (fri_stat & HFM)))
+ /* 站長看的見任何人 */
+ return 1;
+
+ if (uentp->invisible && !(me->userlevel & PERM_SEECLOAK))
+ return 0;
+
+ return !(fri_stat & HRM);
+}
+
+const char *
+modestring(const userinfo_t * uentp, int simple)
+{
+ static char modestr[40];
+ static char *const notonline = "不在站上";
+ register int mode = uentp->mode;
+ register char *word;
+ int fri_stat;
+
+ /* for debugging */
+ if (mode >= MAX_MODES) {
+ syslog(LOG_WARNING, "what!? mode = %d", mode);
+ word = ModeTypeTable[mode % MAX_MODES];
+ } else
+ word = ModeTypeTable[mode];
+
+ fri_stat = friend_stat(currutmp, uentp);
+ if (!(HasUserPerm(PERM_SYSOP) || HasUserPerm(PERM_SEECLOAK)) &&
+ ((uentp->invisible || (fri_stat & HRM)) &&
+ !((fri_stat & HFM) && (fri_stat & HRM))))
+ return notonline;
+ else if (mode == EDITING) {
+ snprintf(modestr, sizeof(modestr), "E:%s",
+ ModeTypeTable[uentp->destuid < EDITING ? uentp->destuid :
+ EDITING]);
+ word = modestr;
+ } else if (!mode && *uentp->chatid == 1) {
+ if (!simple)
+ snprintf(modestr, sizeof(modestr), "回應 %s",
+ isvisible_uid(uentp->destuid) ?
+ getuserid(uentp->destuid) : "空氣");
+ else
+ snprintf(modestr, sizeof(modestr), "回應呼叫");
+ }
+ else if (!mode && *uentp->chatid == 3)
+ snprintf(modestr, sizeof(modestr), "水球準備中");
+ else if (
+#ifdef NOKILLWATERBALL
+ uentp->msgcount > 0
+#else
+ (!mode) && *uentp->chatid == 2
+#endif
+ )
+ if (uentp->msgcount < 10) {
+ const char *cnum[10] =
+ {"", "一", "兩", "三", "四", "五",
+ "六", "七", "八", "九"};
+ snprintf(modestr, sizeof(modestr),
+ "中%s顆水球", cnum[(int)(uentp->msgcount)]);
+ } else
+ snprintf(modestr, sizeof(modestr), "不行了 @_@");
+ else if (!mode)
+ return (uentp->destuid == 6) ? uentp->chatid :
+ IdleTypeTable[(0 <= uentp->destuid && uentp->destuid < 6) ?
+ uentp->destuid : 0];
+ else if (simple)
+ return word;
+ else if (uentp->in_chat && mode == CHATING)
+ snprintf(modestr, sizeof(modestr), "%s (%s)", word, uentp->chatid);
+ else if (mode == TALK || mode == M_FIVE || mode == CHC || mode == UMODE_GO
+ || mode == DARK) {
+ if (!isvisible_uid(uentp->destuid)) /* Leeym 對方(紫色)隱形 */
+ snprintf(modestr, sizeof(modestr), "%s 空氣", word);
+ /* Leeym * 大家自己發揮吧! */
+ else
+ snprintf(modestr, sizeof(modestr),
+ "%s %s", word, getuserid(uentp->destuid));
+ } else if (mode == CHESSWATCHING) {
+ snprintf(modestr, sizeof(modestr), "觀棋");
+ } else if (mode != PAGE && mode != TQUERY)
+ return word;
+ else
+ snprintf(modestr, sizeof(modestr),
+ "%s %s", word, getuserid(uentp->destuid));
+
+ return (modestr);
+}
+
+int
+set_friend_bit(const userinfo_t * me, const userinfo_t * ui)
+{
+ int unum, hit = 0;
+ const int *myfriends;
+
+ /* 判斷對方是否為我的朋友 ? */
+ if( intbsearch(ui->uid, me->myfriend, me->nFriends) )
+ hit = IFH;
+
+ /* 判斷我是否為對方的朋友 ? */
+ if( intbsearch(me->uid, ui->myfriend, ui->nFriends) )
+ hit |= HFM;
+
+ /* 判斷對方是否為我的仇人 ? */
+ myfriends = me->reject;
+ while ((unum = *myfriends++)) {
+ if (unum == ui->uid) {
+ hit |= IRH;
+ break;
+ }
+ }
+
+ /* 判斷我是否為對方的仇人 ? */
+ myfriends = ui->reject;
+ while ((unum = *myfriends++)) {
+ if (unum == me->uid) {
+ hit |= HRM;
+ break;
+ }
+ }
+ return hit;
+}
+
+inline int
+he_reject_me(userinfo_t * uin){
+ int* iter = uin->reject;
+ int unum;
+ while ((unum = *iter++)) {
+ if (unum == currutmp->uid) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int
+reverse_friend_stat(int stat)
+{
+ int stat1 = 0;
+ if (stat & IFH)
+ stat1 |= HFM;
+ if (stat & IRH)
+ stat1 |= HRM;
+ if (stat & HFM)
+ stat1 |= IFH;
+ if (stat & HRM)
+ stat1 |= IRH;
+ if (stat & IBH)
+ stat1 |= IBH;
+ return stat1;
+}
+
+#ifdef OUTTACACHE
+int sync_outta_server(int sfd)
+{
+ int i;
+ int offset = (int)(currutmp - &SHM->uinfo[0]);
+
+ int cmd, res;
+ int nfs;
+ ocfs_t fs[MAX_FRIEND*2];
+
+ cmd = -2;
+ if(towrite(sfd, &cmd, sizeof(cmd))<0 ||
+ towrite(sfd, &offset, sizeof(offset))<0 ||
+ towrite(sfd, &currutmp->uid, sizeof(currutmp->uid)) < 0 ||
+ towrite(sfd, currutmp->myfriend, sizeof(currutmp->myfriend))<0 ||
+ towrite(sfd, currutmp->reject, sizeof(currutmp->reject))<0)
+ return -1;
+
+ if(toread(sfd, &res, sizeof(res))<0)
+ return -1;
+
+ if(res<0)
+ return -1;
+ if(res==2) {
+ close(sfd);
+ outs("登入太頻繁, 為避免系統負荷過重, 請稍後再試\n");
+ refresh();
+ sleep(30);
+ log_usies("REJECTLOGIN", NULL);
+ memset(currutmp, 0, sizeof(userinfo_t));
+ exit(0);
+ }
+
+ if(toread(sfd, &nfs, sizeof(nfs))<0)
+ return -1;
+ if(nfs<0 || nfs>MAX_FRIEND*2) {
+ fprintf(stderr, "invalid nfs=%d\n",nfs);
+ return -1;
+ }
+
+ if(toread(sfd, fs, sizeof(fs[0])*nfs)<0)
+ return -1;
+
+ close(sfd);
+
+ for(i=0; i<nfs; i++) {
+ if( SHM->uinfo[fs[i].index].uid != fs[i].uid )
+ continue; // double check, server may not know user have logout
+ currutmp->friend_online[currutmp->friendtotal++]
+ = fs[i].friendstat;
+ /* XXX: race here */
+ if( SHM->uinfo[fs[i].index].friendtotal < MAX_FRIEND )
+ SHM->uinfo[fs[i].index].friend_online[ SHM->uinfo[fs[i].index].friendtotal++ ] = fs[i].rfriendstat;
+ }
+
+ if(res==1) {
+ vmsg("請勿頻繁登入以免造成系統過度負荷");
+ }
+ return 0;
+}
+#endif
+
+void login_friend_online(void)
+{
+ userinfo_t *uentp;
+ int i, stat, stat1;
+ int offset = (int)(currutmp - &SHM->uinfo[0]);
+
+#ifdef OUTTACACHE
+ int sfd;
+ /* OUTTACACHE is TOO slow, let's prompt user here. */
+ move(b_lines-2, 0); clrtobot();
+ outs("\n正在更新與同步線上使用者及好友名單,系統負荷量大時會需時較久...\n");
+ refresh();
+
+ sfd = toconnect(OUTTACACHEHOST, OUTTACACHEPORT);
+ if(sfd>=0) {
+ int res=sync_outta_server(sfd);
+ if(res==0) // sfd will be closed if return 0
+ return;
+ close(sfd);
+ }
+#endif
+
+ for (i = 0; i < SHM->UTMPnumber && currutmp->friendtotal < MAX_FRIEND; i++) {
+ uentp = (&SHM->uinfo[SHM->sorted[SHM->currsorted][0][i]]);
+ if (uentp && uentp->uid && (stat = set_friend_bit(currutmp, uentp))) {
+ stat1 = reverse_friend_stat(stat);
+ stat <<= 24;
+ stat |= (int)(uentp - &SHM->uinfo[0]);
+ currutmp->friend_online[currutmp->friendtotal++] = stat;
+ if (uentp != currutmp && uentp->friendtotal < MAX_FRIEND) {
+ stat1 <<= 24;
+ stat1 |= offset;
+ uentp->friend_online[uentp->friendtotal++] = stat1;
+ }
+ }
+ }
+ return;
+}
+
+/* TODO merge with util/shmctl.c logout_friend_online() */
+int
+logout_friend_online(userinfo_t * utmp)
+{
+ int my_friend_idx, thefriend;
+ int k;
+ int offset = (int)(utmp - &SHM->uinfo[0]);
+ userinfo_t *ui;
+ for(; utmp->friendtotal>0; utmp->friendtotal--) {
+ if( !(0 <= utmp->friendtotal && utmp->friendtotal < MAX_FRIEND) )
+ return 1;
+ my_friend_idx=utmp->friendtotal-1;
+ thefriend = (utmp->friend_online[my_friend_idx] & 0xFFFFFF);
+ utmp->friend_online[my_friend_idx]=0;
+
+ if( !(0 <= thefriend && thefriend < USHM_SIZE) )
+ continue;
+
+ ui = &SHM->uinfo[thefriend];
+ if(ui->pid==0 || ui==utmp)
+ continue;
+ if(ui->friendtotal > MAX_FRIEND || ui->friendtotal<0)
+ continue;
+ for (k = 0; k < ui->friendtotal && k < MAX_FRIEND &&
+ (int)(ui->friend_online[k] & 0xFFFFFF) != offset; k++);
+ if (k < ui->friendtotal && k < MAX_FRIEND) {
+ ui->friendtotal--;
+ ui->friend_online[k] = ui->friend_online[ui->friendtotal];
+ ui->friend_online[ui->friendtotal] = 0;
+ }
+ }
+ return 0;
+}
+
+
+int
+friend_stat(const userinfo_t * me, const userinfo_t * ui)
+{
+ int i, j, hit = 0;
+ /* 看板好友 */
+ if (me->brc_id && ui->brc_id == me->brc_id) {
+ hit = IBH;
+ }
+ for (i = 0; me->friend_online[i] && i < MAX_FRIEND; i++) {
+ j = (me->friend_online[i] & 0xFFFFFF);
+ if (VALID_USHM_ENTRY(j) && ui == &SHM->uinfo[j]) {
+ hit |= me->friend_online[i] >> 24;
+ break;
+ }
+ }
+ if (PERM_HIDE(ui))
+ return hit & ST_FRIEND;
+ return hit;
+}
+
+int
+isvisible_uid(int tuid)
+{
+ userinfo_t *uentp;
+
+ if (!tuid || !(uentp = search_ulist(tuid)))
+ return 1;
+ return isvisible(currutmp, uentp);
+}
+
+/* 真實動作 */
+static void
+my_kick(userinfo_t * uentp)
+{
+ char genbuf[200];
+
+ getdata(1, 0, msg_sure_ny, genbuf, 4, LCECHO);
+ clrtoeol();
+ if (genbuf[0] == 'y') {
+ snprintf(genbuf, sizeof(genbuf),
+ "%s (%s)", uentp->userid, uentp->nickname);
+ log_usies("KICK ", genbuf);
+ if ((uentp->pid <= 0 || kill(uentp->pid, SIGHUP) == -1) && (errno == ESRCH))
+ purge_utmp(uentp);
+ outs("踢出去囉");
+ } else
+ outs(msg_cancel);
+ pressanykey();
+}
+
+static void
+chicken_query(const char *userid)
+{
+ userec_t xuser;
+ if (getuser(userid, &xuser)) {
+ if (xuser.mychicken.name[0]) {
+ time_diff(&(xuser.mychicken));
+ if (!isdeadth(&(xuser.mychicken))) {
+ show_chicken_data(&(xuser.mychicken), NULL);
+ prints("\n\n以上是 %s 的寵物資料..", xuser.userid);
+ }
+ } else {
+ move(1, 0);
+ clrtobot();
+ prints("\n\n%s 並沒有養寵物..", xuser.userid);
+ }
+ pressanykey();
+ }
+}
+
+int
+my_query(const char *uident)
+{
+ userec_t muser;
+ int tuid, fri_stat = 0;
+ userinfo_t *uentp;
+ const char *sex[8] =
+ {MSG_BIG_BOY, MSG_BIG_GIRL,
+ MSG_LITTLE_BOY, MSG_LITTLE_GIRL,
+ MSG_MAN, MSG_WOMAN, MSG_PLANT, MSG_MIME};
+ static time_t last_query;
+
+ STATINC(STAT_QUERY);
+ if ((tuid = getuser(uident, &muser))) {
+ move(1, 0);
+ clrtobot();
+ move(1, 0);
+ setutmpmode(TQUERY);
+ currutmp->destuid = tuid;
+
+ if ((uentp = (userinfo_t *) search_ulist(tuid)))
+ fri_stat = friend_stat(currutmp, uentp);
+
+ prints("《ID暱稱》%s(%s)%*s《經濟狀況》%s",
+ muser.userid,
+ muser.nickname,
+ strlen(muser.userid) + strlen(muser.nickname) >= 26 ? 0 :
+ (int)(26 - strlen(muser.userid) - strlen(muser.nickname)), "",
+ money_level(muser.money));
+ if (uentp && ((fri_stat & HFM && !uentp->invisible) || strcmp(muser.userid,cuser.userid) == 0))
+ prints(" ($%d)", muser.money);
+ outc('\n');
+
+ prints("《上站次數》%d次", muser.numlogins);
+ move(2, 40);
+#ifdef ASSESS
+ prints("《文章篇數》%d篇 (優:%d/劣:%d)\n", muser.numposts, muser.goodpost, muser.badpost);
+#else
+ prints("《文章篇數》%d篇\n", muser.numposts);
+#endif
+
+ prints(ANSI_COLOR(1;33) "《目前動態》%-28.28s" ANSI_RESET,
+ (uentp && isvisible_stat(currutmp, uentp, fri_stat)) ?
+ modestring(uentp, 0) : "不在站上");
+
+ outs(((uentp && ISNEWMAIL(uentp)) || load_mailalert(muser.userid))
+ ? "《私人信箱》有新進信件還沒看\n" :
+ "《私人信箱》所有信件都看過了\n");
+ prints("《上次上站》%-28.28s《上次故鄉》%s\n",
+ Cdate(&muser.lastlogin),
+ (muser.lasthost[0] ? muser.lasthost : "(不詳)"));
+ prints("《五子棋戰績》%3d 勝 %3d 敗 %3d 和 "
+ "《象棋戰績》%3d 勝 %3d 敗 %3d 和\n",
+ muser.five_win, muser.five_lose, muser.five_tie,
+ muser.chc_win, muser.chc_lose, muser.chc_tie);
+#ifdef ASSESS
+ prints("《競標評比》 優 %d / 劣 %d", muser.goodsale, muser.badsale);
+ move(6, 40);
+#endif
+ if ((uentp && ((fri_stat & HFM) || strcmp(muser.userid,cuser.userid) == 0) && !uentp->invisible))
+ prints("《 性 別 》%-28.28s\n", sex[muser.sex % 8]);
+
+ showplans_userec(&muser);
+ if(HasUserPerm(PERM_SYSOP|PERM_POLICE) )
+ {
+ if(vmsg("T: 開立罰單")=='T')
+ violate_law(&muser, tuid);
+ }
+ else
+ pressanykey();
+ if(now-last_query<1)
+ sleep(2);
+ else if(now-last_query<2)
+ sleep(1);
+ last_query=now;
+ return FULLUPDATE;
+ }
+ return DONOTHING;
+}
+
+static char t_last_write[80];
+
+void check_water_init(void)
+{
+ if(water==NULL) {
+ water = (water_t*)malloc(sizeof(water_t)*6);
+ memset(water, 0, sizeof(water_t)*6);
+ water_which = &water[0];
+
+ strlcpy(water[0].userid, " 全部 ", sizeof(water[0].userid));
+ }
+}
+
+static void
+water_scr(const water_t * tw, int which, char type)
+{
+ if (type == 1) {
+ int i;
+ const int colors[] = {33, 37, 33, 37, 33};
+ move(8 + which, 28);
+ outc(' ');
+ move(8 + which, 28);
+ prints(ANSI_COLOR(1;37;45) " %c %-14s " ANSI_COLOR(0) "",
+ tw->uin ? ' ' : 'x',
+ tw->userid);
+ for (i = 0; i < 5; ++i) {
+ move(16 + i, 4);
+ outc(' ');
+ move(16 + i, 4);
+ if (tw->msg[(tw->top - i + 4) % 5].last_call_in[0] != 0)
+ prints(ANSI_COLOR(0) " " ANSI_COLOR(1;%d;44) "★%-64s" ANSI_COLOR(0) " \n",
+ colors[i],
+ tw->msg[(tw->top - i + 4) % 5].last_call_in);
+ else
+ outs(ANSI_COLOR(0) " \n");
+ }
+
+ move(21, 4);
+ outc(' ');
+ move(21, 4);
+ prints(ANSI_COLOR(0) " " ANSI_COLOR(1;37;46) "%-66s" ANSI_COLOR(0) " \n",
+ tw->msg[5].last_call_in);
+
+ move(0, 0);
+ outc(' ');
+ move(0, 0);
+#ifdef PLAY_ANGEL
+ if (tw->msg[0].msgmode == MSGMODE_TOANGEL)
+ outs(ANSI_COLOR(0) "回答小主人:");
+ else
+#endif
+ prints(ANSI_COLOR(0) "反擊 %s:", tw->userid);
+ clrtoeol();
+ move(0, strlen(tw->userid) + 6);
+ } else {
+ move(8 + which, 28);
+ outs("123456789012345678901234567890");
+ move(8 + which, 28);
+ prints(ANSI_COLOR(1;37;44) " %c %-13s " ANSI_COLOR(0) "",
+ tw->uin ? ' ' : 'x',
+ tw->userid);
+ }
+}
+
+void
+my_write2(void)
+{
+ int i, ch, currstat0;
+ char genbuf[256], msg[80], done = 0, c0, which;
+ water_t *tw;
+ unsigned char mode0;
+
+ check_water_init();
+ if (swater[0] == NULL)
+ return;
+ wmofo = REPLYING;
+ currstat0 = currstat;
+ c0 = currutmp->chatid[0];
+ mode0 = currutmp->mode;
+ currutmp->mode = 0;
+ currutmp->chatid[0] = 3;
+ currstat = DBACK;
+
+ //init screen
+ move(WB_OFO_USER_TOP, WB_OFO_USER_LEFT);
+ outs(ANSI_COLOR(1;33;46) " ↑ 水球反擊對象 ↓" ANSI_COLOR(0) "");
+ for (i = 0; i < WB_OFO_USER_HEIGHT;++i)
+ if (swater[i] == NULL || swater[i]->pid == 0)
+ break;
+ else {
+ if (swater[i]->uin &&
+ (swater[i]->pid != swater[i]->uin->pid ||
+ swater[i]->userid[0] != swater[i]->uin->userid[0]))
+ swater[i]->uin = search_ulist_pid(swater[i]->pid);
+ water_scr(swater[i], i, 0);
+ }
+ move(WB_OFO_MSG_TOP, WB_OFO_MSG_LEFT);
+ outs(ANSI_COLOR(0) " " ANSI_COLOR(1;35) "◇" ANSI_COLOR(1;36) "────────────────"
+ "─────────────────" ANSI_COLOR(1;35) "◇" ANSI_COLOR(0) " ");
+ move(WB_OFO_MSG_BOTTOM, WB_OFO_MSG_LEFT);
+ outs(" " ANSI_COLOR(1;35) "◇" ANSI_COLOR(1;36) "────────────────"
+ "─────────────────" ANSI_COLOR(1;35) "◇" ANSI_COLOR(0) " ");
+ water_scr(swater[0], 0, 1);
+ refresh();
+
+ which = 0;
+ do {
+ switch ((ch = igetch())) {
+ case Ctrl('T'):
+ case KEY_UP:
+ if (water_usies != 1) {
+ water_scr(swater[(int)which], which, 0);
+ which = (which - 1 + water_usies) % water_usies;
+ water_scr(swater[(int)which], which, 1);
+ refresh();
+ }
+ break;
+
+ case KEY_DOWN:
+ case Ctrl('R'):
+ if (water_usies != 1) {
+ water_scr(swater[(int)which], which, 0);
+ which = (which + 1 + water_usies) % water_usies;
+ water_scr(swater[(int)which], which, 1);
+ refresh();
+ }
+ break;
+
+ case KEY_LEFT:
+ done = 1;
+ break;
+
+ case KEY_UNKNOWN:
+ break;
+
+ default:
+ done = 1;
+ tw = swater[(int)which];
+
+ if (!tw->uin)
+ break;
+
+ if (ch != '\r' && ch != '\n') {
+ msg[0] = ch, msg[1] = 0;
+ } else
+ msg[0] = 0;
+ move(0, 0);
+ outs(ANSI_RESET);
+ clrtoeol();
+#ifndef PLAY_ANGEL
+ snprintf(genbuf, sizeof(genbuf), "攻擊 %s:", tw->userid);
+ i = WATERBALL_CONFIRM;
+#else
+ if (tw->msg[0].msgmode == MSGMODE_WRITE) {
+ snprintf(genbuf, sizeof(genbuf), "攻擊 %s:", tw->userid);
+ i = WATERBALL_CONFIRM;
+ } else if (tw->msg[0].msgmode == MSGMODE_TOANGEL) {
+ strcpy(genbuf, "回答小主人:");
+ i = WATERBALL_CONFIRM_ANSWER;
+ } else { /* tw->msg[0].msgmode == MSGMODE_FROMANGEL */
+ strcpy(genbuf, "再問他一次:");
+ i = WATERBALL_CONFIRM_ANGEL;
+ }
+#endif
+ if (!oldgetdata(0, 0, genbuf, msg,
+ 80 - strlen(tw->userid) - 6, DOECHO))
+ break;
+
+ if (my_write(tw->pid, msg, tw->userid, i, tw->uin))
+ strlcpy(tw->msg[5].last_call_in, t_last_write,
+ sizeof(tw->msg[5].last_call_in));
+ break;
+ }
+ } while (!done);
+
+ currstat = currstat0;
+ currutmp->chatid[0] = c0;
+ currutmp->mode = mode0;
+ if (wmofo == RECVINREPLYING) {
+ wmofo = NOTREPLYING;
+ write_request(0);
+ }
+ wmofo = NOTREPLYING;
+}
+
+/*
+ * 被呼叫的時機:
+ * 1. 丟群組水球 flag = WATERBALL_PREEDIT, 1 (pre-edit)
+ * 2. 回水球 flag = WATERBALL_GENERAL, 0
+ * 3. 上站aloha flag = WATERBALL_ALOHA, 2 (pre-edit)
+ * 4. 廣播 flag = WATERBALL_SYSOP, 3 if SYSOP
+ * flag = WATERBALL_PREEDIT, 1 otherwise
+ * 5. 丟水球 flag = WATERBALL_GENERAL, 0
+ * 6. my_write2 flag = WATERBALL_CONFIRM, 4 (pre-edit but confirm)
+ * 7. (when defined PLAY_ANGEL)
+ * 呼叫小天使 flag = WATERBALL_ANGEL, 5 (id = "小天使")
+ * 8. (when defined PLAY_ANGEL)
+ * 回答小主人 flag = WATERBALL_ANSWER, 6 (隱藏 id)
+ * 9. (when defined PLAY_ANGEL)
+ * 呼叫小天使 flag = WATERBALL_CONFIRM_ANGEL, 7 (pre-edit)
+ * 10. (when defined PLAY_ANGEL)
+ * 回答小主人 flag = WATERBALL_CONFIRM_ANSWER, 8 (pre-edit)
+ */
+int
+my_write(pid_t pid, const char *prompt, const char *id, int flag, userinfo_t * puin)
+{
+ int len, currstat0 = currstat, fri_stat = -1;
+ char msg[80], destid[IDLEN + 1];
+ char genbuf[200], buf[200], c0 = currutmp->chatid[0];
+ unsigned char mode0 = currutmp->mode;
+ userinfo_t *uin;
+ uin = (puin != NULL) ? puin : (userinfo_t *) search_ulist_pid(pid);
+ strlcpy(destid, id, sizeof(destid));
+ check_water_init();
+
+ /* what if uin is NULL but other conditions are not true?
+ * will this situation cause SEGV?
+ * should this "!uin &&" replaced by "!uin ||" ?
+ */
+ if ((!uin || !uin->userid[0]) && !((flag == WATERBALL_GENERAL
+#ifdef PLAY_ANGEL
+ || flag == WATERBALL_ANGEL || flag == WATERBALL_ANSWER
+#endif
+ )
+ && water_which->count > 0)) {
+ vmsg("糟糕! 對方已落跑了(不在站上)! ");
+ watermode = -1;
+ return 0;
+ }
+ currutmp->mode = 0;
+ currutmp->chatid[0] = 3;
+ currstat = DBACK;
+
+ if (flag == WATERBALL_GENERAL
+#ifdef PLAY_ANGEL
+ || flag == WATERBALL_ANGEL || flag == WATERBALL_ANSWER
+#endif
+ ) {
+ /* 一般水球 */
+ watermode = 0;
+
+ /* should we alert if we're in disabled mode? */
+ switch(currutmp->pager)
+ {
+ case PAGER_DISABLE:
+ case PAGER_ANTIWB:
+ move(1, 0); clrtoeol();
+ outs(ANSI_COLOR(1;31) "你的呼叫器目前不接受別人丟水球,對方可能無法回話。" ANSI_RESET);
+ break;
+
+ case PAGER_FRIENDONLY:
+#if 0
+ // 如果對方正在下站,這個好像不太穩會 crash (?) */
+ if (uin && uin->userid[0])
+ {
+ fri_stat = friend_stat(currutmp, uin);
+ if(fri_stat & HFM)
+ break;
+ }
+#endif
+ move(1, 0); clrtoeol();
+ outs(ANSI_COLOR(1;31) "你的呼叫器目前只接受好友丟水球,若對方非好友則可能無法回話。" ANSI_RESET);
+ break;
+ }
+
+ if (!(len = getdata(0, 0, prompt, msg, 56, DOECHO))) {
+ currutmp->chatid[0] = c0;
+ currutmp->mode = mode0;
+ currstat = currstat0;
+ watermode = -1;
+ return 0;
+ }
+
+ if (watermode > 0) {
+ int i;
+
+ i = (water_which->top - watermode + MAX_REVIEW) % MAX_REVIEW;
+ uin = (userinfo_t *) search_ulist_pid(water_which->msg[i].pid);
+#ifdef PLAY_ANGEL
+ if (water_which->msg[i].msgmode == MSGMODE_FROMANGEL)
+ flag = WATERBALL_ANGEL;
+ else if (water_which->msg[i].msgmode == MSGMODE_TOANGEL)
+ flag = WATERBALL_ANSWER;
+ else
+ flag = WATERBALL_GENERAL;
+#endif
+ strlcpy(destid, water_which->msg[i].userid, sizeof(destid));
+ }
+ } else {
+ /* pre-edit 的水球 */
+ strlcpy(msg, prompt, sizeof(msg));
+ len = strlen(msg);
+ }
+
+ strip_ansi(msg, msg, STRIP_ALL);
+ if (uin && *uin->userid &&
+ (flag == WATERBALL_GENERAL || flag == WATERBALL_CONFIRM
+#ifdef PLAY_ANGEL
+ || flag == WATERBALL_ANGEL || flag == WATERBALL_ANSWER
+ || flag == WATERBALL_CONFIRM_ANGEL
+ || flag == WATERBALL_CONFIRM_ANSWER
+#endif
+ ))
+ {
+ snprintf(buf, sizeof(buf), "丟給 %s : %s [Y/n]?", destid, msg);
+
+ getdata(0, 0, buf, genbuf, 3, LCECHO);
+ if (genbuf[0] == 'n') {
+ currutmp->chatid[0] = c0;
+ currutmp->mode = mode0;
+ currstat = currstat0;
+ watermode = -1;
+ return 0;
+ }
+ }
+ watermode = -1;
+ if (!uin || !*uin->userid || (strcasecmp(destid, uin->userid)
+#ifdef PLAY_ANGEL
+ && flag != WATERBALL_ANGEL && flag != WATERBALL_CONFIRM_ANGEL) ||
+ ((flag == WATERBALL_ANGEL || flag == WATERBALL_CONFIRM_ANGEL)
+ && strcasecmp(cuser.myangel, uin->userid)
+#endif
+ )) {
+ vmsg("糟糕! 對方已落跑了(不在站上)! ");
+ currutmp->chatid[0] = c0;
+ currutmp->mode = mode0;
+ currstat = currstat0;
+ return 0;
+ }
+ if(fri_stat < 0)
+ fri_stat = friend_stat(currutmp, uin);
+ // else, fri_stat was already calculated. */
+
+ if (flag != WATERBALL_ALOHA) { /* aloha 的水球不用存下來 */
+ /* 存到自己的水球檔 */
+ if (!fp_writelog) {
+ sethomefile(genbuf, cuser.userid, fn_writelog);
+ fp_writelog = fopen(genbuf, "a");
+ }
+ if (fp_writelog) {
+ fprintf(fp_writelog, "To %s: %s [%s]\n",
+ destid, msg, Cdatelite(&now));
+ snprintf(t_last_write, 66, "To %s: %s", destid, msg);
+ }
+ }
+ if (flag == WATERBALL_SYSOP && uin->msgcount) {
+ /* 不懂 */
+ uin->destuip = currutmp - &SHM->uinfo[0];
+ uin->sig = 2;
+ if (uin->pid > 0)
+ kill(uin->pid, SIGUSR1);
+ } else if ((flag != WATERBALL_ALOHA &&
+#ifdef PLAY_ANGEL
+ flag != WATERBALL_ANGEL &&
+ flag != WATERBALL_ANSWER &&
+ flag != WATERBALL_CONFIRM_ANGEL &&
+ flag != WATERBALL_CONFIRM_ANSWER &&
+ /* Angel accept or not is checked outside.
+ * Avoiding new users don't know what pager is. */
+#endif
+ !HasUserPerm(PERM_SYSOP) &&
+ (uin->pager == PAGER_ANTIWB ||
+ uin->pager == PAGER_DISABLE ||
+ (uin->pager == PAGER_FRIENDONLY &&
+ !(fri_stat & HFM))))
+#ifdef PLAY_ANGEL
+ || ((flag == WATERBALL_ANGEL || flag == WATERBALL_CONFIRM_ANGEL)
+ && he_reject_me(uin))
+#endif
+ ) {
+ outmsg(ANSI_COLOR(1;33;41) "糟糕! 對方防水了! " ANSI_COLOR(37) "~>_<~" ANSI_RESET);
+ } else {
+ int write_pos = uin->msgcount; /* try to avoid race */
+ if ( write_pos < (MAX_MSGS - 1) ) { /* race here */
+ unsigned char pager0 = uin->pager;
+
+ uin->msgcount = write_pos + 1;
+ uin->pager = PAGER_DISABLE;
+ uin->msgs[write_pos].pid = currpid;
+#ifdef PLAY_ANGEL
+ if (flag == WATERBALL_ANSWER || flag == WATERBALL_CONFIRM_ANSWER)
+ strlcpy(uin->msgs[write_pos].userid, "小天使",
+ sizeof(uin->msgs[write_pos].userid));
+ else
+#endif
+ strlcpy(uin->msgs[write_pos].userid, cuser.userid,
+ sizeof(uin->msgs[write_pos].userid));
+ strlcpy(uin->msgs[write_pos].last_call_in, msg,
+ sizeof(uin->msgs[write_pos].last_call_in));
+#ifndef PLAY_ANGEL
+ uin->msgs[write_pos].msgmode = MSGMODE_WRITE;
+#else
+ switch (flag) {
+ case WATERBALL_ANGEL:
+ case WATERBALL_CONFIRM_ANGEL:
+ uin->msgs[write_pos].msgmode = MSGMODE_TOANGEL;
+ break;
+ case WATERBALL_ANSWER:
+ case WATERBALL_CONFIRM_ANSWER:
+ uin->msgs[write_pos].msgmode = MSGMODE_FROMANGEL;
+ break;
+ default:
+ uin->msgs[write_pos].msgmode = MSGMODE_WRITE;
+ }
+#endif
+ uin->pager = pager0;
+ } else if (flag != WATERBALL_ALOHA)
+ outmsg(ANSI_COLOR(1;33;41) "糟糕! 對方不行了! (收到太多水球) " ANSI_COLOR(37) "@_@" ANSI_RESET);
+
+ if (uin->msgcount >= 1 &&
+#ifdef NOKILLWATERBALL
+ !(uin->wbtime = now) /* race */
+#else
+ (uin->pid <= 0 || kill(uin->pid, SIGUSR2) == -1)
+#endif
+ && flag != WATERBALL_ALOHA)
+ outmsg(ANSI_COLOR(1;33;41) "糟糕! 沒打中! " ANSI_COLOR(37) "~>_<~" ANSI_RESET);
+ else if (uin->msgcount == 1 && flag != WATERBALL_ALOHA)
+ outmsg(ANSI_COLOR(1;33;44) "水球砸過去了! " ANSI_COLOR(37) "*^o^*" ANSI_RESET);
+ else if (uin->msgcount > 1 && uin->msgcount < MAX_MSGS &&
+ flag != WATERBALL_ALOHA)
+ outmsg(ANSI_COLOR(1;33;44) "再補上一粒! " ANSI_COLOR(37) "*^o^*" ANSI_RESET);
+
+#if defined(NOKILLWATERBALL) && defined(PLAY_ANGEL)
+ /* Questioning and answering should better deliver immediately. */
+ if ((flag == WATERBALL_ANGEL || flag == WATERBALL_ANSWER ||
+ flag == WATERBALL_CONFIRM_ANGEL ||
+ flag == WATERBALL_CONFIRM_ANSWER) && uin->pid)
+ kill(uin->pid, SIGUSR2);
+#endif
+ }
+
+ clrtoeol();
+
+ currutmp->chatid[0] = c0;
+ currutmp->mode = mode0;
+ currstat = currstat0;
+ return 1;
+}
+
+void
+getmessage(msgque_t msg)
+{
+ int write_pos = currutmp->msgcount;
+ if ( write_pos < (MAX_MSGS - 1) ) {
+ unsigned char pager0 = currutmp->pager;
+ currutmp->msgcount = write_pos+1;
+ memcpy(&currutmp->msgs[write_pos], &msg, sizeof(msgque_t));
+ currutmp->pager = pager0;
+ write_request(SIGUSR1);
+ }
+}
+
+void
+t_display_new(void)
+{
+ static int t_display_new_flag = 0;
+ int i, off = 2;
+ if (t_display_new_flag)
+ return;
+ else
+ t_display_new_flag = 1;
+
+ check_water_init();
+ if (WATERMODE(WATER_ORIG))
+ water_which = &water[0];
+ else
+ off = 3;
+
+ if (water[0].count && watermode > 0) {
+ move(1, 0);
+ outs("───────水─球─回─顧───");
+ outs(WATERMODE(WATER_ORIG) ?
+ "──────用[Ctrl-R Ctrl-T]鍵切換─────" :
+ "用[Ctrl-R Ctrl-T Ctrl-F Ctrl-G ]鍵切換────");
+ if (WATERMODE(WATER_NEW)) {
+ move(2, 0);
+ clrtoeol();
+ for (i = 0; i < 6; i++) {
+ if (i > 0)
+ if (swater[i - 1]) {
+
+ if (swater[i - 1]->uin &&
+ (swater[i - 1]->pid != swater[i - 1]->uin->pid ||
+ swater[i - 1]->userid[0] != swater[i - 1]->uin->userid[0]))
+ swater[i - 1]->uin = (userinfo_t *) search_ulist_pid(swater[i - 1]->pid);
+ prints("%s%c%-13.13s" ANSI_RESET,
+ swater[i - 1] != water_which ? "" :
+ swater[i - 1]->uin ? ANSI_COLOR(1;33;47) :
+ ANSI_COLOR(1;33;45),
+ !swater[i - 1]->uin ? '#' : ' ',
+ swater[i - 1]->userid);
+ } else
+ outs(" ");
+ else
+ prints("%s 全部 " ANSI_RESET,
+ water_which == &water[0] ? ANSI_COLOR(1;33;47) " " :
+ " "
+ );
+ }
+ }
+ for (i = 0; i < water_which->count; i++) {
+ int a = (water_which->top - i - 1 + MAX_REVIEW) % MAX_REVIEW;
+ int len = 75 - strlen(water_which->msg[a].last_call_in)
+ - strlen(water_which->msg[a].userid);
+ if (len < 0)
+ len = 0;
+
+ move(i + (WATERMODE(WATER_ORIG) ? 2 : 3), 0);
+ clrtoeol();
+ if (watermode - 1 != i)
+ prints(ANSI_COLOR(1;33;46) " %s " ANSI_COLOR(37;45) " %s " ANSI_RESET "%*s",
+ water_which->msg[a].userid,
+ water_which->msg[a].last_call_in, len,
+ "");
+ else
+ prints(ANSI_COLOR(1;44) ">" ANSI_COLOR(1;33;47) "%s "
+ ANSI_COLOR(37;45) " %s " ANSI_RESET "%*s",
+ water_which->msg[a].userid,
+ water_which->msg[a].last_call_in,
+ len, "");
+ }
+
+ if (t_last_write[0]) {
+ move(i + off, 0);
+ clrtoeol();
+ outs(t_last_write);
+ i++;
+ }
+ move(i + off, 0);
+ outs("──────────────────────"
+ "─────────────────");
+ if (WATERMODE(WATER_NEW))
+ while (i++ <= water[0].count) {
+ move(i + off, 0);
+ clrtoeol();
+ }
+ }
+ t_display_new_flag = 0;
+}
+
+int
+t_display(void)
+{
+ char genbuf[200], ans[4];
+ if (fp_writelog) {
+ fclose(fp_writelog);
+ fp_writelog = NULL;
+ }
+ setuserfile(genbuf, fn_writelog);
+ if (more(genbuf, YEA) != -1) {
+ move(b_lines - 4, 0);
+ clrtobot();
+ outs(ANSI_COLOR(1;33;45) "★現在 Ptt提供創新的水球整理程式★" ANSI_RESET "\n"
+ "您將水球存至信箱後, 在【郵件選單】該信件前按 u,\n"
+ "系統即會將您的水球紀錄重新整理後寄送給您唷! \n");
+ getdata(b_lines - 1, 0, "清除(C) 移至備忘錄(M) 保留(R) (C/M/R)?[R]",
+ ans, sizeof(ans), LCECHO);
+ if (*ans == 'm') {
+ fileheader_t mymail;
+ char title[128], buf[80];
+
+ sethomepath(buf, cuser.userid);
+ stampfile(buf, &mymail);
+
+ mymail.filemode = FILE_READ ;
+ strlcpy(mymail.owner, "[備.忘.錄]", sizeof(mymail.owner));
+ strlcpy(mymail.title, "熱線記錄", sizeof(mymail.title));
+ sethomedir(title, cuser.userid);
+ Rename(genbuf, buf);
+ append_record(title, &mymail, sizeof(mymail));
+ } else if (*ans == 'c')
+ unlink(genbuf);
+ return FULLUPDATE;
+ }
+ return DONOTHING;
+}
+
+static void
+do_talk_nextline(talkwin_t * twin)
+{
+ twin->curcol = 0;
+ if (twin->curln < twin->eline)
+ ++(twin->curln);
+ else
+ region_scroll_up(twin->sline, twin->eline);
+ move(twin->curln, twin->curcol);
+}
+
+static void
+do_talk_char(talkwin_t * twin, int ch, FILE *flog)
+{
+ screenline_t *line;
+ int i;
+ char ch0, buf[81];
+
+ if (isprint2(ch)) {
+ ch0 = big_picture[twin->curln].data[twin->curcol];
+ if (big_picture[twin->curln].len < 79)
+ move(twin->curln, twin->curcol);
+ else
+ do_talk_nextline(twin);
+ outc(ch);
+ ++(twin->curcol);
+ line = big_picture + twin->curln;
+ if (twin->curcol < line->len) { /* insert */
+ ++(line->len);
+ memcpy(buf, line->data + twin->curcol, 80);
+ save_cursor();
+ do_move(twin->curcol, twin->curln);
+ ochar(line->data[twin->curcol] = ch0);
+ for (i = twin->curcol + 1; i < line->len; i++)
+ ochar(line->data[i] = buf[i - twin->curcol - 1]);
+ restore_cursor();
+ }
+ line->data[line->len] = 0;
+ return;
+ }
+ switch (ch) {
+ case Ctrl('H'):
+ case '\177':
+ if (twin->curcol == 0)
+ return;
+ line = big_picture + twin->curln;
+ --(twin->curcol);
+
+ if (twin->curcol < line->len) {
+ int delta = 1;
+#ifdef DBCSAWARE
+ if (twin->curcol > 0 && ISDBCSAWARE() &&
+ getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING)
+ twin->curcol--, delta++;
+#endif
+ line->len -= delta;
+ save_cursor();
+ do_move(twin->curcol, twin->curln);
+ for (i = twin->curcol; i < line->len; i++)
+ ochar(line->data[i] = line->data[i + delta]);
+ while (delta-- > 0)
+ {
+ line->data[i++] = 0;
+ ochar(' ');
+ }
+ restore_cursor();
+ }
+ move(twin->curln, twin->curcol);
+ return;
+ case Ctrl('D'):
+ line = big_picture + twin->curln;
+ if (twin->curcol < line->len) {
+ int delta = 1;
+#ifdef DBCSAWARE
+ if (ISDBCSAWARE() &&
+ getDBCSstatus(line->data, twin->curcol) == DBCS_LEADING)
+ delta++;
+#endif
+ line->len -= delta;
+ save_cursor();
+ do_move(twin->curcol, twin->curln);
+ for (i = twin->curcol; i < line->len; i++)
+ ochar(line->data[i] = line->data[i + delta]);
+ while (delta-- > 0)
+ {
+ line->data[i++] = 0;
+ ochar(' ');
+ }
+ restore_cursor();
+ }
+ return;
+ case Ctrl('G'):
+ bell();
+ return;
+
+ case Ctrl('B'):
+ if (twin->curcol > 0) {
+ --(twin->curcol);
+#ifdef DBCSAWARE
+ line = big_picture + twin->curln;
+ if(twin->curcol > 0 && twin->curcol < line->len && ISDBCSAWARE())
+ {
+ line = big_picture + twin->curln;
+ if(getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING)
+ twin->curcol --;
+ }
+#endif
+ move(twin->curln, twin->curcol);
+ }
+ return;
+ case Ctrl('F'):
+ if (twin->curcol < 79) {
+ ++(twin->curcol);
+#ifdef DBCSAWARE
+ line = big_picture + twin->curln;
+ if(twin->curcol < 79 && twin->curcol < line->len && ISDBCSAWARE())
+ {
+ line = big_picture + twin->curln;
+ if(getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING)
+ twin->curcol++;
+ }
+#endif
+ move(twin->curln, twin->curcol);
+ }
+ return;
+
+ case KEY_TAB:
+ twin->curcol += 8;
+ if (twin->curcol > 80)
+ twin->curcol = 80;
+#ifdef DBCSAWARE
+ line = big_picture + twin->curln;
+ if(twin->curcol > 0 && twin->curcol < line->len &&
+ getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING)
+ twin->curcol--;
+#endif
+ move(twin->curln, twin->curcol);
+ return;
+ case Ctrl('A'):
+ twin->curcol = 0;
+ move(twin->curln, twin->curcol);
+ return;
+ case Ctrl('K'):
+ clrtoeol();
+ return;
+ case Ctrl('Y'):
+ twin->curcol = 0;
+ move(twin->curln, twin->curcol);
+ clrtoeol();
+ return;
+ case Ctrl('E'):
+ twin->curcol = big_picture[twin->curln].len;
+ move(twin->curln, twin->curcol);
+ return;
+ case Ctrl('M'):
+ case Ctrl('J'):
+ line = big_picture + twin->curln;
+ strlcpy(buf, (char *)line->data, line->len + 1);
+ do_talk_nextline(twin);
+ break;
+ case Ctrl('P'):
+ line = big_picture + twin->curln;
+ strlcpy(buf, (char *)line->data, line->len + 1);
+ if (twin->curln > twin->sline) {
+ --(twin->curln);
+ move(twin->curln, twin->curcol);
+ }
+#ifdef DBCSAWARE
+ line = big_picture + twin->curln;
+ if(twin->curcol > 0 && twin->curcol < line->len &&
+ getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING)
+ move(twin->curln, --twin->curcol);
+#endif
+ break;
+ case Ctrl('N'):
+ line = big_picture + twin->curln;
+ strlcpy(buf, (char *)line->data, line->len + 1);
+ if (twin->curln < twin->eline) {
+ ++(twin->curln);
+ move(twin->curln, twin->curcol);
+ }
+#ifdef DBCSAWARE
+ line = big_picture + twin->curln;
+ if(twin->curcol > 0 && twin->curcol < line->len &&
+ getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING)
+ move(twin->curln, --twin->curcol);
+#endif
+ break;
+ }
+ trim(buf);
+ if (*buf)
+ fprintf(flog, "%s%s: %s%s\n",
+ (twin->eline == b_lines - 1) ? ANSI_COLOR(1;35) : "",
+ (twin->eline == b_lines - 1) ?
+ getuserid(currutmp->destuid) : cuser.userid, buf,
+ (ch == Ctrl('P')) ? ANSI_COLOR(37;45) "(Up)" ANSI_RESET : ANSI_RESET);
+}
+
+static void
+do_talk(int fd)
+{
+ struct talkwin_t mywin, itswin;
+ char mid_line[128], data[200];
+ int i, datac, ch;
+ int im_leaving = 0;
+ FILE *log, *flog;
+ struct tm *ptime;
+ char genbuf[200], fpath[100];
+
+ STATINC(STAT_DOTALK);
+ ptime = localtime4(&now);
+
+ setuserfile(fpath, "talk_XXXXXX");
+ flog = fdopen(mkstemp(fpath), "w");
+
+ setuserfile(genbuf, fn_talklog);
+
+ if ((log = fopen(genbuf, "w")))
+ fprintf(log, "[%d/%d %d:%02d] & %s\n",
+ ptime->tm_mon + 1, ptime->tm_mday, ptime->tm_hour,
+ ptime->tm_min, save_page_requestor);
+ setutmpmode(TALK);
+
+ ch = 58 - strlen(save_page_requestor);
+ snprintf(genbuf, sizeof(genbuf), "%s【%s", cuser.userid, cuser.nickname);
+ i = ch - strlen(genbuf);
+ if (i >= 0)
+ i = (i >> 1) + 1;
+ else {
+ genbuf[ch] = '\0';
+ i = 1;
+ }
+ memset(data, ' ', i);
+ data[i] = '\0';
+
+ snprintf(mid_line, sizeof(mid_line),
+ ANSI_COLOR(1;46;37) " 談天說地 " ANSI_COLOR(45) "%s%s】"
+ " 與 %s%s" ANSI_COLOR(0) "", data, genbuf, save_page_requestor, data);
+
+ memset(&mywin, 0, sizeof(mywin));
+ memset(&itswin, 0, sizeof(itswin));
+
+ i = b_lines >> 1;
+ mywin.eline = i - 1;
+ itswin.curln = itswin.sline = i + 1;
+ itswin.eline = b_lines - 1;
+
+ clear();
+ move(i, 0);
+ outs(mid_line);
+ move(0, 0);
+
+ add_io(fd, 0);
+
+ while (1) {
+ ch = igetch();
+ if (ch == I_OTHERDATA) {
+ datac = recv(fd, data, sizeof(data), 0);
+ if (datac <= 0)
+ break;
+ for (i = 0; i < datac; i++)
+ do_talk_char(&itswin, data[i], flog);
+ } else if (ch == KEY_UNKNOWN) {
+ // skip
+ } else {
+ if (ch == Ctrl('C')) {
+ if (im_leaving)
+ break;
+ move(b_lines, 0);
+ clrtoeol();
+ outs("再按一次 Ctrl-C 就正式中止談話囉!");
+ im_leaving = 1;
+ continue;
+ }
+ if (im_leaving) {
+ move(b_lines, 0);
+ clrtoeol();
+ im_leaving = 0;
+ }
+ switch (ch) {
+ case KEY_LEFT: /* 把2byte的鍵改為一byte */
+ ch = Ctrl('B');
+ break;
+ case KEY_RIGHT:
+ ch = Ctrl('F');
+ break;
+ case KEY_UP:
+ ch = Ctrl('P');
+ break;
+ case KEY_DOWN:
+ ch = Ctrl('N');
+ break;
+ }
+ data[0] = (char)ch;
+ if (send(fd, data, 1, 0) != 1)
+ break;
+ if (log)
+ fputc((ch == Ctrl('M')) ? '\n' : (char)*data, log);
+ do_talk_char(&mywin, *data, flog);
+ }
+ }
+ if (log)
+ fclose(log);
+
+ add_io(0, 0);
+ close(fd);
+
+ if (flog) {
+ char ans[4];
+ int i;
+
+ fprintf(flog, "\n" ANSI_COLOR(33;44) "離別畫面 [%s] ... " ANSI_RESET "\n",
+ Cdatelite(&now));
+ for (i = 0; i < scr_lns; i++)
+ fprintf(flog, "%.*s\n", big_picture[i].len, big_picture[i].data);
+ fclose(flog);
+ more(fpath, NA);
+ getdata(b_lines - 1, 0, "清除(C) 移至備忘錄(M). (C/M)?[C]",
+ ans, sizeof(ans), LCECHO);
+ if (*ans == 'm') {
+ fileheader_t mymail;
+ char title[128];
+
+ sethomepath(genbuf, cuser.userid);
+ stampfile(genbuf, &mymail);
+ mymail.filemode = FILE_READ ;
+ strlcpy(mymail.owner, "[備.忘.錄]", sizeof(mymail.owner));
+ snprintf(mymail.title, sizeof(mymail.title),
+ "對話記錄 " ANSI_COLOR(1;36) "(%s)" ANSI_RESET,
+ getuserid(currutmp->destuid));
+ sethomedir(title, cuser.userid);
+ Rename(fpath, genbuf);
+ append_record(title, &mymail, sizeof(mymail));
+ } else
+ unlink(fpath);
+ flog = 0;
+ }
+ setutmpmode(XINFO);
+}
+
+#define lockreturn(unmode, state) if(lockutmpmode(unmode, state)) return
+
+int make_connection_to_somebody(userinfo_t *uin, int timeout){
+ int sock, length, pid, ch;
+ struct sockaddr_in server;
+
+ sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (sock < 0) {
+ perror("sock err");
+ unlockutmpmode();
+ return -1;
+ }
+ server.sin_family = PF_INET;
+ server.sin_addr.s_addr = INADDR_ANY;
+ server.sin_port = 0;
+ if (bind(sock, (struct sockaddr *) & server, sizeof(server)) < 0) {
+ close(sock);
+ perror("bind err");
+ unlockutmpmode();
+ return -1;
+ }
+ length = sizeof(server);
+#if defined(Solaris) && __OS_MAJOR_VERSION__ == 5 && __OS_MINOR_VERSION__ < 7
+ if (getsockname(sock, (struct sockaddr *) & server, & length) < 0) {
+#else
+ if (getsockname(sock, (struct sockaddr *) & server, (socklen_t *) & length) < 0) {
+#endif
+ close(sock);
+ perror("sock name err");
+ unlockutmpmode();
+ return -1;
+ }
+ currutmp->sockactive = YEA;
+ currutmp->sockaddr = server.sin_port;
+ currutmp->destuid = uin->uid;
+ setutmpmode(PAGE);
+ uin->destuip = currutmp - &SHM->uinfo[0];
+ pid = uin->pid;
+ if (pid > 0)
+ kill(pid, SIGUSR1);
+ clear();
+ prints("正呼叫 %s.....\n鍵入 Ctrl-D 中止....", uin->userid);
+
+ if(listen(sock, 1)<0) {
+ close(sock);
+ return -1;
+ }
+ add_io(sock, timeout);
+
+ while (1) {
+ ch = igetch();
+ if (ch == I_TIMEOUT) {
+ ch = uin->mode;
+ if (!ch && uin->chatid[0] == 1 &&
+ uin->destuip == currutmp - &SHM->uinfo[0]) {
+ bell();
+ outmsg("對方回應中...");
+ refresh();
+ } else if (ch == EDITING || ch == TALK || ch == CHATING ||
+ ch == PAGE || ch == MAILALL || ch == MONITOR ||
+ ch == M_FIVE || ch == CHC ||
+ (!ch && (uin->chatid[0] == 1 ||
+ uin->chatid[0] == 3))) {
+ add_io(0, 0);
+ close(sock);
+ currutmp->sockactive = currutmp->destuid = 0;
+ vmsg("人家在忙啦");
+ unlockutmpmode();
+ return -1;
+ } else {
+ // change to longer timeout
+ add_io(sock, 20);
+ move(0, 0);
+ outs("再");
+ bell();
+
+ uin->destuip = currutmp - &SHM->uinfo[0];
+ if (pid <= 0 || kill(pid, SIGUSR1) == -1) {
+ close(sock);
+ currutmp->sockactive = currutmp->destuid = 0;
+ add_io(0, 0);
+ vmsg(msg_usr_left);
+ unlockutmpmode();
+ return -1;
+ }
+ continue;
+ }
+ }
+ if (ch == I_OTHERDATA)
+ break;
+
+ if (ch == '\004') {
+ add_io(0, 0);
+ close(sock);
+ currutmp->sockactive = currutmp->destuid = 0;
+ unlockutmpmode();
+ return -1;
+ }
+ }
+ return sock;
+}
+
+void
+my_talk(userinfo_t * uin, int fri_stat, char defact)
+{
+ int sock, msgsock, error = 0, ch;
+ pid_t pid;
+ char c;
+ char genbuf[4];
+ unsigned char mode0 = currutmp->mode;
+ userec_t xuser;
+
+ genbuf[0] = defact;
+ ch = uin->mode;
+ strlcpy(currauthor, uin->userid, sizeof(currauthor));
+
+ if (ch == EDITING || ch == TALK || ch == CHATING || ch == PAGE ||
+ ch == MAILALL || ch == MONITOR || ch == M_FIVE || ch == CHC ||
+ ch == DARK || ch == UMODE_GO || ch == CHESSWATCHING || ch == REVERSI ||
+ (!ch && (uin->chatid[0] == 1 || uin->chatid[0] == 3)) ||
+ uin->lockmode == M_FIVE || uin->lockmode == CHC) {
+ if (ch == CHC || ch == M_FIVE || ch == UMODE_GO ||
+ ch == CHESSWATCHING || ch == REVERSI) {
+ sock = make_connection_to_somebody(uin, 20);
+ if (sock < 0)
+ vmsg("無法建立連線");
+ else {
+#if defined(Solaris) && __OS_MAJOR_VERSION__ == 5 && __OS_MINOR_VERSION__ < 7
+ msgsock = accept(sock, (struct sockaddr *) 0, 0);
+#else
+ msgsock = accept(sock, (struct sockaddr *) 0, (socklen_t *) 0);
+#endif
+ if (msgsock == -1) {
+ perror("accept");
+ close(sock);
+ return;
+ }
+ close(sock);
+ strlcpy(currutmp->mateid, uin->userid, sizeof(currutmp->mateid));
+
+ switch (uin->sig) {
+ case SIG_CHC:
+ chc(msgsock, CHESS_MODE_WATCH);
+ break;
+
+ case SIG_GOMO:
+ gomoku(msgsock, CHESS_MODE_WATCH);
+ break;
+
+ case SIG_GO:
+ gochess(msgsock, CHESS_MODE_WATCH);
+ break;
+
+ case SIG_REVERSI:
+ reversi(msgsock, CHESS_MODE_WATCH);
+ break;
+ }
+ }
+ }
+ else
+ outs("人家在忙啦");
+ } else if (!HasUserPerm(PERM_SYSOP) &&
+ (((fri_stat & HRM) && !(fri_stat & HFM)) ||
+ ((!uin->pager) && !(fri_stat & HFM)))) {
+ outs("對方關掉呼叫器了");
+ } else if (!HasUserPerm(PERM_SYSOP) &&
+ (((fri_stat & HRM) && !(fri_stat & HFM)) || uin->pager == PAGER_DISABLE)) {
+ outs("對方拔掉呼叫器了");
+ } else if (!HasUserPerm(PERM_SYSOP) &&
+ !(fri_stat & HFM) && uin->pager == PAGER_FRIENDONLY) {
+ outs("對方只接受好友的呼叫");
+ } else if (!(pid = uin->pid) /* || (kill(pid, 0) == -1) */ ) {
+ //resetutmpent();
+ outs(msg_usr_left);
+ } else {
+ int i,j;
+
+ if (!defact) {
+ showplans(uin->userid);
+ move(2, 0);
+ for(i=0;i<2;i++) {
+ if(uin->withme & (WITHME_ALLFLAG<<i)) {
+ if(i==0)
+ outs("歡迎跟我:");
+ else
+ outs("請別找我:");
+ for(j=0; j<32 && withme_str[j/2]; j+=2)
+ if(uin->withme & (1<<(j+i)))
+ if(withme_str[j/2]) {
+ outs(withme_str[j/2]);
+ outc(' ');
+ }
+ outc('\n');
+ }
+ }
+ move(4, 0);
+ outs("要和他(她) (T)談天(F)下五子棋(P)鬥寵物(C)下象棋(D)下暗棋(G)下圍棋(R)下黑白棋");
+ getdata(5, 0, " (N)沒事找錯人了?[N] ", genbuf, 4, LCECHO);
+ }
+
+ switch (*genbuf) {
+ case 'y':
+ case 't':
+ uin->sig = SIG_TALK;
+ break;
+ case 'f':
+ lockreturn(M_FIVE, LOCK_THIS);
+ uin->sig = SIG_GOMO;
+ break;
+ case 'c':
+ lockreturn(CHC, LOCK_THIS);
+ uin->sig = SIG_CHC;
+ break;
+ case 'd':
+ uin->sig = SIG_DARK;
+ break;
+ case 'g':
+ uin->sig = SIG_GO;
+ break;
+ case 'r':
+ uin->sig = SIG_REVERSI;
+ break;
+ case 'p':
+ reload_chicken();
+ getuser(uin->userid, &xuser);
+ if (uin->lockmode == CHICKEN || currutmp->lockmode == CHICKEN)
+ error = 1;
+ if (!cuser.mychicken.name[0] || !xuser.mychicken.name[0])
+ error = 2;
+ if (error) {
+ vmsg(error == 2 ? "並非兩人都養寵物" :
+ "有一方的寵物正在使用中");
+ return;
+ }
+ uin->sig = SIG_PK;
+ break;
+ default:
+ return;
+ }
+
+ uin->turn = 1;
+ currutmp->turn = 0;
+ strlcpy(uin->mateid, currutmp->userid, sizeof(uin->mateid));
+ strlcpy(currutmp->mateid, uin->userid, sizeof(currutmp->mateid));
+
+ sock = make_connection_to_somebody(uin, 5);
+ if(sock==-1) {
+ vmsg("無法建立連線");
+ return;
+ }
+
+#if defined(Solaris) && __OS_MAJOR_VERSION__ == 5 && __OS_MINOR_VERSION__ < 7
+ msgsock = accept(sock, (struct sockaddr *) 0, 0);
+#else
+ msgsock = accept(sock, (struct sockaddr *) 0, (socklen_t *) 0);
+#endif
+ if (msgsock == -1) {
+ perror("accept");
+ close(sock);
+ unlockutmpmode();
+ return;
+ }
+ add_io(0, 0);
+ close(sock);
+ currutmp->sockactive = NA;
+
+ if (uin->sig == SIG_CHC || uin->sig == SIG_GOMO ||
+ uin->sig == SIG_GO || uin->sig == SIG_REVERSI)
+ ChessEstablishRequest(msgsock);
+
+ add_io(msgsock, 0);
+ while ((ch = igetch()) != I_OTHERDATA) {
+ if (ch == Ctrl('D')) {
+ add_io(0, 0);
+ close(msgsock);
+ unlockutmpmode();
+ return;
+ }
+ }
+
+ if (read(msgsock, &c, sizeof(c)) != sizeof(c))
+ c = 'n';
+ add_io(0, 0);
+
+ if (c == 'y') {
+ snprintf(save_page_requestor, sizeof(save_page_requestor),
+ "%s (%s)", uin->userid, uin->nickname);
+ switch (uin->sig) {
+ case SIG_DARK:
+ main_dark(msgsock, uin);
+ break;
+ case SIG_PK:
+ chickenpk(msgsock);
+ break;
+ case SIG_GOMO:
+ gomoku(msgsock, CHESS_MODE_VERSUS);
+ break;
+ case SIG_CHC:
+ chc(msgsock, CHESS_MODE_VERSUS);
+ break;
+ case SIG_GO:
+ gochess(msgsock, CHESS_MODE_VERSUS);
+ break;
+ case SIG_REVERSI:
+ reversi(msgsock, CHESS_MODE_VERSUS);
+ break;
+ case SIG_TALK:
+ default:
+ do_talk(msgsock);
+ }
+ } else {
+ move(9, 9);
+ outs("【回音】 ");
+ switch (c) {
+ case 'a':
+ outs("我現在很忙,請等一會兒再 call 我,好嗎?");
+ break;
+ case 'b':
+ prints("對不起,我有事情不能跟你 %s....", sig_des[uin->sig]);
+ break;
+ case 'd':
+ outs("我要離站囉..下次再聊吧..........");
+ break;
+ case 'c':
+ outs("請不要吵我好嗎?");
+ break;
+ case 'e':
+ outs("找我有事嗎?請先來信唷....");
+ break;
+ case 'f':
+ {
+ char msgbuf[60];
+
+ read(msgsock, msgbuf, 60);
+ prints("對不起,我現在不能跟你 %s,因為\n", sig_des[uin->sig]);
+ move(10, 18);
+ outs(msgbuf);
+ }
+ break;
+ case '1':
+ prints("%s?先拿100銀兩來..", sig_des[uin->sig]);
+ break;
+ case '2':
+ prints("%s?先拿1000銀兩來..", sig_des[uin->sig]);
+ break;
+ default:
+ prints("我現在不想 %s 啦.....:)", sig_des[uin->sig]);
+ }
+ close(msgsock);
+ }
+ }
+ currutmp->mode = mode0;
+ currutmp->destuid = 0;
+ unlockutmpmode();
+ pressanykey();
+}
+
+/* 選單式聊天介面 */
+#define US_PICKUP 1234
+#define US_RESORT 1233
+#define US_ACTION 1232
+#define US_REDRAW 1231
+
+static void
+t_showhelp(void)
+{
+ clear();
+
+ outs(ANSI_COLOR(36) "【 休閒聊天使用說明 】" ANSI_RESET "\n\n"
+ "(←)(e) 結束離開 (h) 看使用說明\n"
+ "(↑)/(↓)(n) 上下移動 (TAB) 切換排序方式\n"
+ "(PgUp)(^B) 上頁選單 ( )(PgDn)(^F) 下頁選單\n"
+ "(Hm)/($)(Ed) 首/尾 (S) 來源/好友描述/戰績 切換\n"
+ "(m) 寄信 (q/c) 查詢網友/寵物\n"
+ "(r) 閱\讀信件 (l/C) 看上次熱訊/切換隱身\n"
+ "(f) 全部/好友列表 (數字) 跳至該使用者\n"
+ "(p) 切換呼叫器 (g/i) 給錢/切換心情\n"
+ "(a/d/o) 好友 增加/刪除/修改 (s) 網友 ID 搜尋\n"
+ "(N) 修改暱稱 (y) 我想找人聊天、下棋…\n");
+
+ if (HasUserPerm(PERM_PAGE)) {
+ outs("\n" ANSI_COLOR(36) "【 交談專用鍵 】" ANSI_RESET "\n"
+ "(→)(t)(Enter) 跟他/她聊天\n"
+ "(w) 熱線 Call in\n"
+ "(^W)切換水球方式 一般 / 進階 / 未來\n"
+ "(b) 對好友廣播 (一定要在好友列表中)\n"
+ "(^R) 即時回應 (有人 Call in 你時)\n");
+ }
+ if (HasUserPerm(PERM_SYSOP)) {
+ outs("\n" ANSI_COLOR(36) "【 站長專用鍵 】" ANSI_RESET "\n\n");
+ outs("(u)/(H) 設定使用者資料/切換隱形模式\n");
+ outs("(K) 把壞蛋踢出去\n");
+#if defined(SHOWBOARD) && defined(DEBUG)
+ outs("(Y) 顯示正在看什麼板\n");
+#endif
+ }
+#ifdef PLAY_ANGEL
+ if (HasUserPerm(PERM_LOGINOK))
+ pressanykey_or_callangel();
+ else
+#endif
+ pressanykey();
+}
+
+/* Kaede show friend description */
+static char *
+friend_descript(const userinfo_t * uentp, char *desc_buf, int desc_buflen)
+{
+ char *space_buf = "", *flag;
+ char fpath[80], name[IDLEN + 2], *desc, *ptr;
+ int len;
+ FILE *fp;
+ char genbuf[STRLEN];
+
+ STATINC(STAT_FRIENDDESC);
+ if((set_friend_bit(currutmp,uentp)&IFH)==0)
+ return space_buf;
+
+ setuserfile(fpath, friend_file[0]);
+
+ STATINC(STAT_FRIENDDESC_FILE);
+ if ((fp = fopen(fpath, "r"))) {
+ snprintf(name, sizeof(name), "%s ", uentp->userid);
+ len = strlen(name);
+ desc = genbuf + 13;
+
+ /* TODO maybe none linear search, or fread, or cache */
+ while ((flag = fgets(genbuf, STRLEN, fp))) {
+ if (!memcmp(genbuf, name, len)) {
+ if ((ptr = strchr(desc, '\n')))
+ ptr[0] = '\0';
+ break;
+ }
+ }
+ fclose(fp);
+ if (flag)
+ strlcpy(desc_buf, desc, desc_buflen);
+ else
+ return space_buf;
+
+ return desc_buf;
+ } else
+ return space_buf;
+}
+
+static const char *
+descript(int show_mode, const userinfo_t * uentp, int diff)
+{
+ static char description[30];
+ switch (show_mode) {
+ case 1:
+ return friend_descript(uentp, description, sizeof(description));
+ case 0:
+ return (((uentp->pager != PAGER_DISABLE && uentp->pager != PAGER_ANTIWB && diff) ||
+ HasUserPerm(PERM_SYSOP)) ?
+#ifdef WHERE
+ uentp->from_alias ? SHM->home_desc[uentp->from_alias] :
+ uentp->from
+#else
+ uentp->from
+#endif
+ : "*");
+ case 2:
+ snprintf(description, sizeof(description),
+ "%4d/%4d/%2d %c", uentp->five_win,
+ uentp->five_lose, uentp->five_tie,
+ (uentp->withme&WITHME_FIVE)?'o':(uentp->withme&WITHME_NOFIVE)?'x':' ');
+ return description;
+ case 3:
+ snprintf(description, sizeof(description),
+ "%4d/%4d/%2d %c", uentp->chc_win,
+ uentp->chc_lose, uentp->chc_tie,
+ (uentp->withme&WITHME_CHESS)?'o':(uentp->withme&WITHME_NOCHESS)?'x':' ');
+ return description;
+ case 4:
+ snprintf(description, sizeof(description),
+ "%4d %s", uentp->chess_elo_rating,
+ (uentp->withme&WITHME_CHESS)?"找我下棋":(uentp->withme&WITHME_NOCHESS)?"別找我":"");
+ return description;
+ case 5:
+ snprintf(description, sizeof(description),
+ "%4d/%4d/%2d %c", uentp->go_win,
+ uentp->go_lose, uentp->go_tie,
+ (uentp->withme&WITHME_GO)?'o':(uentp->withme&WITHME_NOGO)?'x':' ');
+ return description;
+ default:
+ syslog(LOG_WARNING, "damn!!! what's wrong?? show_mode = %d",
+ show_mode);
+ return "";
+ }
+}
+
+/*
+ * userlist
+ *
+ * 有別於其他大部份 bbs在實作使用者名單時, 都是將所有 online users 取一份到
+ * local space 中, 按照所須要的方式 sort 好 (如按照 userid , 五子棋, 來源等
+ * 等) . 這將造成大量的浪費: 為什麼每個人都要為了產生這一頁僅 20 個人的資料
+ * 而去 sort 其他一萬人的資料?
+ *
+ * 一般來說, 一份完整的使用者名單可以分成「好友區」和「非好友區」. 不同人的
+ * 「好友區」應該會長的不一樣, 不過「非好友區」應該是長的一樣的. 針對這項特
+ * 性, 兩區有不同的實作方式.
+ *
+ * + 好友區
+ * 好友區只有在排列方式為 [嗨! 朋友] 的時候「可能」會用到.
+ * 每個 process可以透過 currutmp->friend_online[]得到互相間有好友關係的資
+ * 料 (不包括板友, 板友是另外生出來的) 不過 friend_online[]是 unorder的.
+ * 所以須要先把所有的人拿出來, 重新 sort 一次.
+ * 好友區 (互相任一方有設好友+ 板友) 最多只會有 MAX_FRIENDS個
+ * 因為產生好友區的 cost 相當高, "能不產生就不要產生"
+ *
+ * + 非好友區
+ * 透過 shmctl utmpsortd , 定期 (通常一秒一次) 將全站的人按照各種不同的方
+ * 式 sort 好, 放置在 SHM->sorted中.
+ *
+ * 接下來, 我們每次只從確定的起始位置拿, 特別是除非有須要, 不然不會去產生好
+ * 友區.
+ *
+ * 各個 function 摘要
+ * sort_cmpfriend() sort function, key: friend type
+ * pickup_maxpages() # pages of userlist
+ * pickup_myfriend() 產生好友區
+ * pickup_bfriend() 產生板友
+ * pickup() 產生某一頁使用者名單
+ * draw_pickup() 把畫面輸出
+ * userlist() 主函式, 負責呼叫 pickup()/draw_pickup() 以及按鍵處理
+ *
+ * SEE ALSO
+ * include/pttstruct.h
+ *
+ * BUGS
+ * 搜尋的時候沒有辦法移到該人上面
+ *
+ * AUTHOR
+ * in2 <in2@in2home.org>
+ */
+char nPickups;
+
+static int
+sort_cmpfriend(const void *a, const void *b)
+{
+ if (((((pickup_t *) a)->friend) & ST_FRIEND) ==
+ ((((pickup_t *) b)->friend) & ST_FRIEND))
+ return strcasecmp(((pickup_t *) a)->ui->userid,
+ ((pickup_t *) b)->ui->userid);
+ else
+ return (((pickup_t *) b)->friend & ST_FRIEND) -
+ (((pickup_t *) a)->friend & ST_FRIEND);
+}
+
+int
+pickup_maxpages(int pickupway, int nfriends)
+{
+ int number;
+ if (cuser.uflag & FRIEND_FLAG)
+ number = nfriends;
+ else
+ number = SHM->UTMPnumber +
+ (pickupway == 0 ? nfriends : 0);
+ return (number - 1) / nPickups + 1;
+}
+
+static int
+pickup_myfriend(pickup_t * friends,
+ int *myfriend, int *friendme, int *badfriend)
+{
+ userinfo_t *uentp;
+ int i, where, frstate, ngets = 0;
+
+ STATINC(STAT_PICKMYFRIEND);
+ *badfriend = 0;
+ *myfriend = *friendme = 1;
+ for (i = 0; currutmp->friend_online[i] && i < MAX_FRIEND; ++i) {
+ where = currutmp->friend_online[i] & 0xFFFFFF;
+ if (VALID_USHM_ENTRY(where) &&
+ (uentp = &SHM->uinfo[where]) && uentp->pid &&
+ uentp != currutmp &&
+ isvisible_stat(currutmp, uentp,
+ frstate =
+ currutmp->friend_online[i] >> 24)){
+ if( frstate & IRH )
+ ++*badfriend;
+ if( !(frstate & IRH) || ((frstate & IRH) && (frstate & IFH)) ){
+ friends[ngets].ui = uentp;
+ friends[ngets].uoffset = where;
+ friends[ngets++].friend = frstate;
+ if (frstate & IFH)
+ ++* myfriend;
+ if (frstate & HFM)
+ ++* friendme;
+ }
+ }
+ }
+ /* 把自己加入好友區 */
+ friends[ngets].ui = currutmp;
+ friends[ngets++].friend = (IFH | HFM);
+ return ngets;
+}
+
+static int
+pickup_bfriend(pickup_t * friends, int base)
+{
+ userinfo_t *uentp;
+ int i, ngets = 0;
+ int currsorted = SHM->currsorted, number = SHM->UTMPnumber;
+
+ STATINC(STAT_PICKBFRIEND);
+ friends = friends + base;
+ for (i = 0; i < number && ngets < MAX_FRIEND - base; ++i) {
+ uentp = &SHM->uinfo[SHM->sorted[currsorted][0][i]];
+ /* TODO isvisible() 重複用到了 friend_stat() */
+ if (uentp && uentp->pid && uentp->brc_id == currutmp->brc_id &&
+ currutmp != uentp && isvisible(currutmp, uentp) &&
+ (base || !(friend_stat(currutmp, uentp) & (IFH | HFM)))) {
+ friends[ngets].ui = uentp;
+ friends[ngets++].friend = IBH;
+ }
+ }
+ return ngets;
+}
+
+static void
+pickup(pickup_t * currpickup, int pickup_way, int *page,
+ int *nfriend, int *myfriend, int *friendme, int *bfriend, int *badfriend)
+{
+ /* avoid race condition */
+ int currsorted = SHM->currsorted;
+ int utmpnumber = SHM->UTMPnumber;
+ int friendtotal = currutmp->friendtotal;
+
+ int *ulist;
+ userinfo_t *u;
+ int which, sorted_way, size = 0, friend;
+
+ if (friendtotal == 0)
+ *myfriend = *friendme = 1;
+
+ /* 產生好友區 */
+ which = *page * nPickups;
+ if( (cuser.uflag & FRIEND_FLAG) || /* 只顯示好友模式 */
+ ((pickup_way == 0) && /* [嗨! 朋友] mode */
+ (
+ /* 含板友, 好友區最多只會有 (friendtotal + 板友) 個*/
+ (currutmp->brc_id && which < (friendtotal + 1 +
+ getbcache(currutmp->brc_id)->nuser)) ||
+
+ /* 不含板友, 最多只會有 friendtotal個 */
+ (!currutmp->brc_id && which < friendtotal + 1)
+ ))) {
+ pickup_t friends[MAX_FRIEND];
+
+ /* TODO 當 friendtotal<which 時只需顯示板友, 不需 pickup_myfriend */
+ *nfriend = pickup_myfriend(friends, myfriend, friendme, badfriend);
+
+ if (pickup_way == 0 && currutmp->brc_id != 0
+#ifdef USE_COOLDOWN
+ && !(getbcache(currutmp->brc_id)->brdattr & BRD_COOLDOWN)
+#endif
+ ){
+ /* TODO 只需要 which+nPickups-*nfriend 個板友, 不一定要整個掃一遍 */
+ *nfriend += pickup_bfriend(friends, *nfriend);
+ *bfriend = SHM->bcache[currutmp->brc_id - 1].nuser;
+ }
+ else
+ *bfriend = 0;
+ if (*nfriend > which) {
+ /* 只有在要秀出才有必要 sort */
+ /* TODO 好友跟板友可以分開 sort, 可能只需要其一 */
+ /* TODO 好友上下站才需要 sort 一次, 不需要每次 sort.
+ * 可維護一個 dirty bit 表示是否 sort 過.
+ * suggested by WYchuang@ptt */
+ qsort(friends, *nfriend, sizeof(pickup_t), sort_cmpfriend);
+ size = *nfriend - which;
+ if (size > nPickups)
+ size = nPickups;
+ memcpy(currpickup, friends + which, sizeof(pickup_t) * size);
+ }
+ } else
+ *nfriend = 0;
+
+ if (!(cuser.uflag & FRIEND_FLAG) && size < nPickups) {
+ sorted_way = ((pickup_way == 0) ? 7 : (pickup_way - 1));
+ ulist = SHM->sorted[currsorted][sorted_way];
+ which = *page * nPickups - *nfriend;
+ if (which < 0)
+ which = 0;
+
+ for (; which < utmpnumber && size < nPickups; which++) {
+ u = &SHM->uinfo[ulist[which]];
+
+ friend = friend_stat(currutmp, u);
+ /* TODO isvisible() 重複用到了 friend_stat() */
+ if ((pickup_way ||
+ (currutmp != u && !(friend & ST_FRIEND))) &&
+ isvisible(currutmp, u)) {
+ currpickup[size].ui = u;
+ currpickup[size++].friend = friend;
+ }
+ }
+ }
+
+ for (; size < nPickups; ++size)
+ currpickup[size].ui = 0;
+}
+
+static void
+draw_pickup(int drawall, pickup_t * pickup, int pickup_way,
+ int page, int show_mode, int show_uid, int show_board,
+ int show_pid, int myfriend, int friendme, int bfriend, int badfriend)
+{
+ char *msg_pickup_way[PICKUP_WAYS] = {
+ "嗨! 朋友", "網友代號", "網友動態", "發呆時間", "來自何方", " 五子棋 ", " 象棋 ", " 圍棋 ",
+ };
+ char *MODE_STRING[MAX_SHOW_MODE] = {
+ "故鄉", "好友描述", "五子棋戰績", "象棋戰績", "象棋等級分", "圍棋戰績",
+ };
+ char pagerchar[5] = "* -Wf";
+
+ userinfo_t *uentp;
+ int i, ch, state, friend;
+ char mind[5];
+
+#ifdef SHOW_IDLE_TIME
+ char idlestr[32];
+ int idletime;
+#endif
+
+ /* wide screen support */
+ int wNick = 17, wMode = 12; //13; , one byte give number for ptt always > 10000 online.
+
+ if (t_columns > 80)
+ {
+ int d = t_columns - 80;
+
+ /* rule: try to give extra space to both nick and mode,
+ * because nick is smaller, try nick first then mode. */
+ if (d >= sizeof(cuser.nickname) - wNick)
+ {
+ d -= (sizeof(cuser.nickname) - wNick);
+ wNick = sizeof(cuser.nickname);
+ wMode += d;
+ } else {
+ wNick += d;
+ }
+ }
+
+ if (drawall) {
+ showtitle((cuser.uflag & FRIEND_FLAG) ? "好友列表" : "休閒聊天",
+ BBSName);
+ prints("\n"
+ ANSI_COLOR(7) " %s P%c代號 %-*s%-17s%-*s%10s"
+ ANSI_RESET "\n",
+ show_uid ? "UID " : "編號",
+ (HasUserPerm(PERM_SEECLOAK) || HasUserPerm(PERM_SYSOP)) ?
+ 'C' : ' ',
+ wNick, "暱稱",
+ MODE_STRING[show_mode],
+ wMode, show_board ? "Board" : "動態",
+ show_pid ? " PID" : "心情 "
+#ifdef SHOW_IDLE_TIME
+ "發呆"
+#else
+ " "
+#endif
+ );
+ move(b_lines, 0);
+ outslr(
+ ANSI_COLOR(34;46) " 休閒聊天 "
+ ANSI_COLOR(31;47) " (TAB/f)" ANSI_COLOR(30) "排序/好友 "
+ ANSI_COLOR(31) "(a/o)" ANSI_COLOR(30) "交友 "
+ ANSI_COLOR(31) "(q/w)" ANSI_COLOR(30) "查詢/丟水球 "
+ ANSI_COLOR(31) "(t/m)" ANSI_COLOR(30) "聊天/寫信 ",
+ 80-10,
+ ANSI_COLOR(31) "(h)" ANSI_COLOR(30) "說明 " ANSI_RESET,
+ 8);
+ }
+ move(1, 0);
+ prints(" 排序:[%s] 上站人數:%-4d "
+ ANSI_COLOR(1;32) "我的朋友:%-3d "
+ ANSI_COLOR(33) "與我為友:%-3d "
+ ANSI_COLOR(36) "板友:%-4d "
+ ANSI_COLOR(31) "壞人:%-2d"
+ ANSI_RESET "\n",
+ msg_pickup_way[pickup_way], SHM->UTMPnumber,
+ myfriend, friendme, currutmp->brc_id ? bfriend : 0, badfriend);
+
+ for (i = 0, ch = page * nPickups + 1; i < nPickups; ++i, ++ch) {
+ move(i + 3, 0);
+ outc('a');
+ move(i + 3, 0);
+ uentp = pickup[i].ui;
+ friend = pickup[i].friend;
+ if (uentp == NULL) {
+ outc('\n');
+ continue;
+ }
+ if (!uentp->pid) {
+ prints("%5d < 離站中..>\n", ch);
+ continue;
+ }
+ if (PERM_HIDE(uentp))
+ state = 9;
+ else if (currutmp == uentp)
+ state = 10;
+ else if (friend & IRH && !(friend & IFH))
+ state = 8;
+ else
+ state = (friend & ST_FRIEND) >> 2;
+
+#ifdef SHOW_IDLE_TIME
+ idletime = (now - uentp->lastact);
+ if (idletime > 86400)
+ strlcpy(idlestr, " -----", sizeof(idlestr));
+ else if (idletime >= 3600)
+ snprintf(idlestr, sizeof(idlestr), "%3dh%02d",
+ idletime / 3600, (idletime / 60) % 60);
+ else if (idletime > 0)
+ snprintf(idlestr, sizeof(idlestr), "%3d'%02d",
+ idletime / 60, idletime % 60);
+ else
+ strlcpy(idlestr, " ", sizeof(idlestr));
+#endif
+
+ if ((uentp->userlevel & PERM_VIOLATELAW))
+ memcpy(mind, "通緝", 4);
+ else if (uentp->birth)
+ memcpy(mind, "壽星", 4);
+ else
+ memcpy(mind, uentp->mind, 4);
+ mind[4] = 0;
+
+ /* TODO
+ * will this be faster if we use pure outc/outs?
+ */
+ prints("%7d %c%c%s%-13s%-*.*s " ANSI_RESET "%-16.16s %-*.*s"
+ ANSI_COLOR(33) "%-4.4s" ANSI_RESET "%s\n",
+
+ /* list number or uid */
+#ifdef SHOWUID
+ show_uid ? uentp->uid :
+#endif
+ ch,
+
+ /* super friend or pager */
+ (friend & HRM) ? 'X' : pagerchar[uentp->pager % 5],
+
+ /* visibility */
+ (uentp->invisible ? ')' : ' '),
+
+ /* color of userid, userid */
+ fcolor[state], uentp->userid,
+
+ /* nickname */
+ wNick-1, wNick-1, uentp->nickname,
+
+ /* from */
+ descript(show_mode, uentp,
+ uentp->pager & !(friend & HRM)),
+
+ /* board or mode */
+ wMode, wMode,
+#if defined(SHOWBOARD) && defined(DEBUG)
+ show_board ? (uentp->brc_id == 0 ? "" :
+ getbcache(uentp->brc_id)->brdname) :
+#endif
+ modestring(uentp, 0),
+
+ /* memo */
+ mind,
+
+ /* idle */
+#ifdef SHOW_IDLE_TIME
+ idlestr
+#else
+ ""
+#endif
+ );
+
+ //refresh();
+ }
+}
+
+void set_withme_flag(void)
+{
+ int i;
+ char genbuf[20];
+ int line;
+
+ move(1, 0);
+ clrtobot();
+
+ do {
+ move(1, 0);
+ line=1;
+ for(i=0; i<16 && withme_str[i]; i++) {
+ clrtoeol();
+ if(currutmp->withme&(1<<(i*2)))
+ prints("[%c] 我很想跟人%s, 歡迎任何人找我\n",'a'+i, withme_str[i]);
+ else if(currutmp->withme&(1<<(i*2+1)))
+ prints("[%c] 我不太想%s\n",'a'+i, withme_str[i]);
+ else
+ prints("[%c] (%s)沒意見\n",'a'+i, withme_str[i]);
+ line++;
+ }
+ getdata(line,0,"用字母切換 [想/不想/沒意見]",genbuf, sizeof(genbuf), DOECHO);
+ for(i=0;genbuf[i];i++) {
+ int ch=genbuf[i];
+ ch=tolower(ch);
+ if('a'<=ch && ch<'a'+16) {
+ ch-='a';
+ if(currutmp->withme&(1<<ch*2)) {
+ currutmp->withme&=~(1<<ch*2);
+ currutmp->withme|=1<<(ch*2+1);
+ } else if(currutmp->withme&(1<<(ch*2+1))) {
+ currutmp->withme&=~(1<<(ch*2+1));
+ } else {
+ currutmp->withme|=1<<(ch*2);
+ }
+ }
+ }
+ } while(genbuf[0]!='\0');
+}
+
+int
+call_in(const userinfo_t * uentp, int fri_stat)
+{
+ if (iswritable_stat(uentp, fri_stat)) {
+ char genbuf[60];
+ snprintf(genbuf, sizeof(genbuf), "丟 %s 水球: ", uentp->userid);
+ my_write(uentp->pid, genbuf, uentp->userid, WATERBALL_GENERAL, NULL);
+ return 1;
+ }
+ return 0;
+}
+
+inline static void
+userlist(void)
+{
+ pickup_t *currpickup;
+ userinfo_t *uentp;
+ static char show_mode = 0;
+ static char show_uid = 0;
+ static char show_board = 0;
+ static char show_pid = 0;
+ static int pickup_way = 0;
+ char skippickup = 0, redraw, redrawall;
+ int page, offset, ch, leave, fri_stat;
+ int nfriend, myfriend, friendme, bfriend, badfriend, i;
+ time4_t lastupdate;
+
+ nPickups = b_lines - 3;
+ currpickup = (pickup_t *)malloc(sizeof(pickup_t) * nPickups);
+ page = offset = 0 ;
+ nfriend = myfriend = friendme = bfriend = badfriend = 0;
+ leave = 0;
+ redrawall = 1;
+ /*
+ * 各個 flag :
+ * redraw: 重新 pickup(), draw_pickup() (僅中間區, 不含標題列等等)
+ * redrawall: 全部重畫 (含標題列等等, 須再指定 redraw 才會有效)
+ * leave: 離開使用者名單
+ */
+ while (!leave) {
+ if( !skippickup )
+ pickup(currpickup, pickup_way, &page,
+ &nfriend, &myfriend, &friendme, &bfriend, &badfriend);
+ draw_pickup(redrawall, currpickup, pickup_way, page,
+ show_mode, show_uid, show_board, show_pid,
+ myfriend, friendme, bfriend, badfriend);
+
+ /*
+ * 如果因為換頁的時候, 這一頁有的人數比較少,
+ * (通常都是最後一頁人數不滿的時候) 那要重新計算 offset
+ * 以免指到沒有人的地方
+ */
+ if (offset == -1 || currpickup[offset].ui == NULL) {
+ for (offset = (offset == -1 ? nPickups - 1 : offset);
+ offset >= 0; --offset)
+ if (currpickup[offset].ui != NULL)
+ break;
+ if (offset == -1) {
+ if (--page < 0)
+ page = pickup_maxpages(pickup_way, nfriend) - 1;
+ offset = 0;
+ continue;
+ }
+ }
+ skippickup = redraw = redrawall = 0;
+ lastupdate = now;
+ while (!redraw) {
+ ch = cursor_key(offset + 3, 0);
+ uentp = currpickup[offset].ui;
+ fri_stat = currpickup[offset].friend;
+
+ switch (ch) {
+ case KEY_LEFT:
+ case 'e':
+ case 'E':
+ redraw = leave = 1;
+ break;
+
+ case KEY_TAB:
+ pickup_way = (pickup_way + 1) % PICKUP_WAYS;
+ redraw = 1;
+ redrawall = 1;
+ break;
+
+ case KEY_DOWN:
+ case 'n':
+ case 'j':
+ if (++offset == nPickups || currpickup[offset].ui == NULL) {
+ redraw = 1;
+ if (++page >= pickup_maxpages(pickup_way,
+ nfriend))
+ offset = page = 0;
+ else
+ offset = 0;
+ }
+ break;
+
+ case '0':
+ case KEY_HOME:
+ page = offset = 0;
+ redraw = 1;
+ break;
+
+ case 'H':
+ if (HasUserPerm(PERM_SYSOP)||HasUserPerm(PERM_OLDSYSOP)) {
+ currutmp->userlevel ^= PERM_SYSOPHIDE;
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'D':
+ if (HasUserPerm(PERM_SYSOP)) {
+ char buf[100];
+ snprintf(buf, sizeof(buf),
+ "代號 [%s]:", currutmp->userid);
+ if (!getdata(1, 0, buf, currutmp->userid,
+ sizeof(buf), DOECHO))
+ strlcpy(currutmp->userid, cuser.userid, sizeof(currutmp->userid));
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'F':
+ if (HasUserPerm(PERM_SYSOP)) {
+ char buf[100];
+
+ snprintf(buf, sizeof(buf), "故鄉 [%s]:", currutmp->from);
+ if (!getdata(1, 0, buf, currutmp->from,
+ sizeof(currutmp->from), DOECHO))
+ strlcpy(currutmp->from, buf, sizeof(currutmp->from));
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'C':
+#if !HAVE_FREECLOAK
+ if (HasUserPerm(PERM_CLOAK))
+#endif
+ {
+ currutmp->invisible ^= 1;
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case ' ':
+ case KEY_PGDN:
+ case Ctrl('F'):{
+ int newpage;
+ if ((newpage = page + 1) >= pickup_maxpages(pickup_way,
+ nfriend))
+ newpage = offset = 0;
+ if (newpage != page) {
+ page = newpage;
+ redraw = 1;
+ } else if (now >= lastupdate + 2)
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case KEY_UP:
+ case 'k':
+ if (--offset == -1) {
+ offset = nPickups - 1;
+ if (--page == -1)
+ page = pickup_maxpages(pickup_way, nfriend)
+ - 1;
+ redraw = 1;
+ }
+ break;
+
+ case KEY_PGUP:
+ case Ctrl('B'):
+ case 'P':
+ if (--page == -1)
+ page = pickup_maxpages(pickup_way, nfriend) - 1;
+ offset = 0;
+ redraw = 1;
+ break;
+
+ case KEY_END:
+ case '$':
+ page = pickup_maxpages(pickup_way, nfriend) - 1;
+ offset = -1;
+ redraw = 1;
+ break;
+
+ case '/':
+ /*
+ * getdata_buf(b_lines-1,0,"請輸入暱稱關鍵字:", keyword,
+ * sizeof(keyword), DOECHO); state = US_PICKUP;
+ */
+ break;
+
+ case 's':
+ if (!(cuser.uflag & FRIEND_FLAG)) {
+ int si; /* utmpshm->sorted[X][0][si] */
+ int fi; /* allpickuplist[fi] */
+ char swid[IDLEN + 1];
+ move(1, 0);
+ si = CompleteOnlineUser(msg_uid, swid);
+ if (si >= 0) {
+ pickup_t friends[MAX_FRIEND + 1];
+ int nGots, i;
+ int *ulist =
+ SHM->sorted[SHM->currsorted]
+ [((pickup_way == 0) ? 0 : (pickup_way - 1))];
+
+ fi = ulist[si];
+ nGots = pickup_myfriend(friends, &myfriend,
+ &friendme, &badfriend);
+ for (i = 0; i < nGots; ++i)
+ if (friends[i].uoffset == fi)
+ break;
+
+ fi = 0;
+ offset = 0;
+ if( i != nGots ){
+ page = i / nPickups;
+ for( ; i < nGots && fi < nPickups ; ++i )
+ if( isvisible(currutmp, friends[i].ui) )
+ currpickup[fi++] = friends[i];
+ i = 0;
+ }
+ else{
+ page = (si + nGots) / nPickups;
+ i = si;
+ }
+
+ for( ; fi < nPickups && i < SHM->UTMPnumber ; ++i )
+ {
+ userinfo_t *u;
+ u = &SHM->uinfo[ulist[i]];
+ if( isvisible(currutmp, u) ){
+ currpickup[fi].ui = u;
+ currpickup[fi++].friend = 0;
+ }
+ }
+ skippickup = 1;
+ }
+ redrawall = redraw = 1;
+ }
+ /*
+ * if ((i = search_pickup(num, actor, pklist)) >= 0) num = i;
+ * state = US_ACTION;
+ */
+ break;
+
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ { /* Thor: 可以打數字跳到該人 */
+ int tmp;
+ if ((tmp = search_num(ch, SHM->UTMPnumber)) >= 0) {
+ if (tmp / nPickups == page) {
+ /*
+ * in2:目的在目前這一頁, 直接 更新 offset ,
+ * 不用重畫畫面
+ */
+ offset = tmp % nPickups;
+ } else {
+ page = tmp / nPickups;
+ offset = tmp % nPickups;
+ }
+ redrawall = redraw = 1;
+ }
+ }
+ break;
+
+#ifdef SHOWUID
+ case 'U':
+ if (HasUserPerm(PERM_SYSOP)) {
+ show_uid ^= 1;
+ redrawall = redraw = 1;
+ }
+ break;
+#endif
+#if defined(SHOWBOARD) && defined(DEBUG)
+ case 'Y':
+ if (HasUserPerm(PERM_SYSOP)) {
+ show_board ^= 1;
+ redrawall = redraw = 1;
+ }
+ break;
+#endif
+#ifdef SHOWPID
+ case '#':
+ if (HasUserPerm(PERM_SYSOP)) {
+ show_pid ^= 1;
+ redrawall = redraw = 1;
+ }
+ break;
+#endif
+
+ case 'b': /* broadcast */
+ if (cuser.uflag & FRIEND_FLAG || HasUserPerm(PERM_SYSOP)) {
+ char genbuf[60]="[廣播]";
+ char ans[4];
+
+ if (!getdata(0, 0, "廣播訊息:", genbuf+6, 54, DOECHO))
+ break;
+
+ if (!getdata(0, 0, "確定廣播? [N]",
+ ans, sizeof(ans), LCECHO) ||
+ ans[0] != 'y')
+ break;
+ if (!(cuser.uflag & FRIEND_FLAG) && HasUserPerm(PERM_SYSOP)) {
+ msgque_t msg;
+ getdata(1, 0, "再次確定站長廣播? [N]",
+ ans, sizeof(ans), LCECHO);
+ if( ans[0] != 'y' && ans[0] != 'Y' ){
+ vmsg("abort");
+ break;
+ }
+
+ msg.pid = currpid;
+ strlcpy(msg.userid, cuser.userid, sizeof(msg.userid));
+ snprintf(msg.last_call_in, sizeof(msg.last_call_in),
+ "[廣播]%s", genbuf);
+ for (i = 0; i < SHM->UTMPnumber; ++i) {
+ // XXX why use sorted list?
+ // can we just scan uinfo with proper checking?
+ uentp = &SHM->uinfo[
+ SHM->sorted[SHM->currsorted][0][i]];
+ if (uentp->pid && kill(uentp->pid, 0) != -1){
+ int write_pos = uentp->msgcount;
+ if( write_pos < (MAX_MSGS - 1) ){
+ uentp->msgcount = write_pos + 1;
+ memcpy(&uentp->msgs[write_pos], &msg,
+ sizeof(msg));
+#ifdef NOKILLWATERBALL
+ uentp->wbtime = now;
+#else
+ kill(uentp->pid, SIGUSR2);
+#endif
+ }
+ }
+ }
+ } else {
+ userinfo_t *uentp;
+ int where, frstate;
+ for (i = 0; currutmp->friend_online[i] &&
+ i < MAX_FRIEND; ++i) {
+ where = currutmp->friend_online[i] & 0xFFFFFF;
+ if (VALID_USHM_ENTRY(where) &&
+ (uentp = &SHM->uinfo[where]) &&
+ uentp->pid &&
+ isvisible_stat(currutmp, uentp,
+ frstate =
+ currutmp->friend_online[i] >> 24)
+ && kill(uentp->pid, 0) != -1 &&
+ uentp->pager != PAGER_ANTIWB &&
+ (uentp->pager != PAGER_FRIENDONLY || frstate & HFM) &&
+ !(frstate & IRH)) {
+ my_write(uentp->pid, genbuf, uentp->userid,
+ WATERBALL_PREEDIT, NULL);
+ }
+ }
+ }
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'S': /* 顯示好友描述 */
+ show_mode = (show_mode+1) % MAX_SHOW_MODE;
+#ifdef CHESSCOUNTRY
+ if (show_mode == 2)
+ user_query_mode = 1;
+ else if (show_mode == 3 || show_mode == 4)
+ user_query_mode = 2;
+ else if (show_mode == 5)
+ user_query_mode = 3;
+ else
+ user_query_mode = 0;
+#endif /* defined(CHESSCOUNTRY) */
+ redrawall = redraw = 1;
+ break;
+
+ case 'u': /* 線上修改資料 */
+ if (HasUserPerm(PERM_ACCOUNTS|PERM_SYSOP)) {
+ int id;
+ userec_t muser;
+ strlcpy(currauthor, uentp->userid, sizeof(currauthor));
+ stand_title("使用者設定");
+ move(1, 0);
+ if ((id = getuser(uentp->userid, &muser)) > 0) {
+ user_display(&muser, 1);
+ if( HasUserPerm(PERM_ACCOUNTS) )
+ uinfo_query(&muser, 1, id);
+ else
+ pressanykey();
+ }
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'i':{
+ char mindbuf[5];
+ getdata(b_lines - 1, 0, "現在的心情? ",
+ mindbuf, sizeof(mindbuf), DOECHO);
+ if (strcmp(mindbuf, "通緝") == 0)
+ vmsg("不可以把自己設通緝啦!");
+ else if (strcmp(mindbuf, "壽星") == 0)
+ vmsg("你不是今天生日欸!");
+ else
+ memcpy(currutmp->mind, mindbuf, 4);
+ }
+ redrawall = redraw = 1;
+ break;
+
+ case Ctrl('S'):
+ break;
+
+ case KEY_RIGHT:
+ case '\n':
+ case '\r':
+ case 't':
+ if (HasUserPerm(PERM_LOGINOK)) {
+ if (uentp->pid != currpid &&
+ strcmp(uentp->userid, cuser.userid) != 0) {
+ move(1, 0);
+ clrtobot();
+ move(3, 0);
+ my_talk(uentp, fri_stat, 0);
+ redrawall = redraw = 1;
+ }
+ }
+ break;
+ case 'K':
+ if (HasUserPerm(PERM_ACCOUNTS|PERM_SYSOP)) {
+ my_kick(uentp);
+ redrawall = redraw = 1;
+ }
+ break;
+ case 'w':
+ if (call_in(uentp, fri_stat))
+ redrawall = redraw = 1;
+ break;
+ case 'a':
+ if (HasUserPerm(PERM_LOGINOK) && !(fri_stat & IFH)) {
+ if (getans("確定要加入好友嗎 [N/y]") == 'y') {
+ friend_add(uentp->userid, FRIEND_OVERRIDE,uentp->nickname);
+ friend_load(FRIEND_OVERRIDE);
+ }
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'd':
+ if (HasUserPerm(PERM_LOGINOK) && (fri_stat & IFH)) {
+ if (getans("確定要刪除好友嗎 [N/y]") == 'y') {
+ friend_delete(uentp->userid, FRIEND_OVERRIDE);
+ friend_load(FRIEND_OVERRIDE);
+ }
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'o':
+ if (HasUserPerm(PERM_LOGINOK)) {
+ t_override();
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'f':
+ if (HasUserPerm(PERM_LOGINOK)) {
+ cuser.uflag ^= FRIEND_FLAG;
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'g':
+ if (HasUserPerm(PERM_LOGINOK) &&
+ strcmp(uentp->userid, cuser.userid) != 0) {
+ char genbuf[10];
+ char userid[IDLEN + 1];
+ int touid=uentp->uid;
+ strlcpy(userid, uentp->userid, sizeof(userid));
+ move(b_lines - 2, 0);
+ prints("要給 %s 多少錢呢? ", userid);
+ if (getdata(b_lines - 1, 0, "[銀行轉帳]: ",
+ genbuf, 7, LCECHO)) {
+ clrtoeol();
+ if ((ch = atoi(genbuf)) <= 0 || ch <= give_tax(ch)){
+ redrawall = redraw = 1;
+ break;
+ }
+ if (getans("確定要給 %s %d Ptt 幣嗎? [N/y]",
+ userid, ch) != 'y'){
+ redrawall = redraw = 1;
+ break;
+ }
+ if (do_give_money(userid, touid, ch) < 0)
+ vmsgf("交易失敗,還剩下 %d 錢", SHM->money[usernum - 1]);
+ else
+ vmsgf("交易成功\,還剩下 %d 錢", SHM->money[usernum - 1]);
+ } else {
+ clrtoeol();
+ vmsg(" 交易取消! ");
+ }
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'm':
+ if (HasUserPerm(PERM_LOGINOK)) {
+ char userid[IDLEN + 1];
+ strlcpy(userid, uentp->userid, sizeof(userid));
+ stand_title("寄 信");
+ prints("[寄信] 收信人:%s", userid);
+ my_send(userid);
+ setutmpmode(LUSERS);
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'q':
+ strlcpy(currauthor, uentp->userid, sizeof(currauthor));
+ my_query(uentp->userid);
+ setutmpmode(LUSERS);
+ redrawall = redraw = 1;
+ break;
+
+ case 'c':
+ if (HasUserPerm(PERM_LOGINOK)) {
+ chicken_query(uentp->userid);
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'l':
+ if (HasUserPerm(PERM_LOGINOK)) {
+ t_display();
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'h':
+ t_showhelp();
+ redrawall = redraw = 1;
+ break;
+
+ case 'p':
+ if (HasUserPerm(PERM_BASIC)) {
+ t_pager();
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case Ctrl('W'):
+ if (HasUserPerm(PERM_LOGINOK)) {
+ int tmp;
+ char *wm[3] = {"一般", "進階", "未來"};
+ tmp = cuser.uflag2 & WATER_MASK;
+ cuser.uflag2 -= tmp;
+ tmp = (tmp + 1) % 3;
+ cuser.uflag2 |= tmp;
+ /* vmsg cannot support multi lines */
+ move(b_lines - 4, 0);
+ clrtobot();
+ move(b_lines - 3, 0);
+ prints("系統提供 一般 進階 未來 三種模式\n"
+ "在切換後請正常下線再重新登入, 以確保結構正確\n");
+ vmsgf( "目前切換到 %s 水球模式", wm[tmp]);
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'r':
+ if (HasUserPerm(PERM_LOGINOK)) {
+ m_read();
+ setutmpmode(LUSERS);
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'N':
+ if (HasUserPerm(PERM_LOGINOK)) {
+ oldgetdata(1, 0, "新的暱稱: ",
+ cuser.nickname, sizeof(cuser.nickname), DOECHO);
+ strcpy(currutmp->nickname, cuser.nickname);
+ redrawall = redraw = 1;
+ }
+ break;
+
+ case 'y':
+ set_withme_flag();
+ redrawall = redraw = 1;
+ break;
+
+ default:
+ if (now >= lastupdate + 2)
+ redraw = 1;
+ }
+ }
+ }
+ free(currpickup);
+}
+
+int
+t_users(void)
+{
+ int destuid0 = currutmp->destuid;
+ int mode0 = currutmp->mode;
+ int stat0 = currstat;
+
+ assert(strncmp(cuser.userid, currutmp->userid, IDLEN)==0);
+ if( strncmp(cuser.userid , currutmp->userid, IDLEN) != 0 ){
+ if( HasUserPerm(PERM_SYSOP) )
+ vmsg("warning: currutmp userid is changed");
+ else
+ abort_bbs(0);
+ }
+
+ setutmpmode(LUSERS);
+ userlist();
+ currutmp->mode = mode0;
+ currutmp->destuid = destuid0;
+ currstat = stat0;
+ return 0;
+}
+
+int
+t_pager(void)
+{
+ currutmp->pager = (currutmp->pager + 1) % PAGER_MODES;
+ return 0;
+}
+
+int
+t_idle(void)
+{
+ int destuid0 = currutmp->destuid;
+ int mode0 = currutmp->mode;
+ int stat0 = currstat;
+ char genbuf[20];
+ char passbuf[PASSLEN];
+
+ setutmpmode(IDLE);
+ getdata(b_lines - 1, 0, "理由:[0]發呆 (1)接電話 (2)覓食 (3)打瞌睡 "
+ "(4)裝死 (5)羅丹 (6)其他 (Q)沒事?", genbuf, 3, DOECHO);
+ if (genbuf[0] == 'q' || genbuf[0] == 'Q') {
+ currutmp->mode = mode0;
+ currstat = stat0;
+ return 0;
+ } else if (genbuf[0] >= '1' && genbuf[0] <= '6')
+ currutmp->destuid = genbuf[0] - '0';
+ else
+ currutmp->destuid = 0;
+
+ if (currutmp->destuid == 6)
+ if (!cuser.userlevel ||
+ !getdata(b_lines - 1, 0, "發呆的理由:",
+ currutmp->chatid, sizeof(currutmp->chatid), DOECHO))
+ currutmp->destuid = 0;
+ do {
+ /* FIXME destuid 同時表示發呆原因及 talk uid,
+ * 1. 發呆
+ * 2. 有人 talkrequest, 改到 currutmp->destuid
+ * 3. 打錯密碼
+ * 4. 重新顯示 IdleTypeTable[currutmp->destuid], crash */
+ move(b_lines - 2, 0);
+ clrtoeol();
+ prints("(鎖定螢幕)發呆原因: %s", (currutmp->destuid != 6) ?
+ IdleTypeTable[currutmp->destuid] : currutmp->chatid);
+ refresh();
+ getdata(b_lines - 1, 0, MSG_PASSWD, passbuf, sizeof(passbuf), NOECHO);
+ passbuf[8] = '\0';
+ }
+ while (!checkpasswd(cuser.passwd, passbuf) &&
+ strcmp(STR_GUEST, cuser.userid));
+
+ currutmp->mode = mode0;
+ currutmp->destuid = destuid0;
+ currstat = stat0;
+
+ return 0;
+}
+
+int
+t_qchicken(void)
+{
+ char uident[STRLEN];
+
+ stand_title("查詢寵物");
+ usercomplete(msg_uid, uident);
+ if (uident[0])
+ chicken_query(uident);
+ return 0;
+}
+
+int
+t_query(void)
+{
+ char uident[STRLEN];
+
+ stand_title("查詢網友");
+ usercomplete(msg_uid, uident);
+ if (uident[0])
+ my_query(uident);
+ return 0;
+}
+
+int
+t_talk(void)
+{
+ char uident[16];
+ int tuid, unum, ucount;
+ userinfo_t *uentp;
+ char genbuf[4];
+ /*
+ * if (count_ulist() <= 1){ outs("目前線上只有您一人,快邀請朋友來光臨【"
+ * BBSNAME "】吧!"); return XEASY; }
+ */
+ stand_title("打開話匣子");
+ CompleteOnlineUser(msg_uid, uident);
+ if (uident[0] == '\0')
+ return 0;
+
+ move(3, 0);
+ if (!(tuid = searchuser(uident, uident)) || tuid == usernum) {
+ outs(err_uid);
+ pressanykey();
+ return 0;
+ }
+ /* multi-login check */
+ unum = 1;
+ while ((ucount = count_logins(tuid, 0)) > 1) {
+ outs("(0) 不想 talk 了...\n");
+ count_logins(tuid, 1);
+ getdata(1, 33, "請選擇一個聊天對象 [0]:", genbuf, 4, DOECHO);
+ unum = atoi(genbuf);
+ if (unum == 0)
+ return 0;
+ move(3, 0);
+ clrtobot();
+ if (unum > 0 && unum <= ucount)
+ break;
+ }
+
+ if ((uentp = (userinfo_t *) search_ulistn(tuid, unum)))
+ my_talk(uentp, friend_stat(currutmp, uentp), 0);
+
+ return 0;
+}
+
+int
+reply_connection_request(const userinfo_t *uip)
+{
+ char buf[4], genbuf[200];
+
+ if (uip->mode != PAGE) {
+ snprintf(genbuf, sizeof(genbuf),
+ "%s已停止呼叫,按Enter繼續...", page_requestor);
+ getdata(0, 0, genbuf, buf, sizeof(buf), LCECHO);
+ return -1;
+ }
+ return establish_talk_connection(uip);
+}
+
+int
+establish_talk_connection(const userinfo_t *uip)
+{
+ int a;
+ struct sockaddr_in sin;
+
+ currutmp->msgcount = 0;
+ strlcpy(save_page_requestor, page_requestor, sizeof(save_page_requestor));
+ memset(page_requestor, 0, sizeof(page_requestor));
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = PF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = uip->sockaddr;
+ if ((a = socket(sin.sin_family, SOCK_STREAM, 0)) < 0) {
+ perror("connect err");
+ return -1;
+ }
+ if ((connect(a, (struct sockaddr *) & sin, sizeof(sin)))) {
+ perror("connect err");
+ return -1;
+ }
+ return a;
+}
+
+/* 有人來串門子了,回應呼叫器 */
+void
+talkreply(void)
+{
+ char buf[4];
+ char genbuf[200];
+ int a, sig = currutmp->sig;
+ int currstat0 = currstat;
+ int r;
+ int is_chess;
+ userec_t xuser;
+ void (*sig_pipe_handle)(int);
+
+ uip = &SHM->uinfo[currutmp->destuip];
+ currutmp->destuid = uip->uid;
+ currstat = REPLY; /* 避免出現動畫 */
+
+ is_chess = (sig == SIG_CHC || sig == SIG_GOMO || sig == SIG_GO || sig == SIG_REVERSI);
+
+ a = reply_connection_request(uip);
+ if (a < 0) {
+ clear();
+ currstat = currstat0;
+ return;
+ }
+ if (is_chess)
+ ChessAcceptingRequest(a);
+
+ clear();
+
+ outs("\n\n");
+ // FIXME CRASH here
+ assert(sig>=0 && sig<sizeof(sig_des)/sizeof(sig_des[0]));
+ prints(" (Y) 讓我們 %s 吧!"
+ " (A) 我現在很忙,請等一會兒再 call 我\n", sig_des[sig]);
+ prints(" (N) 我現在不想 %s"
+ " (B) 對不起,我有事情不能跟你 %s\n",
+ sig_des[sig], sig_des[sig]);
+ prints(" (C) 請不要吵我好嗎?"
+ " (D) 我要離站囉..下次再聊吧.......\n");
+ prints(" (E) 有事嗎?請先來信"
+ " (F) " ANSI_COLOR(1;33) "我自己輸入理由好了..." ANSI_RESET "\n");
+ prints(" (1) %s?先拿100銀兩來"
+ " (2) %s?先拿1000銀兩來..\n\n", sig_des[sig], sig_des[sig]);
+
+ snprintf(page_requestor, sizeof(page_requestor),
+ "%s (%s)", uip->userid, uip->nickname);
+ getuser(uip->userid, &xuser);
+ currutmp->msgs[0].pid = uip->pid;
+ strlcpy(currutmp->msgs[0].userid, uip->userid, sizeof(currutmp->msgs[0].userid));
+ strlcpy(currutmp->msgs[0].last_call_in, "呼叫、呼叫,聽到請回答 (Ctrl-R)",
+ sizeof(currutmp->msgs[0].last_call_in));
+ currutmp->msgs[0].msgmode = MSGMODE_TALK;
+ prints("對方來自 [%s],共上站 %d 次,文章 %d 篇\n",
+ uip->from, xuser.numlogins, xuser.numposts);
+
+ if (is_chess)
+ ChessShowRequest();
+ else {
+ showplans(uip->userid);
+ show_call_in(0, 0);
+ }
+
+ snprintf(genbuf, sizeof(genbuf),
+ "你想跟 %s %s啊?請選擇(Y/N/A/B/C/D/E/F/1/2)[N] ",
+ page_requestor, sig_des[sig]);
+ getdata(0, 0, genbuf, buf, sizeof(buf), LCECHO);
+
+ if (!buf[0] || !strchr("yabcdef12", buf[0]))
+ buf[0] = 'n';
+
+ sig_pipe_handle = Signal(SIGPIPE, SIG_IGN);
+ r = write(a, buf, 1);
+ if (buf[0] == 'f' || buf[0] == 'F') {
+ if (!getdata(b_lines, 0, "不能的原因:", genbuf, 60, DOECHO))
+ strlcpy(genbuf, "不告訴你咧 !! ^o^", sizeof(genbuf));
+ r = write(a, genbuf, 60);
+ }
+ Signal(SIGPIPE, sig_pipe_handle);
+
+ if (r == -1) {
+ snprintf(genbuf, sizeof(genbuf),
+ "%s已停止呼叫,按Enter繼續...", page_requestor);
+ getdata(0, 0, genbuf, buf, sizeof(buf), LCECHO);
+ clear();
+ currstat = currstat0;
+ return;
+ }
+
+ uip->destuip = currutmp - &SHM->uinfo[0];
+ if (buf[0] == 'y')
+ switch (sig) {
+ case SIG_DARK:
+ main_dark(a, uip);
+ break;
+ case SIG_PK:
+ chickenpk(a);
+ break;
+ case SIG_GOMO:
+ gomoku(a, CHESS_MODE_VERSUS);
+ break;
+ case SIG_CHC:
+ chc(a, CHESS_MODE_VERSUS);
+ break;
+ case SIG_GO:
+ gochess(a, CHESS_MODE_VERSUS);
+ break;
+ case SIG_REVERSI:
+ reversi(a, CHESS_MODE_VERSUS);
+ break;
+ case SIG_TALK:
+ default:
+ do_talk(a);
+ }
+ else
+ close(a);
+ clear();
+ currstat = currstat0;
+}
+
+#ifdef PLAY_ANGEL
+/* 小天使小主人處理函式 */
+int
+t_changeangel(){
+ char buf[4];
+
+ /* cuser.myangel == "-" means banned for calling angel */
+ if (cuser.myangel[0] == '-' || cuser.myangel[1] == 0) return 0;
+
+ getdata(b_lines - 1, 0,
+ "更換小天使後就無法換回了喔! 是否要更換小天使? [y/N]",
+ buf, 3, LCECHO);
+ if (buf[0] == 'y' || buf[0] == 'Y') {
+ char buf[100];
+ snprintf(buf, sizeof(buf), "%s小主人 %s 換掉 %s 小天使\n",
+ ctime4(&now), cuser.userid, cuser.myangel);
+ buf[24] = ' '; // replace '\n'
+ log_file(BBSHOME "/log/changeangel.log", LOG_CREAT, buf);
+
+ cuser.myangel[0] = 0;
+ outs("小天使更新完成,下次呼叫時會選出新的小天使");
+ }
+ return XEASY;
+}
+
+int t_angelmsg(){
+ char msg[3][74] = { "", "", "" };
+ char nick[10] = "";
+ char buf[512];
+ int i;
+ FILE* fp;
+
+ setuserfile(buf, "angelmsg");
+ fp = fopen(buf, "r");
+ if (fp) {
+ i = 0;
+ if (fgets(msg[0], sizeof(msg[0]), fp)) {
+ chomp(msg[0]);
+ if (strncmp(msg[0], "%%[", 3) == 0) {
+ strlcpy(nick, msg[0] + 3, 7);
+ move(4, 0);
+ prints("原有暱稱:%s", nick);
+ msg[0][0] = 0;
+ } else
+ i = 1;
+ } else
+ msg[0][0] = 0;
+
+ move(5, 0);
+ outs("原有留言:\n");
+ if(msg[0][0])
+ outs(msg[0]);
+ for (; i < 3; ++i) {
+ if(fgets(msg[i], sizeof(msg[0]), fp)) {
+ outs(msg[i]);
+ chomp(msg[i]);
+ } else
+ break;
+ }
+ fclose(fp);
+ }
+
+ getdata_buf(11, 0, "暱稱:", nick, 7, 1);
+ do {
+ move(12, 0);
+ clrtobot();
+ outs("不在的時候要跟小主人說什麼呢?"
+ "最多三行,按[Enter]結束");
+ for (i = 0; i < 3 &&
+ getdata_buf(14 + i, 0, ":", msg[i], sizeof(msg[i]), DOECHO);
+ ++i);
+ getdata(b_lines - 2, 0, "(S)儲存 (E)重新來過 (Q)取消?[S]",
+ buf, 4, LCECHO);
+ } while (buf[0] == 'E' || buf[0] == 'e');
+ if (buf[0] == 'Q' || buf[0] == 'q')
+ return 0;
+ setuserfile(buf, "angelmsg");
+ if (msg[0][0] == 0)
+ unlink(buf);
+ else {
+ FILE* fp = fopen(buf, "w");
+ if(nick[0])
+ fprintf(fp, "%%%%[%s\n", nick);
+ for (i = 0; i < 3 && msg[i][0]; ++i) {
+ fputs(msg[i], fp);
+ fputc('\n', fp);
+ }
+ fclose(fp);
+ }
+ return 0;
+}
+
+static int
+FindAngel(void){
+ int nAngel;
+ int i, j;
+ int choose;
+ int trial = 0;
+ int mask;
+
+ if (cuser.sex < 6) /* 正常性別 */
+ mask = 1 | (2 << (cuser.sex & 1));
+ else
+ mask = 7;
+
+ do{
+ nAngel = 0;
+ j = SHM->currsorted;
+ for (i = 0; i < SHM->UTMPnumber; ++i)
+ if ((SHM->uinfo[SHM->sorted[j][0][i]].userlevel & PERM_ANGEL)
+ && (SHM->uinfo[SHM->sorted[j][0][i]].angel & mask) == 0)
+ ++nAngel;
+
+ if (nAngel == 0)
+ return 0;
+
+ choose = random() % nAngel + 1;
+ j = SHM->currsorted;
+ for (i = 0; i < SHM->UTMPnumber && choose; ++i)
+ if ((SHM->uinfo[SHM->sorted[j][0][i]].userlevel & PERM_ANGEL)
+ && (SHM->uinfo[SHM->sorted[j][0][i]].angel & mask) == 0)
+ --choose;
+
+ if (choose == 0 && SHM->uinfo[SHM->sorted[j][0][i - 1]].uid != currutmp->uid
+ && (SHM->uinfo[SHM->sorted[j][0][i - 1]].userlevel & PERM_ANGEL)
+ && ((SHM->uinfo[SHM->sorted[j][0][i - 1]].angel & mask) == 0)
+ && !he_reject_me(&SHM->uinfo[SHM->sorted[j][0][i - 1]]) ){
+ strlcpy(cuser.myangel, SHM->uinfo[SHM->sorted[j][0][i - 1]].userid, IDLEN + 1);
+ passwd_update(usernum, &cuser);
+ return 1;
+ }
+ }while(++trial < 5);
+ return 0;
+}
+
+static inline void
+GotoNewHand(){
+ if (currutmp && currutmp->mode != EDITING){
+ char old_board[IDLEN + 1] = "";
+ if (currboard)
+ strlcpy(old_board, currboard, IDLEN + 1);
+
+ brc_initial_board("PttNewHand");
+ Read();
+
+ if (old_board[0])
+ brc_initial_board(old_board);
+ }
+}
+
+static inline void
+NoAngelFound(const char* msg){
+ move(b_lines, 0);
+ outs(msg);
+ if (currutmp == NULL || currutmp->mode != EDITING)
+ outs(",請先在新手板上尋找答案或按 Ctrl-P 發問");
+ clrtoeol();
+ refresh();
+ sleep(1);
+ GotoNewHand();
+ return;
+}
+
+static inline void
+AngelNotOnline(){
+ char buf[256];
+ const static char* const not_online_message = "您的小天使現在不在線上";
+ if (cuser.myangel[0] != '-')
+ sethomefile(buf, cuser.myangel, "angelmsg");
+ if (cuser.myangel[0] == '-' || !dashf(buf))
+ NoAngelFound(not_online_message);
+ else {
+ FILE* fp = fopen(buf, "r");
+ clear();
+ showtitle("小天使留言", BBSNAME);
+ move(4, 0);
+ clrtobot();
+
+ buf[0] = 0;
+ fgets(buf, sizeof(buf), fp);
+ if (strncmp(buf, "%%[", 3) == 0) {
+ chomp(buf);
+ prints("您的%s小天使現在不在線上", buf + 3);
+ fgets(buf, sizeof(buf), fp);
+ } else
+ outs(not_online_message);
+
+ outs("\n祂留言給你:\n");
+ outs(ANSI_COLOR(1;31;44) "☉┬──────────────┤" ANSI_COLOR(37) ""
+ "小天使留言" ANSI_COLOR(31) "├──────────────┬☉" ANSI_RESET "\n");
+ outs(ANSI_COLOR(1;31) "╭┤" ANSI_COLOR(32) " 小天使 "
+ " " ANSI_COLOR(31) "├╮" ANSI_RESET "\n");
+
+ do {
+ chomp(buf);
+ prints(ANSI_COLOR(1;31) "│" ANSI_RESET "%-74.74s" ANSI_COLOR(1;31) "│" ANSI_RESET "\n", buf);
+ } while (fgets(buf, sizeof(buf), fp));
+
+ outs(ANSI_COLOR(1;31) "╰┬──────────────────────"
+ "─────────────┬╯" ANSI_RESET "\n");
+ outs(ANSI_COLOR(1;31;44) "☉┴─────────────────────"
+ "──────────────┴☉" ANSI_RESET "\n");
+
+ move(b_lines - 4, 0);
+ outs("小主人使用上問題找不到小天使請到新手版(PttNewhand)\n"
+ " 想留言給小天使請到許\願版(AngelPray)\n"
+ " 想找看板在哪的話可到(AskBoard)\n"
+ "請先在各板上尋找答案或按 Ctrl-P 發問");
+ pressanykey();
+
+ GotoNewHand();
+ }
+}
+
+static void
+TalkToAngel(){
+ static int AngelPermChecked = 0;
+ userinfo_t* uent;
+ userec_t xuser;
+
+ if (strcmp(cuser.myangel, "-") == 0){
+ AngelNotOnline();
+ return;
+ }
+
+ if (cuser.myangel[0] && !AngelPermChecked) {
+ getuser(cuser.myangel, &xuser); // XXX if user doesn't exist
+ if (!(xuser.userlevel & PERM_ANGEL))
+ cuser.myangel[0] = 0;
+ }
+ AngelPermChecked = 1;
+
+ if (cuser.myangel[0] == 0 && ! FindAngel()){
+ NoAngelFound("現在沒有小天使在線上");
+ return;
+ }
+
+ uent = search_ulist_userid(cuser.myangel);
+ if (uent == 0 || (uent->angel & 1) || he_reject_me(uent)){
+ AngelNotOnline();
+ return;
+ }
+
+ more("etc/angel_usage", NA);
+
+ /* 這段話或許可以在小天使回答問題時 show 出來
+ move(b_lines - 1, 0);
+ outs("現在你的id受到保密,回答你問題的小天使並不知道你是誰 \n"
+ "你可以選擇不向對方透露自己身份來保護自己 ");
+ */
+
+ my_write(uent->pid, "問小天使: ", "小天使", WATERBALL_ANGEL, uent);
+ return;
+}
+
+void
+CallAngel(){
+ static int entered = 0;
+ screen_backup_t old_screen;
+
+ if (!HasUserPerm(PERM_LOGINOK) || entered)
+ return;
+ entered = 1;
+
+ screen_backup(&old_screen);
+
+ TalkToAngel();
+
+ screen_restore(&old_screen);
+
+ entered = 0;
+}
+
+void
+SwitchBeingAngel(){
+ cuser.uflag2 ^= REJ_QUESTION;
+ currutmp->angel ^= 1;
+}
+
+void
+SwitchAngelSex(int newmode){
+ ANGEL_SET(newmode);
+ currutmp->angel = (currutmp->angel & ~0x6) | ((newmode & 3) << 1);
+}
+
+int
+t_switchangel(){
+ SwitchBeingAngel();
+ outs(REJECT_QUESTION ? "休息一會兒" : "開放小主人問問題");
+ return XEASY;
+}
+#endif
diff --git a/pttbbs/mbbsd/term.c b/pttbbs/mbbsd/term.c
new file mode 100644
index 00000000..2ef13ed0
--- /dev/null
+++ b/pttbbs/mbbsd/term.c
@@ -0,0 +1,117 @@
+/* $Id$ */
+#include "bbs.h"
+
+/* ----------------------------------------------------- */
+/* basic tty control */
+/* ----------------------------------------------------- */
+void
+init_tty(void)
+{
+ struct termios tty_state, tty_new;
+
+ if (tcgetattr(1, &tty_state) < 0) {
+ syslog(LOG_ERR, "tcgetattr(): %m");
+ return;
+ }
+ memcpy(&tty_new, &tty_state, sizeof(tty_new));
+ tty_new.c_lflag &= ~(ICANON | ECHO | ISIG);
+ /*
+ * tty_new.c_cc[VTIME] = 0; tty_new.c_cc[VMIN] = 1;
+ */
+ tcsetattr(1, TCSANOW, &tty_new);
+ system("stty raw -echo");
+}
+
+/* ----------------------------------------------------- */
+/* init tty control code */
+/* ----------------------------------------------------- */
+
+
+#define TERMCOMSIZE (40)
+
+static void
+sig_term_resize(int sig)
+{
+ struct winsize newsize;
+ Signal(SIGWINCH, SIG_IGN); /* Don't bother me! */
+ ioctl(0, TIOCGWINSZ, &newsize);
+ term_resize(newsize.ws_col, newsize.ws_row);
+}
+
+void term_resize(int w, int h)
+{
+ screenline_t *new_picture;
+
+ Signal(SIGWINCH, SIG_IGN); /* Don't bother me! */
+
+ /* make sure reasonable size */
+ h = MAX(24, MIN(100, h));
+ w = MAX(80, MIN(200, w));
+
+ if (h > t_lines && big_picture) {
+ new_picture = (screenline_t *)
+ calloc(h, sizeof(screenline_t));
+ if (new_picture == NULL) {
+ syslog(LOG_ERR, "calloc(): %m");
+ return;
+ }
+ memcpy(new_picture, big_picture, t_lines * sizeof(screenline_t));
+ free(big_picture);
+ big_picture = new_picture;
+ }
+ t_lines = h;
+ t_columns = w;
+ scr_lns = t_lines; /* XXX: scr_lns 跟 t_lines 有什麼不同, 為何分成兩個 */
+ b_lines = t_lines - 1;
+ p_lines = t_lines - 4;
+
+ Signal(SIGWINCH, sig_term_resize);
+}
+
+int
+term_init(void)
+{
+ Signal(SIGWINCH, sig_term_resize);
+ return YEA;
+}
+
+void
+do_move(int destcol, int destline)
+{
+ char buf[16], *p;
+
+ snprintf(buf, sizeof(buf), ANSI_MOVETO(%d,%d), destline + 1, destcol + 1);
+ for (p = buf; *p; p++)
+ ochar(*p);
+}
+
+void
+save_cursor(void)
+{
+ ochar(ESC_CHR);
+ ochar('7');
+}
+
+void
+restore_cursor(void)
+{
+ ochar(ESC_CHR);
+ ochar('8');
+}
+
+void
+change_scroll_range(int top, int bottom)
+{
+ char buf[16], *p;
+
+ snprintf(buf, sizeof(buf), ESC_STR "[%d;%dr", top + 1, bottom + 1);
+ for (p = buf; *p; p++)
+ ochar(*p);
+}
+
+void
+scroll_forward(void)
+{
+ ochar(ESC_CHR);
+ ochar('D');
+}
diff --git a/pttbbs/mbbsd/time.c b/pttbbs/mbbsd/time.c
new file mode 100644
index 00000000..11f963c3
--- /dev/null
+++ b/pttbbs/mbbsd/time.c
@@ -0,0 +1,20 @@
+#ifdef __dietlibc__
+#include <time.h>
+#warning "hardcoded time zone as GMT+8!"
+extern void __maplocaltime(void);
+extern time_t __tzfile_map(time_t t, int *isdst, int forward);
+extern time_t timegm(struct tm *const t);
+
+time_t mktime(register struct tm* const t) {
+ time_t x=timegm(t);
+ x-=8*3600;
+ return x;
+}
+
+struct tm* localtime_r(const time_t* t, struct tm* r) {
+ time_t tmp;
+ tmp=*t;
+ tmp+=8*3600;
+ return gmtime_r(&tmp,r);
+}
+#endif
diff --git a/pttbbs/mbbsd/topsong.c b/pttbbs/mbbsd/topsong.c
new file mode 100644
index 00000000..a60a26c4
--- /dev/null
+++ b/pttbbs/mbbsd/topsong.c
@@ -0,0 +1,81 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define MAX_SONGS 300
+#define QCAST int (*)(const void *, const void *)
+
+typedef struct songcmp_t {
+ char name[100];
+ char cname[100];
+ int count;
+} songcmp_t;
+
+static int totalcount = 0;
+
+static int
+count_cmp(songcmp_t * b, songcmp_t * a)
+{
+ return (a->count - b->count);
+}
+
+int
+topsong(void)
+{
+ more(FN_TOPSONG, YEA);
+ return 0;
+}
+
+static int
+strip_blank(char *cbuf, char *buf)
+{
+ for (; *buf; buf++)
+ if (*buf != ' ')
+ *cbuf++ = *buf;
+ *cbuf = 0;
+ return 0;
+}
+
+void
+sortsong(void)
+{
+ FILE *fo, *fp = fopen(BBSHOME "/" FN_USSONG, "r");
+ songcmp_t songs[MAX_SONGS + 1];
+ int n;
+ char buf[256], cbuf[256];
+
+ memset(songs, 0, sizeof(songs));
+ if (!fp)
+ return;
+ if (!(fo = fopen(FN_TOPSONG, "w"))) {
+ fclose(fp);
+ return;
+ }
+ totalcount = 0;
+ /* XXX: 除了前 MAX_SONGS 首, 剩下不會排序 */
+ while (fgets(buf, 200, fp)) {
+ chomp(buf);
+ strip_blank(cbuf, buf);
+ if (!cbuf[0] || !isprint2((int)cbuf[0]))
+ continue;
+
+ for (n = 0; n < MAX_SONGS && songs[n].name[0]; n++)
+ if (!strcmp(songs[n].cname, cbuf))
+ break;
+ strlcpy(songs[n].name, buf, sizeof(songs[n].name));
+ strlcpy(songs[n].cname, cbuf, sizeof(songs[n].cname));
+ songs[n].count++;
+ totalcount++;
+ }
+ qsort(songs, MAX_SONGS, sizeof(songcmp_t), (QCAST) count_cmp);
+ fprintf(fo,
+ " " ANSI_COLOR(36) "──" ANSI_COLOR(37) "名次" ANSI_COLOR(36) "──────" ANSI_COLOR(37) "歌"
+ " 名" ANSI_COLOR(36) "───────────" ANSI_COLOR(37) "次數" ANSI_COLOR(36) ""
+ "──" ANSI_COLOR(32) "共%d次" ANSI_COLOR(36) "──" ANSI_RESET "\n", totalcount);
+ for (n = 0; n < 100 && songs[n].name[0]; n++) {
+ fprintf(fo, " %5d. %-38.38s %4d " ANSI_COLOR(32) "[%.2f]" ANSI_RESET "\n", n + 1,
+ songs[n].name, songs[n].count,
+ (float)songs[n].count / totalcount);
+ }
+ fclose(fp);
+ fclose(fo);
+}
diff --git a/pttbbs/mbbsd/user.c b/pttbbs/mbbsd/user.c
new file mode 100644
index 00000000..4abc44ca
--- /dev/null
+++ b/pttbbs/mbbsd/user.c
@@ -0,0 +1,2069 @@
+/* $Id$ */
+#include "bbs.h"
+static char * const sex[8] = {
+ MSG_BIG_BOY, MSG_BIG_GIRL, MSG_LITTLE_BOY, MSG_LITTLE_GIRL,
+ MSG_MAN, MSG_WOMAN, MSG_PLANT, MSG_MIME
+};
+
+#ifdef CHESSCOUNTRY
+static const char * const chess_photo_name[3] = {
+ "photo_fivechess", "photo_cchess", "photo_go",
+};
+
+static const char * const chess_type[3] = {
+ "五子棋", "象棋", "圍棋",
+};
+#endif
+
+int
+kill_user(int num, char *userid)
+{
+ userec_t u;
+ char src[256], dst[256];
+
+ if(!userid || num<=0 ) return -1;
+ sethomepath(src, userid);
+ snprintf(dst, sizeof(dst), "tmp/%s", userid);
+ friend_delete_all(userid, FRIEND_ALOHA);
+ delete_allpost(userid);
+ if (dashd(src) && Rename(src, dst) == 0) {
+ snprintf(src, sizeof(src), "/bin/rm -fr home/%c/%s >/dev/null 2>&1", userid[0], userid);
+ system(src);
+ }
+
+ memset(&u, 0, sizeof(userec_t));
+ log_usies("KILL", getuserid(num));
+ setuserid(num, "");
+ passwd_update(num, &u);
+ return 0;
+}
+int
+u_loginview(void)
+{
+ int i;
+ unsigned int pbits = cuser.loginview;
+
+ clear();
+ move(4, 0);
+ for (i = 0; i < NUMVIEWFILE; i++)
+ prints(" %c. %-20s %-15s \n", 'A' + i,
+ loginview_file[i][1], ((pbits >> i) & 1 ? "ˇ" : "X"));
+
+ clrtobot();
+ while ((i = getkey("請按 [A-N] 切換設定,按 [Return] 結束:"))!='\r')
+ {
+ i = i - 'a';
+ if (i >= NUMVIEWFILE || i < 0)
+ bell();
+ else {
+ pbits ^= (1 << i);
+ move(i + 4, 28);
+ outs((pbits >> i) & 1 ? "ˇ" : "X");
+ }
+ }
+
+ if (pbits != cuser.loginview) {
+ cuser.loginview = pbits;
+ passwd_update(usernum, &cuser);
+ }
+ return 0;
+}
+int u_cancelbadpost(void)
+{
+ int day;
+ if(cuser.badpost==0)
+ {vmsg("你並沒有劣文."); return 0;}
+
+ if(search_ulistn(usernum,2))
+ {vmsg("請登出其他視窗, 否則不受理."); return 0;}
+
+ passwd_query(usernum, &cuser);
+ day = 180 - (now - cuser.timeremovebadpost ) / 86400;
+ if(day>0 && day<=180)
+ {
+ vmsgf("每 180 天才能申請一次, 還剩 %d 天.", day);
+ vmsg("您也可以注意站方是否有勞動服務方式刪除劣文.");
+ return 0;
+ }
+
+ if(
+ getkey("我願意尊守站方規定,組規,以及板規[y/N]?")!='y' ||
+ getkey("我願意尊重不歧視族群,不鬧板,尊重各板主權力[y/N]?")!='y' ||
+ getkey("我願意謹慎發表有意義言論,不謾罵攻擊,不跨板廣告[y/N]?")!='y' )
+
+ {vmsg("請您思考清楚後再來申請刪除."); return 0;}
+
+ if(search_ulistn(usernum,2))
+ {vmsg("請登出其他視窗, 否則不受理."); return 0;}
+ if(cuser.badpost)
+ {
+ cuser.badpost--;
+ cuser.timeremovebadpost = now;
+ passwd_update(usernum, &cuser);
+ log_file("log/cancelbadpost.log", LOG_VF|LOG_CREAT,
+ "%s %s 刪除一篇劣文\n", Cdate(&now), cuser.userid);
+ }
+ vmsg("恭喜您已經成功\刪除一篇劣文.");
+ return 0;
+}
+
+void
+user_display(const userec_t * u, int adminmode)
+{
+ int diff = 0;
+ char genbuf[200];
+
+ clrtobot();
+ prints(
+ " " ANSI_COLOR(30;41) "┴┬┴┬┴┬" ANSI_RESET " " ANSI_COLOR(1;30;45) " 使 用 者"
+ " 資 料 "
+ " " ANSI_RESET " " ANSI_COLOR(30;41) "┴┬┴┬┴┬" ANSI_RESET "\n");
+ prints(" 代號暱稱: %s(%s)\n"
+ " 真實姓名: %s"
+#if FOREIGN_REG_DAY > 0
+ " %s%s"
+#elif defined(FOREIGN_REG)
+ " %s"
+#endif
+ "\n"
+ " 居住住址: %s\n"
+ " 電子信箱: %s\n"
+ " 性 別: %s\n"
+ " 銀行帳戶: %d 銀兩\n",
+ u->userid, u->nickname, u->realname,
+#if FOREIGN_REG_DAY > 0
+ u->uflag2 & FOREIGN ? "(外籍: " : "",
+ u->uflag2 & FOREIGN ?
+ (u->uflag2 & LIVERIGHT) ? "永久居留)" : "未取得居留權)"
+ : "",
+#elif defined(FOREIGN_REG)
+ u->uflag2 & FOREIGN ? "(外籍)" : "",
+#endif
+ u->address, u->email,
+ sex[u->sex % 8], u->money);
+
+ sethomedir(genbuf, u->userid);
+ prints(" 私人信箱: %d 封 (購買信箱: %d 封)\n"
+ " 手機號碼: %010d\n"
+ " 生 日: %04i/%02i/%02i\n"
+ " 優 劣 文: 優:%d / 劣:%d\n",
+ get_num_records(genbuf, sizeof(fileheader_t)),
+ u->exmailbox, u->mobile,
+ u->year + 1900, u->month, u->day,
+ u->goodpost, u->badpost);
+ prints(" 上站位置: %s\n", u->lasthost);
+
+#ifdef PLAY_ANGEL
+ if (adminmode)
+ prints(" 小 天 使: %s\n",
+ u->myangel[0] ? u->myangel : "無");
+#endif
+ prints(" 註冊日期: %s", ctime4(&u->firstlogin));
+ prints(" 前次光臨: %s", ctime4(&u->lastlogin));
+ prints(" 上站文章: %d 次 / %d 篇\n",
+ u->numlogins, u->numposts);
+
+#ifdef CHESSCOUNTRY
+ {
+ int i, j;
+ FILE* fp;
+ for(i = 0; i < 2; ++i){
+ sethomefile(genbuf, u->userid, chess_photo_name[i]);
+ fp = fopen(genbuf, "r");
+ if(fp != NULL){
+ for(j = 0; j < 11; ++j)
+ fgets(genbuf, 200, fp);
+ fgets(genbuf, 200, fp);
+ prints("%12s棋國自我描述: %s", chess_type[i], genbuf + 11);
+ fclose(fp);
+ }
+ }
+ }
+#endif
+
+ if (adminmode) {
+ strcpy(genbuf, "bTCPRp#@XWBA#VSM0123456789ABCDEF");
+ for (diff = 0; diff < 32; diff++)
+ if (!(u->userlevel & (1 << diff)))
+ genbuf[diff] = '-';
+ prints(" 認證資料: %s\n"
+ " user權限: %s\n",
+ u->justify, genbuf);
+ } else {
+ diff = (now - login_start_time) / 60;
+ prints(" 停留期間: %d 小時 %2d 分\n",
+ diff / 60, diff % 60);
+ }
+
+ /* Thor: 想看看這個 user 是那些板的板主 */
+ if (u->userlevel >= PERM_BM) {
+ int i;
+ boardheader_t *bhdr;
+
+ outs(" 擔任板主: ");
+
+ for (i = 0, bhdr = bcache; i < numboards; i++, bhdr++) {
+ if (is_uBM(bhdr->BM, u->userid)) {
+ outs(bhdr->brdname);
+ outc(' ');
+ }
+ }
+ outc('\n');
+ }
+ outs(" " ANSI_COLOR(30;41) "┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴"
+ "┬┴┬┴┬┴┬" ANSI_RESET);
+
+ outs((u->userlevel & PERM_LOGINOK) ?
+ "\n您的註冊程序已經完成,歡迎加入本站" :
+ "\n如果要提昇權限,請參考本站公佈欄辦理註冊");
+
+#ifdef NEWUSER_LIMIT
+ if ((u->lastlogin - u->firstlogin < 3 * 86400) && !HasUserPerm(PERM_POST))
+ outs("\n新手上路,三天後開放權限");
+#endif
+}
+
+void
+mail_violatelaw(const char *crime, const char *police, const char *reason, const char *result)
+{
+ char genbuf[200];
+ fileheader_t fhdr;
+ FILE *fp;
+
+ sendalert(crime, ALERT_PWD_PERM);
+
+ sethomepath(genbuf, crime);
+ stampfile(genbuf, &fhdr);
+ if (!(fp = fopen(genbuf, "w")))
+ return;
+ fprintf(fp, "作者: [Ptt警察局]\n"
+ "標題: [報告] 違法報告\n"
+ "時間: %s\n"
+ ANSI_COLOR(1;32) "%s" ANSI_RESET "判決:\n " ANSI_COLOR(1;32) "%s" ANSI_RESET
+ "因" ANSI_COLOR(1;35) "%s" ANSI_RESET "行為,\n違反本站站規,處以" ANSI_COLOR(1;35) "%s" ANSI_RESET ",特此通知"
+ "\n請到 PttLaw 查詢相關法規資訊,並到 Play-Pay-ViolateLaw 繳交罰單",
+ ctime4(&now), police, crime, reason, result);
+ fclose(fp);
+ strcpy(fhdr.title, "[報告] 違法判決報告");
+ strcpy(fhdr.owner, "[Ptt警察局]");
+ sethomedir(genbuf, crime);
+ append_record(genbuf, &fhdr, sizeof(fhdr));
+}
+
+void
+kick_all(char *user)
+{
+ userinfo_t *ui;
+ int num = searchuser(user, NULL), i=1;
+ while((ui = (userinfo_t *) search_ulistn(num, i))>0)
+ {
+ if(ui == currutmp) i++;
+ if ((ui->pid <= 0 || kill(ui->pid, SIGHUP) == -1))
+ purge_utmp(ui);
+ log_usies("KICK ALL", user);
+ }
+}
+
+void
+violate_law(userec_t * u, int unum)
+{
+ char ans[4], ans2[4];
+ char reason[128];
+ move(1, 0);
+ clrtobot();
+ move(2, 0);
+ outs("(1)Cross-post (2)亂發廣告信 (3)亂發連鎖信\n");
+ outs("(4)騷擾站上使用者 (8)其他以罰單處置行為\n(9)砍 id 行為\n");
+ getdata(5, 0, "(0)結束", ans, 3, DOECHO);
+ switch (ans[0]) {
+ case '1':
+ strcpy(reason, "Cross-post");
+ break;
+ case '2':
+ strcpy(reason, "亂發廣告信");
+ break;
+ case '3':
+ strcpy(reason, "亂發連鎖信");
+ break;
+ case '4':
+ while (!getdata(7, 0, "請輸入被檢舉理由以示負責:", reason, 50, DOECHO));
+ strcat(reason, "[騷擾站上使用者]");
+ break;
+ case '8':
+ case '9':
+ while (!getdata(6, 0, "請輸入理由以示負責:", reason, 50, DOECHO));
+ break;
+ default:
+ return;
+ }
+ getdata(7, 0, msg_sure_ny, ans2, 3, LCECHO);
+ if (*ans2 != 'y')
+ return;
+ if (ans[0] == '9') {
+ if (HasUserPerm(PERM_POLICE) && (u->numlogins > 100 || u->numposts > 100))
+ return;
+
+ kill_user(unum, u->userid);
+ post_violatelaw(u->userid, cuser.userid, reason, "砍除 ID");
+ } else {
+ kick_all(u->userid);
+ u->userlevel |= PERM_VIOLATELAW;
+ u->timeviolatelaw = now;
+ u->vl_count++;
+ passwd_update(unum, u);
+ post_violatelaw(u->userid, cuser.userid, reason, "罰單處份");
+ mail_violatelaw(u->userid, "站務警察", reason, "罰單處份");
+ }
+ pressanykey();
+}
+
+void Customize(void)
+{
+ char done = 0;
+ int dirty = 0;
+ int key;
+
+ /* cuser.uflag settings */
+ static const unsigned int masks1[] = {
+ MOVIE_FLAG,
+ DBCSAWARE_FLAG,
+ 0,
+ };
+
+ static const char* desc1[] = {
+ "動態看板",
+#ifdef DBCSAWARE
+ "自動偵測雙位元字集(如全型中文)",
+#endif
+ 0,
+ };
+
+ /* cuser.uflag2 settings */
+ static const unsigned int masks2[] = {
+ REJ_OUTTAMAIL,
+ FAVNEW_FLAG,
+ FAVNOHILIGHT,
+ 0,
+ };
+
+ static const char* desc2[] = {
+ "拒收站外信",
+ "新板自動進我的最愛",
+ "停用變色顯示我的最愛",
+ 0,
+ };
+
+ showtitle("個人化設定", "個人化設定");
+
+ while ( !done ) {
+ int i = 0, ia = 0, ic = 0; /* general uflags */
+ int iax = 0; /* extended flags */
+
+ clear();
+ move(2, 0);
+ outs("您目前的個人化設定: ");
+ move(4, 0);
+
+ /* print uflag options */
+ for (i = 0; masks1[i]; i++, ia++)
+ {
+ prints("%c. %-40s%10s\n",
+ 'a' + ia,
+ desc1[i],
+ (cuser.uflag & masks1[i]) ? "是" : "否");
+ }
+ ic = i;
+ /* print uflag2 options */
+ for (i = 0; masks2[i]; i++, ia++)
+ {
+ prints("%c. %-40s%10s\n",
+ 'a' + ia,
+ desc2[i],
+ (cuser.uflag2 & masks2[i]) ? "是" : "否");
+ }
+ /* extended stuff */
+ {
+ char mindbuf[5];
+ const static char *wm[] =
+ {"一般", "進階", "未來", ""};
+
+ prints("%c. %-40s%10s\n",
+ '1' + iax++,
+ "水球模式",
+ wm[(cuser.uflag2 & WATER_MASK)]);
+ memcpy(mindbuf, &currutmp->mind, 4);
+ mindbuf[4] = 0;
+ prints("%c. %-40s%10s\n",
+ '1' + iax++,
+ "目前的心情",
+ mindbuf);
+#ifdef PLAY_ANGEL
+ /* damn it, dirty stuff here */
+ if( HasUserPerm(PERM_ANGEL) )
+ {
+ const char *am[4] =
+ {"男女皆可", "限女生", "限男生", "暫不接受新的小主人"};
+ prints("%c. %-40s%10s\n",
+ '1' + iax++,
+ "開放小主人詢問",
+ (REJECT_QUESTION ? "否" : "是"));
+ prints("%c. %-40s%10s\n",
+ '1' + iax++,
+ "接受的小主人性別",
+ am[ANGEL_STATUS()]);
+ }
+#endif
+ }
+
+ /* input */
+ key = getkey("請按 [a-%c,1-%c] 切換設定,其它任意鍵結束: ",
+ 'a' + ia-1, '1' + iax -1);
+
+ if (key >= 'a' && key < 'a' + ia)
+ {
+ /* normal pref */
+ key -= 'a';
+ dirty = 1;
+
+ if(key < ic)
+ {
+ cuser.uflag ^= masks1[key];
+ } else {
+ key -= ic;
+ cuser.uflag2 ^= masks2[key];
+ }
+ continue;
+ }
+
+ if (key < '1' || key >= '1' + iax)
+ {
+ done = 1; continue;
+ }
+ /* extended keys */
+ key -= '1';
+
+ switch(key)
+ {
+ case 0:
+ {
+ int currentset = cuser.uflag2 & WATER_MASK;
+ currentset = (currentset + 1) % 3;
+ cuser.uflag2 &= ~WATER_MASK;
+ cuser.uflag2 |= currentset;
+ vmsg("修正水球模式後請正常離線再重新上線");
+ dirty = 1;
+ }
+ continue;
+ case 1:
+ {
+ char mindbuf[6] = "";
+ getdata(b_lines - 1, 0, "現在的心情? ",
+ mindbuf, 5, DOECHO);
+ if (strcmp(mindbuf, "通緝") == 0)
+ vmsg("不可以把自己設通緝啦!");
+ else if (strcmp(mindbuf, "壽星") == 0)
+ vmsg("你不是今天生日欸!");
+ else
+ memcpy(currutmp->mind, mindbuf, 4);
+ dirty = 1;
+ }
+ continue;
+ }
+#ifdef PLAY_ANGEL
+ if( HasUserPerm(PERM_ANGEL) ){
+ if (key == iax-2)
+ {
+ SwitchBeingAngel();
+ dirty = 1; continue;
+ }
+ else if (key == iax-1)
+ {
+ SwitchAngelSex(ANGEL_STATUS() + 1);
+ dirty = 1; continue;
+ }
+ }
+#endif
+
+ }
+
+ if(dirty)
+ passwd_update(usernum, &cuser);
+
+ 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;
+ const char *alphabet = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";
+
+ /* generate a new regcode */
+ buf[13] = 0;
+ buf[0] = 'v';
+ buf[1] = '6';
+ for( i = 2 ; i < 13 ; ++i )
+ buf[i] = alphabet[random() % 52];
+
+ 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)
+{
+ userec_t x;
+ register int i = 0, fail, mail_changed;
+ int uid, ans;
+ char buf[STRLEN], *p;
+ char genbuf[200], reason[50];
+ int money = 0;
+ int flag = 0, temp = 0, money_change = 0;
+
+ fail = mail_changed = 0;
+
+ memcpy(&x, u, sizeof(userec_t));
+ ans = getans(adminmode ?
+ "(1)改資料(2)設密碼(3)設權限(4)砍帳號(5)改ID"
+ "(6)殺/復活寵物(7)審判(M)改信箱 [0]結束 " :
+ "請選擇 (1)修改資料 (2)設定密碼 (M)修改信箱 (C) 個人化設定 ==> [0]結束 ");
+
+ if (ans > '2' && ans != 'm' && ans != 'c' && !adminmode)
+ ans = '0';
+
+ if (ans == '1' || ans == '3' || ans == 'm') {
+ clear();
+ i = 1;
+ move(i++, 0);
+ outs(msg_uid);
+ outs(x.userid);
+ }
+ switch (ans) {
+ case 'c':
+ Customize();
+ return;
+ case 'm':
+ do {
+ getdata_str(i, 0, "電子信箱[變動要重新認證]:", buf, 50, DOECHO,
+ x.email);
+ } while (!isvalidemail(buf) && vmsg("認證信箱不能用使用免費信箱"));
+ i++;
+ if (strcmp(buf, x.email) && strchr(buf, '@')) {
+ strlcpy(x.email, buf, sizeof(x.email));
+ mail_changed = 1 - adminmode;
+ }
+ break;
+ case '7':
+ violate_law(&x, unum);
+ return;
+ case '1':
+ move(0, 0);
+ outs("請逐項修改。");
+
+ getdata_buf(i++, 0, " 暱 稱 :", x.nickname,
+ sizeof(x.nickname), DOECHO);
+ if (adminmode) {
+ getdata_buf(i++, 0, "真實姓名:",
+ x.realname, sizeof(x.realname), DOECHO);
+ getdata_buf(i++, 0, "居住地址:",
+ x.address, sizeof(x.address), DOECHO);
+ }
+ snprintf(buf, sizeof(buf), "%010d", x.mobile);
+ getdata_buf(i++, 0, "手機號碼:", buf, 11, LCECHO);
+ x.mobile = atoi(buf);
+ snprintf(genbuf, sizeof(genbuf), "%i", (u->sex + 1) % 8);
+ getdata_str(i++, 0, "性別 (1)葛格 (2)姐接 (3)底迪 (4)美眉 (5)薯叔 "
+ "(6)阿姨 (7)植物 (8)礦物:",
+ buf, 3, DOECHO, genbuf);
+ if (buf[0] >= '1' && buf[0] <= '8')
+ x.sex = (buf[0] - '1') % 8;
+ else
+ x.sex = u->sex % 8;
+
+ while (1) {
+ snprintf(genbuf, sizeof(genbuf), "%04i/%02i/%02i",
+ u->year + 1900, u->month, u->day);
+ if (getdata_str(i, 0, "生日 西元/月月/日日:", buf, 11, DOECHO, genbuf) == 0) {
+ x.month = u->month;
+ x.day = u->day;
+ x.year = u->year;
+ } else {
+ int y, m, d;
+ if (ParseDate(buf, &y, &m, &d))
+ continue;
+ x.month = (unsigned char)m;
+ x.day = (unsigned char)d;
+ x.year = (unsigned char)(y - 1900);
+ }
+ if (!adminmode && x.year < 40)
+ continue;
+ i++;
+ break;
+ }
+
+#ifdef PLAY_ANGEL
+ if (adminmode) {
+ const char* prompt;
+ userec_t the_angel;
+ if (x.myangel[0] == 0 || x.myangel[0] == '-' ||
+ (getuser(x.myangel, &the_angel) &&
+ the_angel.userlevel & PERM_ANGEL))
+ prompt = "小天使:";
+ else
+ prompt = "小天使(此帳號已無小天使資格):";
+ while (1) {
+ userec_t xuser;
+ getdata_str(i, 0, prompt, buf, IDLEN + 1, DOECHO,
+ x.myangel);
+ if(buf[0] == 0 || strcmp(buf, "-") == 0 ||
+ (getuser(buf, &xuser) &&
+ (xuser.userlevel & PERM_ANGEL)) ||
+ strcmp(x.myangel, buf) == 0){
+ strlcpy(x.myangel, xuser.userid, IDLEN + 1);
+ ++i;
+ break;
+ }
+
+ prompt = "小天使:";
+ }
+ }
+#endif
+
+#ifdef CHESSCOUNTRY
+ {
+ int j, k;
+ FILE* fp;
+ for(j = 0; j < 2; ++j){
+ sethomefile(genbuf, u->userid, chess_photo_name[j]);
+ fp = fopen(genbuf, "r");
+ if(fp != NULL){
+ FILE* newfp;
+ char mybuf[200];
+ for(k = 0; k < 11; ++k)
+ fgets(genbuf, 200, fp);
+ fgets(genbuf, 200, fp);
+ chomp(genbuf);
+
+ snprintf(mybuf, 200, "%s棋國自我描述:", chess_type[j]);
+ getdata_buf(i, 0, mybuf, genbuf + 11, 80 - 11, DOECHO);
+ ++i;
+
+ sethomefile(mybuf, u->userid, chess_photo_name[j]);
+ strcat(mybuf, ".new");
+ if((newfp = fopen(mybuf, "w")) != NULL){
+ rewind(fp);
+ for(k = 0; k < 11; ++k){
+ fgets(mybuf, 200, fp);
+ fputs(mybuf, newfp);
+ }
+ fputs(genbuf, newfp);
+ fputc('\n', newfp);
+
+ fclose(newfp);
+
+ sethomefile(genbuf, u->userid, chess_photo_name[j]);
+ sethomefile(mybuf, u->userid, chess_photo_name[j]);
+ strcat(mybuf, ".new");
+
+ Rename(mybuf, genbuf);
+ }
+ fclose(fp);
+ }
+ }
+ }
+#endif
+
+ if (adminmode) {
+ int l;
+ if (HasUserPerm(PERM_BBSADM)) {
+ snprintf(genbuf, sizeof(genbuf), "%d", x.money);
+ if (getdata_str(i++, 0, "銀行帳戶:", buf, 10, DOECHO, genbuf))
+ if ((l = atol(buf)) != 0) {
+ if (l != x.money) {
+ money_change = 1;
+ money = x.money;
+ x.money = l;
+ }
+ }
+ }
+ snprintf(genbuf, sizeof(genbuf), "%d", x.exmailbox);
+ if (getdata_str(i++, 0, "購買信箱數:", buf, 6,
+ DOECHO, genbuf))
+ if ((l = atol(buf)) != 0)
+ x.exmailbox = (int)l;
+
+ getdata_buf(i++, 0, "認證資料:", x.justify,
+ sizeof(x.justify), DOECHO);
+ getdata_buf(i++, 0, "最近光臨機器:",
+ x.lasthost, sizeof(x.lasthost), DOECHO);
+
+ // XXX 一變數不要多用途亂用 "fail"
+ snprintf(genbuf, sizeof(genbuf), "%d", x.numlogins);
+ if (getdata_str(i++, 0, "上線次數:", buf, 10, DOECHO, genbuf))
+ if ((fail = atoi(buf)) >= 0)
+ x.numlogins = fail;
+ snprintf(genbuf, sizeof(genbuf), "%d", u->numposts);
+ if (getdata_str(i++, 0, "文章數目:", buf, 10, DOECHO, genbuf))
+ if ((fail = atoi(buf)) >= 0)
+ x.numposts = fail;
+ snprintf(genbuf, sizeof(genbuf), "%d", u->goodpost);
+ if (getdata_str(i++, 0, "優良文章數:", buf, 10, DOECHO, genbuf))
+ if ((fail = atoi(buf)) >= 0)
+ x.goodpost = fail;
+ snprintf(genbuf, sizeof(genbuf), "%d", u->badpost);
+ if (getdata_str(i++, 0, "惡劣文章數:", buf, 10, DOECHO, genbuf))
+ if ((fail = atoi(buf)) >= 0)
+ x.badpost = fail;
+ snprintf(genbuf, sizeof(genbuf), "%d", u->vl_count);
+ if (getdata_str(i++, 0, "違法記錄:", buf, 10, DOECHO, genbuf))
+ if ((fail = atoi(buf)) >= 0)
+ x.vl_count = fail;
+
+ snprintf(genbuf, sizeof(genbuf),
+ "%d/%d/%d", u->five_win, u->five_lose, u->five_tie);
+ if (getdata_str(i++, 0, "五子棋戰績 勝/敗/和:", buf, 16, DOECHO,
+ genbuf))
+ while (1) {
+ char *strtok_pos;
+ p = strtok_r(buf, "/\r\n", &strtok_pos);
+ if (!p)
+ break;
+ x.five_win = atoi(p);
+ p = strtok_r(NULL, "/\r\n", &strtok_pos);
+ if (!p)
+ break;
+ x.five_lose = atoi(p);
+ p = strtok_r(NULL, "/\r\n", &strtok_pos);
+ if (!p)
+ break;
+ x.five_tie = atoi(p);
+ break;
+ }
+ snprintf(genbuf, sizeof(genbuf),
+ "%d/%d/%d", u->chc_win, u->chc_lose, u->chc_tie);
+ if (getdata_str(i++, 0, "象棋戰績 勝/敗/和:", buf, 16, DOECHO,
+ genbuf))
+ while (1) {
+ char *strtok_pos;
+ p = strtok_r(buf, "/\r\n", &strtok_pos);
+ if (!p)
+ break;
+ x.chc_win = atoi(p);
+ p = strtok_r(NULL, "/\r\n", &strtok_pos);
+ if (!p)
+ break;
+ x.chc_lose = atoi(p);
+ p = strtok_r(NULL, "/\r\n", &strtok_pos);
+ if (!p)
+ break;
+ x.chc_tie = atoi(p);
+ break;
+ }
+#ifdef FOREIGN_REG
+ if (getdata_str(i++, 0, "住在 1)台灣 2)其他:", buf, 2, DOECHO, x.uflag2 & FOREIGN ? "2" : "1"))
+ if ((fail = atoi(buf)) > 0){
+ if (fail == 2){
+ x.uflag2 |= FOREIGN;
+ }
+ else
+ x.uflag2 &= ~FOREIGN;
+ }
+ if (x.uflag2 & FOREIGN)
+ if (getdata_str(i++, 0, "永久居留權 1)是 2)否:", buf, 2, DOECHO, x.uflag2 & LIVERIGHT ? "1" : "2")){
+ if ((fail = atoi(buf)) > 0){
+ if (fail == 1){
+ x.uflag2 |= LIVERIGHT;
+ x.userlevel |= (PERM_LOGINOK | PERM_POST);
+ }
+ else{
+ x.uflag2 &= ~LIVERIGHT;
+ x.userlevel &= ~(PERM_LOGINOK | PERM_POST);
+ }
+ }
+ }
+#endif
+ fail = 0;
+ }
+ break;
+
+ case '2':
+ i = 19;
+ if (!adminmode) {
+ if (!getdata(i++, 0, "請輸入原密碼:", buf, PASSLEN, NOECHO) ||
+ !checkpasswd(u->passwd, buf)) {
+ outs("\n\n您輸入的密碼不正確\n");
+ fail++;
+ break;
+ }
+ } else {
+ FILE *fp;
+ char witness[3][32], title[100];
+ for (i = 0; i < 3; i++) {
+ if (!getdata(19 + i, 0, "請輸入協助證明之使用者:",
+ witness[i], sizeof(witness[i]), DOECHO)) {
+ outs("\n不輸入則無法更改\n");
+ fail++;
+ break;
+ } else if (!(uid = searchuser(witness[i], NULL))) {
+ outs("\n查無此使用者\n");
+ fail++;
+ break;
+ } else {
+ userec_t atuser;
+ passwd_query(uid, &atuser);
+ if (now - atuser.firstlogin < 6 * 30 * 24 * 60 * 60) {
+ outs("\n註冊未超過半年,請重新輸入\n");
+ i--;
+ }
+ strcpy(witness[i], atuser.userid);
+ // Adjust upper or lower case
+ }
+ }
+ if (i < 3)
+ break;
+
+ sprintf(title, "%s 的密碼重設通知 (by %s)",u->userid, cuser.userid);
+ unlink("etc/updatepwd.log");
+ if(! (fp = fopen("etc/updatepwd.log", "w")))
+ break;
+
+ fprintf(fp, "%s 要求密碼重設:\n"
+ "見證人為 %s, %s, %s",
+ u->userid, witness[0], witness[1], witness[2] );
+ fclose(fp);
+
+ post_file("Security", title, "etc/updatepwd.log", "[系統安全局]");
+ mail_id(u->userid, title, "etc/updatepwd.log", cuser.userid);
+ for(i=0; i<3; i++)
+ {
+ mail_id(witness[i], title, "etc/updatepwd.log", cuser.userid);
+ }
+ i = 20;
+ }
+
+ if (!getdata(i++, 0, "請設定新密碼:", buf, PASSLEN, NOECHO)) {
+ outs("\n\n密碼設定取消, 繼續使用舊密碼\n");
+ fail++;
+ break;
+ }
+ strlcpy(genbuf, buf, PASSLEN);
+
+ getdata(i++, 0, "請檢查新密碼:", buf, PASSLEN, NOECHO);
+ if (strncmp(buf, genbuf, PASSLEN)) {
+ outs("\n\n新密碼確認失敗, 無法設定新密碼\n");
+ fail++;
+ break;
+ }
+ buf[8] = '\0';
+ strlcpy(x.passwd, genpasswd(buf), sizeof(x.passwd));
+ break;
+
+ case '3':
+ i = setperms(x.userlevel, str_permid);
+ if (i == x.userlevel)
+ fail++;
+ else {
+ flag = 1;
+ temp = x.userlevel;
+ x.userlevel = i;
+ }
+ break;
+
+ case '4':
+ i = QUIT;
+ break;
+
+ case '5':
+ if (getdata_str(b_lines - 3, 0, "新的使用者代號:", genbuf, IDLEN + 1,
+ DOECHO, x.userid)) {
+ if (searchuser(genbuf, NULL)) {
+ outs("錯誤! 已經有同樣 ID 的使用者");
+ fail++;
+ } else
+ 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;
+ }
+
+ if (fail) {
+ pressanykey();
+ return;
+ }
+ if (getans(msg_sure_ny) == 'y') {
+ if (flag) {
+ post_change_perm(temp, i, cuser.userid, x.userid);
+#ifdef PLAY_ANGEL
+ if (i & ~temp & PERM_ANGEL)
+ mail_id(x.userid, "翅膀長出來了!", "etc/angel_notify", "[上帝]");
+#endif
+ }
+ if (strcmp(u->userid, x.userid)) {
+ char src[STRLEN], dst[STRLEN];
+
+ sethomepath(src, u->userid);
+ sethomepath(dst, x.userid);
+ Rename(src, dst);
+ setuserid(unum, x.userid);
+ }
+ if (mail_changed) {
+ char justify_tmp[REGLEN + 1];
+ char *phone, *career;
+ char *strtok_pos;
+ strlcpy(justify_tmp, u->justify, sizeof(justify_tmp));
+
+ 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);
+ email_justify(&x);
+ }
+ memcpy(u, &x, sizeof(x));
+ if (i == QUIT) {
+ kill_user(unum, x.userid);
+ return;
+ } else
+ log_usies("SetUser", x.userid);
+ if (money_change) {
+ char title[TTLEN+1];
+ char msg[200];
+ clrtobot();
+ clear();
+ while (!getdata(5, 0, "請輸入理由以示負責:",
+ reason, sizeof(reason), DOECHO));
+
+ snprintf(msg, sizeof(msg),
+ " 站長" ANSI_COLOR(1;32) "%s" ANSI_RESET "把" ANSI_COLOR(1;32) "%s" ANSI_RESET "的錢"
+ "從" ANSI_COLOR(1;35) "%d" ANSI_RESET "改成" ANSI_COLOR(1;35) "%d" ANSI_RESET "\n"
+ " " ANSI_COLOR(1;37) "站長%s修改錢理由是:%s" ANSI_RESET,
+ cuser.userid, x.userid, money, x.money,
+ cuser.userid, reason);
+ snprintf(title, sizeof(title),
+ "[公安報告] 站長%s修改%s錢報告", cuser.userid,
+ x.userid);
+ post_msg("Security", title, msg, "[系統安全局]");
+ setumoney(unum, x.money);
+ }
+ passwd_update(unum, &x);
+ if(flag)
+ sendalert(x.userid, ALERT_PWD_PERM); // force to reload perm
+ }
+}
+
+int
+u_info(void)
+{
+ move(2, 0);
+ user_display(&cuser, 0);
+ uinfo_query(&cuser, 0, usernum);
+ strlcpy(currutmp->nickname, cuser.nickname, sizeof(currutmp->nickname));
+ return 0;
+}
+
+int
+u_cloak(void)
+{
+ outs((currutmp->invisible ^= 1) ? MSG_CLOAKED : MSG_UNCLOAK);
+ return XEASY;
+}
+
+void
+showplans_userec(userec_t *user)
+{
+ char genbuf[200];
+
+ if(user->userlevel & PERM_VIOLATELAW)
+ {
+ outs(" \033[1;31m此人違規 尚未繳交罰單\033[m");
+ return;
+ }
+
+#ifdef CHESSCOUNTRY
+ if (user_query_mode) {
+ int i = 0;
+ FILE *fp;
+
+ sethomefile(genbuf, user->userid, chess_photo_name[user_query_mode - 1]);
+ if ((fp = fopen(genbuf, "r")) != NULL)
+ {
+ char photo[6][256];
+ int kingdom_bid = 0;
+ int win = 0, lost = 0;
+
+ move(7, 0);
+ while (i < 12 && fgets(genbuf, 256, fp))
+ {
+ chomp(genbuf);
+ if (i < 6) /* 讀照片檔 */
+ strcpy(photo[i], genbuf);
+ else if (i == 6)
+ kingdom_bid = atoi(genbuf);
+ else
+ prints("%s %s\n", photo[i - 7], genbuf);
+
+ i++;
+ }
+ fclose(fp);
+
+ if (user_query_mode == 1) {
+ win = user->five_win;
+ lost = user->five_lose;
+ } else if(user_query_mode == 2) {
+ win = user->chc_win;
+ lost = user->chc_lose;
+ }
+ prints("%s <總共戰績> %d 勝 %d 敗\n", photo[5], win, lost);
+
+
+ /* 棋國國徽 */
+ setapath(genbuf, bcache[kingdom_bid - 1].brdname);
+ strlcat(genbuf, "/chess_ensign", sizeof(genbuf));
+ show_file(genbuf, 13, 10, ONLY_COLOR);
+ return;
+ }
+ }
+#endif /* defined(CHESSCOUNTRY) */
+
+ sethomefile(genbuf, user->userid, fn_plans);
+ if (!show_file(genbuf, 7, MAX_QUERYLINES, ONLY_COLOR))
+ prints("《個人名片》%s 目前沒有名片", user->userid);
+}
+
+void
+showplans(const char *uid)
+{
+ userec_t user;
+ if(getuser(uid, &user))
+ showplans_userec(&user);
+}
+/*
+ * return value: how many items displayed */
+int
+showsignature(char *fname, int *j, SigInfo *si)
+{
+ FILE *fp;
+ char buf[256];
+ int i, lines = scr_lns;
+ char ch;
+
+ clear();
+ move(2, 0);
+ lines -= 3;
+
+ setuserfile(fname, "sig.0");
+ *j = strlen(fname) - 1;
+ si->total = 0;
+ si->max = 0;
+
+ for (ch = '1'; ch <= '9'; ch++) {
+ fname[*j] = ch;
+ if ((fp = fopen(fname, "r"))) {
+ si->total ++;
+ si->max = ch - '1';
+ if(lines > 0 && si->max >= si->show_start)
+ {
+ prints(ANSI_COLOR(36) "【 簽名檔.%c 】" ANSI_RESET "\n", ch);
+ lines--;
+ if(lines > MAX_SIGLINES/2)
+ si->show_max = si->max;
+ for (i = 0; lines > 0 && i < MAX_SIGLINES &&
+ fgets(buf, sizeof(buf), fp) != NULL; i++)
+ outs(buf), lines--;
+ }
+ fclose(fp);
+ }
+ }
+ if(lines > 0)
+ si->show_max = si->max;
+ return si->max;
+}
+
+int
+u_editsig(void)
+{
+ int aborted;
+ char ans[4];
+ int j, browsing = 0;
+ char genbuf[MAXPATHLEN];
+ SigInfo si;
+
+ memset(&si, 0, sizeof(si));
+
+browse_sigs:
+
+ showsignature(genbuf, &j, &si);
+ getdata(0, 0, (browsing || (si.max > si.show_max)) ?
+ "簽名檔 (E)編輯 (D)刪除 (N)翻頁 (Q)取消?[Q] ":
+ "簽名檔 (E)編輯 (D)刪除 (Q)取消?[Q] ",
+ ans, sizeof(ans), LCECHO);
+
+ if(ans[0] == 'n')
+ {
+ si.show_start = si.show_max + 1;
+ if(si.show_start > si.max)
+ si.show_start = 0;
+ browsing = 1;
+ goto browse_sigs;
+ }
+
+ aborted = 0;
+ if (ans[0] == 'd')
+ aborted = 1;
+ else if (ans[0] == 'e')
+ aborted = 2;
+
+ if (aborted) {
+ if (!getdata(1, 0, "請選擇簽名檔(1-9)?[1] ", ans, sizeof(ans), DOECHO))
+ ans[0] = '1';
+ if (ans[0] >= '1' && ans[0] <= '9') {
+ genbuf[j] = ans[0];
+ if (aborted == 1) {
+ unlink(genbuf);
+ outs(msg_del_ok);
+ } else {
+ setutmpmode(EDITSIG);
+ aborted = vedit(genbuf, NA, NULL);
+ if (aborted != -1)
+ outs("簽名檔更新完畢");
+ }
+ }
+ pressanykey();
+ }
+ return 0;
+}
+
+int
+u_editplan(void)
+{
+ char genbuf[200];
+
+ getdata(b_lines - 1, 0, "名片 (D)刪除 (E)編輯 [Q]取消?[Q] ",
+ genbuf, 3, LCECHO);
+
+ if (genbuf[0] == 'e') {
+ int aborted;
+
+ setutmpmode(EDITPLAN);
+ setuserfile(genbuf, fn_plans);
+ aborted = vedit(genbuf, NA, NULL);
+ if (aborted != -1)
+ outs("名片更新完畢");
+ pressanykey();
+ return 0;
+ } else if (genbuf[0] == 'd') {
+ setuserfile(genbuf, fn_plans);
+ unlink(genbuf);
+ outmsg("名片刪除完畢");
+ }
+ return 0;
+}
+
+int
+u_editcalendar(void)
+{
+ char genbuf[200];
+
+ getdata(b_lines - 1, 0, "行事曆 (D)刪除 (E)編輯 (H)說明 [Q]取消?[Q] ",
+ genbuf, 3, LCECHO);
+
+ if (genbuf[0] == 'e') {
+ int aborted;
+
+ setutmpmode(EDITPLAN);
+ sethomefile(genbuf, cuser.userid, "calendar");
+ aborted = vedit(genbuf, NA, NULL);
+ if (aborted != -1)
+ vmsg("行事曆更新完畢");
+ return 0;
+ } else if (genbuf[0] == 'd') {
+ sethomefile(genbuf, cuser.userid, "calendar");
+ unlink(genbuf);
+ vmsg("行事曆刪除完畢");
+ } else if (genbuf[0] == 'h') {
+ move(1, 0);
+ clrtoline(b_lines);
+ move(3, 0);
+ prints("行事曆格式說明:\n編輯時以一行為單位,如:\n\n# 井號開頭的是註解\n2006/05/04 red 上批踢踢!\n\n其中的 red 是指表示的顏色。");
+ pressanykey();
+ }
+ return 0;
+}
+
+/* 使用者填寫註冊表格 */
+static void
+getfield(int line, const char *info, const char *desc, char *buf, int len)
+{
+ char prompt[STRLEN];
+ char genbuf[200];
+
+ move(line, 2);
+ prints("原先設定:%-30.30s (%s)", buf, info);
+ snprintf(prompt, sizeof(prompt), "%s:", desc);
+ if (getdata_str(line + 1, 2, prompt, genbuf, len, DOECHO, buf))
+ strcpy(buf, genbuf);
+ move(line, 2);
+ prints("%s:%s", desc, buf);
+ clrtoeol();
+}
+
+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)
+{
+ FILE *fp;
+ char buf[128], *c;
+ if (!strstr(email, "@"))
+ return 0;
+ for (c = strstr(email, "@"); *c != 0; ++c)
+ if ('A' <= *c && *c <= 'Z')
+ *c += 32;
+
+ if ((fp = fopen("etc/banemail", "r"))) {
+ while (fgets(buf, sizeof(buf), fp)) {
+ if (buf[0] == '#')
+ continue;
+ chomp(buf);
+ if (buf[0] == 'A' && strcasecmp(&buf[1], email) == 0)
+ return 0;
+ if (buf[0] == 'P' && strcasestr(email, &buf[1]))
+ return 0;
+ if (buf[0] == 'S' && strcasecmp(strstr(email, "@") + 1, &buf[1]) == 0)
+ return 0;
+ }
+ fclose(fp);
+ }
+ return 1;
+}
+
+static void
+toregister(char *email, char *genbuf, char *phone, char *career,
+ char *rname, char *addr, char *mobile)
+{
+ FILE *fn;
+ char buf[128];
+
+ 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(17, 0);
+ outs("指定的手機號碼不合法,"
+ "若您無手機門號請選擇其他方式認證");
+ }
+
+ }
+#endif
+ else if (isvalidemail(email)) {
+ char yn[3];
+ getdata(16, 0, "請再次確認您輸入的 E-Mail 位置正確嘛? [y/N]",
+ yn, sizeof(yn), LCECHO);
+ if (yn[0] == 'Y' || yn[0] == 'y')
+ break;
+ } else {
+ move(17, 0);
+ outs("指定的 E-Mail 不合法, 若您無 E-Mail 請輸入 x 由站長手動認證\n");
+ outs("但注意手動認證通常會花上數天的時間。\n");
+ }
+ }
+ 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);
+ }
+ } else {
+ if (phone != NULL) {
+#ifdef HAVEMOBILE
+ if (strcmp(email, "m") == 0 || strcmp(email, "M") == 0)
+ sprintf(genbuf, sizeof(genbuf),
+ "%s:%s:<Mobile>", phone, career);
+ else
+#endif
+ snprintf(genbuf, sizeof(genbuf),
+ "%s:%s:<Email>", phone, career);
+ strlcpy(cuser.justify, genbuf, sizeof(cuser.justify));
+ sethomefile(buf, cuser.userid, "justify");
+ }
+ 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;
+}
+
+static 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;
+}
+
+static char *isvalidaddr(char *addr)
+{
+ const char *rejectstr[] =
+ {"地球", "銀河", "火星", NULL};
+
+ if (!removespace(addr) || addr[0] > 0 || 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));
+ snprintf(mobile, sizeof(mobile), "0%09d", cuser.mobile);
+ if (cuser.month == 0 && cuser.day && 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;
+ }
+
+ if (cuser.year != 0 && /* 已經第一次填過了~ ^^" */
+ strcmp(cuser.email, "x") != 0 && /* 上次手動認證失敗 */
+ strcmp(cuser.email, "X") != 0) {
+ clear();
+ stand_title("EMail認證");
+ move(2, 0);
+ prints("%s(%s) 您好,請輸入您的認證碼。\n"
+ "或您可以輸入 x 來重新填寫 E-Mail 或改由站長手動認證\n",
+ cuser.userid, cuser.nickname);
+ inregcode[0] = 0;
+
+ do{
+ getdata(10, 0, "您的認證碼:",
+ inregcode, sizeof(inregcode), DOECHO);
+ if( strcmp(inregcode, "x") == 0 || strcmp(inregcode, "X") == 0 )
+ break;
+ if( inregcode[0] != 'v' || inregcode[1] != '6' ) {
+ /* old regcode */
+ vmsg("您輸入的認證碼因系統昇級已失效,"
+ "請輸入 x 重填一次 E-Mail");
+ } else if( strlen(inregcode) != 13 )
+ vmsg("認證碼輸入不完全,應該一共有十三碼。");
+ else
+ break;
+ } while( 1 );
+
+ if (strcmp(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 (strcmp(inregcode, "x") != 0 &&
+ strcmp(inregcode, "X") != 0) {
+ vmsg("認證碼錯誤!");
+ } else {
+ toregister(email, genbuf, 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);
+ }
+ while (1) {
+ getfield(11, "含" 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(13, "不加-(), 包括長途區號", "連絡電話", phone, 11);
+ if( (errcode = isvalidphone(phone)) == NULL )
+ break;
+ else
+ vmsg(errcode);
+ }
+ getfield(15, "只輸入數字 如:0912345678 (可不填)",
+ "手機號碼", mobile, 20);
+ while (1) {
+ getfield(17, "西元/月月/日日 如: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(19, "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, genbuf, phone, career, rname, addr, mobile);
+
+ return FULLUPDATE;
+}
+
+/* 列出所有註冊使用者 */
+static int usercounter, totalusers;
+static unsigned short u_list_special;
+
+static int
+u_list_CB(int num, userec_t * uentp)
+{
+ static int i;
+ char permstr[8], *ptr;
+ register int level;
+
+ if (uentp == NULL) {
+ move(2, 0);
+ clrtoeol();
+ prints(ANSI_COLOR(7) " 使用者代號 %-25s 上站 文章 %s "
+ "最近光臨日期 " ANSI_COLOR(0) "\n",
+ "綽號暱稱",
+ HasUserPerm(PERM_SEEULEVELS) ? "等級" : "");
+ i = 3;
+ return 0;
+ }
+ if (bad_user_id(uentp->userid))
+ return 0;
+
+ if ((uentp->userlevel & ~(u_list_special)) == 0)
+ return 0;
+
+ if (i == b_lines) {
+ prints(ANSI_COLOR(34;46) " 已顯示 %d/%d 人(%d%%) " ANSI_COLOR(31;47) " "
+ "(Space)" ANSI_COLOR(30) " 看下一頁 " ANSI_COLOR(31) "(Q)" ANSI_COLOR(30) " 離開 " ANSI_RESET,
+ usercounter, totalusers, usercounter * 100 / totalusers);
+ i = igetch();
+ if (i == 'q' || i == 'Q')
+ return QUIT;
+ i = 3;
+ }
+ if (i == 3) {
+ move(3, 0);
+ clrtobot();
+ }
+ level = uentp->userlevel;
+ strlcpy(permstr, "----", 8);
+ if (level & PERM_SYSOP)
+ permstr[0] = 'S';
+ else if (level & PERM_ACCOUNTS)
+ permstr[0] = 'A';
+ else if (level & PERM_SYSOPHIDE)
+ permstr[0] = 'p';
+
+ if (level & (PERM_BOARD))
+ permstr[1] = 'B';
+ else if (level & (PERM_BM))
+ permstr[1] = 'b';
+
+ if (level & (PERM_XEMPT))
+ permstr[2] = 'X';
+ else if (level & (PERM_LOGINOK))
+ permstr[2] = 'R';
+
+ if (level & (PERM_CLOAK | PERM_SEECLOAK))
+ permstr[3] = 'C';
+
+ ptr = (char *)Cdate(&uentp->lastlogin);
+ ptr[18] = '\0';
+ prints("%-14s %-27.27s%5d %5d %s %s\n",
+ uentp->userid,
+ uentp->nickname,
+ uentp->numlogins, uentp->numposts,
+ HasUserPerm(PERM_SEEULEVELS) ? permstr : "", ptr);
+ usercounter++;
+ i++;
+ return 0;
+}
+
+int
+u_list(void)
+{
+ char genbuf[3];
+
+ setutmpmode(LAUSERS);
+ u_list_special = usercounter = 0;
+ totalusers = SHM->number;
+ if (HasUserPerm(PERM_SEEULEVELS)) {
+ getdata(b_lines - 1, 0, "觀看 [1]特殊等級 (2)全部?",
+ genbuf, 3, DOECHO);
+ if (genbuf[0] != '2')
+ u_list_special = PERM_BASIC | PERM_CHAT | PERM_PAGE | PERM_POST | PERM_LOGINOK | PERM_BM;
+ }
+ u_list_CB(0, NULL);
+ if (passwd_apply(u_list_CB) == -1) {
+ outs(msg_nobody);
+ return XEASY;
+ }
+ move(b_lines, 0);
+ clrtoeol();
+ prints(ANSI_COLOR(34;46) " 已顯示 %d/%d 的使用者(系統容量無上限) "
+ ANSI_COLOR(31;47) " (請按任意鍵繼續) " ANSI_RESET, usercounter, totalusers);
+ igetch();
+ return 0;
+}
+
+#ifdef DBCSAWARE
+
+/* detect if user is using an evil client that sends double
+ * keys for DBCS data.
+ * True if client is evil.
+ */
+
+int u_detectDBCSAwareEvilClient()
+{
+ int ret = 0;
+
+ clear();
+ move(1, 0);
+ outs(ANSI_RESET
+ "* 本站支援自動偵測中文字的移動與編輯,但有些連線程式(如xxMan)\n"
+ " 會自行處理、多送按鍵,於是便會造成" ANSI_COLOR(1;37)
+ "一次移動兩個中文字的現象。" ANSI_RESET "\n\n"
+ "* 讓連線程式處理移動容易造成許\多"
+ "顯示及移動上的問題,所以我們建議您\n"
+ " 關閉該程式上的此項設定(通常叫「偵測(全型或雙位元組)中文」),\n"
+ " 讓 BBS 系統可以正確的控制你的畫面。\n\n"
+ ANSI_COLOR(1;33)
+ "* 如果您看不懂上面的說明也無所謂,我們會自動偵測適合您的設定。"
+ ANSI_RESET "\n"
+ " 請在設定好連線程式成您偏好的模式後按" ANSI_COLOR(1;33)
+ "一下" ANSI_RESET "您鍵盤上的" ANSI_COLOR(1;33)
+ "←" ANSI_RESET "\n" ANSI_COLOR(1;36)
+ " (另外左右方向鍵或寫 BS/Backspace 的倒退鍵與 Del 刪除鍵均可)\n"
+ ANSI_RESET);
+
+ /* clear buffer */
+ while(num_in_buf() > 0)
+ igetch();
+
+ while (1)
+ {
+ int ch = 0;
+
+ move(12, 0);
+ outs("這是偵測區,您的游標會出現在"
+ ANSI_COLOR(7) "這裡" ANSI_RESET);
+ move(12, 15*2);
+ ch = igetch();
+ if(ch != KEY_LEFT && ch != KEY_RIGHT &&
+ ch != Ctrl('H') && ch != '\177')
+ {
+ move(14, 0);
+ outs("請按一下上面指定的鍵! 你按到別的鍵了!");
+ } else {
+ move(16, 0);
+ /* Actually you may also use num_in_buf here. those clients
+ * usually sends doubled keys together in one packet.
+ * However when I was writing this, a bug (existed for more than 3
+ * years) of num_in_buf forced me to write new wait_input.
+ * Anyway it is fixed now.
+ */
+ if(wait_input(0.1, 1))
+ // if(igetch() == ch)
+ // if (num_in_buf() > 0)
+ {
+ /* evil dbcs aware client */
+ outs("偵測到您的連線程式會自行處理游標移動。\n\n"
+ // "若日後因此造成瀏覽上的問題本站恕不處理。\n\n"
+ "已設定為「讓您的連線程式處理游標移動」\n");
+ ret = 1;
+ } else {
+ /* good non-dbcs aware client */
+ outs("您的連線程式似乎不會多送按鍵,"
+ "這樣 BBS 可以更精準的控制畫面。\n\n"
+ "已設定為「讓 BBS 伺服器直接處理游標移動」\n");
+ ret = 0;
+ }
+ outs( "\n若想改變設定請至 個人設定區 → 個人化設定 → \n"
+ " 調整「自動偵測雙位元字集(如全型中文)」之設定");
+ while(num_in_buf())
+ igetch();
+ break;
+ }
+ }
+ pressanykey();
+ return ret;
+}
+#endif
+
+/* vim:sw=4
+ */
diff --git a/pttbbs/mbbsd/var.c b/pttbbs/mbbsd/var.c
new file mode 100644
index 00000000..39c87442
--- /dev/null
+++ b/pttbbs/mbbsd/var.c
@@ -0,0 +1,654 @@
+/* $Id$ */
+#define INCLUDE_VAR_H
+#include "bbs.h"
+
+const char * const str_permid[] = {
+ "基本權力", /* PERM_BASIC */
+ "進入聊天室", /* PERM_CHAT */
+ "找人聊天", /* PERM_PAGE */
+ "發表文章", /* PERM_POST */
+ "註冊程序認證", /* PERM_LOGINOK */
+ "信件無上限", /* PERM_MAILLIMIT */
+ "隱身術", /* PERM_CLOAK */
+ "看見忍者", /* PERM_SEECLOAK */
+ "永久保留帳號", /* PERM_XEMPT */
+ "站長隱身術", /* PERM_DENYPOST */
+ "板主", /* PERM_BM */
+ "帳號總管", /* PERM_ACCOUNTS */
+ "聊天室總管", /* PERM_CHATCLOAK */
+ "看板總管", /* PERM_BOARD */
+ "站長", /* PERM_SYSOP */
+ "BBSADM", /* PERM_POSTMARK */
+ "不列入排行榜", /* PERM_NOTOP */
+ "違法通緝中", /* PERM_VIOLATELAW */
+#ifdef PLAY_ANGEL
+ "可擔任小天使", /* PERM_ANGEL */
+#else
+ "未使用",
+#endif
+ "不允許\認證碼註冊", /* PERM_NOREGCODE */
+ "視覺站長", /* PERM_VIEWSYSOP */
+ "觀察使用者行蹤", /* PERM_LOGUSER */
+ "禠奪公權", /* PERM_NOCITIZEN */
+ "群組長", /* PERM_SYSSUPERSUBOP */
+ "帳號審核組", /* PERM_ACCTREG */
+ "程式組", /* PERM_PRG */
+ "活動組", /* PERM_ACTION */
+ "美工組", /* PERM_PAINT */
+ "警察總管", /* PERM_POLICE_MAN */
+ "小組長", /* PERM_SYSSUBOP */
+ "退休站長", /* PERM_OLDSYSOP */
+ "警察" /* PERM_POLICE */
+};
+
+const char * const str_permboard[] = {
+ "不可 Zap", /* BRD_NOZAP */
+ "不列入統計", /* BRD_NOCOUNT */
+ "不轉信", /* BRD_NOTRAN */
+ "群組板", /* BRD_GROUPBOARD */
+ "隱藏板", /* BRD_HIDE */
+ "限制(不需設定)", /* BRD_POSTMASK */
+ "匿名板", /* BRD_ANONYMOUS */
+ "預設匿名板", /* BRD_DEFAULTANONYMOUS */
+ "違法改進中看板", /* BRD_BAD */
+ "連署專用看板", /* BRD_VOTEBOARD */
+ "已警告要廢除", /* BRD_WARNEL */
+ "熱門看板群組", /* BRD_TOP */
+ "不可推薦", /* BRD_NORECOMMEND */
+ "布落格", /* BRD_BLOG */
+ "板主設定列入記錄", /* BRD_BMCOUNT */
+ "連結看板", /* BRD_SYMBOLIC */
+ "不可噓", /* BRD_NOBOO */
+ "預設 Local Save", /* BRD_LOCALSAVE */
+ "限板友發文", /* BRD_RESTRICTEDPOST */
+ "Guest可以發表", /* BRD_GUESTPOST */
+#ifdef USE_COOLDOWN
+ "冷靜", /* BRD_COOLDOWN */
+#else
+ "冷靜(本站無效)", /* BRD_COOLDOWN */
+#endif
+#ifdef USE_AUTOCPLOG
+ "自動留轉錄記錄", /* BRD_CPLOG */
+#else
+ "轉錄記錄(本站無效)", /* BRD_CPLOG */
+#endif
+ "禁止快速推文", /* BRD_NOFASTRECMD */
+ "推文記錄 IP", /* BRD_IPLOGRECMD */
+ "十八禁", /* BRD_OVER18 */
+ "沒想到",
+ "沒想到",
+ "沒想到",
+ "沒想到",
+ "沒想到",
+ "沒想到",
+ "沒想到",
+};
+
+int usernum;
+int currmode = 0;
+int currsrmode = 0;
+int curredit = 0;
+int paste_level;
+int currbid;
+char quote_file[80] = "\0";
+char quote_user[80] = "\0";
+char currtitle[TTLEN + 1] = "\0";
+char currauthor[IDLEN + 2] = "\0";
+const char *currboard = "\0";
+char currBM[IDLEN * 3 + 10];
+const char reset_color[4] = ANSI_RESET;
+char margs[64] = "\0"; /* main argv list */
+pid_t currpid; /* current process ID */
+time4_t login_start_time;
+time4_t start_time;
+userec_t cuser; /* current user structure */
+crosspost_t postrecord; /* anti cross post */
+unsigned int currbrdattr;
+unsigned int currstat;
+unsigned char currfmode; /* current file mode */
+
+/* global string variables */
+/* filename */
+
+char * const fn_passwd = FN_PASSWD;
+char * const fn_board = FN_BOARD;
+char * const fn_register = "register.new";
+char * const fn_note_ans = FN_NOTE_ANS;
+const char * const fn_plans = "plans";
+const char * const fn_writelog = "writelog";
+const char * const fn_talklog = "talklog";
+const char * const fn_overrides = FN_OVERRIDES;
+const char * const fn_reject = FN_REJECT;
+const char * const fn_canvote = FN_CANVOTE;
+const char * const fn_notes = "notes";
+const char * const fn_water = FN_WATER;
+const char * const fn_visable = FN_VISABLE;
+const char * const fn_mandex = "/.Names";
+const char * const fn_boardlisthelp = FN_BRDLISTHELP;
+const char * const fn_boardhelp = FN_BOARDHELP;
+
+/* are descript in userec.loginview */
+
+char * const loginview_file[NUMVIEWFILE][2] = {
+ {FN_NOTE_ANS, "酸甜苦辣流言板"},
+ {FN_TOPSONG, "點歌排行榜"},
+ {"etc/topusr", "十大排行榜"},
+ {"etc/topusr100", "百大排行榜"},
+ {"etc/weather.tmp", "天氣快報"},
+ {"etc/stock.tmp", "股市快報"},
+ {"etc/day", "今日十大話題"},
+ {"etc/week", "一週五十大話題"},
+ {"etc/today", "今天上站人次"},
+ {"etc/yesterday", "昨日上站人次"},
+ {"etc/history", "歷史上的今天"},
+ {"etc/topboardman", "精華區排行榜"},
+ {"etc/topboard.tmp", "看板人氣排行榜"}
+};
+
+/* message */
+char * const msg_seperator = MSG_SEPERATOR;
+char * const msg_shortulist = MSG_SHORTULIST;
+
+char * const msg_cancel = MSG_CANCEL;
+char * const msg_usr_left = MSG_USR_LEFT;
+char * const msg_nobody = MSG_NOBODY;
+
+char * const msg_sure_ny = MSG_SURE_NY;
+char * const msg_sure_yn = MSG_SURE_YN;
+
+char * const msg_bid = MSG_BID;
+char * const msg_uid = MSG_UID;
+
+char * const msg_del_ok = MSG_DEL_OK;
+char * const msg_del_ny = MSG_DEL_NY;
+
+char * const msg_fwd_ok = MSG_FWD_OK;
+char * const msg_fwd_err1 = MSG_FWD_ERR1;
+char * const msg_fwd_err2 = MSG_FWD_ERR2;
+
+char * const err_board_update = ERR_BOARD_UPDATE;
+char * const err_bid = ERR_BID;
+char * const err_uid = ERR_UID;
+char * const err_filename = ERR_FILENAME;
+
+char * const str_mail_address = "." BBSUSER "@" MYHOSTNAME;
+char * const str_new = "new";
+char * const str_reply = "Re: ";
+char * const str_space = " \t\n\r";
+char * const str_sysop = "SYSOP";
+char * const str_author1 = STR_AUTHOR1;
+char * const str_author2 = STR_AUTHOR2;
+char * const str_post1 = STR_POST1;
+char * const str_post2 = STR_POST2;
+char * const BBSName = BBSNAME;
+
+/* #define MAX_MODES 78 */
+/* MAX_MODES is defined in common.h */
+
+char * const ModeTypeTable[MAX_MODES] = {
+ "發呆", /* IDLE */
+ "主選單", /* MMENU */
+ "系統維護", /* ADMIN */
+ "郵件選單", /* MAIL */
+ "交談選單", /* TMENU */
+ "使用者選單", /* UMENU */
+ "XYZ 選單", /* XMENU */
+ "分類看板", /* CLASS */
+ "Play選單", /* PMENU */
+ "編特別名單", /* NMENU */
+ "Ptt量販店", /* PSALE */
+ "發表文章", /* POSTING */
+ "看板列表", /* READBRD */
+ "閱\讀文章", /* READING */
+ "新文章列表", /* READNEW */
+ "選擇看板", /* SELECT */
+ "讀信", /* RMAIL */
+ "寫信", /* SMAIL */
+ "聊天室", /* CHATING */
+ "其他", /* XMODE */
+ "尋找好友", /* FRIEND */
+ "上線使用者", /* LAUSERS */
+ "使用者名單", /* LUSERS */
+ "追蹤站友", /* MONITOR */
+ "呼叫", /* PAGE */
+ "查詢", /* TQUERY */
+ "交談", /* TALK */
+ "編名片檔", /* EDITPLAN */
+ "編簽名檔", /* EDITSIG */
+ "投票中", /* VOTING */
+ "設定資料", /* XINFO */
+ "寄給站長", /* MSYSOP */
+ "汪汪汪", /* WWW */
+ "打大老二", /* BIG2 */
+ "回應", /* REPLY */
+ "被水球打中", /* HIT */
+ "水球準備中", /* DBACK */
+ "筆記本", /* NOTE */
+ "編輯文章", /* EDITING */
+ "發系統通告", /* MAILALL */
+ "摸兩圈", /* MJ */
+ "電腦擇友", /* P_FRIEND */
+ "上站途中", /* LOGIN */
+ "查字典", /* DICT */
+ "打橋牌", /* BRIDGE */
+ "找檔案", /* ARCHIE */
+ "打地鼠", /* GOPHER */
+ "看News", /* NEWS */
+ "情書產生器", /* LOVE */
+ "編輯輔助器", /* EDITEXP */
+ "申請IP位址", /* IPREG */
+ "網管辦公中", /* NetAdm */
+ "虛擬實業坊", /* DRINK */
+ "計算機", /* CAL */
+ "編輯座右銘", /* PROVERB */
+ "公佈欄", /* ANNOUNCE */
+ "刻流言板", /* EDNOTE */
+ "英漢翻譯機", /* CDICT */
+ "檢視自己物品", /* LOBJ */
+ "點歌", /* OSONG */
+ "正在玩小雞", /* CHICKEN */
+ "玩彩券", /* TICKET */
+ "猜數字", /* GUESSNUM */
+ "遊樂場", /* AMUSE */
+ "黑白棋", /* OTHELLO */
+ "玩骰子", /* DICE */
+ "發票對獎", /* VICE */
+ "逼逼摳ing", /* BBCALL */
+ "繳罰單", /* CROSSPOST */
+ "五子棋", /* M_FIVE */
+ "21點ing", /* JACK_CARD */
+ "10點半ing", /* TENHALF */
+ "超級九十九", /* CARD_99 */
+ "火車查詢", /* RAIL_WAY */
+ "搜尋選單", /* SREG */
+ "下象棋", /* CHC */
+ "下暗棋", /* DARK */
+ "NBA大猜測", /* TMPJACK */
+ "Ptt查榜系統", /* JCEE */
+ "重編文章", /* REEDIT */
+ "部落格", /* BLOGGING */
+ "看棋", /* CHESSWATCHING */
+ "下圍棋", /* UMODE_GO */
+ "[系統錯誤]", /* DEBUGSLEEPING */
+ "連六棋", /* UMODE_CONN6 */
+ "", /* for future usage */
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+};
+
+/* term.c */
+int b_lines = 23; // bottom line of screen
+int t_lines = 24; // term lines
+int p_lines = 20;
+int t_columns = 80;
+
+/* refer to ansi.h for *len */
+char * const strtstandout = ANSI_COLOR(7);
+const int strtstandoutlen = 4;
+char * const endstandout = ANSI_RESET;
+const int endstandoutlen = 3;
+char * const clearbuf = ESC_STR "[H" ESC_STR "[J";
+const int clearbuflen = 6;
+char * const cleolbuf = ESC_STR "[K";
+const int cleolbuflen = 3;
+char * const scrollrev = ESC_STR "M";
+const int scrollrevlen = 2;
+int automargins = 1;
+
+/* io.c */
+time4_t now;
+int KEY_ESC_arg;
+int watermode = -1;
+int wmofo = NOTREPLYING;
+/*
+ * WATERMODE(WATER_ORIG) | WATERMODE(WATER_NEW):
+ * ????????????????????
+ * Ptt 水球回顧 (FIXME: guessed by scw)
+ * watermode = -1 沒在回水球
+ * = 0 在回上一顆水球 (Ctrl-R)
+ * > 0 在回前 n 顆水球 (Ctrl-R Ctrl-R)
+ *
+ * WATERMODE(WATER_OFO) by in2
+ * wmofo = NOTREPLYING 沒在回水球
+ * = REPLYING 正在回水球
+ * = RECVINREPLYING 回水球間又接到水球
+ *
+ * wmofo >=0 時收到水球將只顯示, 不會到water[]裡,
+ * 待回完水球的時候一次寫入.
+ */
+
+
+/* cache.c */
+int numboards = -1;
+SHM_t *SHM;
+boardheader_t *bcache;
+userinfo_t *currutmp;
+
+/* read.c */
+int TagNum; /* tag's number */
+int TagBoard = -1; /* TagBoard = 0 : user's mailbox */
+ /* TagBoard > 0 : bid where last taged */
+char currdirect[64];
+
+/* edit.c */
+char save_title[STRLEN];
+
+/* bbs.c */
+time4_t board_visit_time = 0;
+char real_name[IDLEN + 1];
+char local_article;
+
+/* mbbsd.c */
+char raw_connection = 0;
+char fromhost[STRLEN] = "\0";
+char water_usies = 0;
+FILE *fp_writelog = NULL;
+water_t *water, *swater[6], *water_which;
+char over18 = 0;
+
+/* chc_play.c */
+
+/* user.c */
+#ifdef CHESSCOUNTRY
+int user_query_mode;
+/*
+ * user_query_mode = 0 simple data
+ * = 1 gomoku chess country data
+ * = 2 chc chess country data
+ * = 3 go chess country data
+ */
+#endif /* defined(CHESSCOUNTRY) */
+
+/* screen.c */
+#define scr_lns t_lines
+#define scr_cols ANSILINELEN
+screenline_t *big_picture = NULL;
+char roll = 0;
+char msg_occupied = 0;
+
+/* gomo.c */
+const char * const bw_chess[] = {"○", "●", "。", "•"};
+const unsigned char * const pat_gomoku /* [1954] */ =(unsigned char*)
+ /* 0 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 16 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x44\x55\xcc\x00\x00\x00\x00"
+ /* 32 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x33\x00\x44\x00\x33\x00\x00\x00"
+ /* 48 */ "\x00\x22\x00\x55\x00\x22\x00\x00\x00\x44\x33\x66\x55\xcc\x33\x66"
+ /* 64 */ "\x55\xcc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00"
+ /* 80 */ "\x55\x00\x55\x00\x05\x00\x55\x02\x46\x00\xaa\x00\x00\x55\x00\x55"
+ /* 96 */ "\x00\x05\x00\x55\x00\x05\x00\x55\x00\x00\x44\xcc\x44\xcc\x05\xbb"
+ /* 112 */ "\x44\xcc\x05\xbb\x44\xcc\x05\xbb\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 128 */ "\x00\x00\x33\x00\x00\x00\x44\x00\x00\x00\x00\x00\x33\x00\x44\x00"
+ /* 144 */ "\x33\x22\x66\x00\x55\x55\xcc\x00\x33\x00\x00\x00\x00\x22\x00\x55"
+ /* 160 */ "\x00\x22\x00\x55\x00\x02\x00\x05\x00\x22\x00\x00\x33\x44\x33\x66"
+ /* 176 */ "\x55\xcc\x33\x66\x55\xcc\x33\x46\x05\xbb\x33\x66\x55\xcc\x00\x00"
+ /* 192 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x44\x00\x00\x00"
+ /* 208 */ "\x33\x00\x00\x22\x55\x22\x55\x02\x05\x22\x55\x02\x46\x22\xaa\x55"
+ /* 224 */ "\xcc\x22\x55\x02\x46\x22\xaa\x00\x22\x55\x22\x55\x02\x05\x22\x55"
+ /* 240 */ "\x02\x05\x22\x55\x02\x05\x22\x55\x02\x05\x22\x55\x02\x44\x66\xcc"
+ /* 256 */ "\x66\xcc\x46\xbb\x66\xcc\x46\xbb\x66\xcc\x46\xbb\x66\xcc\x46\xbb"
+ /* 272 */ "\x66\xcc\x46\xbb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x33\x00"
+ /* 288 */ "\x00\x00\x44\x00\x00\x00\x33\x00\x22\x22\x66\x00\x00\x00\x00\x00"
+ /* 304 */ "\x03\x00\x44\x00\x33\x22\x66\x00\x55\x55\xcc\x00\x33\x22\x66\x00"
+ /* 320 */ "\x55\x55\xcc\x00\x03\x00\x00\x00\x00\x02\x00\x55\x00\x02\x00\x55"
+ /* 336 */ "\x00\x02\x00\x05\x00\x02\x00\x55\x00\x02\x02\x46\x00\x02\x00\x55"
+ /* 352 */ "\x55\x05\x55\x46\xaa\xcc\x55\x46\xaa\xcc\x55\x06\x5a\xbb\x55\x46"
+ /* 368 */ "\xaa\xcc\x55\x06\x5a\xbb\x55\x46\xaa\xcc\x00\x00\x00\x00\x00\x00"
+ /* 384 */ "\x00\x00\x00\x00\x03\x00\x00\x00\x44\x00\x00\x00\x33\x00\x22\x22"
+ /* 400 */ "\x66\x00\x00\x00\x55\x00\x55\x55\x05\x55\x05\x55\x05\x55\x05\x55"
+ /* 416 */ "\x46\x55\x5a\xaa\xcc\x55\x05\x55\x46\x55\x5a\xaa\xcc\x55\x05\x55"
+ /* 432 */ "\x06\x55\x0a\x55\x55\x05\x55\x05\x55\x05\x55\x05\x55\x05\x55\x05"
+ /* 448 */ "\x55\x05\x55\x05\x55\x05\x55\x05\x55\x46\x55\x05\x55\x5a\x55\x5a"
+ /* 464 */ "\xaa\xcc\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb"
+ /* 480 */ "\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb"
+ /* 496 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x33\x00\x00\x00\x44\x00"
+ /* 512 */ "\x00\x00\x33\x00\x22\x22\x66\x00\x00\x00\x55\x00\x55\x55\xcc\x00"
+ /* 528 */ "\x00\x00\x00\x00\x33\x00\x44\x00\x33\x22\x66\x00\x55\x55\xcc\x00"
+ /* 544 */ "\x33\x22\x66\x00\x55\x55\xcc\x00\x33\x02\x46\x00\x05\x05\xbb\x00"
+ /* 560 */ "\x33\x00\x00\x00\x00\x22\x00\x55\x00\x22\x00\x55\x00\x02\x00\x05"
+ /* 576 */ "\x00\x22\x00\x55\x00\x02\x02\x46\x00\x22\x00\xaa\x00\x55\x55\xcc"
+ /* 592 */ "\x00\x22\x00\x00\x33\x44\x33\x66\x55\xcc\x33\x66\x55\xcc\x33\x46"
+ /* 608 */ "\x05\xbb\x33\x66\x55\xcc\x33\x46\x05\xbb\x33\x66\x55\xcc\x33\x46"
+ /* 624 */ "\x05\xbb\x33\x66\x55\xcc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 640 */ "\x03\x00\x00\x00\x44\x00\x00\x00\x33\x00\x22\x22\x66\x00\x00\x00"
+ /* 656 */ "\x55\x00\x55\x55\xcc\x00\x00\x00\x33\x00\x00\x22\x55\x22\x55\x02"
+ /* 672 */ "\x05\x22\x55\x02\x46\x22\xaa\x55\xcc\x22\x55\x02\x46\x22\xaa\x55"
+ /* 688 */ "\xcc\x22\x55\x02\x06\x22\x5a\x05\xbb\x22\x55\x02\x46\x22\xaa\x00"
+ /* 704 */ "\x22\x55\x22\x55\x02\x05\x22\x55\x02\x05\x22\x55\x02\x05\x22\x55"
+ /* 720 */ "\x02\x05\x22\x55\x02\x46\x22\x55\x02\x5a\x22\xaa\x55\xcc\x22\x55"
+ /* 736 */ "\x02\x05\x22\x55\x02\x44\x66\xcc\x66\xcc\x46\xbb\x66\xcc\x46\xbb"
+ /* 752 */ "\x66\xcc\x46\xbb\x66\xcc\x46\xbb\x66\xcc\x46\xbb\x66\xcc\x46\xbb"
+ /* 768 */ "\x66\xcc\x46\xbb\x66\xcc\x46\xbb\x66\xcc\x46\xbb\x00\x00\x00\x00"
+ /* 784 */ "\x00\x00\x00\x00\x00\x00\x33\x00\x00\x00\x44\x00\x00\x00\x33\x00"
+ /* 800 */ "\x22\x22\x66\x00\x00\x00\x55\x00\x55\x55\xcc\x00\x00\x00\x33\x00"
+ /* 816 */ "\x22\x22\x66\x00\x00\x00\x00\x00\x03\x00\x44\x00\x33\x22\x66\x00"
+ /* 832 */ "\x55\x55\xcc\x00\x33\x22\x66\x00\x55\x55\xcc\x00\x03\x02\x46\x00"
+ /* 848 */ "\x05\x05\xbb\x00\x33\x22\x66\x00\x55\x55\xcc\x00\x03\x00\x00\x00"
+ /* 864 */ "\x00\x02\x00\x55\x00\x02\x00\x55\x00\x02\x00\x05\x00\x02\x00\x55"
+ /* 880 */ "\x00\x02\x02\x46\x00\x02\x00\xaa\x00\x55\x55\xcc\x00\x02\x00\x55"
+ /* 896 */ "\x00\x02\x02\x46\x00\x02\x00\x55\x55\x05\x55\x46\xaa\xcc\x55\x46"
+ /* 912 */ "\xaa\xcc\x55\x06\x5a\xbb\x55\x46\xaa\xcc\x55\x06\x5a\xbb\x55\x46"
+ /* 928 */ "\xaa\xcc\x55\x06\x5a\xbb\x55\x46\xaa\xcc\x55\x06\x5a\xbb\x55\x46"
+ /* 944 */ "\xaa\xcc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00"
+ /* 960 */ "\x44\x00\x00\x00\x33\x00\x22\x22\x66\x00\x00\x00\x55\x00\x55\x55"
+ /* 976 */ "\xcc\x00\x00\x00\x33\x00\x22\x22\x66\x00\x00\x00\x55\x00\x55\x55"
+ /* 992 */ "\x05\x55\x05\x55\x05\x55\x05\x55\x46\x55\x5a\xaa\xcc\x55\x05\x55"
+ /* 1008 */ "\x46\x55\x5a\xaa\xcc\x55\x05\x55\x06\x55\x0a\x5a\xbb\x55\x05\x55"
+ /* 1024 */ "\x46\x55\x5a\xaa\xcc\x55\x05\x55\x06\x55\x0a\x55\x55\x05\x55\x05"
+ /* 1040 */ "\x55\x05\x55\x05\x55\x05\x55\x05\x55\x05\x55\x05\x55\x05\x55\x05"
+ /* 1056 */ "\x55\x46\x55\x05\x55\x5a\x55\x5a\xaa\xcc\x55\x05\x55\x05\x55\x05"
+ /* 1072 */ "\x55\x46\x55\x05\x55\x5a\x55\x5a\xaa\xcc\xcc\xbb\xcc\xbb\xcc\xbb"
+ /* 1088 */ "\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb"
+ /* 1104 */ "\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb"
+ /* 1120 */ "\xcc\xbb\xcc\xbb\xcc\xbb\xcc\xbb\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 1136 */ "\x00\x00\x33\x00\x00\x00\x44\x00\x00\x00\x33\x00\x22\x22\x66\x00"
+ /* 1152 */ "\x00\x00\x55\x00\x55\x55\xcc\x00\x00\x00\x33\x00\x22\x22\x66\x00"
+ /* 1168 */ "\x00\x00\x55\x00\x55\x55\xcc\x00\x00\x00\x00\x00\x33\x00\x44\x00"
+ /* 1184 */ "\x33\x22\x66\x00\x55\x55\xcc\x00\x33\x22\x66\x00\x55\x55\xcc\x00"
+ /* 1200 */ "\x33\x02\x46\x00\x05\x05\xbb\x00\x33\x22\x66\x00\x55\x55\xcc\x00"
+ /* 1216 */ "\x33\x02\x46\x00\x05\x05\xbb\x00\x33\x00\x00\x00\x00\x22\x00\x55"
+ /* 1232 */ "\x00\x22\x00\x55\x00\x02\x00\x05\x00\x22\x00\x55\x00\x02\x02\x46"
+ /* 1248 */ "\x00\x22\x00\xaa\x00\x55\x55\xcc\x00\x22\x00\x55\x00\x02\x02\x46"
+ /* 1264 */ "\x00\x22\x00\xaa\x00\x55\x55\xcc\x00\x22\x00\x00\x03\x44\x33\x66"
+ /* 1280 */ "\x55\xcc\x33\x66\x55\xcc\x03\x46\x05\xbb\x33\x66\x55\xcc\x03\x46"
+ /* 1296 */ "\x05\xbb\x33\x66\x55\xcc\x03\x46\x05\xbb\x33\x66\x55\xcc\x03\x46"
+ /* 1312 */ "\x05\xbb\x33\x66\x55\xcc\x03\x46\x05\xbb\x33\x66\x55\xcc\x00\x00"
+ /* 1328 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x44\x00\x00\x00"
+ /* 1344 */ "\x33\x00\x22\x22\x66\x00\x00\x00\x55\x00\x55\x55\xcc\x00\x00\x00"
+ /* 1360 */ "\x33\x00\x22\x22\x66\x00\x00\x00\x55\x00\x55\x55\xcc\x00\x00\x00"
+ /* 1376 */ "\x03\x00\x00\x02\x55\x02\x55\x02\x05\x02\x55\x02\x46\x02\xaa\x55"
+ /* 1392 */ "\xcc\x02\x55\x02\x46\x02\xaa\x55\xcc\x02\x55\x02\x06\x02\x5a\x05"
+ /* 1408 */ "\xbb\x02\x55\x02\x46\x02\xaa\x55\xcc\x02\x55\x02\x06\x02\x5a\x05"
+ /* 1424 */ "\xbb\x02\x55\x02\x46\x02\xaa\x00\x02\x55\x02\x55\x02\x05\x02\x55"
+ /* 1440 */ "\x02\x05\x02\x55\x02\x05\x02\x55\x02\x05\x02\x55\x02\x46\x02\x55"
+ /* 1456 */ "\x02\x5a\x02\xaa\x55\xcc\x02\x55\x02\x05\x02\x55\x02\x46\x02\x55"
+ /* 1472 */ "\x02\x5a\x02\xaa\x55\xcc\x02\x55\x02\x05\x02\x55\x02\x05\x46\xcc"
+ /* 1488 */ "\x46\xcc\x06\xbb\x46\xcc\x06\xbb\x46\xcc\x06\xbb\x46\xcc\x06\xbb"
+ /* 1504 */ "\x46\xcc\x06\xbb\x46\xcc\x06\xbb\x46\xcc\x06\xbb\x46\xcc\x06\xbb"
+ /* 1520 */ "\x46\xcc\x06\xbb\x46\xcc\x06\xbb\x46\xcc\x06\xbb\x46\xcc\x06\xbb"
+ /* 1536 */ "\x46\xcc\x06\xbb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x33\x00"
+ /* 1552 */ "\x00\x00\x44\x00\x00\x00\x33\x00\x22\x22\x66\x00\x00\x00\x55\x00"
+ /* 1568 */ "\x55\x55\xcc\x00\x00\x00\x33\x00\x22\x22\x66\x00\x00\x00\x55\x00"
+ /* 1584 */ "\x55\x55\xcc\x00\x00\x00\x33\x00\x02\x02\x46\x00\x00\x00\x00\x00"
+ /* 1600 */ "\x03\x00\x44\x00\x33\x22\x66\x00\x55\x55\xcc\x00\x33\x22\x66\x00"
+ /* 1616 */ "\x55\x55\xcc\x00\x03\x02\x46\x00\x05\x05\xbb\x00\x33\x22\x66\x00"
+ /* 1632 */ "\x55\x55\xcc\x00\x03\x02\x46\x00\x05\x05\xbb\x00\x33\x22\x66\x00"
+ /* 1648 */ "\x55\x55\xcc\x00\x03\x00\x00\x00\x00\x02\x00\x55\x00\x02\x00\x55"
+ /* 1664 */ "\x00\x02\x00\x05\x00\x02\x00\x55\x00\x02\x02\x46\x00\x02\x00\xaa"
+ /* 1680 */ "\x00\x55\x55\xcc\x00\x02\x00\x55\x00\x02\x02\x46\x00\x02\x00\xaa"
+ /* 1696 */ "\x00\x55\x55\xcc\x00\x02\x00\x55\x00\x02\x02\x06\x00\x02\x00\x05"
+ /* 1712 */ "\x05\x05\x05\x46\x5a\xcc\x05\x46\x5a\xcc\x05\x06\x0a\xbb\x05\x46"
+ /* 1728 */ "\x5a\xcc\x05\x06\x0a\xbb\x05\x46\x5a\xcc\x05\x06\x0a\xbb\x05\x46"
+ /* 1744 */ "\x5a\xcc\x05\x06\x0a\xbb\x05\x46\x5a\xcc\x05\x06\x0a\xbb\x05\x46"
+ /* 1760 */ "\x5a\xcc\x05\x06\x0a\xbb\x05\x46\x5a\xcc\x00\x00\x00\x00\x00\x00"
+ /* 1776 */ "\x00\x00\x00\x00\x03\x00\x00\x00\x44\x00\x00\x00\x33\x00\x22\x22"
+ /* 1792 */ "\x66\x00\x00\x00\x55\x00\x55\x55\xcc\x00\x00\x00\x33\x00\x22\x22"
+ /* 1808 */ "\x66\x00\x00\x00\x55\x00\x55\x55\xcc\x00\x00\x00\x03\x00\x02\x02"
+ /* 1824 */ "\x46\x00\x00\x00\x05\x00\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05"
+ /* 1840 */ "\x46\x05\x5a\x5a\xcc\x05\x05\x05\x46\x05\x5a\x5a\xcc\x05\x05\x05"
+ /* 1856 */ "\x06\x05\x0a\x0a\xbb\x05\x05\x05\x46\x05\x5a\x5a\xcc\x05\x05\x05"
+ /* 1872 */ "\x06\x05\x0a\x0a\xbb\x05\x05\x05\x46\x05\x5a\x5a\xcc\x05\x05\x05"
+ /* 1888 */ "\x06\x05\x0a\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05"
+ /* 1904 */ "\x05\x05\x05\x05\x05\x05\x05\x05\x05\x46\x05\x05\x05\x5a\x05\x5a"
+ /* 1920 */ "\x5a\xcc\x05\x05\x05\x05\x05\x05\x05\x46\x05\x05\x05\x5a\x05\x5a"
+ /* 1936 */ "\x5a\xcc\x05\x05\x05\x05\x05\x05\x05\x06\x05\x05\x05\x0a\x05\x0a"
+ /* 1952 */ "\x0a";
+
+const unsigned char * const adv_gomoku /* [978] */ = (unsigned char*)
+ /* 0 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 16 */ "\x00\x00\x00\x00\xa0\x00\xa0\x00\x04\x00\x04\x00\x00\xd0\x00\xd0"
+ /* 32 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 48 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 64 */ "\x00\x70\x00\x00\x00\x00\xa0\x00\xa1\x00\x00\x00\xa0\x00\x04\x00"
+ /* 80 */ "\x04\x00\x00\x00\x04\x00\xd0\xd0\x00\xd0\x00\xd0\x00\xd0\x00\x00"
+ /* 96 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x70\x08\x08\x00\x08\x00\x08\x00"
+ /* 112 */ "\x08\x00\x08\x00\x40\x40\x00\x40\x00\x40\x00\x40\x00\x40\x00\x00"
+ /* 128 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x70"
+ /* 144 */ "\x00\x00\x00\x70\x21\x00\x00\x00\x00\x00\xa1\x00\x00\x00\xa1\x00"
+ /* 160 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 176 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 192 */ "\x00\x00\x00\x00\x00\x00\x70\x21\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 208 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 224 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 240 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x70\x00\x00"
+ /* 256 */ "\x00\x70\x21\x00\x00\x00\x00\x00\x00\x00\xa0\x00\xa1\x00\x00\x00"
+ /* 272 */ "\xa1\x00\x00\x00\xa0\x00\x00\x00\xa0\x00\x04\x00\x04\x00\x00\x00"
+ /* 288 */ "\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\xd0\xd0\x00\xd0\x00\xd0"
+ /* 304 */ "\x00\xd0\x00\xd0\x00\xd0\x00\xd0\x00\xd0\x00\x00\x00\x00\x00\x00"
+ /* 320 */ "\x00\x00\x00\x00\x70\x21\x00\x00\x00\x00\x00\x00\x70\x08\x08\x00"
+ /* 336 */ "\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00"
+ /* 352 */ "\x40\x40\x00\x40\x00\x40\x00\x40\x00\x40\x00\x40\x00\x40\x00\x40"
+ /* 368 */ "\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 384 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x70\x00\x00\x00\x70"
+ /* 400 */ "\x21\x00\x00\x00\x00\x00\x00\x70\x21\x00\x00\x00\x00\x00\xa1\x00"
+ /* 416 */ "\x00\x00\xa1\x00\x00\x00\x00\x00\x00\x00\xa1\x00\x00\x00\x00\x00"
+ /* 432 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 448 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 464 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 480 */ "\x00\x00\x70\x21\x00\x00\x00\x00\x00\x00\x70\x21\x00\x00\x00\x00"
+ /* 496 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 512 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 528 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 544 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 560 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x70\x00\x00\x00\x70\x21\x00"
+ /* 576 */ "\x00\x00\x00\x00\x00\x70\x21\x00\x00\x00\x00\x00\x00\x00\xa0\x00"
+ /* 592 */ "\xa1\x00\x00\x00\xa1\x00\x00\x00\xa0\x00\x00\x00\xa1\x00\x00\x00"
+ /* 608 */ "\xa0\x00\x00\x00\xa0\x00\x04\x00\x04\x00\x00\x00\x04\x00\x00\x00"
+ /* 624 */ "\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\xd0"
+ /* 640 */ "\x00\xd0\x00\x00\x00\xd0\x00\x00\x00\xd0\x00\x00\x00\xd0\x00\x00"
+ /* 656 */ "\x00\xd0\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 672 */ "\x70\x21\x00\x00\x00\x00\x00\x00\x70\x21\x00\x00\x00\x00\x00\x00"
+ /* 688 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 704 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 720 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 736 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 752 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 768 */ "\x00\x00\x00\x00\x00\x00\x00\x70\x00\x00\x00\x70\x21\x00\x00\x00"
+ /* 784 */ "\x00\x00\x00\x70\x21\x00\x00\x00\x00\x00\x00\x70\x00\x00\x00\x00"
+ /* 800 */ "\x00\x00\xa1\x00\x00\x00\xa1\x00\x00\x00\x00\x00\x00\x00\xa1\x00"
+ /* 816 */ "\x00\x00\x00\x00\x00\x00\xa1\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 832 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 848 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 864 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 880 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x70\x21"
+ /* 896 */ "\x00\x00\x00\x00\x00\x00\x70\x21\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 912 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 928 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 944 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 960 */ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ /* 976 */ "\x00";
+
+/* name.c */
+word_t *toplev;
+
+#ifndef _BBS_UTIL_C_
+/* menu.c */
+const commands_t cmdlist[] = {
+ {admin, PERM_SYSOP|PERM_ACCOUNTS|PERM_BOARD|PERM_VIEWSYSOP|PERM_ACCTREG|PERM_POLICE_MAN,
+ "00Admin 【 系統維護區 】"},
+ {Announce, 0, "AAnnounce 【 精華公佈欄 】"},
+#ifdef DEBUG
+ {Favorite, 0, "FFavorite 【 我的最不愛 】"},
+#else
+ {Favorite, 0, "FFavorite 【 我 的 最愛 】"},
+#endif
+ {Class, 0, "CClass 【 分組討論區 】"},
+ {Mail, PERM_BASIC, "MMail 【 私人信件區 】"},
+ {Talk, 0, "TTalk 【 休閒聊天區 】"},
+ {User, 0, "UUser 【 個人設定區 】"},
+ {Xyz, 0, "XXyz 【 系統工具區 】"},
+ {Play_Play, PERM_BASIC, "PPlay 【 娛樂與休閒 】"},
+ {Name_Menu, PERM_LOGINOK, "NNamelist 【 編特別名單 】"},
+#ifdef DEBUG
+ {Goodbye, 0, "GGoodbye 再見再見再見再見"},
+#else
+ {Goodbye, 0, "GGoodbye 離開,再見… "},
+#endif
+ {NULL, 0, NULL}
+};
+#endif
+
+/* friend.c */
+/* Ptt 各種特別名單的檔名 */
+char *friend_file[8] = {
+ FN_OVERRIDES,
+ FN_REJECT,
+ "alohaed",
+ "postlist",
+ "", /* may point to other filename */
+ FN_CANVOTE,
+ FN_WATER,
+ FN_VISABLE
+};
+
+#ifdef NOKILLWATERBALL
+char reentrant_write_request = 0;
+#endif
+
+#ifdef PTTBBS_UTIL
+ #ifdef OUTTA_TIMER
+ #define COMMON_TIME (SHM->GV2.e.now)
+ #else
+ #define COMMON_TIME (time(0))
+ #endif
+#else
+ #define COMMON_TIME (now)
+#endif
diff --git a/pttbbs/mbbsd/vice.c b/pttbbs/mbbsd/vice.c
new file mode 100644
index 00000000..14e4e8f4
--- /dev/null
+++ b/pttbbs/mbbsd/vice.c
@@ -0,0 +1,153 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define VICE_PLAY BBSHOME "/etc/vice/vice.play"
+#define VICE_DATA "vice.new"
+#define VICE_BINGO BBSHOME "/etc/vice.bingo"
+#define VICE_SHOW BBSHOME "/etc/vice.show"
+#define VICE_LOST BBSHOME "/etc/vice/vice.lost"
+#define VICE_WIN BBSHOME "/etc/vice/vice.win"
+#define VICE_END BBSHOME "/etc/vice/vice.end"
+#define VICE_NO BBSHOME "/etc/vice/vice.no"
+#define MAX_NO_PICTURE 2
+#define MAX_WIN_PICTURE 2
+#define MAX_LOST_PICTURE 3
+#define MAX_END_PICTURE 5
+
+static int
+vice_load(char tbingo[6][15])
+{
+ FILE *fb = fopen(VICE_BINGO, "r");
+ char buf[16], *ptr;
+ int i = 0;
+ if (!fb)
+ return -1;
+ bzero((char *)tbingo, 6*15);
+ while (i < 6 && fgets(buf, 15, fb)) {
+ if ((ptr = strchr(buf, '\n')))
+ *ptr = 0;
+ strcpy(tbingo[i++], buf);
+ }
+ fclose(fb);
+ return 0;
+}
+
+static int
+check(char tbingo[6][15], const char *data)
+{
+ int i = 0, j;
+
+ if (!strcmp(data, tbingo[0]))
+ return 8;
+
+ for (j = 8; j > 0; j--)
+ for (i = 1; i < 6; i++)
+ if (!strncmp(data + 8 - j, tbingo[i] + 8 - j, j))
+ return j - 1;
+ return 0;
+}
+/* TODO Ptt:showfile ran_showfile more 三者要合 */
+static int
+ran_showfile(int y, int x, const char *filename, int maxnum)
+{
+ FILE *fs;
+ char buf[512];
+
+ bzero(buf, sizeof(buf));
+ snprintf(buf, sizeof(buf), "%s%d", filename, (int)(random() % maxnum + 1));
+ if (!(fs = fopen(buf, "r"))) {
+ move(10, 10);
+ prints("can't open file: %s", buf);
+ return 0;
+ }
+ move(y, x);
+
+ while (fgets(buf, sizeof(buf), fs))
+ outs(buf);
+
+ fclose(fs);
+ return 1;
+}
+
+static int
+ran_showmfile(const char *filename, int maxnum)
+{
+ char buf[256];
+
+ snprintf(buf, sizeof(buf), "%s%d", filename, (int)(random() % maxnum + 1));
+ return more(buf, YEA);
+}
+
+
+int
+vice_main(void)
+{
+ FILE *fd;
+ char tbingo[6][15];
+ char buf_data[256],
+ serial[16], ch[2], *ptr;
+ int TABLE[] = {0, 10, 200, 1000, 4000, 10000, 40000, 100000, 200000};
+ int total = 0, money, i = 4, j = 0;
+
+ setuserfile(buf_data, VICE_DATA);
+ if (!dashf(buf_data)) {
+ ran_showmfile(VICE_NO, MAX_NO_PICTURE);
+ return 0;
+ }
+ if (vice_load(tbingo) < 0)
+ return -1;
+ clear();
+ ran_showfile(0, 0, VICE_PLAY, 1);
+ ran_showfile(10, 0, VICE_SHOW, 1);
+
+ if (!(fd = fopen(buf_data, "r")))
+ return 0;
+ j = 0;
+ i = 0;
+ move(10, 24);
+ clrtoeol();
+ outs("這一期的發票號碼");
+ // FIXME 當發票多於一行, 開獎的號碼就會被蓋掉
+ while (fgets(serial, 15, fd)) {
+ if ((ptr = strchr(serial, '\r')))
+ *ptr = 0;
+ if (j == 0)
+ i++;
+ if( i >= 14 )
+ break;
+ move(10 + i, 24 + j);
+ outs(serial);
+ j += 9;
+ j %= 45;
+ }
+ getdata(8, 0, "按'c'開始對獎了(或是任意鍵離開)): ",
+ ch, sizeof(ch), LCECHO);
+ if (ch[0] != 'c' || lockutmpmode(VICE, LOCK_MULTI)) {
+ fclose(fd);
+ return 0;
+ }
+ showtitle("發票對獎", BBSNAME);
+ rewind(fd);
+ while (fgets(serial, 15, fd)) {
+ if ((ptr = strchr(serial, '\n')))
+ *ptr = 0;
+ money = TABLE[check(tbingo, serial)];
+ total += money;
+ prints("%s 中了 %d\n", serial, money);
+ }
+ pressanykey();
+ if (total > 0) {
+ ran_showmfile(VICE_WIN, MAX_WIN_PICTURE);
+ move(22, 0);
+ clrtoeol();
+ prints("全部的發票中了 %d 塊錢\n", total);
+ demoney(total);
+ } else
+ ran_showmfile(VICE_LOST, MAX_LOST_PICTURE);
+
+ fclose(fd);
+ unlink(buf_data);
+ pressanykey();
+ unlockutmpmode();
+ return 0;
+}
diff --git a/pttbbs/mbbsd/vote.c b/pttbbs/mbbsd/vote.c
new file mode 100644
index 00000000..d15ee08f
--- /dev/null
+++ b/pttbbs/mbbsd/vote.c
@@ -0,0 +1,1193 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define MAX_VOTE_NR 20
+#define MAX_VOTE_PAGE 5
+#define ITEM_PER_PAGE 30
+
+const char * const STR_bv_control = "control"; /* 投票日期 選項 */
+const char * const STR_bv_desc = "desc"; /* 投票目的 */
+const char * const STR_bv_ballots = "ballots"; /* 投的票 (per byte) */
+const char * const STR_bv_flags = "flags";
+const char * const STR_bv_comments = "comments"; /* 投票者的建議 */
+const char * const STR_bv_limited = "limited"; /* 私人投票 */
+const char * const STR_bv_limits = "limits"; /* 投票資格限制 */
+const char * const STR_bv_title = "vtitle";
+
+const char * const STR_bv_results = "results";
+
+typedef struct {
+ char control[sizeof("controlXX\0")];
+ char desc[sizeof("descXX\0")];
+ char ballots[sizeof("ballotsXX\0")];
+ char flags[sizeof("flagsXX\0")];
+ char comments[sizeof("commentsXX\0")];
+ char limited[sizeof("limitedXX\0")];
+ char limits[sizeof("limitsXX\0")];
+ char title[sizeof("vtitleXX\0")];
+} vote_buffer_t;
+
+#if 0 // convert the filenames of first vote
+void convert_first_vote(boardheader_t *fhp)
+{
+ const char *filename[] = {
+ STR_bv_ballots, STR_bv_control, STR_bv_desc, STR_bv_desc,
+ STR_bv_flags, STR_bv_comments, STR_bv_limited, STR_bv_title,
+ NULL
+ };
+ char buf[256], buf2[256], oldname[64], newname[64];
+ int j;
+ for (j = 0; filename[j] != NULL; j++) {
+ snprintf(oldname, sizeof(oldname), "%s", filename[j]);
+ setbfile(buf, fhp->brdname, oldname);
+ if (!dashf(buf))
+ continue;
+ snprintf(newname, sizeof(newname), "%s0", filename[j]);
+ setbfile(buf2, fhp->brdname, newname);
+ if (dashf(buf2))
+ continue;
+ // old style format should be removed later
+ if (link(buf, buf2) < 0) {
+ vmsg(strerror(errno));
+ unlink(buf);
+ }
+ }
+}
+#endif
+
+#if 0 // backward compatible
+
+static FILE *
+convert_to_newversion(FILE *fp, char *file, char *ballots)
+{
+ char buf[256], buf2[256];
+ short blah;
+ int count = -1, tmp, fd, fdw;
+ FILE *fpw;
+
+ assert(fp);
+ flock(fileno(fp), LOCK_EX);
+ rewind(fp);
+ fgets(buf, sizeof(buf), fp);
+ if (index(buf, ',')) {
+ rewind(fp);
+ flock(fileno(fp), LOCK_UN);
+ return fp;
+ }
+ sscanf(buf, " %d", &tmp);
+
+ if ((fd = open(ballots, O_RDONLY)) != -1) {
+ sprintf(buf, "%s.new", ballots);
+ fdw = open(buf, O_WRONLY | O_CREAT, 0600);
+ flock(fd, LOCK_EX); /* Thor: 防止多人同時算 */
+ while (read(fd, &buf2[0], 1) == 1) {
+ blah = buf2[0];
+ if (blah >= 'A')
+ blah -= 'A';
+ write(fdw, &blah, sizeof(short));
+ }
+ flock(fd, LOCK_UN);
+ close(fd);
+ close(fdw);
+ Rename(buf, ballots);
+ }
+
+ sprintf(buf2, "%s.new", file);
+ if (!(fpw = fopen(buf2, "w"))) {
+ rewind(fp);
+ flock(fileno(fp), LOCK_UN);
+ return NULL;
+ }
+ fprintf(fpw, "000,000\n");
+ while (fgets(buf, sizeof(buf), fp)) {
+ fputs(buf, fpw);
+ count++;
+ }
+ rewind(fpw);
+ fprintf(fpw, "%3d,%3d", count, tmp);
+ fclose(fpw);
+ flock(fileno(fp), LOCK_UN);
+ fclose(fp);
+ unlink(file);
+ Rename(buf2, file);
+ fp = fopen(file, "r");
+ assert(fp != NULL);
+ return fp;
+}
+#endif
+
+void
+b_suckinfile(FILE * fp, char *fname)
+{
+ FILE *sfp;
+
+ if ((sfp = fopen(fname, "r"))) {
+ char inbuf[256];
+
+ while (fgets(inbuf, sizeof(inbuf), sfp))
+ fputs(inbuf, fp);
+ fclose(sfp);
+ }
+}
+
+void
+b_suckinfile_invis(FILE * fp, char *fname, const char *boardname)
+{
+ FILE *sfp;
+
+ if ((sfp = fopen(fname, "r"))) {
+ char inbuf[256];
+ if(fgets(inbuf, sizeof(inbuf), sfp))
+ {
+ /* first time, try if boardname revealed. */
+ char *post = strstr(inbuf, str_post1);
+ if(!post) post = strstr(inbuf, str_post2);
+ if(post)
+ post = strstr(post, boardname);
+ if(post) {
+ /* found releaved stuff. */
+ /*
+ // mosaic method 1
+ while(*boardname++)
+ *post++ = '?';
+ */
+ // mosaic method 2
+ strcpy(post, "(某隱形看板)\n");
+ }
+ fputs(inbuf, fp);
+ while (fgets(inbuf, sizeof(inbuf), sfp))
+ fputs(inbuf, fp);
+ }
+ fclose(sfp);
+ }
+}
+
+static void
+b_count(const char *buf, int counts[], short item_num, int *total)
+{
+ short choice;
+ int fd;
+
+ memset(counts, 0, item_num * sizeof(counts[0]));
+ *total = 0;
+ if ((fd = open(buf, O_RDONLY)) != -1) {
+ flock(fd, LOCK_EX); /* Thor: 防止多人同時算 */
+ while (read(fd, &choice, sizeof(short)) == sizeof(short)) {
+ if (choice >= item_num)
+ continue;
+ counts[choice]++;
+ (*total)++;
+ }
+ flock(fd, LOCK_UN);
+ close(fd);
+ }
+}
+
+
+static int
+b_nonzeroNum(const char *buf)
+{
+ int i = 0;
+ char inchar;
+ int fd;
+
+ if ((fd = open(buf, O_RDONLY)) != -1) {
+ while (read(fd, &inchar, 1) == 1)
+ if (inchar)
+ i++;
+ close(fd);
+ }
+ return i;
+}
+
+static void
+vote_report(const char *bname, const char *fname, char *fpath)
+{
+ register char *ip;
+ time4_t dtime;
+ int fd, bid;
+ fileheader_t header;
+
+ ip = fpath;
+ while (*(++ip));
+ *ip++ = '/';
+
+ /* get a filename by timestamp */
+
+ dtime = now;
+ for (;;) {
+ sprintf(ip, "M.%d.A", (int)++dtime);
+ fd = open(fpath, O_CREAT | O_EXCL | O_WRONLY, 0644);
+ if (fd >= 0)
+ break;
+ dtime++;
+ }
+ close(fd);
+
+ unlink(fpath);
+ link(fname, fpath);
+
+ /* append record to .DIR */
+
+ memset(&header, 0, sizeof(fileheader_t));
+ strlcpy(header.owner, "[馬路探子]", sizeof(header.owner));
+ snprintf(header.title, sizeof(header.title), "[%s] 看板 選情報導", bname);
+ {
+ register struct tm *ptime = localtime4(&dtime);
+
+ snprintf(header.date, sizeof(header.date),
+ "%2d/%02d", ptime->tm_mon + 1, ptime->tm_mday);
+ }
+ strlcpy(header.filename, ip, sizeof(header.filename));
+
+ strcpy(ip, ".DIR");
+ if ((fd = open(fpath, O_WRONLY | O_CREAT, 0644)) >= 0) {
+ flock(fd, LOCK_EX);
+ lseek(fd, 0, SEEK_END);
+ write(fd, &header, sizeof(fileheader_t));
+ flock(fd, LOCK_UN);
+ close(fd);
+ if ((bid = getbnum(bname)) > 0)
+ setbtotal(bid);
+
+ }
+}
+
+static void
+b_result_one(vote_buffer_t *vbuf, boardheader_t * fh, int ind, int *total)
+{
+ FILE *cfp, *tfp, *frp, *xfp;
+ char *bname;
+ char buf[STRLEN];
+ char inbuf[80];
+ int *counts;
+ int people_num;
+ short item_num, junk;
+ char b_control[64];
+ char b_newresults[64];
+ char b_report[64];
+ time4_t closetime;
+
+ fh->bvote--;
+
+ snprintf(vbuf->ballots, sizeof(vbuf->ballots), "%s%d", STR_bv_ballots, ind);
+ snprintf(vbuf->control, sizeof(vbuf->control),"%s%d", STR_bv_control, ind);
+ snprintf(vbuf->desc, sizeof(vbuf->desc), "%s%d", STR_bv_desc, ind);
+ snprintf(vbuf->flags, sizeof(vbuf->flags), "%s%d", STR_bv_flags, ind);
+ snprintf(vbuf->comments, sizeof(vbuf->comments), "%s%d", STR_bv_comments, ind);
+ snprintf(vbuf->limited, sizeof(vbuf->limited), "%s%d", STR_bv_limited, ind);
+ snprintf(vbuf->limits, sizeof(vbuf->limits), "%s%d", STR_bv_limits, ind);
+ snprintf(vbuf->title, sizeof(vbuf->title), "%s%d", STR_bv_title, ind);
+
+ bname = fh->brdname;
+
+ setbfile(buf, bname, vbuf->control);
+ cfp = fopen(buf, "r");
+#if 0 // backward compatible
+ setbfile(b_control, bname, STR_new_ballots);
+ cfp = convert_to_newversion(cfp, buf, b_control);
+#endif
+ assert(cfp);
+ fscanf(cfp, "%hd,%hd\n%d\n", &item_num, &junk, &closetime);
+ fclose(cfp);
+
+ // prevent death caused by a bug, it should be remove later.
+ if (item_num <= 0)
+ return;
+
+ counts = (int *)malloc(item_num * sizeof(int));
+
+ setbfile(b_control, bname, "tmp");
+ if (rename(buf, b_control) == -1)
+ return;
+ setbfile(buf, bname, vbuf->flags);
+ people_num = b_nonzeroNum(buf);
+ unlink(buf);
+ setbfile(buf, bname, vbuf->ballots);
+#if 0 // backward compatible
+ if (!newversion)
+ b_count_old(buf, counts, total);
+ else
+#endif
+ b_count(buf, counts, item_num, total);
+ unlink(buf);
+
+ setbfile(b_newresults, bname, "newresults");
+ if ((tfp = fopen(b_newresults, "w")) == NULL)
+ return;
+
+ setbfile(buf, bname, vbuf->title);
+
+ if ((xfp = fopen(buf, "r"))) {
+ fgets(inbuf, sizeof(inbuf), xfp);
+ fprintf(tfp, "%s\n◆ 投票名稱: %s\n\n", msg_seperator, inbuf);
+ fclose(xfp);
+ }
+ fprintf(tfp, "%s\n◆ 投票中止於: %s\n\n◆ 票選題目描述:\n\n",
+ msg_seperator, ctime4(&closetime));
+ fh->vtime = now;
+
+ setbfile(buf, bname, vbuf->desc);
+
+ b_suckinfile(tfp, buf);
+ unlink(buf);
+
+ if ((cfp = fopen(b_control, "r"))) {
+ fgets(inbuf, sizeof(inbuf), cfp);
+ fgets(inbuf, sizeof(inbuf), cfp);
+ fprintf(tfp, "\n◆投票結果:(共有 %d 人投票,每人最多可投 %hd 票)\n",
+ people_num, junk);
+ fprintf(tfp, " 選 項 總票數 得票率 得票分布\n");
+ for (junk = 0; junk < item_num; junk++) {
+ fgets(inbuf, sizeof(inbuf), cfp);
+ chomp(inbuf);
+ fprintf(tfp, " %-42s %3d 票 %6.2f%% %6.2f%%\n", inbuf + 3, counts[junk],
+ (float)(counts[junk] * 100) / (float)(people_num),
+ (float)(counts[junk] * 100) / (float)(*total));
+ }
+ fclose(cfp);
+ }
+ unlink(b_control);
+ free(counts);
+
+ fprintf(tfp, "%s\n◆ 使用者建議:\n\n", msg_seperator);
+ setbfile(buf, bname, vbuf->comments);
+ b_suckinfile(tfp, buf);
+ unlink(buf);
+
+ fprintf(tfp, "%s\n◆ 總票數 = %d 票\n\n", msg_seperator, *total);
+ fclose(tfp);
+
+ setbfile(b_report, bname, "report");
+ if ((frp = fopen(b_report, "w"))) {
+ b_suckinfile(frp, b_newresults);
+ fclose(frp);
+ }
+ setbpath(inbuf, bname);
+ vote_report(bname, b_report, inbuf);
+ if (!(fh->brdattr & BRD_NOCOUNT)) {
+ setbpath(inbuf, "Record");
+ vote_report(bname, b_report, inbuf);
+ }
+ unlink(b_report);
+
+ tfp = fopen(b_newresults, "a");
+ setbfile(buf, bname, STR_bv_results);
+ b_suckinfile(tfp, buf);
+ fclose(tfp);
+ Rename(b_newresults, buf);
+}
+
+static void
+b_result(vote_buffer_t *vbuf, boardheader_t * fh)
+{
+ FILE *cfp;
+ time4_t closetime;
+ int i, total;
+ char buf[STRLEN];
+ char temp[STRLEN];
+
+ for (i = 0; i < MAX_VOTE_NR; i++) {
+ snprintf(vbuf->control, sizeof(vbuf->control), "%s%d", STR_bv_control, i);
+
+ setbfile(buf, fh->brdname, vbuf->control);
+ cfp = fopen(buf, "r");
+ if (!cfp)
+ continue;
+ fgets(temp, sizeof(temp), cfp);
+ fscanf(cfp, "%d\n", &closetime);
+ fclose(cfp);
+ if (closetime < now)
+ b_result_one(vbuf, fh, i, &total);
+ }
+}
+
+static int
+b_close(boardheader_t * fh, vote_buffer_t *vbuf)
+{
+ b_result(vbuf, fh);
+ return 1;
+}
+
+static int
+b_closepolls(void)
+{
+ boardheader_t *fhp;
+ int pos;
+ vote_buffer_t vbuf;
+
+#ifndef BARRIER_HAS_BEEN_IN_SHM
+ char *fn_vote_polling = ".polling";
+ unsigned long last;
+ FILE *cfp;
+ /* XXX necessary to lock ? */
+ if ((cfp = fopen(fn_vote_polling, "r"))) {
+ char timebuf[100];
+ fgets(timebuf, sizeof(timebuf), cfp);
+ sscanf(timebuf, "%lu", &last);
+ fclose(cfp);
+ if (last + 3600 >= (unsigned long)now)
+ return 0;
+ }
+ if ((cfp = fopen(fn_vote_polling, "w")) == NULL)
+ return 0;
+ fprintf(cfp, "%d\n%s\n", now, ctime4(&now));
+ fclose(cfp);
+#endif
+
+ for (fhp = bcache, pos = 1; pos <= numboards; fhp++, pos++) {
+ if (fhp->bvote && b_close(fhp, &vbuf)) {
+ if (substitute_record(fn_board, fhp, sizeof(*fhp), pos) == -1)
+ outs(err_board_update);
+ else
+ reset_board(pos);
+ }
+ }
+
+ return 0;
+}
+
+void
+auto_close_polls(void)
+{
+ /* 最多一天開票一次 */
+ if (now - SHM->close_vote_time > 86400) {
+ b_closepolls();
+ SHM->close_vote_time = now;
+ }
+}
+
+static int
+vote_view(vote_buffer_t *vbuf, const char *bname, int vote_index)
+{
+ boardheader_t *fhp;
+ FILE *fp;
+ char buf[STRLEN], genbuf[STRLEN], inbuf[STRLEN];
+ short item_num, i;
+ int num = 0, pos, *counts, total;
+ time4_t closetime;
+
+ snprintf(vbuf->ballots, sizeof(vbuf->ballots),"%s%d", STR_bv_ballots, vote_index);
+ snprintf(vbuf->control, sizeof(vbuf->control), "%s%d", STR_bv_control, vote_index);
+ snprintf(vbuf->desc, sizeof(vbuf->desc), "%s%d", STR_bv_desc, vote_index);
+ snprintf(vbuf->flags, sizeof(vbuf->flags), "%s%d", STR_bv_flags, vote_index);
+ snprintf(vbuf->comments, sizeof(vbuf->comments), "%s%d", STR_bv_comments, vote_index);
+ snprintf(vbuf->limited, sizeof(vbuf->limited), "%s%d", STR_bv_limited, vote_index);
+ snprintf(vbuf->limits, sizeof(vbuf->limits), "%s%d", STR_bv_limits, vote_index);
+ snprintf(vbuf->title, sizeof(vbuf->title), "%s%d", STR_bv_title, vote_index);
+
+ setbfile(buf, bname, vbuf->ballots);
+ if ((num = dashs(buf)) < 0) /* file size */
+ num = 0;
+
+ setbfile(buf, bname, vbuf->title);
+ move(0, 0);
+ clrtobot();
+
+ if ((fp = fopen(buf, "r"))) {
+ fgets(inbuf, sizeof(inbuf), fp);
+ prints("\n投票名稱: %s", inbuf);
+ fclose(fp);
+ }
+ setbfile(buf, bname, vbuf->control);
+ fp = fopen(buf, "r");
+#if 0 // backward compatible
+ setbfile(genbuf, bname, STR_new_ballots);
+ fp = convert_to_newversion(fp, buf, genbuf);
+#endif
+ assert(fp);
+ fscanf(fp, "%hd,%hd\n%d\n", &item_num, &i, &closetime);
+ counts = (int *)malloc(item_num * sizeof(int));
+
+ prints("\n◆ 預知投票紀事: 每人最多可投 %d 票,目前共有 %d 票,\n"
+ "本次投票將結束於 %s", atoi(inbuf), (int)(num / sizeof(short)),
+ ctime4(&closetime));
+
+ /* Thor: 開放 票數 預知 */
+ setbfile(buf, bname, vbuf->flags);
+ prints("共有 %d 人投票\n", b_nonzeroNum(buf));
+
+ setbfile(buf, bname, vbuf->ballots);
+#if 0 // backward compatible
+ if (!newversion)
+ b_count_old(buf, counts, &total);
+ else
+#endif
+ b_count(buf, counts, item_num, &total);
+
+ total = 0;
+
+ for (i = num = 0; i < item_num; i++, num++) {
+ fgets(inbuf, sizeof(inbuf), fp);
+ chomp(inbuf);
+ inbuf[30] = '\0'; /* truncate */
+ move(num % 15 + 6, num / 15 * 40);
+ prints(" %-32s%3d 票", inbuf, counts[i]);
+ total += counts[i];
+ if (num == 29) {
+ num = -1;
+ pressanykey();
+ move(6, 0);
+ clrtobot();
+ }
+ }
+ fclose(fp);
+ free(counts);
+ pos = getbnum(bname);
+ assert(0<=pos-1 && pos-1<MAX_BOARD);
+ fhp = bcache + pos - 1;
+ move(t_lines - 3, 0);
+ prints("◆ 目前總票數 = %d 票", total);
+ getdata(b_lines - 1, 0, "(A)取消投票 (B)提早開票 (C)繼續?[C] ", genbuf,
+ 4, LCECHO);
+ if (genbuf[0] == 'a') {
+ getdata(b_lines - 1, 0, "請再次確認取消投票 (Y/N) [N] ", genbuf,
+ 4, LCECHO);
+ if (genbuf[0] == 'n')
+ return FULLUPDATE;
+
+ setbfile(buf, bname, vbuf->control);
+ unlink(buf);
+ setbfile(buf, bname, vbuf->flags);
+ unlink(buf);
+ setbfile(buf, bname, vbuf->ballots);
+ unlink(buf);
+ setbfile(buf, bname, vbuf->desc);
+ unlink(buf);
+ setbfile(buf, bname, vbuf->limited);
+ unlink(buf);
+ setbfile(buf, bname, vbuf->limits);
+ unlink(buf);
+ setbfile(buf, bname, vbuf->title);
+ unlink(buf);
+
+ fhp->bvote--;
+
+ if (substitute_record(fn_board, fhp, sizeof(*fhp), pos) == -1)
+ outs(err_board_update);
+ reset_board(pos);
+ } else if (genbuf[0] == 'b') {
+ b_result_one(vbuf, fhp, vote_index, &total);
+ if (substitute_record(fn_board, fhp, sizeof(*fhp), pos) == -1)
+ outs(err_board_update);
+
+ reset_board(pos);
+ }
+ return FULLUPDATE;
+}
+
+static int
+vote_view_all(vote_buffer_t *vbuf, const char *bname)
+{
+ int i;
+ int x = -1;
+ FILE *fp, *xfp;
+ char buf[STRLEN], genbuf[STRLEN];
+ char inbuf[80];
+
+ move(0, 0);
+ for (i = 0; i < MAX_VOTE_NR; i++) {
+ snprintf(vbuf->control, sizeof(vbuf->control), "%s%d", STR_bv_control, i);
+ snprintf(vbuf->title, sizeof(vbuf->title), "%s%d", STR_bv_title, i);
+ setbfile(buf, bname, vbuf->control);
+ if ((fp = fopen(buf, "r"))) {
+ prints("(%d) ", i);
+ x = i;
+ fclose(fp);
+
+ setbfile(buf, bname, vbuf->title);
+ if ((xfp = fopen(buf, "r"))) {
+ fgets(inbuf, sizeof(inbuf), xfp);
+ fclose(xfp);
+ } else
+ strlcpy(inbuf, "無標題", sizeof(inbuf));
+ prints("%s\n", inbuf);
+ }
+ }
+
+ if (x < 0)
+ return FULLUPDATE;
+ snprintf(buf, sizeof(buf), "要看幾號投票 [%d] ", x);
+
+ getdata(b_lines - 1, 0, buf, genbuf, 4, LCECHO);
+
+
+ if (atoi(genbuf) < 0 || atoi(genbuf) > MAX_VOTE_NR)
+ snprintf(genbuf, sizeof(genbuf), "%d", x);
+ snprintf(vbuf->control, sizeof(vbuf->control), "%s%d", STR_bv_control, atoi(genbuf));
+
+ setbfile(buf, bname, vbuf->control);
+
+ if (dashf(buf)) {
+ return vote_view(vbuf, bname, atoi(genbuf));
+ } else
+ return FULLUPDATE;
+}
+
+static int
+vote_maintain(const char *bname)
+{
+ FILE *fp = NULL;
+ char inbuf[STRLEN], buf[STRLEN];
+ int num = 0, aborted, pos, x, i;
+ time4_t closetime;
+ boardheader_t *fhp;
+ char genbuf[4];
+ vote_buffer_t vbuf;
+
+ if (!(currmode & MODE_BOARD))
+ return 0;
+ if ((pos = getbnum(bname)) <= 0)
+ return 0;
+
+ assert(0<=pos-1 && pos-1<MAX_BOARD);
+ fhp = bcache + pos - 1;
+
+ if (fhp->bvote != 0) {
+
+#if 0 // convert the filenames of first vote
+ convert_first_vote(fhp);
+#endif
+ getdata(b_lines - 1, 0,
+ "(V)觀察目前投票 (M)舉辦新投票 (A)取消所有投票 (Q)繼續 [Q]",
+ genbuf, 4, LCECHO);
+ if (genbuf[0] == 'v')
+ return vote_view_all(&vbuf, bname);
+ else if (genbuf[0] == 'a') {
+ fhp->bvote = 0;
+
+ for (i = 0; i < MAX_VOTE_NR; i++) {
+ int j;
+ char buf2[64];
+ const char *filename[] = {
+ STR_bv_ballots, STR_bv_control, STR_bv_desc, STR_bv_desc,
+ STR_bv_flags, STR_bv_comments, STR_bv_limited, STR_bv_limits,
+ STR_bv_title, NULL
+ };
+ for (j = 0; filename[j] != NULL; j++) {
+ snprintf(buf2, sizeof(buf2), "%s%d", filename[j], i);
+ setbfile(buf, bname, buf2);
+ unlink(buf);
+ }
+ }
+ if (substitute_record(fn_board, fhp, sizeof(*fhp), pos) == -1)
+ outs(err_board_update);
+
+ return FULLUPDATE;
+ } else if (genbuf[0] != 'm') {
+ if (fhp->bvote >= MAX_VOTE_NR)
+ vmsg("不得舉辦過多投票");
+ return FULLUPDATE;
+ }
+ }
+
+ x = 1;
+ do {
+ snprintf(vbuf.control, sizeof(vbuf.control), "%s%d", STR_bv_control, x);
+ setbfile(buf, bname, vbuf.control);
+ x++;
+ } while (dashf(buf) && x <= MAX_VOTE_NR);
+
+ --x;
+ if (x >= MAX_VOTE_NR)
+ return FULLUPDATE;
+
+ getdata(b_lines - 1, 0,
+ "確定要舉辦投票嗎? [y/N]: ",
+ inbuf, 4, LCECHO);
+ if (inbuf[0] != 'y')
+ return FULLUPDATE;
+
+ stand_title("舉辦投票");
+ snprintf(vbuf.ballots, sizeof(vbuf.ballots), "%s%d", STR_bv_ballots, x);
+ snprintf(vbuf.control, sizeof(vbuf.control), "%s%d", STR_bv_control, x);
+ snprintf(vbuf.desc, sizeof(vbuf.desc), "%s%d", STR_bv_desc, x);
+ snprintf(vbuf.flags, sizeof(vbuf.flags), "%s%d", STR_bv_flags, x);
+ snprintf(vbuf.comments, sizeof(vbuf.comments), "%s%d", STR_bv_comments, x);
+ snprintf(vbuf.limited, sizeof(vbuf.limited), "%s%d", STR_bv_limited, x);
+ snprintf(vbuf.limits, sizeof(vbuf.limits), "%s%d", STR_bv_limits, x);
+ snprintf(vbuf.title, sizeof(vbuf.title), "%s%d", STR_bv_title, x);
+
+ clear();
+ move(0, 0);
+ prints("第 %d 號投票\n", x);
+ setbfile(buf, bname, vbuf.title);
+ getdata(4, 0, "請輸入投票名稱:", inbuf, 50, LCECHO);
+ if (inbuf[0] == '\0')
+ strlcpy(inbuf, "不知名的", sizeof(inbuf));
+ fp = fopen(buf, "w");
+ assert(fp);
+ fputs(inbuf, fp);
+ fclose(fp);
+
+ vmsg("按任何鍵開始編輯此次 [投票宗旨]");
+ setbfile(buf, bname, vbuf.desc);
+ aborted = vedit(buf, NA, NULL);
+ if (aborted == -1) {
+ vmsg("取消此次投票");
+ return FULLUPDATE;
+ }
+ aborted = 0;
+ setbfile(buf, bname, vbuf.flags);
+ unlink(buf);
+
+ getdata(4, 0,
+ "是否限定投票者名單:(y)編輯可投票人員名單[n]任何人皆可投票:[N]",
+ inbuf, 2, LCECHO);
+ setbfile(buf, bname, vbuf.limited);
+ if (inbuf[0] == 'y') {
+ fp = fopen(buf, "w");
+ assert(fp);
+ //fprintf(fp, "此次投票設限");
+ fclose(fp);
+ friend_edit(FRIEND_CANVOTE);
+ } else {
+ if (dashf(buf))
+ unlink(buf);
+ }
+ getdata(5, 0,
+ "是否限定投票資格:(y)限制投票資格[n]不設限:[N]",
+ inbuf, 2, LCECHO);
+ setbfile(buf, bname, vbuf.limits);
+ if (inbuf[0] == 'y') {
+ fp = fopen(buf, "w");
+ assert(fp);
+ do {
+ getdata(6, 0, "註冊時間限制 (以'月'為單位,0~120):", inbuf, 4, DOECHO);
+ closetime = atoi(inbuf); // borrow variable
+ } while (closetime < 0 || closetime > 120);
+ fprintf(fp, "%d\n", now - (2592000 * closetime));
+ do {
+ getdata(6, 0, "上站次數下限", inbuf, 6, DOECHO);
+ closetime = atoi(inbuf); // borrow variable
+ } while (closetime < 0);
+ fprintf(fp, "%d\n", closetime);
+ do {
+ getdata(6, 0, "文章篇數下限", inbuf, 6, DOECHO);
+ closetime = atoi(inbuf); // borrow variable
+ } while (closetime < 0);
+ fprintf(fp, "%d\n", closetime);
+ fclose(fp);
+ } else {
+ if (dashf(buf))
+ unlink(buf);
+ }
+ clear();
+ getdata(0, 0, "此次投票進行幾天 (一到三十天)?", inbuf, 4, DOECHO);
+
+ closetime = atoi(inbuf);
+ if (closetime <= 0)
+ closetime = 1;
+ else if (closetime > 30)
+ closetime = 30;
+
+ closetime = closetime * 86400 + now;
+ setbfile(buf, bname, vbuf.control);
+ fp = fopen(buf, "w");
+ assert(fp);
+ fprintf(fp, "000,000\n%d\n", closetime);
+
+ outs("\n請依序輸入選項, 按 ENTER 完成設定");
+ num = 0;
+ x = 0; /* x is the page number */
+ while (!aborted) {
+ if( num % 15 == 0 ){
+ for( i = num ; i < num + 15 ; ++i ){
+ move((i % 15) + 2, (i / 15) * 40);
+ prints(ANSI_COLOR(1;30) "%c)" ANSI_RESET " ", i + 'A');
+ }
+ }
+ snprintf(buf, sizeof(buf), "%c) ", num + 'A');
+ getdata((num % 15) + 2, (num / 15) * 40, buf,
+ inbuf, 37, DOECHO);
+ if (*inbuf) {
+ fprintf(fp, "%1c) %s\n", (num + 'A'), inbuf);
+ num++;
+ }
+ if ((*inbuf == '\0' && num >= 1) || x == MAX_VOTE_PAGE)
+ aborted = 1;
+ if (num == ITEM_PER_PAGE) {
+ x++;
+ num = 0;
+ }
+ }
+ snprintf(buf, sizeof(buf), "請問每人最多可投幾票?([1]∼%d): ", x * ITEM_PER_PAGE + num);
+
+ getdata(t_lines - 3, 0, buf, inbuf, 3, DOECHO);
+
+ if (atoi(inbuf) <= 0 || atoi(inbuf) > (x * ITEM_PER_PAGE + num)) {
+ inbuf[0] = '1';
+ inbuf[1] = 0;
+ }
+
+ rewind(fp);
+ fprintf(fp, "%3d,%3d\n", x * ITEM_PER_PAGE + num, MAX(1, atoi(inbuf)));
+ fclose(fp);
+
+ fhp->bvote++;
+
+ if (substitute_record(fn_board, fhp, sizeof(*fhp), pos) == -1)
+ outs(err_board_update);
+ reset_board(pos);
+ outs("開始投票了!");
+
+ return FULLUPDATE;
+}
+
+static int
+vote_flag(vote_buffer_t *vbuf, const char *bname, int index, char val)
+{
+ char buf[256], flag;
+ int fd, num, size;
+
+ snprintf(vbuf->flags, sizeof(vbuf->flags), "%s%d", STR_bv_flags, index);
+
+ num = usernum - 1;
+ setbfile(buf, bname, vbuf->flags);
+ if ((fd = open(buf, O_RDWR | O_CREAT, 0600)) == -1)
+ return -1;
+ size = lseek(fd, 0, SEEK_END);
+ memset(buf, 0, sizeof(buf));
+ while (size <= num) {
+ write(fd, buf, sizeof(buf));
+ size += sizeof(buf);
+ }
+ lseek(fd, num, SEEK_SET);
+ read(fd, &flag, 1);
+ if (flag == 0 && val != 0) {
+ lseek(fd, num, SEEK_SET);
+ write(fd, &val, 1);
+ }
+ close(fd);
+ return flag;
+}
+
+static int
+user_vote_one(vote_buffer_t *vbuf, const char *bname, int ind)
+{
+ FILE *cfp, *fcm;
+ char buf[STRLEN], redo;
+ boardheader_t *fhp;
+ short pos = 0, i = 0, count, tickets, fd;
+ short curr_page, item_num, max_page;
+ char inbuf[80], choices[ITEM_PER_PAGE+1], vote[4], *chosen;
+ time4_t closetime;
+
+ snprintf(vbuf->ballots, sizeof(vbuf->ballots), "%s%d", STR_bv_ballots, ind);
+ snprintf(vbuf->control, sizeof(vbuf->control), "%s%d", STR_bv_control, ind);
+ snprintf(vbuf->desc, sizeof(vbuf->desc), "%s%d", STR_bv_desc, ind);
+ snprintf(vbuf->flags, sizeof(vbuf->flags),"%s%d", STR_bv_flags, ind);
+ snprintf(vbuf->comments, sizeof(vbuf->comments), "%s%d", STR_bv_comments, ind);
+ snprintf(vbuf->limited, sizeof(vbuf->limited), "%s%d", STR_bv_limited, ind);
+ snprintf(vbuf->limits, sizeof(vbuf->limits), "%s%d", STR_bv_limits, ind);
+
+ setbfile(buf, bname, vbuf->control);
+ cfp = fopen(buf, "r");
+ if (!cfp)
+ return FULLUPDATE;
+
+ setbfile(buf, bname, vbuf->limited); /* Ptt */
+ if (dashf(buf)) {
+ setbfile(buf, bname, FN_CANVOTE);
+ if (!belong(buf, cuser.userid)) {
+ fclose(cfp);
+ vmsg("對不起! 這是私人投票..你並沒有受邀唷!");
+ return FULLUPDATE;
+ } else {
+ vmsg("恭喜你受邀此次私人投票. <檢視此次受邀名單>");
+ more(buf, YEA);
+ }
+ }
+ setbfile(buf, bname, vbuf->limits);
+ if (dashf(buf)) {
+ int limits_logins, limits_posts;
+ FILE * lfp = fopen(buf, "r");
+ assert(lfp);
+ fscanf(lfp, "%d", &closetime);
+ fscanf(lfp, "%d", &limits_logins);
+ fscanf(lfp, "%d", &limits_posts);
+ fclose(lfp);
+ if (cuser.firstlogin > closetime || cuser.numposts < limits_posts ||
+ cuser.numlogins < limits_logins) {
+ vmsg("你不夠資深喔! (可在看板內按大寫 I 查看限制)");
+ return FULLUPDATE;
+ }
+ }
+ if (vote_flag(vbuf, bname, ind, '\0')) {
+ vmsg("此次投票,你已投過了!");
+ return FULLUPDATE;
+ }
+ setutmpmode(VOTING);
+ setbfile(buf, bname, vbuf->desc);
+ more(buf, YEA);
+
+ stand_title("投票箱");
+ if ((pos = getbnum(bname)) <= 0)
+ return 0;
+
+ assert(0<=pos-1 && pos-1<MAX_BOARD);
+ fhp = bcache + pos - 1;
+#if 0 // backward compatible
+ setbfile(buf, bname, STR_new_control);
+ setbfile(inbuf, bname, STR_new_ballots);
+ cfp = convert_to_newversion(cfp, buf, inbuf);
+#endif
+ assert(cfp);
+ fscanf(cfp, "%hd,%hd\n%d\n", &item_num, &tickets, &closetime);
+ chosen = (char *)malloc(item_num+100); // XXX dirty fix 板主增加選項的問題
+ memset(chosen, 0, item_num+100);
+ memset(choices, 0, sizeof(choices));
+ max_page = (item_num - 1)/ ITEM_PER_PAGE + 1;
+
+ prints("投票方式:確定好您的選擇後,輸入其代碼(A, B, C...)即可。\n"
+ "此次投票你可以投 %1hd 票。按 0 取消投票, 1 完成投票, > 下一頁, < 上一頁\n"
+ "此次投票將結束於:%s \n",
+ tickets, ctime4(&closetime));
+
+#define REDO_DRAW 1
+#define REDO_SCAN 2
+ redo = REDO_DRAW;
+ curr_page = 0;
+ while (1) {
+
+ if (redo) {
+ int i, j;
+ move(5, 0);
+ clrtobot();
+
+ /* 想不到好方法 因為不想整個讀進 memory
+ * 而且大部分的投票不會超過一頁 所以再從檔案 scan */
+ /* XXX 投到一半板主增加選項則 chosen 太小 */
+ if (redo & REDO_SCAN) {
+ for (i = 0; i < curr_page; i++)
+ for (j = 0; j < ITEM_PER_PAGE; j++)
+ fgets(inbuf, sizeof(inbuf), cfp);
+ }
+
+ count = 0;
+ for (i = 0; i < ITEM_PER_PAGE && fgets(inbuf, sizeof(inbuf), cfp); i++) {
+ chomp(inbuf);
+ move((count % 15) + 5, (count / 15) * 40);
+ prints("%c%s", chosen[curr_page * ITEM_PER_PAGE + i] ? '*' : ' ',
+ inbuf);
+ choices[count % ITEM_PER_PAGE] = inbuf[0];
+ count++;
+ }
+ redo = 0;
+ }
+
+ vote[0] = vote[1] = '\0';
+ move(t_lines - 2, 0);
+ prints("你還可以投 %2d 票 [ 目前所在頁數 %2d / 共 %2d 頁 (可輸入 '<' '>' 換頁) ]", tickets - i, curr_page + 1, max_page);
+ getdata(t_lines - 4, 0, "輸入您的選擇: ", vote, sizeof(vote), DOECHO);
+ *vote = toupper(*vote);
+
+#define CURRENT_CHOICE \
+ chosen[curr_page * ITEM_PER_PAGE + vote[0] - 'A']
+ if (vote[0] == '0' || (!vote[0] && !i)) {
+ outs("記得再來投喔!!");
+ break;
+ } else if (vote[0] == '1' && i);
+ else if (!vote[0])
+ continue;
+ else if (vote[0] == '<' && max_page > 1) {
+ curr_page = (curr_page + max_page - 1) % max_page;
+ rewind(cfp);
+ fgets(inbuf, sizeof(inbuf), cfp);
+ fgets(inbuf, sizeof(inbuf), cfp);
+ redo = REDO_DRAW | REDO_SCAN;
+ continue;
+ }
+ else if (vote[0] == '>' && max_page > 1) {
+ curr_page = (curr_page + 1) % max_page;
+ if (curr_page == 0) {
+ rewind(cfp);
+ fgets(inbuf, sizeof(inbuf), cfp);
+ fgets(inbuf, sizeof(inbuf), cfp);
+ }
+ redo = REDO_DRAW;
+ continue;
+ }
+ else if (index(choices, vote[0]) == NULL) /* 無效 */
+ continue;
+ else if ( CURRENT_CHOICE ) { /* 已選 */
+ move(((vote[0] - 'A') % 15) + 5, (((vote[0] - 'A')) / 15) * 40);
+ outc(' ');
+ CURRENT_CHOICE = 0;
+ i--;
+ continue;
+ } else {
+ if (i == tickets)
+ continue;
+ move(((vote[0] - 'A') % 15) + 5, (((vote[0] - 'A')) / 15) * 40);
+ outc('*');
+ CURRENT_CHOICE = 1;
+ i++;
+ continue;
+ }
+
+ if (vote_flag(vbuf, bname, ind, vote[0]) != 0)
+ outs("重複投票! 不予計票。");
+ else {
+ setbfile(buf, bname, vbuf->ballots);
+ if ((fd = open(buf, O_WRONLY | O_CREAT | O_APPEND, 0600)) == 0)
+ outs("無法投入票匭\n");
+ else {
+ struct stat statb;
+ char buf[3], mycomments[3][74], b_comments[80];
+
+ for (i = 0; i < 3; i++)
+ strlcpy(mycomments[i], "\n", sizeof(mycomments[i]));
+
+ flock(fd, LOCK_EX);
+ for (count = 0; count < item_num; count++) {
+ if (chosen[count])
+ write(fd, &count, sizeof(short));
+ }
+ flock(fd, LOCK_UN);
+ fstat(fd, &statb);
+ close(fd);
+ getdata(b_lines - 2, 0,
+ "您對這次投票有什麼寶貴的意見嗎?(y/n)[N]",
+ buf, 3, DOECHO);
+ if (buf[0] == 'Y' || buf[0] == 'y') {
+ do {
+ move(5, 0);
+ clrtobot();
+ outs("請問您對這次投票有什麼寶貴的意見?"
+ "最多三行,按[Enter]結束");
+ for (i = 0; (i < 3) &&
+ getdata(7 + i, 0, ":",
+ mycomments[i], sizeof(mycomments[i]),
+ DOECHO); i++);
+ getdata(b_lines - 2, 0, "(S)儲存 (E)重新來過 "
+ "(Q)取消?[S]", buf, 3, LCECHO);
+ } while (buf[0] == 'E' || buf[0] == 'e');
+ if (buf[0] == 'Q' || buf[0] == 'q')
+ break;
+ setbfile(b_comments, bname, vbuf->comments);
+ if (mycomments[0])
+ if ((fcm = fopen(b_comments, "a"))) {
+ fprintf(fcm,
+ ANSI_COLOR(36) "○使用者" ANSI_COLOR(1;36) " %s "
+ ANSI_COLOR(;36) "的建議:" ANSI_RESET "\n",
+ cuser.userid);
+ for (i = 0; i < 3; i++)
+ fprintf(fcm, " %s\n", mycomments[i]);
+ fprintf(fcm, "\n");
+ fclose(fcm);
+ }
+ }
+ move(b_lines - 1, 0);
+ outs("已完成投票!\n");
+ }
+ }
+ break;
+ }
+ free(chosen);
+ fclose(cfp);
+ pressanykey();
+ return FULLUPDATE;
+}
+
+static int
+user_vote(const char *bname)
+{
+ int pos;
+ boardheader_t *fhp;
+ char buf[STRLEN];
+ FILE *fp, *xfp;
+ int i, x = -1;
+ char genbuf[STRLEN];
+ char inbuf[80];
+ vote_buffer_t vbuf;
+
+ if ((pos = getbnum(bname)) <= 0)
+ return 0;
+
+ assert(0<=pos-1 && pos-1<MAX_BOARD);
+ fhp = bcache + pos - 1;
+
+ move(0, 0);
+ clrtobot();
+
+ if (fhp->bvote == 0) {
+ vmsg("目前並沒有任何投票舉行。");
+ return FULLUPDATE;
+ }
+#if 0 // convert the filenames of first vote
+ convert_first_vote(fhp);
+#endif
+ if (!HasUserPerm(PERM_LOGINOK)) {
+ vmsg("對不起! 您未滿二十歲, 還沒有投票權喔!");
+ return FULLUPDATE;
+ }
+
+ move(0, 0);
+ for (i = 0; i < MAX_VOTE_NR; i++) {
+ snprintf(vbuf.control, sizeof(vbuf.control), "%s%d", STR_bv_control, i);
+ snprintf(vbuf.title, sizeof(vbuf.title), "%s%d", STR_bv_title, i);
+ setbfile(buf, bname, vbuf.control);
+ if ((fp = fopen(buf, "r"))) {
+ prints("(%d) ", i);
+ x = i;
+ fclose(fp);
+
+ setbfile(buf, bname, vbuf.title);
+ if ((xfp = fopen(buf, "r"))) {
+ fgets(inbuf, sizeof(inbuf), xfp);
+ fclose(xfp);
+ } else
+ strlcpy(inbuf, "無標題", sizeof(inbuf));
+ prints("%s\n", inbuf);
+ }
+ }
+
+ if (x < 0)
+ return FULLUPDATE;
+
+ snprintf(buf, sizeof(buf), "要投幾號投票 [%d] ", x);
+
+ getdata(b_lines - 1, 0, buf, genbuf, 4, LCECHO);
+
+ if (atoi(genbuf) < 0 || atoi(genbuf) > MAX_VOTE_NR)
+ snprintf(genbuf, sizeof(genbuf), "%d", x);
+
+ snprintf(vbuf.control, sizeof(vbuf.control), "%s%d", STR_bv_control, atoi(genbuf));
+
+ setbfile(buf, bname, vbuf.control);
+
+ if (dashf(buf)) {
+ return user_vote_one(&vbuf, bname, atoi(genbuf));
+ } else
+ return FULLUPDATE;
+}
+
+static int
+vote_results(const char *bname)
+{
+ char buf[STRLEN];
+
+ setbfile(buf, bname, STR_bv_results);
+ if (more(buf, YEA) == -1)
+ vmsg("目前沒有任何投票的結果。");
+ return FULLUPDATE;
+}
+
+int
+b_vote_maintain(void)
+{
+ return vote_maintain(currboard);
+}
+
+int
+b_vote(void)
+{
+ return user_vote(currboard);
+}
+
+int
+b_results(void)
+{
+ return vote_results(currboard);
+}
diff --git a/pttbbs/mbbsd/voteboard.c b/pttbbs/mbbsd/voteboard.c
new file mode 100644
index 00000000..10594079
--- /dev/null
+++ b/pttbbs/mbbsd/voteboard.c
@@ -0,0 +1,364 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define VOTEBOARD "NewBoard"
+void
+do_voteboardreply(const fileheader_t * fhdr)
+{
+ char genbuf[256];
+ char reason[36]="";
+ char fpath[80];
+ char oldfpath[80];
+ char opnion[10];
+ char *ptr;
+ FILE *fo,*fi;
+ fileheader_t votefile;
+ int yes=0, no=0, len;
+ int fd;
+ unsigned long endtime=0;
+
+
+ clear();
+ if (!CheckPostPerm()||HasUserPerm(PERM_NOCITIZEN)) {
+ move(5, 10);
+ vmsg("對不起,您目前無法在此發表文章!");
+ return;
+ }
+ if (cuser.firstlogin > (now - (time4_t)fhdr->multi.vote_limits.regtime * 2592000) ||
+ cuser.badpost > (255 - (unsigned int)(fhdr->multi.vote_limits.badpost)) ||
+ cuser.numlogins < ((unsigned int)(fhdr->multi.vote_limits.logins) * 10) ||
+ cuser.numposts < ((unsigned int)(fhdr->multi.vote_limits.posts) * 10) ) {
+ move(5, 10);
+ vmsg("你不夠資深喔! (可按大寫 I 查看限制)");
+ return;
+ }
+ setbpath(fpath, currboard);
+ stampfile(fpath, &votefile);
+
+ setbpath(oldfpath, currboard);
+
+ strcat(oldfpath, "/");
+ strcat(oldfpath, fhdr->filename);
+
+ fi = fopen(oldfpath, "r");
+ assert(fi);
+
+ while (fgets(genbuf, sizeof(genbuf), fi)) {
+ char *space;
+
+ if (yes>=0)
+ {
+ if (!strncmp(genbuf, "----------",10))
+ {yes=-1; continue;}
+ else
+ yes++;
+ }
+ if (yes>3) outs(genbuf);
+
+ if (!strncmp(genbuf, "連署結束時間", 12)) {
+ ptr = strchr(genbuf, '(');
+ assert(ptr);
+ sscanf(ptr + 1, "%lu", &endtime);
+ if (endtime < (unsigned long)now) {
+ vmsg("連署時間已過");
+ fclose(fi);
+ return;
+ }
+ }
+ if(yes>=0) continue;
+
+ space = strpbrk(genbuf+4, " \n");
+ if(space) *space='\0';
+ if (!strncmp(genbuf + 4, cuser.userid, IDLEN)) {
+ move(5, 10);
+ outs("您已經連署過本篇了");
+ getdata(17, 0, "要修改您之前的連署嗎?(Y/N) [N]", opnion, 3, LCECHO);
+ if (opnion[0] != 'y') {
+ fclose(fi);
+ return;
+ }
+ strlcpy(reason, genbuf + 19, 34);
+ break;
+ }
+ }
+ fclose(fi);
+ do {
+ if (!getdata(19, 0, "請問您 (Y)支持 (N)反對 這個議題:", opnion, 3, LCECHO)) {
+ return;
+ }
+ } while (opnion[0] != 'y' && opnion[0] != 'n');
+ sprintf(genbuf, "請問您與這個議題的關係或%s理由為何:",
+ opnion[0] == 'y' ? "支持" : "反對");
+ if (!getdata_buf(20, 0, genbuf, reason, 35, DOECHO)) {
+ return;
+ }
+ if ((fd = open(oldfpath, O_RDONLY)) == -1)
+ return;
+ if(flock(fd, LOCK_EX)==-1 )
+ {close(fd); return;}
+ if(!(fi = fopen(oldfpath, "r")))
+ {flock(fd, LOCK_UN); close(fd); return;}
+
+ if(!(fo = fopen(fpath, "w")))
+ {
+ flock(fd, LOCK_UN);
+ close(fd);
+ fclose(fi);
+ return;
+ }
+
+ while (fgets(genbuf, sizeof(genbuf), fi)) {
+ if (!strncmp("----------", genbuf, 10))
+ break;
+ fputs(genbuf, fo);
+ }
+ if (!endtime) {
+ now += 14 * 24 * 60 * 60;
+ fprintf(fo, "連署結束時間: (%d)%s\n", now, ctime4(&now));
+ now -= 14 * 24 * 60 * 60;
+ }
+ fputs(genbuf, fo);
+ len = strlen(cuser.userid);
+ for(yes=0; fgets(genbuf, sizeof(genbuf), fi);) {
+ if (!strncmp("----------", genbuf, 10))
+ break;
+ if (strlen(genbuf)<30 || (genbuf[4+len]==' ' && !strncmp(genbuf + 4, cuser.userid, len)))
+ continue;
+ fprintf(fo, "%3d.%s", ++yes, genbuf + 4);
+ }
+ if (opnion[0] == 'y')
+ fprintf(fo, "%3d.%-15s%-34s 來源:%s\n", ++yes, cuser.userid, reason, cuser.lasthost);
+ fputs(genbuf, fo);
+
+ for(no=0; fgets(genbuf, sizeof(genbuf), fi);) {
+ if (!strncmp("----------", genbuf, 10))
+ break;
+ if (strlen(genbuf)<30 || (genbuf[4+len]==' ' && !strncmp(genbuf + 4, cuser.userid, len)))
+ continue;
+ fprintf(fo, "%3d.%s", ++no, genbuf + 4);
+ }
+ if (opnion[0] == 'n')
+ fprintf(fo, "%3d.%-15s%-34s 來源:%s\n", ++no, cuser.userid, reason, cuser.lasthost);
+ fprintf(fo, "----------總計----------\n");
+ fprintf(fo, "支持人數:%-9d反對人數:%-9d\n", yes, no);
+ fprintf(fo, "\n--\n※ 發信站 :" BBSNAME "(" MYHOSTNAME
+ ") \n◆ From: 連署文章\n");
+
+ flock(fd, LOCK_UN);
+ close(fd);
+ fclose(fi);
+ fclose(fo);
+ unlink(oldfpath);
+ rename(fpath, oldfpath);
+}
+
+int
+do_voteboard(int type)
+{
+ fileheader_t votefile;
+ char topic[100];
+ char title[80];
+ char genbuf[1024];
+ char fpath[80];
+ FILE *fp;
+ int temp;
+
+ clear();
+ if (!CheckPostPerm()) {
+ move(5, 10);
+ vmsg("對不起,您目前無法在此發表文章!");
+ return FULLUPDATE;
+ }
+ if ( cuser.firstlogin > (now - (time4_t)bcache[currbid - 1].vote_limit_regtime * 2592000) ||
+ cuser.badpost > (255 - (unsigned int)(bcache[currbid - 1].vote_limit_badpost)) ||
+ cuser.numlogins < ((unsigned int)(bcache[currbid - 1].vote_limit_logins) * 10) ||
+ cuser.numposts < ((unsigned int)(bcache[currbid - 1].vote_limit_posts) * 10) ) {
+ move(5, 10);
+ vmsg("你不夠資深喔! (可按大寫 I 查看限制)");
+ return FULLUPDATE;
+ }
+ move(0, 0);
+ clrtobot();
+ outs("您正在使用 PTT 的連署系統\n");
+ outs("本連署系統將詢問您一些問題,請小心回答才能開始連署\n");
+ outs("任意提出連署案者,將被列入不受歡迎使用者喔\n");
+ move(4, 0);
+ clrtobot();
+ outs("(1)活動連署 (2)記名公投 ");
+ if(type==0)
+ outs("(3)申請新板 (4)廢除舊板 (5)連署板主 \n(6)罷免板主 (7)連署小組長 (8)罷免小組長 (9)申請新群組\n");
+
+ do {
+ getdata(6, 0, "請輸入連署類別 [0:取消]:", topic, 3, DOECHO);
+ temp = atoi(topic);
+ } while (temp < 0 || temp > 9 || (type && temp>2));
+ switch (temp) {
+ case 0:
+ return FULLUPDATE;
+ case 1:
+ if (!getdata(7, 0, "請輸入活動主題:", topic, 30, DOECHO))
+ return FULLUPDATE;
+ snprintf(title, sizeof(title), "%s %s", "[活動連署]", topic);
+ snprintf(genbuf, sizeof(genbuf),
+ "%s\n\n%s%s\n", "活動連署", "活動主題: ", topic);
+ strcat(genbuf, "\n活動內容: \n");
+ break;
+ case 2:
+ if (!getdata(7, 0, "請輸入公投主題:", topic, 30, DOECHO))
+ return FULLUPDATE;
+ snprintf(title, sizeof(title), "%s %s", "[記名公投]", topic);
+ snprintf(genbuf, sizeof(genbuf),
+ "%s\n\n%s%s\n", "記名公投", "公投主題: ", topic);
+ strcat(genbuf, "\n公投原因: \n");
+ break;
+ case 3:
+ do {
+ if (!getdata(7, 0, "請輸入看板英文名稱:", topic, IDLEN + 1, DOECHO))
+ return FULLUPDATE;
+ else if (invalid_brdname(topic))
+ outs("不是正確的看板名稱");
+ else if (getbnum(topic) > 0)
+ outs("本名稱已經存在");
+ else
+ break;
+ } while (temp > 0);
+ snprintf(title, sizeof(title), "[申請新板] %s", topic);
+ snprintf(genbuf, sizeof(genbuf),
+ "%s\n\n%s%s\n%s", "申請新板", "英文名稱: ", topic, "中文名稱: ");
+
+ if (!getdata(8, 0, "請輸入看板中文名稱:", topic, BTLEN + 1, DOECHO))
+ return FULLUPDATE;
+ strcat(genbuf, topic);
+ strcat(genbuf, "\n看板類別: ");
+ if (!getdata(9, 0, "請輸入看板類別:", topic, 20, DOECHO))
+ return FULLUPDATE;
+ strcat(genbuf, topic);
+ strcat(genbuf, "\n板主名單: ");
+ getdata(10, 0, "請輸入板主名單:", topic, IDLEN * 3 + 3, DOECHO);
+ strcat(genbuf, topic);
+ strcat(genbuf, "\n申請原因: \n");
+ break;
+ case 4:
+ move(1,0); clrtobot();
+ generalnamecomplete("請輸入看板英文名稱:",
+ topic, IDLEN+1,
+ SHM->Bnumber,
+ &completeboard_compar,
+ &completeboard_permission,
+ &completeboard_getname);
+ snprintf(title, sizeof(title), "[廢除舊板] %s", topic);
+ snprintf(genbuf, sizeof(genbuf),
+ "%s\n\n%s%s\n", "廢除舊板", "英文名稱: ", topic);
+ strcat(genbuf, "\n廢除原因: \n");
+ break;
+ case 5:
+ move(1,0); clrtobot();
+ generalnamecomplete("請輸入看板英文名稱:",
+ topic, IDLEN+1,
+ SHM->Bnumber,
+ &completeboard_compar,
+ &completeboard_permission,
+ &completeboard_getname);
+ snprintf(title, sizeof(title), "[連署板主] %s", topic);
+ snprintf(genbuf, sizeof(genbuf), "%s\n\n%s%s\n%s%s", "連署板主", "英文名稱: ", topic, "申請 ID : ", cuser.userid);
+ strcat(genbuf, "\n申請政見: \n");
+ break;
+ case 6:
+ move(1,0); clrtobot();
+ generalnamecomplete("請輸入看板英文名稱:",
+ topic, IDLEN+1,
+ SHM->Bnumber,
+ &completeboard_compar,
+ &completeboard_permission,
+ &completeboard_getname);
+ snprintf(title, sizeof(title), "[罷免板主] %s", topic);
+ snprintf(genbuf, sizeof(genbuf),
+ "%s\n\n%s%s\n%s", "罷免板主", "英文名稱: ",
+ topic, "板主 ID : ");
+ temp=getbnum(topic);
+ assert(0<=temp-1 && temp-1<MAX_BOARD);
+ do {
+ if (!getdata(7, 0, "請輸入板主ID:", topic, IDLEN + 1, DOECHO))
+ return FULLUPDATE;
+ }while (!userid_is_BM(topic, bcache[temp - 1].BM));
+ strcat(genbuf, topic);
+ strcat(genbuf, "\n罷免原因: \n");
+ break;
+ case 7:
+ if (!getdata(7, 0, "請輸入小組中英文名稱:", topic, 30, DOECHO))
+ return FULLUPDATE;
+ snprintf(title, sizeof(title), "[連署小組長] %s", topic);
+ snprintf(genbuf, sizeof(genbuf),
+ "%s\n\n%s%s\n%s%s", "連署小組長", "小組名稱: ",
+ topic, "申請 ID : ", cuser.userid);
+ strcat(genbuf, "\n申請政見: \n");
+ break;
+ case 8:
+ if (!getdata(7, 0, "請輸入小組中英文名稱:", topic, 30, DOECHO))
+ return FULLUPDATE;
+ snprintf(title, sizeof(title), "[罷免小組長] %s", topic);
+ snprintf(genbuf, sizeof(genbuf), "%s\n\n%s%s\n%s",
+ "罷免小組長", "小組名稱: ", topic, "小組長 ID : ");
+ if (!getdata(8, 0, "請輸入小組長ID:", topic, IDLEN + 1, DOECHO))
+ return FULLUPDATE;
+ strcat(genbuf, topic);
+ strcat(genbuf, "\n罷免原因: \n");
+ break;
+ case 9:
+ if (!getdata(7, 0, "請輸入群組中英文名稱:", topic, 30, DOECHO))
+ return FULLUPDATE;
+ snprintf(title, sizeof(title), "[申請新群組] %s", topic);
+ snprintf(genbuf, sizeof(genbuf), "%s\n\n%s%s\n%s%s",
+ "申請群組", "群組名稱: ", topic, "申請 ID : ", cuser.userid);
+ strcat(genbuf, "\n申請政見: \n");
+ break;
+ default:
+ return FULLUPDATE;
+ }
+ outs("請輸入簡介或政見(至多五行),要清楚填寫");
+ for (temp = 12; temp < 17; temp++) {
+ if (!getdata(temp, 0, ":", topic, 60, DOECHO))
+ break;
+ strcat(genbuf, topic);
+ strcat(genbuf, "\n");
+ }
+ if (temp == 11)
+ return FULLUPDATE;
+ strcat(genbuf, "連署結束時間: ");
+ now += 14 * 24 * 60 * 60;
+ snprintf(topic, sizeof(topic), "(%d)", now);
+ strcat(genbuf, topic);
+ strcat(genbuf, ctime4(&now));
+ strcat(genbuf, "\n");
+ now -= 14 * 24 * 60 * 60;
+ strcat(genbuf, "----------支持----------\n");
+ strcat(genbuf, "----------反對----------\n");
+ outs("開始連署嘍");
+ setbpath(fpath, currboard);
+ stampfile(fpath, &votefile);
+
+ if (!(fp = fopen(fpath, "w"))) {
+ outs("開檔失敗,請稍候重來一次");
+ return FULLUPDATE;
+ }
+ fprintf(fp, "%s%s %s%s\n%s%s\n%s%s\n", "作者: ", cuser.userid,
+ "看板: ", currboard,
+ "標題: ", title,
+ "時間: ", ctime4(&now));
+ fprintf(fp, "%s\n", genbuf);
+ fclose(fp);
+ strlcpy(votefile.owner, cuser.userid, sizeof(votefile.owner));
+ strlcpy(votefile.title, title, sizeof(votefile.title));
+ votefile.filemode |= FILE_VOTE;
+ /* use lower 16 bits of 'money' to store limits */
+ /* lower 8 bits are posts, higher 8 bits are logins */
+ votefile.multi.vote_limits.regtime = bcache[currbid - 1].vote_limit_regtime;
+ votefile.multi.vote_limits.logins = bcache[currbid - 1].vote_limit_logins;
+ votefile.multi.vote_limits.posts = bcache[currbid - 1].vote_limit_posts;
+ votefile.multi.vote_limits.badpost = bcache[currbid - 1].vote_limit_badpost;
+ setbdir(genbuf, currboard);
+ if (append_record(genbuf, &votefile, sizeof(votefile)) != -1)
+ setbtotal(currbid);
+ do_voteboardreply(&votefile);
+ return FULLUPDATE;
+}
diff --git a/pttbbs/mbbsd/xyz.c b/pttbbs/mbbsd/xyz.c
new file mode 100644
index 00000000..0b6b1141
--- /dev/null
+++ b/pttbbs/mbbsd/xyz.c
@@ -0,0 +1,376 @@
+/* $Id$ */
+#include "bbs.h"
+
+#if 0
+/* 各種統計及相關資訊列表 */
+/* Ptt90年度大學聯招查榜系統 */
+int
+x_90(void)
+{
+ use_dict("(90)准考證號/姓名/學校/科系/類組", "etc/90");
+ return 0;
+}
+
+/* Ptt89年度大學聯招查榜系統 */
+int
+x_89(void)
+{
+ use_dict("(89)准考證號/姓名/學校/科系/類組", "etc/89");
+ return 0;
+}
+/* Ptt88年度大學聯招查榜系統 */
+int
+x_88(void)
+{
+ use_dict("(88)准考證號/姓名/學校/科系/類組", "etc/88");
+ return 0;
+}
+/* Ptt87年度大學聯招查榜系統 */
+int
+x_87(void)
+{
+ use_dict("(87)准考證號/姓名/學校/科系", "etc/87");
+ return 0;
+}
+
+/* Ptt86年度大學聯招查榜系統 */
+int
+x_86(void)
+{
+ use_dict("(86)准考證號/姓名/學校/科系", "etc/86");
+ return 0;
+}
+
+#endif
+int
+x_boardman(void)
+{
+ more("etc/topboardman", YEA);
+ return 0;
+}
+
+int
+x_user100(void)
+{
+ more("etc/topusr100", YEA);
+ return 0;
+}
+
+int
+x_history(void)
+{
+ more("etc/history", YEA);
+ return 0;
+}
+
+#ifdef HAVE_X_BOARDS
+static int
+x_boards(void)
+{
+ more("etc/topboard.tmp", YEA);
+ return 0;
+}
+#endif
+
+int
+x_birth(void)
+{
+ more("etc/birth.today", YEA);
+ return 0;
+}
+
+int
+x_weather(void)
+{
+ more("etc/weather.tmp", YEA);
+ return 0;
+}
+
+int
+x_mrtmap(void)
+{
+ more("etc/MRT.map", YEA);
+ return 0;
+}
+
+int
+x_stock(void)
+{
+ more("etc/stock.tmp", YEA);
+ return 0;
+}
+
+int
+x_note(void)
+{
+ more(fn_note_ans, YEA);
+ return 0;
+}
+
+int
+x_issue(void)
+{
+ more("etc/day", YEA);
+ return 0;
+}
+
+int
+x_week(void)
+{
+ more("etc/week", YEA);
+ return 0;
+}
+
+int
+x_today(void)
+{
+ more("etc/today", YEA);
+ return 0;
+}
+
+int
+x_yesterday(void)
+{
+ more("etc/yesterday", YEA);
+ return 0;
+}
+
+int
+x_login(void)
+{
+ more("etc/Welcome_login.0", YEA);
+ return 0;
+}
+
+#ifdef HAVE_INFO
+static int
+x_program(void)
+{
+ more("etc/version", YEA);
+ return 0;
+}
+#endif
+
+#ifdef HAVE_LICENSE
+static int
+x_gpl(void)
+{
+ more("etc/GPL", YEA);
+ return 0;
+}
+#endif
+
+int
+note(void)
+{
+ char *fn_note_tmp = "note.tmp";
+ char *fn_note_dat = "note.dat";
+ int total = 0, i, collect, len;
+ struct stat st;
+ char buf[256], buf2[80];
+ int fd, fx;
+ FILE *fp, *foo;
+
+ typedef struct notedata_t {
+ time4_t date;
+ char userid[IDLEN + 1];
+ char nickname[19];
+ char buf[3][80];
+ } notedata_t;
+ notedata_t myitem;
+
+ if (cuser.money < 5) {
+ vmsg(ANSI_COLOR(1;41) " 哎呀! 要投五銀才能留言...沒錢耶.." ANSI_RESET);
+ return 0;
+ }
+ setutmpmode(EDNOTE);
+ do {
+ myitem.buf[0][0] = myitem.buf[1][0] = myitem.buf[2][0] = '\0';
+ move(12, 0);
+ clrtobot();
+ outs("\n投五銀... 嗶... 請留言 (至多三行),按[Enter]結束");
+ for (i = 0; (i < 3) && getdata(16 + i, 0, ":", myitem.buf[i],
+ sizeof(myitem.buf[i]) - 5, DOECHO)
+ && *myitem.buf[i]; i++);
+ getdata(b_lines - 1, 0, "(S)儲存 (E)重新來過 (Q)取消?[S] ",
+ buf, 3, LCECHO);
+
+ if (buf[0] == 'q' || (i == 0 && *buf != 'e'))
+ return 0;
+ } while (buf[0] == 'e');
+ demoney(-5);
+ strcpy(myitem.userid, cuser.userid);
+ strlcpy(myitem.nickname, cuser.nickname, sizeof(myitem.nickname));
+ myitem.date = now;
+
+ /* begin load file */
+ if ((foo = fopen(".note", "a")) == NULL)
+ return 0;
+
+ if ((fp = fopen(fn_note_ans, "w")) == NULL) {
+ fclose(fp);
+ return 0;
+ }
+
+ if ((fx = open(fn_note_tmp, O_WRONLY | O_CREAT, 0644)) <= 0) {
+ fclose(foo);
+ fclose(fp);
+ return 0;
+ }
+
+ if ((fd = open(fn_note_dat, O_RDONLY)) == -1)
+ total = 1;
+ else if (fstat(fd, &st) != -1) {
+ total = st.st_size / sizeof(notedata_t) + 1;
+ if (total > MAX_NOTE)
+ total = MAX_NOTE;
+ }
+ fputs(ANSI_COLOR(1;31;44) "☉┬──────────────┤"
+ ANSI_COLOR(37) "酸甜苦辣板" ANSI_COLOR(31) "├──────────────┬☉"
+ ANSI_RESET "\n", fp);
+ collect = 1;
+
+ while (total) {
+ snprintf(buf, sizeof(buf), ANSI_COLOR(1;31) "摃t" ANSI_COLOR(32) " %s " ANSI_COLOR(37) "(%s)",
+ myitem.userid, myitem.nickname);
+ len = strlen(buf);
+
+ for (i = len; i < 71; i++)
+ strcat(buf, " ");
+ snprintf(buf2, sizeof(buf2), " " ANSI_COLOR(1;36) "%.16s" ANSI_COLOR(31) " ├" ANSI_RESET "\n",
+ Cdate(&(myitem.date)));
+ strcat(buf, buf2);
+ fputs(buf, fp);
+ if (collect)
+ fputs(buf, foo);
+ for (i = 0; i < 3 && *myitem.buf[i]; i++) {
+ fprintf(fp, ANSI_COLOR(1;31) "│" ANSI_RESET "%-74.74s" ANSI_COLOR(1;31) "│" ANSI_RESET "\n",
+ myitem.buf[i]);
+ if (collect)
+ fprintf(foo, ANSI_COLOR(1;31) "│" ANSI_RESET "%-74.74s" ANSI_COLOR(1;31) "│" ANSI_RESET "\n",
+ myitem.buf[i]);
+ }
+ fputs(ANSI_COLOR(1;31) "聝s───────────────────────"
+ "────────────┬" ANSI_RESET "\n", fp);
+
+ if (collect) {
+ fputs(ANSI_COLOR(1;31) "聝s─────────────────────"
+ "──────────────┬" ANSI_RESET "\n", foo);
+ fclose(foo);
+ collect = 0;
+ }
+ write(fx, &myitem, sizeof(myitem));
+
+ if (--total)
+ read(fd, (char *)&myitem, sizeof(myitem));
+ }
+ fputs(ANSI_COLOR(1;31;44) "☉┴───────────────────────"
+ "────────────┴☉" ANSI_RESET "\n", fp);
+ fclose(fp);
+ close(fd);
+ close(fx);
+ Rename(fn_note_tmp, fn_note_dat);
+ more(fn_note_ans, YEA);
+ return 0;
+}
+
+static void
+mail_sysop(void)
+{
+ FILE *fp;
+ char genbuf[200];
+
+ if ((fp = fopen("etc/sysop", "r"))) {
+ int i, j;
+ char *ptr;
+
+ typedef struct sysoplist_t {
+ char userid[IDLEN + 1];
+ char duty[40];
+ } sysoplist_t;
+ sysoplist_t sysoplist[9];
+
+ j = 0;
+ while (fgets(genbuf, 128, fp)) {
+ if ((ptr = strchr(genbuf, '\n'))) {
+ *ptr = '\0';
+ if ((ptr = strchr(genbuf, ':'))) {
+ *ptr = '\0';
+ do {
+ i = *++ptr;
+ } while (i == ' ' || i == '\t');
+ if (i) {
+ strcpy(sysoplist[j].userid, genbuf);
+ strcpy(sysoplist[j++].duty, ptr);
+ }
+ }
+ }
+ }
+ fclose(fp);
+
+ move(12, 0);
+ clrtobot();
+ outs(" 編號 站長 ID 權責劃分\n\n");
+
+ for (i = 0; i < j; i++)
+ prints("%15d. " ANSI_COLOR(1;%d) "%-16s%s" ANSI_COLOR(0) "\n",
+ i + 1, 31 + i % 7, sysoplist[i].userid, sysoplist[i].duty);
+ prints("%-14s0. " ANSI_COLOR(1;%d) "離開" ANSI_COLOR(0) "", "", 31 + j % 7);
+ getdata(b_lines - 1, 0, " 請輸入代碼[0]:",
+ genbuf, 4, DOECHO);
+ i = genbuf[0] - '0' - 1;
+ if (i >= 0 && i < j) {
+ clear();
+ do_send(sysoplist[i].userid, NULL);
+ }
+ }
+}
+
+int
+m_sysop(void)
+{
+ setutmpmode(MSYSOP);
+ mail_sysop();
+ return 0;
+}
+
+int
+Goodbye(void)
+{
+ char genbuf[100];
+
+ getdata(b_lines - 1, 0, "您確定要離開【 " BBSNAME " 】嗎(Y/N)?[N] ",
+ genbuf, 3, LCECHO);
+
+ if (*genbuf != 'y')
+ return 0;
+
+ movie(999999);
+ if (cuser.userlevel) {
+ getdata(b_lines - 1, 0,
+ "(G)隨風而逝 (M)托夢站長 (N)酸甜苦辣流言板?[G] ",
+ genbuf, 3, LCECHO);
+ if (genbuf[0] == 'm')
+ mail_sysop();
+ else if (genbuf[0] == 'n')
+ note();
+ }
+ clear();
+
+
+ more("etc/Logout", NA);
+
+ {
+ int diff = (now - login_start_time) / 60;
+ sprintf(genbuf, "此次停留時間: %d 小時 %2d 分",
+ diff / 60, diff % 60);
+ }
+ if(!(cuser.userlevel & PERM_LOGINOK))
+ vmsg("尚未完成註冊。如要提昇權限請參考本站公佈欄辦理註冊");
+ else
+ vmsg(genbuf);
+ // pressanykey();
+
+ u_exit("EXIT ");
+ return QUIT;
+}
diff --git a/pttbbs/pttbbs.mk b/pttbbs/pttbbs.mk
new file mode 100644
index 00000000..7f88be49
--- /dev/null
+++ b/pttbbs/pttbbs.mk
@@ -0,0 +1,81 @@
+# $Id$
+# 定義基本初值
+BBSHOME?= $(HOME)
+BBSHOME?= /home/bbs
+
+OS!= uname
+OS_MAJOR_VER!= uname -r|cut -d . -f 1
+OS_MINOR_VER!= uname -r|cut -d . -f 2
+OSTYPE?= $(OS)
+
+CC= gcc
+CCACHE!= which ccache|sed -e 's/^.*\///'
+PTT_CFLAGS= -Wall -pipe -DBBSHOME='"$(BBSHOME)"' -I../include
+PTT_LDFLAGS= -L/usr/local/lib
+PTT_LIBS= -lhz
+
+# enable assert()
+#PTT_CFLAGS+= -DNDEBUG
+
+# FreeBSD特有的環境
+CFLAGS_FreeBSD= -I/usr/local/include
+LDFLAGS_FreeBSD=
+LIBS_FreeBSD= -lkvm -liconv
+
+# Linux特有的環境
+CFLAGS_Linux=
+LDFLAGS_Linux=
+LIBS_Linux=
+
+# SunOS特有的環境
+CFLAGS_Solaris= -DSolaris -I/usr/local/include
+LDFLAGS_Solaris= -L/usr/local/lib -L/usr/lib
+LIBS_Solaris= -lnsl -lsocket -liconv -lkstat
+
+OS_FLAGS= -D__OS_MAJOR_VERSION__="$(OS_MAJOR_VER)" \
+ -D__OS_MINOR_VERSION__="$(OS_MINOR_VER)"
+
+# CFLAGS, LDFLAGS, LIBS 加入 OS 相關參數
+PTT_CFLAGS+= $(CFLAGS_$(OSTYPE)) $(OS_FLAGS)
+PTT_LDFLAGS+= $(LDFLAGS_$(OSTYPE))
+PTT_LIBS+= $(LIBS_$(OSTYPE))
+
+
+# 若有定義 PROFILING
+.if defined(PROFILING)
+PTT_CFLAGS+= -pg
+PTT_LDFLAGS+= -pg
+NO_OMITFP= yes
+NO_FORK= yes
+.endif
+
+# 若有定義 DEBUG, 則在 CFLAGS內定義 DEBUG
+.if defined(DEBUG)
+GDB= 1
+#CFLAGS+= -DDEBUG
+PTT_CFLAGS+= -DDEBUG
+.endif
+
+.if defined(USE_ICC)
+CC= icc
+CFLAGS= $(PTT_CFLAGS) -O1 -tpp6 -mcpu=pentiumpro -march=pentiumiii \
+ -ip -ipo
+LDFLAGS+= -O1 -tpp6 -mcpu=pentiumpro -march=pentiumiii -ip -ipo \
+ $(PTT_LDFLAGS) $(PTT_LIBS)
+.elif defined(GDB)
+CFLAGS= -g -O0 $(PTT_CFLAGS)
+LDFLAGS= -O0 $(PTT_LDFLAGS) $(PTT_LIBS)
+.else
+CFLAGS+= -g -Os $(PTT_CFLAGS) $(EXT_CFLAGS)
+LDFLAGS+= -Os $(PTT_LDFLAGS) $(PTT_LIBS)
+
+.if defined(OMITFP)
+CFLAGS+= -fomit-frame-pointer
+.endif
+.endif
+
+
+# 若有定義 NO_FORK, 則在 CFLAGS內定義 NO_FORK
+.if defined(NO_FORK)
+CFLAGS+= -DNO_FORK
+.endif
diff --git a/pttbbs/sample/FILTERMAIL.pm b/pttbbs/sample/FILTERMAIL.pm
new file mode 100644
index 00000000..2b71a917
--- /dev/null
+++ b/pttbbs/sample/FILTERMAIL.pm
@@ -0,0 +1,23 @@
+#!/usr/bin/perl
+# $Id$
+# 本範例僅供參考, 配合 util/filtermail.pl 使用.
+# 請依自行需要改寫後放置於 /home/bbs/bin 下.
+# checkheader() 或 checkbody() 傳回為假時表示直接丟掉該封信.
+package FILTERMAIL;
+
+sub checkheader
+{
+ return 0
+ if( $_[0] =~ /^Subject: .*行銷光碟/im ||
+ $_[0] =~ /^From: .*SpamCompany\.com/im );
+ 1;
+}
+
+sub checkbody
+{
+ return 0
+ if( $_[0] =~ /<script language=\"JavaScript\"/im );
+ 1;
+}
+
+1;
diff --git a/pttbbs/sample/LocalVars.pm b/pttbbs/sample/LocalVars.pm
new file mode 100644
index 00000000..7b5d7c9f
--- /dev/null
+++ b/pttbbs/sample/LocalVars.pm
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+package LocalVars;
+require Exporter;
+@ISA = qw/Exporter/;
+@EXPORT = qw/
+ $hostname $MYHOSTNAME $FQDN $SMTPSERVER
+ $BBSHOME $JOBSPOOL $TMP
+ $TAR $LYNX $GREP
+ $BLOGDATA $BLOGCACHE
+ $BLOGdbname $BLOGdbhost $BLOGdbuser $BLOGdbpasswd $BLOGdefault
+ $MANDATA $MANIDX $MANCACHE
+/;
+
+# host
+$hostname = 'ptt';
+$MYHOSTNAME = 'ptt.cc';
+$FQDN = 'ptt.cc';
+$SMTPSERVER = 'ptt2.cc';
+
+# dir
+$BBSHOME = '/home/bbs';
+$JOBSPOOL = "$BBSHOME/jobspool";
+$TMP = '/tmp';
+
+# program
+$TAR = '/usr/bin/tar';
+$LYNX = '/usr/local/bin/lynx'; # /usr/ports/www/lynx
+$GREP = '/usr/bin/grep';
+
+# BLOG:
+# $BLOGDATA 是用來放置 Blog 資料的路徑
+# $BLOGCACHE是用來放置 Template compiled 資料的路徑,
+# 須為 apache owner 可寫入
+$BLOGDATA = '/home/bbs/blog/data';
+$BLOGCACHE = '/home/bbs/blog/cache';
+$BLOGdbname = 'myblog';
+$BLOGdbhost = 'localhost';
+$BLOGdbuser = 'root';
+$BLOGdbpasswd = '';
+$BLOGdefault = 'Blog';
+
+$MANDATA = '/home/web/ptt.man.data';
+$MANIDX = '/home/web/ptt.man.data';
+$MANCACHE = '/home/web/ptt.man.cache';
+
+1;
diff --git a/pttbbs/sample/Makefile b/pttbbs/sample/Makefile
new file mode 100644
index 00000000..832eba59
--- /dev/null
+++ b/pttbbs/sample/Makefile
@@ -0,0 +1,11 @@
+SUBDIR=etc innd
+BBSHOME?=$(HOME)
+
+all:
+
+install:
+ @for i in $(SUBDIR); do\
+ cd $$i;\
+ $(MAKE) BBSHOME=$(BBSHOME) $@;\
+ cd ..;\
+ done
diff --git a/pttbbs/sample/README.BLOG b/pttbbs/sample/README.BLOG
new file mode 100644
index 00000000..fddeae1d
--- /dev/null
+++ b/pttbbs/sample/README.BLOG
@@ -0,0 +1,18 @@
+歡迎進入批踢踢兔布落格的世界!
+
+系統已經在您看板的第一層目錄下建立了名為「Blog」的目錄 (請麻煩先離開精華區
+再進來) , 在該目錄內目前只有一個名為「configure」 , 內放置一些相關的設定,
+您會需要編輯 (請在該項目前按下大寫E ) 「config」這個檔案, 修正一些資訊 (包
+括 BOARDNAME, BANNER, SINCE, wikibase 等等) . 存檔後按下大寫 B, 選擇「1.製
+作部落格樣板格式」. 如此就完成初始化您布落格的功能.
+
+開一個瀏覽器, 連到 http://您的ID.blog.ptt2.cc 看看!
+
+您只要將文章放到 Blog 的目錄裡面, 按照標題設為「日期, 篇名」
+ (例如: 2004.01.01, 我的第一篇部落格! )
+在文章前面按下小寫 b, 就會將這篇文章加入部落格中.
+
+其他大部份的功能都放在大寫 B裡面,
+或是您可以在 Blog 這個看板內找到相關的說明以及協助.
+您同時可以在您 Blog 目錄內的「2004.01.01, 我的第一次部落格! 」
+或是看板 Blog 內的 Blog.Default 這個目錄中找到這篇文章.
diff --git a/pttbbs/sample/crontab b/pttbbs/sample/crontab
new file mode 100644
index 00000000..71521202
--- /dev/null
+++ b/pttbbs/sample/crontab
@@ -0,0 +1,119 @@
+# 信件紀錄
+20 7 * * * bin/mailog.sh
+
+# 每天 2:00, 11:00, 16:00, 21:00 開獎
+0 2,11,16,21 * * * bin/openticket.sh
+
+# 每天早上清除過期使用者
+# warning: 目前這個程式無法正常運作, 且可能會搞爛整個密碼檔!!
+#10 7 * * * bin/reaper
+
+# 每個小時 1分執行上站人次統計
+1 * * * * bin/account
+
+# 每個小時 10 分執行熱門話題統計
+10 * * * * bin/poststat /home/bbs
+
+# 每天 5:30 執行使用者排行榜更新
+30 5 * * * bin/topusr 10 etc/topusr
+30 5 * * * bin/topusr 100 etc/topusr100
+30 5 * * * bin/yearsold
+30 5 * * * bin/horoscope
+
+# 每個月初一, 十五點歌排行榜
+20 6 1,15 * * bin/topsong.sh
+
+# 每週三, 六統計轉信所有板
+35 6 * * 6 bin/showboard .BOARDS > etc/BOARD.rec
+
+# 每天清 logins.bad
+20 3 * * * /bin/rm -f logins.bad
+
+# 清版 (視情況用每天或每週)
+20 3 * * 1 bin/expire
+
+# 清使用者目錄
+1 7 * * 1 bin/deluserfile
+
+# 每週二早上 4:30 , 將 BBS boards 中超過七天的 SR. 系列及 ~/tmp/的檔案砍掉,
+30 4 * * 2 /usr/bin/find /home/bbs/boards/ -mtime +7 -name SR\* -exec rm -f {} ';'
+30 4 * * 2 /bin/rm -f ~/tmp/*
+
+# 每天執行一次生日程式
+1 2 * * * bin/birth
+
+# 每星期二五砍掉 boards/d/deleted
+20 6 * * 2,5 /bin/rm -rf boards/d/deleted; mkdir boards/d/deleted
+
+# 除每月一號整個重新計算精華區, 其他都只在星期二, 四, 六算有更動過的
+# 計算前先砍掉 deleted的精華區
+20 6 * * * /bin/rm -rf man/boards/d/deleted; mkdir man/boards/d/deleted
+30 6 1 * * bin/mandex
+30 6 2-31 * 2,4,6 bin/mandex -x
+
+# 每天發票開獎
+40 6 * * * bin/openvice
+
+# 每天砍掉點歌超過 5天檔案
+0 7 * * * /usr/bin/find /home/bbs/etc/SONGO/M* -mtime +5 -exec rm -f {} ';'
+
+# 天氣, 股票
+0 5,11,17,23 * * * bin/weather.sh
+20 12 * * * bin/stock.sh
+
+# 每個月十號早上 3:50 , 將 BBS 系統中長度為零的檔案砍掉
+50 3 10 * * /usr/bin/find /home/bbs/boards/*/ -size 0 -exec rm -f {} ';' ; /usr/bin/find /home/bbs/home/*/ -size 0 -exec rm -f {} ';'
+
+# 每天早上 6:50 備份 .PASSWDS, .BRD
+50 6 * * * bin/backpasswd.sh
+
+
+# mrtg每五分鐘計算一次
+#*/5 * * * * bin/shmctl utmpnum > /tmp/utmpnum
+
+# ???
+10 7 * * * bin/buildAnnounce
+#10 7 * * * bin/toplazyBM.sh
+#10 7 * * * bin/toplazyBBM.sh
+#*/5 * * * * bin/shmsweep
+#*/10 * * * * bin/userlist
+
+# 轉信
+*/30 * * * * (kill -0 `cat /tmp/innbbsd-7777.pid` || innd/innbbsd 7777)
+0,30 2-21 * * * innd/bbslink /home/bbs
+0 3 * * * bin/inndBM
+10 3 * * * innd/ctlinnbbsd reload
+17,47 2,4-21 * * * innd/bbsnnrpall.auto.sh
+40 3 * * * /bin/mv innd/bbslog innd/bbslog.old
+40 2 * * * /bin/mv innd/log/inndBM.log innd/log/inndBM.log.old
+40 2 * * * /bin/mv innd/log/inndBM.log.err innd/log/inndBM.log.err.old
+
+# jobspool
+10 3-20 * * * bin/waterball.pl
+30 3 * * * bin/tarqueue.pl
+
+# 每日備份
+30 3 * * * bin/dailybackup.pl
+
+# 每日砍除 ALLPOST
+30 5 * * * /bin/rm boards/A/ALLPOST boards/A/ALLHIDPOST; mkdir boards/A/ALLPOST boards/A/ALLHIDPOST
+
+# for safty
+9 * * * * bin/shmctl bBMC
+
+# rebuildaloha
+25 6 3 * * bin/rebuildaloha.pl > /dev/null
+
+# 過期板友通知
+#17 7 25 * * bin/chkhbf
+
+# utmpfix
+0 2-20 * * * bin/shmctl utmpfix -n
+0 21 * * * bin/shmctl utmpfix -t 10800
+10 21 * * * bin/shmctl utmpfix -t 7200
+20 21 * * * bin/shmctl utmpfix -t 3600
+30,40,50 21 * * * bin/shmctl utmpfix -t 1800
+*/20 22,23,0,1 * * * bin/shmctl utmpfix -t 1200
+
+# 如果開啟 LOGPOST 的話需順便開這個, 每天 rotate log
+#0 6 * * * /bin/mv log/post log/post.old
diff --git a/pttbbs/sample/crontab.old b/pttbbs/sample/crontab.old
new file mode 100644
index 00000000..edec7b89
--- /dev/null
+++ b/pttbbs/sample/crontab.old
@@ -0,0 +1,56 @@
+20 7 * * * bin/mailog.sh > /dev/null
+20 6 * * * rm -f out/*; rm -f out/.DIR
+
+# 每天 12:00 17:00 20:00 00:00 開獎。
+0 2,11,16,21 * * * bin/openticket.sh > /dev/null
+
+# 每天早上清除過期使用者
+10 7 * * * bin/reaper > /dev/null 2> /dev/null
+
+# 每個小時 1 分執行上站人次統計
+1 * * * * bin/account > /dev/null 2> /dev/null
+
+# 每個小時 10 分執行熱門話題統計
+50 * * * * bin/parse_news
+10 * * * * bin/poststat /home/bbs > /dev/null
+
+# 每天5:30執行使用者排行榜更新
+30 5 * * * bin/topusr 10 etc/topusr > /dev/null
+30 3 * * * bin/topusr 100 etc/topusr100 > /dev/null
+30 5 * * * bin/yearsold > /dev/null
+30 5 * * * bin/horoscope
+
+# 每個月一, 十五號點歌排行榜
+20 6 1,15 * * bin/topsong.sh
+
+# 每週三,六統計轉信所有板
+35 6 * * 6 bin/showboard ~/.BOARDS > ~/etc/BOARD.rec
+
+# 每週一早上把user home清一清
+20 3 * * * (/bin/rm -f logins.bad; bin/expire ) > /dev/null
+1 7 * * 1 bin/deluserfile > /dev/null
+
+# 每週二早上 4:30 , 將 BBS boards 中超過七天的 SR. 系列的檔案砍掉
+30 4 * * 2 /usr/bin/find /home/bbs/boards/ -mtime +7 -name SR\* -exec rm -f {} ';'
+30 4 * * 2 /bin/rm -f ~/tmp/*
+
+# 每天執行一次生日程式
+1 2 * * * bin/birth > /dev/null
+
+# 每天精華區index一次 index前砍掉deleted的精華區
+15 6 * * * rm -rf man/boards/deleted
+20 6 * * * bin/mandex > /dev/null
+30 6 * * * bin/openvice > /dev/null
+
+# 每天砍掉點歌超過5天檔案
+40 6 * * * ( find /home/bbs/etc/SONGO/M* -mtime +5 -exec rm -f {} ';' )
+
+#天氣股票
+0 5,11,17,23 * * * bin/weather.sh > /dev/null
+20 12 * * * bin/stock.sh > /dev/null
+
+# 每個月一號早上 3:50 , 將 BBS 系統中長度為零的檔案砍掉
+50 3 1 * * ( /usr/bin/find /home1/bbs -size 0 -exec rm -f {} ';' )
+
+#每天早上 6:30 把bbs中的home和passwords備份
+30 6 * * * bin/backpasswd.sh > /dev/null
diff --git a/pttbbs/sample/etc/@five b/pttbbs/sample/etc/@five
new file mode 100644
index 00000000..8c51c8d6
--- /dev/null
+++ b/pttbbs/sample/etc/@five
@@ -0,0 +1,16 @@
+ A B C D E F G H I J K L M N O
+ 15┌┬┬┬┬┬┬┬┬┬┬┬┬┬┐
+ 14├┼┼┼┼┼┼┼┼┼┼┼┼┼┤ [q] 認輸離開
+ 13├┼┼┼┼┼┼┼┼┼┼┼┼┼┤ [u] 悔棋
+ 12├┼┼┼┼┼┼┼┼┼┼┼┼┼┤ [p] 要求和棋
+ 11├┼┼┼┼┼┼┼┼┼┼┼┼┼┤
+ 10├┼┼┼┼┼┼┼┼┼┼┼┼┼┤
+ 9├┼┼┼┼┼┼┼┼┼┼┼┼┼┤
+ 8├┼┼┼┼┼┼┼┼┼┼┼┼┼┤ [歡迎到five_chess討論五子棋喔]
+ 7├┼┼┼┼┼┼┼┼┼┼┼┼┼┤
+ 6├┼┼┼┼┼┼┼┼┼┼┼┼┼┤
+ 5├┼┼┼┼┼┼┼┼┼┼┼┼┼┤
+ 4├┼┼┼┼┼┼┼┼┼┼┼┼┼┤
+ 3├┼┼┼┼┼┼┼┼┼┼┼┼┼┤
+ 2├┼┼┼┼┼┼┼┼┼┼┼┼┼┤
+ 1└┴┴┴┴┴┴┴┴┴┴┴┴┴┘
diff --git a/pttbbs/sample/etc/Logout b/pttbbs/sample/etc/Logout
new file mode 100644
index 00000000..c1552af8
--- /dev/null
+++ b/pttbbs/sample/etc/Logout
@@ -0,0 +1,20 @@
+
+▆█ ▏  總 監
+ ▍ ▏▋◤ ◥  (root)
+ ▎ ▌▌ PTT   DavidYu
+  ︳ ▏▊◤ ◤ I`ll  
+  / ▋▌  be back!  執行製作
+ / ◢/  ◣   (bbsadm)
+ ▊◢◣●◤ ▆▃▃◣ ◢   DavidYu
+ ▋▂██  ◢ ◤  導 演
+ ▋   (sysop)
+   ─▏   DomaDoma Ricas 
+ ▄▄▂▍ ▍   keelar stary
+ ▋ ◥ ▌ 
+ ▊▅▁ ◥╴ ▎  美術指導
+▇▄▂◥╴╴╴/◥▄▃▁   Skyline(視覺站長) Vica 
+ ◢█◤ ◥  精 華 區
+ ◢◤◢▅▂ ▇▄▂▂▁▂  cbcdf
+ ▋ ▆▂ ◥ ◤  特別感謝
+ ▍ ▆▂▉ ◣  *s
+  Skyjade◣ ◤  (本站的最佳主角喔)
diff --git a/pttbbs/sample/etc/MRT.map b/pttbbs/sample/etc/MRT.map
new file mode 100644
index 00000000..7d8a179f
--- /dev/null
+++ b/pttbbs/sample/etc/MRT.map
@@ -0,0 +1,66 @@
+
+ ●淡水
+
+ ○紅樹林 ════內湖─木柵線
+ ════淡水─信義線
+ ○竹圍  ══新北投支線
+ ════松山─新店線
+ ○關渡  ══小碧潭支線
+ ●新北投 ════新莊─中和線
+ ○忠義  ════蘆洲─中和線
+  ════南港─板橋─土城線
+ 耤郭丐◎══葔 ○ 一般車站
+ 復興崗 北投  ◎ 轉乘站
+ ○奇岩  ● 起迄站
+ 
+ ○唭哩岸
+ 
+ ○石牌
+ 
+ ●蘆洲 ○明德
+  
+ ○三民高中 ○芝山
+    大
+ ○徐匯中學 ○士林 劍 湖
+    南 西 港 文 內 公
+ ○三和國中 ○劍潭 路 湖 墘 德 湖 園
+   摃丑郭丐丑郭丐丑郭丐丑郭丐丑郭丐丑郭丐片
+ ○三重國小 ○圓山 ○大直 葫洲○
+    
+台北橋 民權西路 ○松山機場 
+搳郭片銚丐◎══[1;33◎══○══   東湖○
+  大橋國小  中山國小 行 ○中山國中 矙
+○菜寮 ○雙連 ○天  南港
+  松江宮 南京 市立 南京 軟體園區○
+○三重 中山 南京 東路 體育場 三民 松山 
+ 摃丐丐◎═════◎══◎════○═══○═══● 
+○先嗇宮 ○北門  善    市 搳郭丑郭丑郭◎
+   台北 導 忠孝忠孝 忠孝 國父 政 永後 昆 南 南
+○頭前庄 西門 車站 寺 新生復興 敦化 紀念館 府 春山 陽 港 港
+ ◎═══◎═══○═◎══◎═══○══○══○═○埤 展
+○新莊  台大  大安  覽
+  ○醫院摃丐片森林  世貿 館
+○輔大 小南門  東門 公園大安 安和路 中心 象山
+ 耤郭丐◎══◎═══○═◎════○═══○══●
+○丹鳳  中正  古亭 
+  紀念堂聝丐◎═══ ○科技大樓
+●迴龍 摃片  聝丐丐丐丐片
+  ○龍山寺 ○頂溪 ○台電大樓 ○六張犁
+    
+  ○江子翠 ○永安市場 ○公館 ○麟光
+    
+ ○新埔 ○景安  ○萬隆 ○辛亥
+    
+ ○板橋 ●南勢角 ○景美 ○萬芳醫院
+   聝丐丑郭丐丐丑郭丐丐丑斐
+ ○府中 ○大坪林 萬芳社區 木柵 動物園
+  小碧潭 
+ ○亞東醫院 ●══◎七張
+  
+  ○海山 ○新店市公所
+  
+  ○土城 ●新店
+ 
+ ●永寧
+
+ wutonyuugi 作 -- Ptt MRT 板
diff --git a/pttbbs/sample/etc/Makefile b/pttbbs/sample/etc/Makefile
new file mode 100644
index 00000000..c9853374
--- /dev/null
+++ b/pttbbs/sample/etc/Makefile
@@ -0,0 +1,22 @@
+SUBDIR= chess chickens
+BBSHOME?=$(HOME)
+TARGET=$(BBSHOME)/etc/
+FILES= @five Welcome register today_boring \
+ Welcome_birth domain_name_query.cidr registered \
+ ve.hlp Logout Welcome_login expire.conf \
+ registeredmail MRT.map bad_host feast \
+ registermail Makefile banemail goodbye \
+ sysop board.help boardlist.help
+
+all:
+
+install_sub:
+ @for i in $(SUBDIR); do\
+ cd $$i;\
+ $(MAKE) BBSHOME=$(BBSHOME) install;\
+ cd ..;\
+ done
+
+install: install_sub
+ install -d $(TARGET)
+ install -c -m 644 $(FILES) $(TARGET)
diff --git a/pttbbs/sample/etc/Welcome b/pttbbs/sample/etc/Welcome
new file mode 100644
index 00000000..c8abb5a0
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome
@@ -0,0 +1,18 @@
+
+Welcome to ...
+
+
+
+
+
+ ____ _ _ ____ ____ ____
+| _ \| |_| |_ | __ )| __ ) ___|
+| |_) | __| __| | _ \| _ \___ \
+| __/| |_| |_ | |_) | |_) |__) |
+|_| \__|\__| |____/|____/____/
+
+
+
+
+
+歡迎蒞臨◎PttBBS◎目前站上有[*u]人
diff --git a/pttbbs/sample/etc/Welcome_birth b/pttbbs/sample/etc/Welcome_birth
new file mode 100644
index 00000000..5ea57404
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_birth
@@ -0,0 +1,23 @@
+ 嗨*s 忙嗎? 不要忘記自己的生日喔!! 記得發表感言... 
+
+  ╭╮╭╮╭──╮╭──╮╭──╮╭╮╭╮
+  │╰╯││ ☆ ││ ☆ ││ ☆ ││╰╯│
+  │╭╮││╭╮││╭─╯│╭─╯╰╮╭╯
+  ╰╯╰╯╰╯╰╯╰╯ ╰╯ ╰╯
+  ╭╮
+ ╭╮ ☆ ╭──╮╭╯╰╮╭╮ ╭╮╭──╮╭╮╭╮
+ │╰─╮╭╮│╭─╯╰╮╭╯│╰─╮╭─╯││ ☆ ││╰╯│
+ │ ☆ │││││ │╰╮│╭╮││ ☆ ││╭╮│╰╮╭╯
+ ╰──╯╰╯╰╯ ╰─╯╰╯╰╯╰──╯╰╯╰╯ ╰╯
+
+贈詩一首送佳人,  ☉ ☉ ☉ ☉ 批踢踢實業坊全體員工
+予時霜雪臘月天;  ╭╮╭╮╭╮╭╮
+祝音輕吟繞東海, ╭┴┴┴┴┴┴┴┴╮ 誠心的祝福!
+汝影蹤跡現南山; │☆★☆★☆★☆★│ 一個人生的另一個階段就此開始
+生機蓬勃冬不斷, ╭┴────────┴╮ 望能一帆風順.
+日轉星移待明春; 澺
+快意暢活人間行, ││
+樂音環身樂自添. ┌┴──────────┴┐
+ └────────────┘
+ ★★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
diff --git a/pttbbs/sample/etc/Welcome_birth.1 b/pttbbs/sample/etc/Welcome_birth.1
new file mode 100644
index 00000000..5ea57404
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_birth.1
@@ -0,0 +1,23 @@
+ 嗨*s 忙嗎? 不要忘記自己的生日喔!! 記得發表感言... 
+
+  ╭╮╭╮╭──╮╭──╮╭──╮╭╮╭╮
+  │╰╯││ ☆ ││ ☆ ││ ☆ ││╰╯│
+  │╭╮││╭╮││╭─╯│╭─╯╰╮╭╯
+  ╰╯╰╯╰╯╰╯╰╯ ╰╯ ╰╯
+  ╭╮
+ ╭╮ ☆ ╭──╮╭╯╰╮╭╮ ╭╮╭──╮╭╮╭╮
+ │╰─╮╭╮│╭─╯╰╮╭╯│╰─╮╭─╯││ ☆ ││╰╯│
+ │ ☆ │││││ │╰╮│╭╮││ ☆ ││╭╮│╰╮╭╯
+ ╰──╯╰╯╰╯ ╰─╯╰╯╰╯╰──╯╰╯╰╯ ╰╯
+
+贈詩一首送佳人,  ☉ ☉ ☉ ☉ 批踢踢實業坊全體員工
+予時霜雪臘月天;  ╭╮╭╮╭╮╭╮
+祝音輕吟繞東海, ╭┴┴┴┴┴┴┴┴╮ 誠心的祝福!
+汝影蹤跡現南山; │☆★☆★☆★☆★│ 一個人生的另一個階段就此開始
+生機蓬勃冬不斷, ╭┴────────┴╮ 望能一帆風順.
+日轉星移待明春; 澺
+快意暢活人間行, ││
+樂音環身樂自添. ┌┴──────────┴┐
+ └────────────┘
+ ★★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
diff --git a/pttbbs/sample/etc/Welcome_birth.10 b/pttbbs/sample/etc/Welcome_birth.10
new file mode 100644
index 00000000..5ea57404
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_birth.10
@@ -0,0 +1,23 @@
+ 嗨*s 忙嗎? 不要忘記自己的生日喔!! 記得發表感言... 
+
+  ╭╮╭╮╭──╮╭──╮╭──╮╭╮╭╮
+  │╰╯││ ☆ ││ ☆ ││ ☆ ││╰╯│
+  │╭╮││╭╮││╭─╯│╭─╯╰╮╭╯
+  ╰╯╰╯╰╯╰╯╰╯ ╰╯ ╰╯
+  ╭╮
+ ╭╮ ☆ ╭──╮╭╯╰╮╭╮ ╭╮╭──╮╭╮╭╮
+ │╰─╮╭╮│╭─╯╰╮╭╯│╰─╮╭─╯││ ☆ ││╰╯│
+ │ ☆ │││││ │╰╮│╭╮││ ☆ ││╭╮│╰╮╭╯
+ ╰──╯╰╯╰╯ ╰─╯╰╯╰╯╰──╯╰╯╰╯ ╰╯
+
+贈詩一首送佳人,  ☉ ☉ ☉ ☉ 批踢踢實業坊全體員工
+予時霜雪臘月天;  ╭╮╭╮╭╮╭╮
+祝音輕吟繞東海, ╭┴┴┴┴┴┴┴┴╮ 誠心的祝福!
+汝影蹤跡現南山; │☆★☆★☆★☆★│ 一個人生的另一個階段就此開始
+生機蓬勃冬不斷, ╭┴────────┴╮ 望能一帆風順.
+日轉星移待明春; 澺
+快意暢活人間行, ││
+樂音環身樂自添. ┌┴──────────┴┐
+ └────────────┘
+ ★★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
diff --git a/pttbbs/sample/etc/Welcome_birth.11 b/pttbbs/sample/etc/Welcome_birth.11
new file mode 100644
index 00000000..5ea57404
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_birth.11
@@ -0,0 +1,23 @@
+ 嗨*s 忙嗎? 不要忘記自己的生日喔!! 記得發表感言... 
+
+  ╭╮╭╮╭──╮╭──╮╭──╮╭╮╭╮
+  │╰╯││ ☆ ││ ☆ ││ ☆ ││╰╯│
+  │╭╮││╭╮││╭─╯│╭─╯╰╮╭╯
+  ╰╯╰╯╰╯╰╯╰╯ ╰╯ ╰╯
+  ╭╮
+ ╭╮ ☆ ╭──╮╭╯╰╮╭╮ ╭╮╭──╮╭╮╭╮
+ │╰─╮╭╮│╭─╯╰╮╭╯│╰─╮╭─╯││ ☆ ││╰╯│
+ │ ☆ │││││ │╰╮│╭╮││ ☆ ││╭╮│╰╮╭╯
+ ╰──╯╰╯╰╯ ╰─╯╰╯╰╯╰──╯╰╯╰╯ ╰╯
+
+贈詩一首送佳人,  ☉ ☉ ☉ ☉ 批踢踢實業坊全體員工
+予時霜雪臘月天;  ╭╮╭╮╭╮╭╮
+祝音輕吟繞東海, ╭┴┴┴┴┴┴┴┴╮ 誠心的祝福!
+汝影蹤跡現南山; │☆★☆★☆★☆★│ 一個人生的另一個階段就此開始
+生機蓬勃冬不斷, ╭┴────────┴╮ 望能一帆風順.
+日轉星移待明春; 澺
+快意暢活人間行, ││
+樂音環身樂自添. ┌┴──────────┴┐
+ └────────────┘
+ ★★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
diff --git a/pttbbs/sample/etc/Welcome_birth.12 b/pttbbs/sample/etc/Welcome_birth.12
new file mode 100644
index 00000000..5ea57404
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_birth.12
@@ -0,0 +1,23 @@
+ 嗨*s 忙嗎? 不要忘記自己的生日喔!! 記得發表感言... 
+
+  ╭╮╭╮╭──╮╭──╮╭──╮╭╮╭╮
+  │╰╯││ ☆ ││ ☆ ││ ☆ ││╰╯│
+  │╭╮││╭╮││╭─╯│╭─╯╰╮╭╯
+  ╰╯╰╯╰╯╰╯╰╯ ╰╯ ╰╯
+  ╭╮
+ ╭╮ ☆ ╭──╮╭╯╰╮╭╮ ╭╮╭──╮╭╮╭╮
+ │╰─╮╭╮│╭─╯╰╮╭╯│╰─╮╭─╯││ ☆ ││╰╯│
+ │ ☆ │││││ │╰╮│╭╮││ ☆ ││╭╮│╰╮╭╯
+ ╰──╯╰╯╰╯ ╰─╯╰╯╰╯╰──╯╰╯╰╯ ╰╯
+
+贈詩一首送佳人,  ☉ ☉ ☉ ☉ 批踢踢實業坊全體員工
+予時霜雪臘月天;  ╭╮╭╮╭╮╭╮
+祝音輕吟繞東海, ╭┴┴┴┴┴┴┴┴╮ 誠心的祝福!
+汝影蹤跡現南山; │☆★☆★☆★☆★│ 一個人生的另一個階段就此開始
+生機蓬勃冬不斷, ╭┴────────┴╮ 望能一帆風順.
+日轉星移待明春; 澺
+快意暢活人間行, ││
+樂音環身樂自添. ┌┴──────────┴┐
+ └────────────┘
+ ★★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
diff --git a/pttbbs/sample/etc/Welcome_birth.2 b/pttbbs/sample/etc/Welcome_birth.2
new file mode 100644
index 00000000..5ea57404
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_birth.2
@@ -0,0 +1,23 @@
+ 嗨*s 忙嗎? 不要忘記自己的生日喔!! 記得發表感言... 
+
+  ╭╮╭╮╭──╮╭──╮╭──╮╭╮╭╮
+  │╰╯││ ☆ ││ ☆ ││ ☆ ││╰╯│
+  │╭╮││╭╮││╭─╯│╭─╯╰╮╭╯
+  ╰╯╰╯╰╯╰╯╰╯ ╰╯ ╰╯
+  ╭╮
+ ╭╮ ☆ ╭──╮╭╯╰╮╭╮ ╭╮╭──╮╭╮╭╮
+ │╰─╮╭╮│╭─╯╰╮╭╯│╰─╮╭─╯││ ☆ ││╰╯│
+ │ ☆ │││││ │╰╮│╭╮││ ☆ ││╭╮│╰╮╭╯
+ ╰──╯╰╯╰╯ ╰─╯╰╯╰╯╰──╯╰╯╰╯ ╰╯
+
+贈詩一首送佳人,  ☉ ☉ ☉ ☉ 批踢踢實業坊全體員工
+予時霜雪臘月天;  ╭╮╭╮╭╮╭╮
+祝音輕吟繞東海, ╭┴┴┴┴┴┴┴┴╮ 誠心的祝福!
+汝影蹤跡現南山; │☆★☆★☆★☆★│ 一個人生的另一個階段就此開始
+生機蓬勃冬不斷, ╭┴────────┴╮ 望能一帆風順.
+日轉星移待明春; 澺
+快意暢活人間行, ││
+樂音環身樂自添. ┌┴──────────┴┐
+ └────────────┘
+ ★★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
diff --git a/pttbbs/sample/etc/Welcome_birth.3 b/pttbbs/sample/etc/Welcome_birth.3
new file mode 100644
index 00000000..5ea57404
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_birth.3
@@ -0,0 +1,23 @@
+ 嗨*s 忙嗎? 不要忘記自己的生日喔!! 記得發表感言... 
+
+  ╭╮╭╮╭──╮╭──╮╭──╮╭╮╭╮
+  │╰╯││ ☆ ││ ☆ ││ ☆ ││╰╯│
+  │╭╮││╭╮││╭─╯│╭─╯╰╮╭╯
+  ╰╯╰╯╰╯╰╯╰╯ ╰╯ ╰╯
+  ╭╮
+ ╭╮ ☆ ╭──╮╭╯╰╮╭╮ ╭╮╭──╮╭╮╭╮
+ │╰─╮╭╮│╭─╯╰╮╭╯│╰─╮╭─╯││ ☆ ││╰╯│
+ │ ☆ │││││ │╰╮│╭╮││ ☆ ││╭╮│╰╮╭╯
+ ╰──╯╰╯╰╯ ╰─╯╰╯╰╯╰──╯╰╯╰╯ ╰╯
+
+贈詩一首送佳人,  ☉ ☉ ☉ ☉ 批踢踢實業坊全體員工
+予時霜雪臘月天;  ╭╮╭╮╭╮╭╮
+祝音輕吟繞東海, ╭┴┴┴┴┴┴┴┴╮ 誠心的祝福!
+汝影蹤跡現南山; │☆★☆★☆★☆★│ 一個人生的另一個階段就此開始
+生機蓬勃冬不斷, ╭┴────────┴╮ 望能一帆風順.
+日轉星移待明春; 澺
+快意暢活人間行, ││
+樂音環身樂自添. ┌┴──────────┴┐
+ └────────────┘
+ ★★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
diff --git a/pttbbs/sample/etc/Welcome_birth.4 b/pttbbs/sample/etc/Welcome_birth.4
new file mode 100644
index 00000000..5ea57404
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_birth.4
@@ -0,0 +1,23 @@
+ 嗨*s 忙嗎? 不要忘記自己的生日喔!! 記得發表感言... 
+
+  ╭╮╭╮╭──╮╭──╮╭──╮╭╮╭╮
+  │╰╯││ ☆ ││ ☆ ││ ☆ ││╰╯│
+  │╭╮││╭╮││╭─╯│╭─╯╰╮╭╯
+  ╰╯╰╯╰╯╰╯╰╯ ╰╯ ╰╯
+  ╭╮
+ ╭╮ ☆ ╭──╮╭╯╰╮╭╮ ╭╮╭──╮╭╮╭╮
+ │╰─╮╭╮│╭─╯╰╮╭╯│╰─╮╭─╯││ ☆ ││╰╯│
+ │ ☆ │││││ │╰╮│╭╮││ ☆ ││╭╮│╰╮╭╯
+ ╰──╯╰╯╰╯ ╰─╯╰╯╰╯╰──╯╰╯╰╯ ╰╯
+
+贈詩一首送佳人,  ☉ ☉ ☉ ☉ 批踢踢實業坊全體員工
+予時霜雪臘月天;  ╭╮╭╮╭╮╭╮
+祝音輕吟繞東海, ╭┴┴┴┴┴┴┴┴╮ 誠心的祝福!
+汝影蹤跡現南山; │☆★☆★☆★☆★│ 一個人生的另一個階段就此開始
+生機蓬勃冬不斷, ╭┴────────┴╮ 望能一帆風順.
+日轉星移待明春; 澺
+快意暢活人間行, ││
+樂音環身樂自添. ┌┴──────────┴┐
+ └────────────┘
+ ★★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
diff --git a/pttbbs/sample/etc/Welcome_birth.5 b/pttbbs/sample/etc/Welcome_birth.5
new file mode 100644
index 00000000..5ea57404
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_birth.5
@@ -0,0 +1,23 @@
+ 嗨*s 忙嗎? 不要忘記自己的生日喔!! 記得發表感言... 
+
+  ╭╮╭╮╭──╮╭──╮╭──╮╭╮╭╮
+  │╰╯││ ☆ ││ ☆ ││ ☆ ││╰╯│
+  │╭╮││╭╮││╭─╯│╭─╯╰╮╭╯
+  ╰╯╰╯╰╯╰╯╰╯ ╰╯ ╰╯
+  ╭╮
+ ╭╮ ☆ ╭──╮╭╯╰╮╭╮ ╭╮╭──╮╭╮╭╮
+ │╰─╮╭╮│╭─╯╰╮╭╯│╰─╮╭─╯││ ☆ ││╰╯│
+ │ ☆ │││││ │╰╮│╭╮││ ☆ ││╭╮│╰╮╭╯
+ ╰──╯╰╯╰╯ ╰─╯╰╯╰╯╰──╯╰╯╰╯ ╰╯
+
+贈詩一首送佳人,  ☉ ☉ ☉ ☉ 批踢踢實業坊全體員工
+予時霜雪臘月天;  ╭╮╭╮╭╮╭╮
+祝音輕吟繞東海, ╭┴┴┴┴┴┴┴┴╮ 誠心的祝福!
+汝影蹤跡現南山; │☆★☆★☆★☆★│ 一個人生的另一個階段就此開始
+生機蓬勃冬不斷, ╭┴────────┴╮ 望能一帆風順.
+日轉星移待明春; 澺
+快意暢活人間行, ││
+樂音環身樂自添. ┌┴──────────┴┐
+ └────────────┘
+ ★★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
diff --git a/pttbbs/sample/etc/Welcome_birth.6 b/pttbbs/sample/etc/Welcome_birth.6
new file mode 100644
index 00000000..5ea57404
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_birth.6
@@ -0,0 +1,23 @@
+ 嗨*s 忙嗎? 不要忘記自己的生日喔!! 記得發表感言... 
+
+  ╭╮╭╮╭──╮╭──╮╭──╮╭╮╭╮
+  │╰╯││ ☆ ││ ☆ ││ ☆ ││╰╯│
+  │╭╮││╭╮││╭─╯│╭─╯╰╮╭╯
+  ╰╯╰╯╰╯╰╯╰╯ ╰╯ ╰╯
+  ╭╮
+ ╭╮ ☆ ╭──╮╭╯╰╮╭╮ ╭╮╭──╮╭╮╭╮
+ │╰─╮╭╮│╭─╯╰╮╭╯│╰─╮╭─╯││ ☆ ││╰╯│
+ │ ☆ │││││ │╰╮│╭╮││ ☆ ││╭╮│╰╮╭╯
+ ╰──╯╰╯╰╯ ╰─╯╰╯╰╯╰──╯╰╯╰╯ ╰╯
+
+贈詩一首送佳人,  ☉ ☉ ☉ ☉ 批踢踢實業坊全體員工
+予時霜雪臘月天;  ╭╮╭╮╭╮╭╮
+祝音輕吟繞東海, ╭┴┴┴┴┴┴┴┴╮ 誠心的祝福!
+汝影蹤跡現南山; │☆★☆★☆★☆★│ 一個人生的另一個階段就此開始
+生機蓬勃冬不斷, ╭┴────────┴╮ 望能一帆風順.
+日轉星移待明春; 澺
+快意暢活人間行, ││
+樂音環身樂自添. ┌┴──────────┴┐
+ └────────────┘
+ ★★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
diff --git a/pttbbs/sample/etc/Welcome_birth.7 b/pttbbs/sample/etc/Welcome_birth.7
new file mode 100644
index 00000000..5ea57404
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_birth.7
@@ -0,0 +1,23 @@
+ 嗨*s 忙嗎? 不要忘記自己的生日喔!! 記得發表感言... 
+
+  ╭╮╭╮╭──╮╭──╮╭──╮╭╮╭╮
+  │╰╯││ ☆ ││ ☆ ││ ☆ ││╰╯│
+  │╭╮││╭╮││╭─╯│╭─╯╰╮╭╯
+  ╰╯╰╯╰╯╰╯╰╯ ╰╯ ╰╯
+  ╭╮
+ ╭╮ ☆ ╭──╮╭╯╰╮╭╮ ╭╮╭──╮╭╮╭╮
+ │╰─╮╭╮│╭─╯╰╮╭╯│╰─╮╭─╯││ ☆ ││╰╯│
+ │ ☆ │││││ │╰╮│╭╮││ ☆ ││╭╮│╰╮╭╯
+ ╰──╯╰╯╰╯ ╰─╯╰╯╰╯╰──╯╰╯╰╯ ╰╯
+
+贈詩一首送佳人,  ☉ ☉ ☉ ☉ 批踢踢實業坊全體員工
+予時霜雪臘月天;  ╭╮╭╮╭╮╭╮
+祝音輕吟繞東海, ╭┴┴┴┴┴┴┴┴╮ 誠心的祝福!
+汝影蹤跡現南山; │☆★☆★☆★☆★│ 一個人生的另一個階段就此開始
+生機蓬勃冬不斷, ╭┴────────┴╮ 望能一帆風順.
+日轉星移待明春; 澺
+快意暢活人間行, ││
+樂音環身樂自添. ┌┴──────────┴┐
+ └────────────┘
+ ★★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
diff --git a/pttbbs/sample/etc/Welcome_birth.8 b/pttbbs/sample/etc/Welcome_birth.8
new file mode 100644
index 00000000..5ea57404
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_birth.8
@@ -0,0 +1,23 @@
+ 嗨*s 忙嗎? 不要忘記自己的生日喔!! 記得發表感言... 
+
+  ╭╮╭╮╭──╮╭──╮╭──╮╭╮╭╮
+  │╰╯││ ☆ ││ ☆ ││ ☆ ││╰╯│
+  │╭╮││╭╮││╭─╯│╭─╯╰╮╭╯
+  ╰╯╰╯╰╯╰╯╰╯ ╰╯ ╰╯
+  ╭╮
+ ╭╮ ☆ ╭──╮╭╯╰╮╭╮ ╭╮╭──╮╭╮╭╮
+ │╰─╮╭╮│╭─╯╰╮╭╯│╰─╮╭─╯││ ☆ ││╰╯│
+ │ ☆ │││││ │╰╮│╭╮││ ☆ ││╭╮│╰╮╭╯
+ ╰──╯╰╯╰╯ ╰─╯╰╯╰╯╰──╯╰╯╰╯ ╰╯
+
+贈詩一首送佳人,  ☉ ☉ ☉ ☉ 批踢踢實業坊全體員工
+予時霜雪臘月天;  ╭╮╭╮╭╮╭╮
+祝音輕吟繞東海, ╭┴┴┴┴┴┴┴┴╮ 誠心的祝福!
+汝影蹤跡現南山; │☆★☆★☆★☆★│ 一個人生的另一個階段就此開始
+生機蓬勃冬不斷, ╭┴────────┴╮ 望能一帆風順.
+日轉星移待明春; 澺
+快意暢活人間行, ││
+樂音環身樂自添. ┌┴──────────┴┐
+ └────────────┘
+ ★★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
diff --git a/pttbbs/sample/etc/Welcome_birth.9 b/pttbbs/sample/etc/Welcome_birth.9
new file mode 100644
index 00000000..5ea57404
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_birth.9
@@ -0,0 +1,23 @@
+ 嗨*s 忙嗎? 不要忘記自己的生日喔!! 記得發表感言... 
+
+  ╭╮╭╮╭──╮╭──╮╭──╮╭╮╭╮
+  │╰╯││ ☆ ││ ☆ ││ ☆ ││╰╯│
+  │╭╮││╭╮││╭─╯│╭─╯╰╮╭╯
+  ╰╯╰╯╰╯╰╯╰╯ ╰╯ ╰╯
+  ╭╮
+ ╭╮ ☆ ╭──╮╭╯╰╮╭╮ ╭╮╭──╮╭╮╭╮
+ │╰─╮╭╮│╭─╯╰╮╭╯│╰─╮╭─╯││ ☆ ││╰╯│
+ │ ☆ │││││ │╰╮│╭╮││ ☆ ││╭╮│╰╮╭╯
+ ╰──╯╰╯╰╯ ╰─╯╰╯╰╯╰──╯╰╯╰╯ ╰╯
+
+贈詩一首送佳人,  ☉ ☉ ☉ ☉ 批踢踢實業坊全體員工
+予時霜雪臘月天;  ╭╮╭╮╭╮╭╮
+祝音輕吟繞東海, ╭┴┴┴┴┴┴┴┴╮ 誠心的祝福!
+汝影蹤跡現南山; │☆★☆★☆★☆★│ 一個人生的另一個階段就此開始
+生機蓬勃冬不斷, ╭┴────────┴╮ 望能一帆風順.
+日轉星移待明春; 澺
+快意暢活人間行, ││
+樂音環身樂自添. ┌┴──────────┴┐
+ └────────────┘
+ ★★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★祝你生日快樂★
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
diff --git a/pttbbs/sample/etc/Welcome_login b/pttbbs/sample/etc/Welcome_login
new file mode 100644
index 00000000..7f7bbbb0
--- /dev/null
+++ b/pttbbs/sample/etc/Welcome_login
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+ 歡迎使用 PttBBS !
+
+
+ 有任何問題請到 ptt.cc 的 PttCurrent 板討論唷!
diff --git a/pttbbs/sample/etc/angel_notify b/pttbbs/sample/etc/angel_notify
new file mode 100644
index 00000000..6b14832f
--- /dev/null
+++ b/pttbbs/sample/etc/angel_notify
@@ -0,0 +1,23 @@
+ 恭喜你成為天使囉
+ 以下是小天使須遵守的條約喔! ﹒ 
+   ﹒  
+   ☆第一條 小天使任務是服務小主人,  
+    最首要的任務是教導小主人使用Ptt,不可因任何理由廢弛 
+   *     次要任務是替小主人解決其他問題,帶來歡樂  
+  " 
+  ★第二條 小天使只能默默服務"不"能主動讓小主人知道身分" ○β" 
+  .   ◣ 
+  ☆第三條 小天使不能因為小主人的性別年齡,而有所歧視  
+  
+  ★第四條 小天使不是連誼功能,在小主人呼喚後現ID主動騷擾小主人, 
+    (如要照片電話) 
+  遭檢舉確認者將拔除天使翅膀成為墮落天使,且公告id以及永不錄用 
+  ﹒ ˙
+  " ☆第五條 小天使任期半年一任,任期屆滿將拔除小天使權限, 表現特別 
+ α○"  優良可視意願續任(表現優良如參與各組活動或是人氣前幾名等等
+  ◢ ·  
+  ★第六條 時常不開"是否開放小主人"選項,經程式屢次檢查出來者, 
+  將拔除天使翅 成為墮落天使 (滿五次解除權限) 
+   。 
+  
+  • 
diff --git a/pttbbs/sample/etc/angel_usage b/pttbbs/sample/etc/angel_usage
new file mode 100644
index 00000000..36ac9da5
--- /dev/null
+++ b/pttbbs/sample/etc/angel_usage
@@ -0,0 +1,23 @@
+
+ 歈
+  
+  ◎小天使是用來解答bbs使用上的問題,不一定會跟小主人聊天。 
+  
+  ☉小天使是真人,但是他被大天使限制不能洩漏id、性別等私人資訊。 
+  
+  ☆每個月29日呼叫小天使並點歌可以得到P幣雨。 值 
+   日 
+  △小主人問問題找不到小天使請到新手版(PttNewhand), 生 
+   想留言給小天使請到許願版(AngelPray) ▃▅◤ 炎g▃  G 
+  ◢█◤ ◥█ ◣ 吉歐 
+  ██▂▄▅▆▆▅▄▃██▊米K 
+   █||█  ▃▃ ▃▃    ◥██ ▋██◤妮酷 
+▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ◤◥◤ ◥◤◣▄▄▄▄▄
+ ◢▇◣  酷 ◢█◣
+ ◢██ ██◣
+ ◢███ █ ██ ◥██◣
+ ██ ● ● ▌██
+ 很正很正的正太小茵 ◤ ▌ ▃▃▂▂▂▂▃▃ /  ◥
+ 很蛋很蛋的彈珠汽水  ★︳ ◥ ◤ \ ◤
+ 提醒您 ☆朅
+ ψccfg & s75287 ★ ▅▄▃▂▂▃▄▅
diff --git a/pttbbs/sample/etc/bad_host b/pttbbs/sample/etc/bad_host
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/pttbbs/sample/etc/bad_host
diff --git a/pttbbs/sample/etc/banemail b/pttbbs/sample/etc/banemail
new file mode 100644
index 00000000..03286bf0
--- /dev/null
+++ b/pttbbs/sample/etc/banemail
@@ -0,0 +1,22 @@
+# 用來設定哪些信箱不接受
+# 開頭 A表示全部 match的時候就不接受 (ex: 不歡迎的使用者)
+# 開頭 P表示 match這個部份就不接受
+# 開頭 S表示不接受這台 server
+#--
+# 不接受打 ip 的mail server
+P@[
+Purl.com.tw
+P.bbs@
+Skkcity.com.tw
+Syahoo.com
+Syahoo.com.tw
+Syahoo.com.hk
+Skimo.com.tw
+Skimo.com
+Ssinamail.com
+Spchome.com.tw
+Shotmail.com
+Smsn.com
+Syam.com
+Syammail.com
+Smail.taipeilink.net
diff --git a/pttbbs/sample/etc/board.help b/pttbbs/sample/etc/board.help
new file mode 100644
index 00000000..41e04b35
--- /dev/null
+++ b/pttbbs/sample/etc/board.help
@@ -0,0 +1,22 @@
+【 全功能看板操作說明 】
+
+【 基本命令 】
+ (p/↑)/(n/↓) 上移/下移一篇文章 (數字鍵) 跳到指定號碼的文章
+(P/PgUp)(N/PgDn) 下移/下移一頁 (Home)/(End/$) 跳到首篇/末篇文章
+ (r)(→) 閱讀此篇文章 (=[]<>-+S) 主題式閱讀
+ (^P)/(y)/(X) 發表/回覆/推薦文章 (F/U)/(x) 轉寄至信箱/轉錄至其它看板
+
+【 進階命令 】
+ (/)(?)/(a)/(Z) 搜尋 關鍵字/作者/推文數 (^H)/(!) 只列主要標題/不列關鍵字
+ (G)/(A)/(S) 搜尋 保留標記/稿酬/標題 (^Q)/(w) 作者資料/丟作者水球
+ (z)/(TAB)/(b) 閱讀精華區/文摘/進板畫面 (Q)/(^W) 查詢價格或匿名/我在哪裡
+ (^O)/(X)/(f) 競標/下標/參與賭盤 (^V)/(V)/(R) 活動連署/投票/投票結果
+ (d)/(E) 刪文/重編文章 (I) 查詢看板設定(隱藏,推文,..)
+
+【 板主命令 】
+ (M/o) 舉行投票/編輯投票名單 (m/c/g/^Z) 保留/選錄精華/文摘/置底
+ (D) 刪除一段範圍的文章 (Y) 取消推薦文章
+ (T/B) 重編文章標題/重編看板標題 (t/^D) 標記文章/砍除標記的文章
+ (O)/(i) 發表注意事項/文章類別 (W)/(K)/(v) 編進板畫面/水桶/可見名單
+ (^G) 舉辦賭盤/停止下注/開獎 (H) 切換看板隱藏
+ (I) 看板設定(隱藏,推文,...)
diff --git a/pttbbs/sample/etc/boardlist.help b/pttbbs/sample/etc/boardlist.help
new file mode 100644
index 00000000..c7677ed9
--- /dev/null
+++ b/pttbbs/sample/etc/boardlist.help
@@ -0,0 +1,23 @@
+【 看板選單輔助說明 】
+
+【 基本指令 】
+ (p)(↑)/(n)(↓)上一個看板 / 下一個看板
+ (P)(^B)(PgUp) 上一頁看板
+ (N)(^F)(PgDn) 下一頁看板
+ ($)/(s)/(/) 最後一個看板 / 搜尋看板 / 以中文搜尋看板關鍵字
+ (數字)/(^W) 跳至該項目 / 迷路了,我在哪裡
+ (r)(→)/(q)(←)進入看板 / 回到主選單
+
+【 進階指令 】
+ 閱讀: (v/V/S) 通通看完 / 全部未讀 / 切換排序方式
+ 訂閱: (y) 切換下列模式: 我的最愛 / 訂閱看板(所有看板)
+ 標記: (t/^T) 標記看板 / 取消標記
+ (*) 全選(標記)、全不選、或改變所有看板標記狀態
+ 編輯: (m)(z)/(M) 把看板加入或移出我的最愛 / 改變看板位置
+ (a)(i)/(g)/(L) 新增 看板 / 目錄 / 分隔線 至我的最愛
+ (^A/^D) 將已標記看板加入 / 移出我的最愛
+ 整理: (K/T/w) 備份,清理我的最愛 / 修改目錄名稱 / 寫入已讀未讀紀錄
+
+【 小組長指令 】
+ (E/W/B) 設定看板 / 設定小組備忘 / 開新看板
+ (^P) 移動已標記看板到此分類
diff --git a/pttbbs/sample/etc/chess/1.poem b/pttbbs/sample/etc/chess/1.poem
new file mode 100644
index 00000000..f50247ab
--- /dev/null
+++ b/pttbbs/sample/etc/chess/1.poem
@@ -0,0 +1,10 @@
+象弈 南宋 劉克莊
+
+ 或遲如圍莒,或速如入蔡。
+
+ 遠炮勿虛發,冗卒要精汰。
+
+ 負非由寡少,勝豈繫強大?
+
+ 昆陽以象奔,陳濤以車敗。
+
diff --git a/pttbbs/sample/etc/chess/10.poem b/pttbbs/sample/etc/chess/10.poem
new file mode 100644
index 00000000..b9dc2107
--- /dev/null
+++ b/pttbbs/sample/etc/chess/10.poem
@@ -0,0 +1,10 @@
+無題 明 明太子朱高熾
+
+ 二國爭強各用兵,擺成隊伍定輸贏。
+
+ 馬行曲路當先道,將守深宮戒遠征。
+
+ 乘險出車收敗卒,隔河飛炮下重城。
+
+ 等閒識得軍情事,一著功成見太平。
+
diff --git a/pttbbs/sample/etc/chess/11.poem b/pttbbs/sample/etc/chess/11.poem
new file mode 100644
index 00000000..01331372
--- /dev/null
+++ b/pttbbs/sample/etc/chess/11.poem
@@ -0,0 +1,6 @@
+無題 不明
+
+ 清風明月之夜,詩歌唱詠;
+
+ 古松流水之間,敲棋品茗。
+
diff --git a/pttbbs/sample/etc/chess/12.poem b/pttbbs/sample/etc/chess/12.poem
new file mode 100644
index 00000000..53a68b72
--- /dev/null
+++ b/pttbbs/sample/etc/chess/12.poem
@@ -0,0 +1,6 @@
+諷宋薛昂處士負棋作詩 不明
+
+ 好笑當年薛乞兒,荊公座上賭新詩,
+
+ 而今又向江東去,奉勸先生莫下棋。
+
diff --git a/pttbbs/sample/etc/chess/13.poem b/pttbbs/sample/etc/chess/13.poem
new file mode 100644
index 00000000..01331372
--- /dev/null
+++ b/pttbbs/sample/etc/chess/13.poem
@@ -0,0 +1,6 @@
+無題 不明
+
+ 清風明月之夜,詩歌唱詠;
+
+ 古松流水之間,敲棋品茗。
+
diff --git a/pttbbs/sample/etc/chess/14.poem b/pttbbs/sample/etc/chess/14.poem
new file mode 100644
index 00000000..d41950e8
--- /dev/null
+++ b/pttbbs/sample/etc/chess/14.poem
@@ -0,0 +1,10 @@
+無題 明 明象棋高手李開先 村翁
+
+ 小有兼逢大有年,田家多穫即為賢。
+
+ 有時撒網為漁父,日常登床作睡仙。
+
+ 破局棋堆隨手應,無弦琴不用音傳。
+
+ 一身之外無所慕,下有青山上碧天。
+
diff --git a/pttbbs/sample/etc/chess/15.poem b/pttbbs/sample/etc/chess/15.poem
new file mode 100644
index 00000000..139597f9
--- /dev/null
+++ b/pttbbs/sample/etc/chess/15.poem
@@ -0,0 +1,2 @@
+
+
diff --git a/pttbbs/sample/etc/chess/16.poem b/pttbbs/sample/etc/chess/16.poem
new file mode 100644
index 00000000..139597f9
--- /dev/null
+++ b/pttbbs/sample/etc/chess/16.poem
@@ -0,0 +1,2 @@
+
+
diff --git a/pttbbs/sample/etc/chess/2.poem b/pttbbs/sample/etc/chess/2.poem
new file mode 100644
index 00000000..119e98ae
--- /dev/null
+++ b/pttbbs/sample/etc/chess/2.poem
@@ -0,0 +1,10 @@
+棋詩 明 曾子棨
+
+ 兩軍對敵立雙營,坐運神機決死生。
+
+ 千里封疆馳鐵馬,一川波浪動金兵。
+
+ 虞姬歌舞悲垓下,反將旌旗逼楚城。
+
+ 興盡計窮征戰罷,松蔭花影滿棋秤。
+
diff --git a/pttbbs/sample/etc/chess/3.poem b/pttbbs/sample/etc/chess/3.poem
new file mode 100644
index 00000000..d69636b7
--- /dev/null
+++ b/pttbbs/sample/etc/chess/3.poem
@@ -0,0 +1,6 @@
+觀棋 清 袁枚
+
+ 攏袖觀棋有所思,分明楚漢兩軍峙。
+
+ 非常喜歡非常惱,不著棋人總不如。
+
diff --git a/pttbbs/sample/etc/chess/4.poem b/pttbbs/sample/etc/chess/4.poem
new file mode 100644
index 00000000..f86edc12
--- /dev/null
+++ b/pttbbs/sample/etc/chess/4.poem
@@ -0,0 +1,10 @@
+和春深 唐 白居易
+
+ 何處深為好,春到博弈家。
+
+ 一先爭破眼,六聚斗成花,
+
+ 鼓應投壹馬,兵沖象戲車。
+
+ 評棋局上事,最妙是長斜。
+
diff --git a/pttbbs/sample/etc/chess/5.poem b/pttbbs/sample/etc/chess/5.poem
new file mode 100644
index 00000000..cffd55e1
--- /dev/null
+++ b/pttbbs/sample/etc/chess/5.poem
@@ -0,0 +1,8 @@
+無題 宋 王安石
+
+ 飄飄凌雲志,強御莫能懾;
+
+ 忘情塞上馬,適志夢中蝶;
+
+ 經論安所施,有寓聊自愜。
+
diff --git a/pttbbs/sample/etc/chess/6.poem b/pttbbs/sample/etc/chess/6.poem
new file mode 100644
index 00000000..00c4cff8
--- /dev/null
+++ b/pttbbs/sample/etc/chess/6.poem
@@ -0,0 +1,6 @@
+無題 呂洞賓
+
+ 教著殘局山月曉,
+
+ 一聲長嘯海天秋。
+
diff --git a/pttbbs/sample/etc/chess/7.poem b/pttbbs/sample/etc/chess/7.poem
new file mode 100644
index 00000000..b525cba0
--- /dev/null
+++ b/pttbbs/sample/etc/chess/7.poem
@@ -0,0 +1,10 @@
+吊棋 明 錢鶴灘
+
+ 敲棋終日與偏幽,誰道今朝結父仇。
+
+ 兵足下河車不救,將軍落水士難留。
+
+ 馬行千里隨波去,象渡三江逐淚流。
+
+ 炮響一聲驚霹靂,臥龍投起碧雲浮。
+
diff --git a/pttbbs/sample/etc/chess/8.poem b/pttbbs/sample/etc/chess/8.poem
new file mode 100644
index 00000000..01360b29
--- /dev/null
+++ b/pttbbs/sample/etc/chess/8.poem
@@ -0,0 +1,10 @@
+詠象戲 北宋 程顥
+
+ 大都博弈皆戲劇,象戲翻能學用兵。
+
+ 車馬尚存周戰法,偏裨兼備漢官名。
+
+ 中軍八面將軍重,河外尖斜步卒輕。
+
+ 卻憑紋楸聊自笑,雄如劉項亦間爭。
+
diff --git a/pttbbs/sample/etc/chess/9.poem b/pttbbs/sample/etc/chess/9.poem
new file mode 100644
index 00000000..01331372
--- /dev/null
+++ b/pttbbs/sample/etc/chess/9.poem
@@ -0,0 +1,6 @@
+無題 不明
+
+ 清風明月之夜,詩歌唱詠;
+
+ 古松流水之間,敲棋品茗。
+
diff --git a/pttbbs/sample/etc/chess/Makefile b/pttbbs/sample/etc/chess/Makefile
new file mode 100644
index 00000000..a01e3cf1
--- /dev/null
+++ b/pttbbs/sample/etc/chess/Makefile
@@ -0,0 +1,11 @@
+BBSHOME?=$(HOME)
+TARGET=$(BBSHOME)/etc/chess/
+FILES= 1.poem 2.poem 3.poem 4.poem 5.poem 6.poem 7.poem 8.poem 9.poem \
+ 10.poem 11.poem 12.poem 13.poem 14.poem 15.poem 16.poem
+
+all:
+
+install:
+ install -d $(TARGET)
+ install -c -m 644 $(FILES) $(TARGET)
+
diff --git a/pttbbs/sample/etc/chickens/Makefile b/pttbbs/sample/etc/chickens/Makefile
new file mode 100644
index 00000000..5fecba89
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/Makefile
@@ -0,0 +1,25 @@
+BBSHOME?=$(HOME)
+TARGET=$(BBSHOME)/etc/chickens/
+FILES= buymedicine buyoo clean deadth eat food hit kiss\
+ medicine nofood nohp nosatis oo read sell toofat tootired\
+ a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 a16\
+ b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 b10 b11 b12 b13 b14 b15 b16\
+ c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 c16\
+ d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16\
+ e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 e10 e11 e12 e13 e14 e15 e16\
+ f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15 f16\
+ g0 g1 g2 g3 g4 g5 g6 g7 g8 g9 g10 g11 g12 g13 g14 g15 g16\
+ h0 h1 h2 h3 h4 h5 h6 h7 h8 h9 h10 h11 h12 h13 h14 h15 h16\
+ i0 i1 i2 i3 i4 i5 i6 i7 i8 i9 i10 i11 i12 i13 i14 i15 i16\
+ j0 j1 j2 j3 j4 j5 j6 j7 j8 j9 j10 j11 j12 j13 j14 j15 j16\
+ k0 k1 k2 k3 k4 k5 k6 k7 k8 k9 k10 k11 k12 k13 k14 k15 k16\
+ l0 l1 l2 l3 l4 l5 l6 l7 l8 l9 l10 l11 l12 l13 l14 l15 l16\
+ m0 m1 m2 m3 m4 m5 m6 m7 m8 m9 m10 m11 m12 m13 m14 m15 m16\
+ n0 n1 n2 n3 n4 n5 n6 n7 n8 n9 n10 n11 n12 n13 n14 n15 n16\
+ o0 o1 o2 o3 o4 o5 o6 o7 o8 o9 o10 o11 o12 o13 o14 o15 o16
+
+all:
+
+install:
+ install -d $(TARGET)
+ install -c -m 644 $(FILES) $(TARGET)
diff --git a/pttbbs/sample/etc/chickens/a0 b/pttbbs/sample/etc/chickens/a0
new file mode 100644
index 00000000..db81a6e2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a0
@@ -0,0 +1,11 @@
+
+
+  ///
+  ☆ 
+  ●● 
+  ●●● 
+  ●● 
+  
+
+
+
diff --git a/pttbbs/sample/etc/chickens/a1 b/pttbbs/sample/etc/chickens/a1
new file mode 100644
index 00000000..e0517a99
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a1
@@ -0,0 +1,11 @@
+
+
+
+ ??
+ ▂██▂ ★!!
+ ● ● ╱//
+ ● ● |/
+ /●●/|
+ /|\
+
+
diff --git a/pttbbs/sample/etc/chickens/a10 b/pttbbs/sample/etc/chickens/a10
new file mode 100644
index 00000000..f57b0c1d
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a10
@@ -0,0 +1,15 @@
+  ◢◢
+  ◤ ●
+  ● ●● ●  ◎●
+  ● ● ● ● ●  ◣
+  ●  ● ● ● ●●● ● ●
+  ● ●  ● ● ●
+  ● ●  ● ● ●
+  ●  ● ● ● ●
+  ●  ● ● ●
+ ●●●╲●● ●● ●●
+  ╱ ╲ ●●● ●●●
+  /\╲ /\╲ ●● ●●
+
+
+
diff --git a/pttbbs/sample/etc/chickens/a11 b/pttbbs/sample/etc/chickens/a11
new file mode 100644
index 00000000..34c70f42
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a11
@@ -0,0 +1,15 @@
+  ◢◢
+  ◤ ●
+  ● ●● ●  ◎●
+  ● ● ● ● ●  ◣
+  ●  ● ● ● ●●● ● ●  ●● ●●
+  ● ●  ● ● ● ● ☉● ● ☉●
+  ● ●  ● ● ● ● ◣ ●  ◣
+  ●  ● ● ● ● ● ● ● ●
+  ●  ● ● ● ● ● ● ●
+ ●●●╲●● ●●● ●●●
+  ╱ ╲  / \ / \
+  /\╲ /\╲ /|\ /|\ /|\ /|\
+
+
+
diff --git a/pttbbs/sample/etc/chickens/a12 b/pttbbs/sample/etc/chickens/a12
new file mode 100644
index 00000000..1f5a40e6
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a12
@@ -0,0 +1,13 @@
+  ◢◢
+ ●●  ◤ ●
+ ● ● ●● ●  ◎●
+ ● ● ●  ● ● ●  ◣
+ ● ●  ● ● ● ● ●● ● ●
+ ● ● ●  ● ●  ● ●●
+  ● ●  ● ●  ● ● ●
+  ●  ● ● ●  ● ● ●
+  ●  ● ●  ●● ●
+ ●●●╲●●
+  ╱ ╲
+  /\╲ /\╲
+
diff --git a/pttbbs/sample/etc/chickens/a13 b/pttbbs/sample/etc/chickens/a13
new file mode 100644
index 00000000..2304849a
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a13
@@ -0,0 +1,14 @@
+
+  ◢◢
+  ◤ ●
+  ** ● ●● ●  ◎●
+  *  ● ●  ● ● ●  ◣
+  *  ●  ● ● ● ● ●● ● ●
+ *  ● ● ● ●  ● ●●
+ * * ● ●  ● ●  ● ● ●
+  * *  ●  ● ● ●  ● ● ●
+ *  *  *  ● ●  ●● ●
+  * * ●●●╲●●
+  ╱ ╲
+  /\╲ /\╲
+
diff --git a/pttbbs/sample/etc/chickens/a14 b/pttbbs/sample/etc/chickens/a14
new file mode 100644
index 00000000..c8fc7efa
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a14
@@ -0,0 +1,17 @@
+  ◢◢
+  ◤ ●
+  *● ●● ●  ●●
+  *  ● ●  ● ● ●  ◣ 殺氣!!
+  *  ●  ● ● ● ● ●● ● ●
+ * ● ● ● ●  ● ●● 鬥氣!!
+ * * ● ●  ● ●  ● ● ●  ◥
+  * *  ●  ● ● ●  ● ● ● ◣●-
+ *  *  *  ● ●  ●● ● |●-
+  * * ●●●╲●● |●/ / /
+  ╱ ╲ | ●●●<
+  /\╲ /\╲ \ \ \
+
+
+
+
+
diff --git a/pttbbs/sample/etc/chickens/a15 b/pttbbs/sample/etc/chickens/a15
new file mode 100644
index 00000000..79457982
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a15
@@ -0,0 +1,15 @@
+
+ 嘿嘿!I CATCH YOU!! ◢◢ /
+ ◤ ● | ●<
+  * ● ●● ● ∩● |●\ \
+  *  ● ●  ● ● ● ◣●\
+  *  ●  ● ● ● ● ●● ● ● /●\
+ *  ● ● ● ●  ● ●● /●\
+ * * ● ●  ● ●  ● ● ● ●◥
+  * *  ●  ● ● ●  ● ● ● ◣ ﹏help...
+ *  *  *  ● ●  ●● ●
+  * * ●●●●╲●
+ ╱ ╲
+  /\╲ /\╲
+
+
diff --git a/pttbbs/sample/etc/chickens/a16 b/pttbbs/sample/etc/chickens/a16
new file mode 100644
index 00000000..79457982
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a16
@@ -0,0 +1,15 @@
+
+ 嘿嘿!I CATCH YOU!! ◢◢ /
+ ◤ ● | ●<
+  * ● ●● ● ∩● |●\ \
+  *  ● ●  ● ● ● ◣●\
+  *  ●  ● ● ● ● ●● ● ● /●\
+ *  ● ● ● ●  ● ●● /●\
+ * * ● ●  ● ●  ● ● ● ●◥
+  * *  ●  ● ● ●  ● ● ● ◣ ﹏help...
+ *  *  *  ● ●  ●● ●
+  * * ●●●●╲●
+ ╱ ╲
+  /\╲ /\╲
+
+
diff --git a/pttbbs/sample/etc/chickens/a2 b/pttbbs/sample/etc/chickens/a2
new file mode 100644
index 00000000..6308e505
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a2
@@ -0,0 +1,14 @@
+  !!!  ╲\\
+  ﹌  ▎
+ ●●  █︴
+ ● ● █
+ ☆ ● ▎
+ ∣\╲ ●
+
+ ● ●
+ /● ●\
+ /|\ /|\  ╲∣/ \∣/
+  ﹌﹌ ﹌ 
+
+
+
diff --git a/pttbbs/sample/etc/chickens/a3 b/pttbbs/sample/etc/chickens/a3
new file mode 100644
index 00000000..d076a956
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a3
@@ -0,0 +1,13 @@
+  ●●
+  ● ●
+  ● >●
+  ●  ◣
+  ● ●
+  ● ●  //╱
+  ● ●  ☆
+  ●●●●╲|/ ●
+ ╱  | ● ●●
+ /|\  ● ● ● ●
+  ●● ● ● 
+ ﹌﹌
+
diff --git a/pttbbs/sample/etc/chickens/a4 b/pttbbs/sample/etc/chickens/a4
new file mode 100644
index 00000000..ea9d6535
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a4
@@ -0,0 +1,13 @@
+  ●●●
+  ● ●  ??? 
+  ● ☉●
+  ● ◣
+  ● ●  ??
+  ● ● ●  ●  ∪  ●
+  ● ● ●  ●▕  ●
+  ●●● ●  ●●
+  ●●●●●  ▍
+ / \  * ▍ *
+ /|\ /|\  * ▍*
+  ** 
+
diff --git a/pttbbs/sample/etc/chickens/a5 b/pttbbs/sample/etc/chickens/a5
new file mode 100644
index 00000000..9a014666
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a5
@@ -0,0 +1,15 @@
+  My Brother??
+  ●●●
+  ● ●  ▋
+  ● ●  ●●
+  ●  ☉ ☉  ●  ● ●
+  ● ●  ● ●
+  ●  ◥◤  ●  ● ●
+  ● ●  ● ●
+  ● ●  ●  酒  ●
+ /  ●●●●● \  ● ●
+  /|\ /|\  ●●●●●
+
+
+
+
diff --git a/pttbbs/sample/etc/chickens/a6 b/pttbbs/sample/etc/chickens/a6
new file mode 100644
index 00000000..e85fba5a
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a6
@@ -0,0 +1,13 @@
+  ●●●
+  ●  ● ●  ●
+  ● ● ● ● ● ●
+  ●  ●  ∩ ∩  ●  ●  !!!! 
+  ●  ● ●  ●
+  ● ●  ◥◤  ● ●  \/
+  ● ●  ◎◎ .
+  ● ●  ●● . .
+  ● ● . .
+  ╱ ●●●●● ╲  . . .
+  / | \ / | \ . . . . .
+
+
diff --git a/pttbbs/sample/etc/chickens/a7 b/pttbbs/sample/etc/chickens/a7
new file mode 100644
index 00000000..6b245d46
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a7
@@ -0,0 +1,16 @@
+  ◢◢ 
+  ◤ ●  I see you!!
+ ●  ◎●
+ ●  ↘◣ 
+ ●●● ● ●↘
+ ● ●●● ● ● ↘
+ ● ● ↘
+ ● ● ↘
+ ● ● ↘
+ ●●●╲●●  .. !!!!
+  ╱ ╲   ∩
+  /\╲ /\╲  ∟﹏
+
+
+
+
diff --git a/pttbbs/sample/etc/chickens/a8 b/pttbbs/sample/etc/chickens/a8
new file mode 100644
index 00000000..9482ca62
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a8
@@ -0,0 +1,13 @@
+  ◢◢
+  ◤ ●
+  ● ●● ●  ◎●
+  ● ● ● ● ●  ◣
+  ●  ● ● ● ●●● ● ●
+  ● ●  ● ● ●
+  ● ●  ● ● ●
+  ●  ● ● ● ●
+  ●  ● ● ●
+ ●●●╲●●
+  ╱ ╲
+  /\╲ /\╲
+
diff --git a/pttbbs/sample/etc/chickens/a9 b/pttbbs/sample/etc/chickens/a9
new file mode 100644
index 00000000..e2010a91
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/a9
@@ -0,0 +1,14 @@
+  ◢◢  ◣◣
+  ◤ ● ●  ◥
+  ● ●● ●  ◎● ●◎ ●
+  ● ● ● ● ●  ◣ ◢ ●
+  ●  ● ● ● ●●● ● ● ● ● ●●●
+  ● ●  ● ● ● ● ● ●● ●
+  ● ●  ● ● ● ● ●
+  ●  ● ● ● ● ● ●
+  ●  ● ● ● ● ●
+ ●●●╲●● ●●╱●●
+  ╱ ╲  ╱ ╲ 
+  /\╲ /\╲  ╱/\ ╱/\
+
+
diff --git a/pttbbs/sample/etc/chickens/b0 b/pttbbs/sample/etc/chickens/b0
new file mode 100644
index 00000000..5df42752
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b0
@@ -0,0 +1,9 @@
+
+
+
+
+  
+  ◤ ◥ 
+   美  
+  ◥ ◤ 
+  
diff --git a/pttbbs/sample/etc/chickens/b1 b/pttbbs/sample/etc/chickens/b1
new file mode 100644
index 00000000..0f6bb405
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b1
@@ -0,0 +1,9 @@
+
+
+  
+  ◤ ▂◥ 
+   ▃ ▃  
+   ◥O◤  
+   ● ●  
+  ◥ ◤ 
+  
diff --git a/pttbbs/sample/etc/chickens/b10 b/pttbbs/sample/etc/chickens/b10
new file mode 100644
index 00000000..920fd9a9
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b10
@@ -0,0 +1,14 @@
+
+  
+  ◤▂▂ ◥ 
+   ︵ ︵  
+   ● ︽  —☆ 
+   ◥ ▼◤ 
+  ◤▼◥ 
+   口口  
+      
+  ◤/II\◥ 
+    
+    
+   ▄  
+  
diff --git a/pttbbs/sample/etc/chickens/b11 b/pttbbs/sample/etc/chickens/b11
new file mode 100644
index 00000000..920fd9a9
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b11
@@ -0,0 +1,14 @@
+
+  
+  ◤▂▂ ◥ 
+   ︵ ︵  
+   ● ︽  —☆ 
+   ◥ ▼◤ 
+  ◤▼◥ 
+   口口  
+      
+  ◤/II\◥ 
+    
+    
+   ▄  
+  
diff --git a/pttbbs/sample/etc/chickens/b12 b/pttbbs/sample/etc/chickens/b12
new file mode 100644
index 00000000..0519b4ed
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b12
@@ -0,0 +1,15 @@
+
+  ◤▂▂◥ 
+   ︵︵  
+   ●●  
+   ◥▼◤  
+   ◤▼◥  
+    ◥◤   
+        
+   IIII  
+  I●●I 
+  IIIIIIII 
+  IIIIIIII 
+  IIIIIIII 
+    
+   ▄  
diff --git a/pttbbs/sample/etc/chickens/b13 b/pttbbs/sample/etc/chickens/b13
new file mode 100644
index 00000000..84f408bf
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b13
@@ -0,0 +1,17 @@
+  
+  ◤▂▂◥ e 
+   ︵︵  
+   ︽︽  
+  ◥◥◤◤ 
+  ◤▼◥ 
+   ▼  
+   :  
+   :  
+   :  
+   ∥∥  
+  ∥∥ 
+  ∥∥ 
+  ∥∥ 
+  | 
+   ▄  
+  
diff --git a/pttbbs/sample/etc/chickens/b14 b/pttbbs/sample/etc/chickens/b14
new file mode 100644
index 00000000..1c7cd389
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b14
@@ -0,0 +1,16 @@
+
+  ●▅▅● 
+  ◤  ︵︵  ◥ 
+     ●●    
+  ◥ ◥▼◤ ◤ 
+  ◤▼◥ 
+   ]  
+  ▄     ▄ 
+  ▄▄IIII▄▄ 
+  IIIIII 
+  IIIIIIII 
+  IIIIIIII 
+  IIIIIIII 
+  | 
+   ▄  
+  
diff --git a/pttbbs/sample/etc/chickens/b15 b/pttbbs/sample/etc/chickens/b15
new file mode 100644
index 00000000..0e325c1c
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b15
@@ -0,0 +1,17 @@
+
+  ◤ ◥ 
+   ︵  
+   ◤●  
+   ◥◤ ▌ 
+  ▌◤▼◥▌ 
+  ▌ p ▌ 
+  ▌   ▌ 
+    IIII  
+   IIIIII  
+  IIIIIIII 
+  IIIIIIII 
+  IIIIIIII 
+  IIIIIIII 
+    
+   ▄  
+  
diff --git a/pttbbs/sample/etc/chickens/b16 b/pttbbs/sample/etc/chickens/b16
new file mode 100644
index 00000000..b25212a0
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b16
@@ -0,0 +1,18 @@
+  
+  ◤ ◥ 
+   ︵  
+   ◤︽  
+   ◥◤ ▌ 
+  ▌◤▼◥▌ 
+  ▌ W ▌ 
+  ▌  |  ▌ 
+  ▌ ◤◥ ▌ 
+  ▌ IIII█ 
+  ▌ IIII█ 
+  IIII 
+  IIII 
+  IIII 
+  | 
+   ▄  
+  
+
diff --git a/pttbbs/sample/etc/chickens/b2 b/pttbbs/sample/etc/chickens/b2
new file mode 100644
index 00000000..ffbe656a
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b2
@@ -0,0 +1,11 @@
+
+
+  
+  ◤ ◥ 
+   . .  呀 
+  ◥O◤ 
+  ● ● 
+  ◤ ◥ 
+  _ _ 
+  
+
diff --git a/pttbbs/sample/etc/chickens/b3 b/pttbbs/sample/etc/chickens/b3
new file mode 100644
index 00000000..68a81361
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b3
@@ -0,0 +1,10 @@
+
+
+  
+  ◤▂▂◥ 
+   ︵︵  
+   ◥▼◤  
+  ● ● 
+  ◤ ◥ 
+  ▃ 
+  
diff --git a/pttbbs/sample/etc/chickens/b4 b/pttbbs/sample/etc/chickens/b4
new file mode 100644
index 00000000..fde65f4c
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b4
@@ -0,0 +1,11 @@
+
+
+  
+  ◤▂▂◥ 
+   ︵︵  
+   ◥▼◤  
+  ● ● 
+  ◤ ◥ 
+    
+  ▃ 
+  
diff --git a/pttbbs/sample/etc/chickens/b5 b/pttbbs/sample/etc/chickens/b5
new file mode 100644
index 00000000..b74b0bfb
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b5
@@ -0,0 +1,12 @@
+
+
+  
+  ◤▂▂◥ 
+   ︵︵  
+   ◥▼◤  
+   ◥◤  
+      
+  ◤ ◥ 
+    
+  ▃ 
+  
diff --git a/pttbbs/sample/etc/chickens/b6 b/pttbbs/sample/etc/chickens/b6
new file mode 100644
index 00000000..279b0c2a
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b6
@@ -0,0 +1,12 @@
+
+
+  
+  ◤▂▂◥ 
+   ︵︵  
+   ◥▼◤  
+   ◥◤  
+    :   
+  ◤ ◥ 
+    
+  ▃ 
+  
diff --git a/pttbbs/sample/etc/chickens/b7 b/pttbbs/sample/etc/chickens/b7
new file mode 100644
index 00000000..279b0c2a
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b7
@@ -0,0 +1,12 @@
+
+
+  
+  ◤▂▂◥ 
+   ︵︵  
+   ◥▼◤  
+   ◥◤  
+    :   
+  ◤ ◥ 
+    
+  ▃ 
+  
diff --git a/pttbbs/sample/etc/chickens/b8 b/pttbbs/sample/etc/chickens/b8
new file mode 100644
index 00000000..0b429152
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b8
@@ -0,0 +1,13 @@
+
+
+  
+  ◤▂▂ ◥ 
+   ︵ ︵  
+   ● ︽  —☆ 
+   ◥ ▼◤ 
+  ◤▼◥ 
+   口口  
+  ◤/II\◥ 
+    
+   ▄  
+  
diff --git a/pttbbs/sample/etc/chickens/b9 b/pttbbs/sample/etc/chickens/b9
new file mode 100644
index 00000000..0b429152
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/b9
@@ -0,0 +1,13 @@
+
+
+  
+  ◤▂▂ ◥ 
+   ︵ ︵  
+   ● ︽  —☆ 
+   ◥ ▼◤ 
+  ◤▼◥ 
+   口口  
+  ◤/II\◥ 
+    
+   ▄  
+  
diff --git a/pttbbs/sample/etc/chickens/buymedicine b/pttbbs/sample/etc/chickens/buymedicine
new file mode 100644
index 00000000..5fe11de7
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/buymedicine
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+  W R I G L E Y'S ◥
+  ψ《DOUBLEMINT》  ▔▔▔▔▔▍
+  CHEWING GUM  INT》 ▍
+ ◥ ◥ ▍
+ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
+
+
+ 買口香糖當感冒藥...
diff --git a/pttbbs/sample/etc/chickens/buyoo b/pttbbs/sample/etc/chickens/buyoo
new file mode 100644
index 00000000..21d5f32a
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/buyoo
@@ -0,0 +1,14 @@
+
+ │ │
+  ▲____________________▲
+ Ⅲ ◢◣ ┼ ◢◣ Ⅲ
+ ≡ 田田 田田 ≡
+ ▲ █====田田====田田====█ ▲
+ █ΨΨΨΨ█ΠΠΠΠΠΠΠΠΠΠ█ΨΨΨΨ█
+ ▕▔▔▔◥◎◤▔▔▔▔▔▔▔▔◥◎◤▔▔▔▏
+ ▕ 田田 █ 田田 田田 田田 █ 田田 ▏
+  ▕ 田田 █ 田田§▉▉§田田 █ 田田 ▏
+  ▕※※※※※█※※※※▉▉※※※※█※※※※※▏
+ ■══════════◢灨岷丐丐丐丐丐丐丐丐丐丑
+
+ 到教堂求得神奇大補丸一顆
diff --git a/pttbbs/sample/etc/chickens/c0 b/pttbbs/sample/etc/chickens/c0
new file mode 100644
index 00000000..388926ab
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c0
@@ -0,0 +1,8 @@
+
+
+ ◢█◣
+  \█/  哇∼哇∼
+ ◥O◤
+ /╱█
+ ╱██
+ ◥█◤
diff --git a/pttbbs/sample/etc/chickens/c1 b/pttbbs/sample/etc/chickens/c1
new file mode 100644
index 00000000..8311a4af
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c1
@@ -0,0 +1,9 @@
+
+ ◢
+ ████ 什麼是勇士啊?
+ █◤︵◥◣
+ ◥██i█
+ ◥██◤
+ ◢ ▼ ◣
+ █ ◎ █
+
diff --git a/pttbbs/sample/etc/chickens/c10 b/pttbbs/sample/etc/chickens/c10
new file mode 100644
index 00000000..bfafeffc
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c10
@@ -0,0 +1,11 @@
+ ◣
+ ████ 朝真暮偽誰能辨
+ ██◤◥█
+ █◤▌██◤ 智愚永是兩公平
+ ◤◥██◤
+ ◥█◎◤▼◥◎█◤
+ ◥█ ╳ █◤
+ █◥ 癡 ◤█
+ ◥█◣ █◢ █
+ ███●▄▄▄▄▄▄▄▄▄▄▄▄◤
+ ▋◥
diff --git a/pttbbs/sample/etc/chickens/c11 b/pttbbs/sample/etc/chickens/c11
new file mode 100644
index 00000000..0170493b
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c11
@@ -0,0 +1,10 @@
+
+ ◥█◣ ◢
+ ████ 人稱一流刀一流
+ ◥◣▍ █◤◥██
+◢████████████●██ ◥██▌◥█ 刀稱一流人一流
+ ◢◤█ ◥██◤◥
+ ██◣ ◥ ▼ ◤ ◢████▅
+ █◣ ◥◤ ◢█
+ ██◣ ◢██
+ ██ 武 ██
diff --git a/pttbbs/sample/etc/chickens/c12 b/pttbbs/sample/etc/chickens/c12
new file mode 100644
index 00000000..020ae4ca
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c12
@@ -0,0 +1,12 @@
+
+ ◣ ◢█◤
+ ████ 東風吹醒英雄夢
+ ██◤◥█
+ █◤▌██◤ 笑對千山萬重天
+ ◤◥██◤
+ ◣◥█◎◤▼◥◎█◤◢
+ ◣◥█ ╳ █◤◢
+◢█◣ █◥ 殺 ◤█
+◤ ◥█◣ █◢◤ █
+ ███●▄▄▄▄▄▄▄▄▄▄▄▄▄◤
+ ▋◥◣
diff --git a/pttbbs/sample/etc/chickens/c13 b/pttbbs/sample/etc/chickens/c13
new file mode 100644
index 00000000..f842255c
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c13
@@ -0,0 +1,11 @@
+ ◥█◣ ◢
+ ████ 寒劍默聽君子意
+ █◤◥██
+ ◥██▌◥█ 傲視人間笑紅塵
+ ◥██◤◥
+ ◥◥ ▽ ◤◤▼◥◥ ▽ ◤◤
+ ◥◥◤◤ ∣ ◥◥◤◤
+  ◥◤█ 天 █◥◤ 
+  ◥◣ █◢◤ 
+  ◥███●▄▄▄▄▄▄▄▄▄▄▄▄▄◤
+   ▋◥◣ 
diff --git a/pttbbs/sample/etc/chickens/c14 b/pttbbs/sample/etc/chickens/c14
new file mode 100644
index 00000000..35636ed9
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c14
@@ -0,0 +1,12 @@
+ ◥█◣ ◢
+ ████ 一簫一劍平生意
+ █◤◥██ ▍
+ ◥██▌◥█ █ 負盡狂名十五年
+ ◥██◤◥ █
+ ◣◥◥◤◤▼◥◥◤◤◢█
+ ◣◥█□╳□█◤◢
+ ◢█◣  █◥◣╳ ◤◢ 
+ ◤ ◥█◣  █◢◤悟  
+ ███●▄▄▄▄▄▄▄▄▄▄▄▄▄▄◤
+  ▋◥◣∣  
+  ◤∣  
diff --git a/pttbbs/sample/etc/chickens/c15 b/pttbbs/sample/etc/chickens/c15
new file mode 100644
index 00000000..89822c4f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c15
@@ -0,0 +1,12 @@
+  ◣ ◣ ◢
+ ◥ ◥ ◤ ◢ 簫中弦音藏柔情
+ ◣◥█◣ ◢ ◤ 劍下腥血記恨仇
+ ◥◣ ◥ ████ ◢ ◢◤ ◢◤ 來何洶湧需揮劍
+ ◥◣ ◣  █◤◥██ ◤ ◢◤ ◢◤ 去向纏綿可復簫
+ ◥  ◥██▌◥█ ◣
+ ◥◣ ◣◥ ◥██◤◥ ◤◢ ◢◤
+ ◣◥██◤▼◥██◤◢████●▄▄▄▄▄▄▄▄▄▄▄▄▄▄◤
+ ◢◣◥×□╳□×◤◢◣ ▆◥◣
+ ◢ ◣◥ ╳ ◤◢ ◣ ◤
+ ◢ █◣ 怒 ◢ ◣
+ ◢ █ ███ ◣
diff --git a/pttbbs/sample/etc/chickens/c16 b/pttbbs/sample/etc/chickens/c16
new file mode 100644
index 00000000..93b54373
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c16
@@ -0,0 +1,11 @@
+
+ 愛落紅塵心已死 ◢
+ ◢████
+ 持刀抱劍了一生 ██◤◥██
+ ◢ ██ ▌◥█
+ ◥◣ ◣◥ ◢████◤◥ ◤◢
+◥▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄●████◣◥██◤▼◥██◤◢
+ ◢◤▆ ◣◥█ ╳ █◤◢◣
+ ◥ █◣◥ ╳ ◤◢ █◣
+ ∼刀狂劍痴∼ ██◣ 隱 ◢█ ██◣
+ ██████ █ ███◣
diff --git a/pttbbs/sample/etc/chickens/c2 b/pttbbs/sample/etc/chickens/c2
new file mode 100644
index 00000000..19901098
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c2
@@ -0,0 +1,9 @@
+
+ ◣
+ ████
+ ◢◤︵◥█
+ █i██◤
+ ◥██◤
+ ◢ ▼ ◣
+ ◢◤ ◎ ◥◣
+ █ █
diff --git a/pttbbs/sample/etc/chickens/c3 b/pttbbs/sample/etc/chickens/c3
new file mode 100644
index 00000000..362d06f0
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c3
@@ -0,0 +1,8 @@
+
+ ◣
+ ████
+ ◢◤︵◥█
+ █i██◤
+ ◥██◤
+ ███◥ ▼ ◤███
+  ◥◤ 
diff --git a/pttbbs/sample/etc/chickens/c4 b/pttbbs/sample/etc/chickens/c4
new file mode 100644
index 00000000..88095521
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c4
@@ -0,0 +1,11 @@
+
+ 
+ i
+ i ◢ 你!敢接受我的挑戰嗎?
+ i ████
+ i █◤︵◥█
+ i ◥██i█◥
+ ◥●◤ ◥██◤
+ ███◥█▼█◤███▅
+ █ ◢ ███ ◣
+ ◢ █勇█ ◣
diff --git a/pttbbs/sample/etc/chickens/c5 b/pttbbs/sample/etc/chickens/c5
new file mode 100644
index 00000000..ad2311ba
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c5
@@ -0,0 +1,12 @@
+
+
+ 
+ i
+ i ◢ 你!敢接受我的挑戰嗎?
+ i ████
+ i █◤︵◥█
+ i ◥██i█◥
+ ◥●◤ ◥██◤
+ ███◥█▼█◤███▅
+ █ ◢ ███ ◣
+ ◢ █勇█ ◣
diff --git a/pttbbs/sample/etc/chickens/c6 b/pttbbs/sample/etc/chickens/c6
new file mode 100644
index 00000000..ee018e38
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c6
@@ -0,0 +1,10 @@
+
+  ◣
+ i ████ 放馬過來!
+ i █◤︵██
+ i ◤█i◥█◤
+ i ◥██◤
+ i ◢█◤▼◥█◣
+  ◥●◤ ◢◤█╳██◣
+ ▆███◤█ 忍 █ ◣
+ █ █ █ █
diff --git a/pttbbs/sample/etc/chickens/c7 b/pttbbs/sample/etc/chickens/c7
new file mode 100644
index 00000000..0db89687
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c7
@@ -0,0 +1,12 @@
+
+ 
+ i 別 人 的 失 敗 就 是 我 的 快 樂 啦 !
+ i ◣
+ i ████
+ i █◤︵◥█
+ i ◤█i██◤
+ i ◥██◤
+ i ◥█◣▼◢█◤
+ ◥●◤ ◢◤◥█◤█◣
+ ███◤█ █ ◣
+ █ █ 命 █ █
diff --git a/pttbbs/sample/etc/chickens/c8 b/pttbbs/sample/etc/chickens/c8
new file mode 100644
index 00000000..22be6bab
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c8
@@ -0,0 +1,12 @@
+ ◢
+ ████ 雙腳踢翻塵世浪
+ █◤◥██
+ ◥██▌◥█ 一肩擔盡古今愁
+ ◥██◤◥
+ █∞◤▼◥∞█
+ █◤ ◥█
+ ◤█ 狂 █◥
+ ◥◣ █◢
+ ◥███●▄▄▄▄▄▄▄▄▄▄▄▄▄◤
+ ◢ ▋◥
+ ◤
diff --git a/pttbbs/sample/etc/chickens/c9 b/pttbbs/sample/etc/chickens/c9
new file mode 100644
index 00000000..676a0da9
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/c9
@@ -0,0 +1,11 @@
+ ◣
+ ◥◣
+ ◥◣ ◣
+ ◥◣ ████ 征衣紅塵化雲煙
+ ◥◣ ██◤◥█
+ ◥◣ █◤▌██◤ 江湖落拓不知年
+ ◥◣ ◤◥██◤
+ ◥◣ ◢██◣ ▼ ◢██◣
+ ◥◣ ◢◤◥◣◢◤◥◣
+ ◥█◣◢◤ ◣◥◤◢ █
+ ▆█◤ ◥ 劍 ◤ █
diff --git a/pttbbs/sample/etc/chickens/clean b/pttbbs/sample/etc/chickens/clean
new file mode 100644
index 00000000..9d9118a3
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/clean
@@ -0,0 +1,14 @@
+  朅_____________________▕▔▔▏_______▕\___
+  矙朅______________ /▏ ● ●_________●▕___
+  矙矙鱌_____________▕ ● _________▕ )____●___
+  朅______________●_____________●____________
+ 
+ 『████
+ ████
+ ▁▁▁▁▁▁▁▁◎████
+ ▆▆▆▆▆▆▆▆▉████
+ ◥ 
+ ◥█████
+ ◢
+  ▔▔▔▔▔▔
+ 洗洗澡..清除便便..
diff --git a/pttbbs/sample/etc/chickens/d0 b/pttbbs/sample/etc/chickens/d0
new file mode 100644
index 00000000..ea818c4d
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d0
@@ -0,0 +1,10 @@
+
+ |/╱
+ ☆
+  
+  ●● 
+  ●蛛● 
+  ●● 
+  
+
+
diff --git a/pttbbs/sample/etc/chickens/d1 b/pttbbs/sample/etc/chickens/d1
new file mode 100644
index 00000000..91e04a2f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d1
@@ -0,0 +1,9 @@
+
+ 生命??
+ ﹏
+ ╲\| ●●
+ ★ ●︴ ★
+ !! ● ◢▎
+ ● ● ◣
+ ●● \|/ ▎\|/ \|/
+  ﹌ 
diff --git a/pttbbs/sample/etc/chickens/d10 b/pttbbs/sample/etc/chickens/d10
new file mode 100644
index 00000000..714254ea
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d10
@@ -0,0 +1,11 @@
+
+ ●● ● !!!!
+ ● ● ∩\
+ ● ☆ ● ●●\\ !!!! ||∣
+ ● ★ ● ☉ ||∣
+ ●● ● ● ● ◣ ☉___________
+ ●/_●﹍● ●\_ ︼︾ ☉---︶︶︶︶ . . . . . .
+ ●\ ●\ ●/ ●/ ||∣
+ ◥ ◥ ▼ ◤ ∪/
+
+
diff --git a/pttbbs/sample/etc/chickens/d11 b/pttbbs/sample/etc/chickens/d11
new file mode 100644
index 00000000..b7e86634
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d11
@@ -0,0 +1,15 @@
+
+ 等等我!! ◢◣◢◣
+ ████
+ ◥██◤
+ ●● ● ◥◤
+ ● ☆ ●
+ ● ★ ● ●●\\ ●●
+ ● ☆ ● ∩ ● ● ●●\
+ ● ● ● ●● ◣ ● ● ∩
+ ●▅●▅●▅--●▃︼︾ ▅●▅●▅ ●▃ ◣
+ ●/ ●/ ●/ \● // // // \\
+ ◥\ ◥\ ◥\ |◤ \| \| \| |/
+  
+
+
diff --git a/pttbbs/sample/etc/chickens/d12 b/pttbbs/sample/etc/chickens/d12
new file mode 100644
index 00000000..ab421653
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d12
@@ -0,0 +1,13 @@
+ ▂◢▂ ▂◢▂ ▂▎
+ ◥ . ◥ ▎
+ . ▎
+ ●● ● . ▎
+ ● ☆ ● >●●∠ ▎
+ ● ★ ● ●●\\ —●●- ▎
+ ● ☆ ● ∩ / OO \ ▎
+ ● ● ● ●● ◣ ▎
+ ●▅●▅●▅--●▃︼︾ ▎
+ ●/ ●/ ●/ ●/ ▎
+ ◥\ ◥\ ◥\ ◤
+  
+
diff --git a/pttbbs/sample/etc/chickens/d13 b/pttbbs/sample/etc/chickens/d13
new file mode 100644
index 00000000..48cef654
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d13
@@ -0,0 +1,13 @@
+ 小魔星??
+ ● ● ●
+ ● * ●
+ ● * ● ●●\\
+ ● * ●● ︵●
+ ● * ∩ ●
+ ● ●* ● *●●● ◣
+ ● ●/ |●╱ ●\ ● ●●︼
+ ●/—●/—●\ ●\
+ ●\ ●\ ●\ ●\
+ ◤ ◥ ◥ ◥ \|/ \|/
+  
+
diff --git a/pttbbs/sample/etc/chickens/d14 b/pttbbs/sample/etc/chickens/d14
new file mode 100644
index 00000000..02f91a47
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d14
@@ -0,0 +1,13 @@
+ ︵ ︵
+ 鬥!! ╲╱ / |╱ | ︴
+ ● ● ● ◎◎/ ╱ / ︴
+ ● * ● ▼●╱ //
+ ●* * * * ● ●●\\ / / 》_╱ ╱
+ ● * * ●● ︵● / /○ _╱
+ ● * * * * ● ● // ● ﹏
+ ●*● *● ●● ◣ ● ●
+ ● ●/ /● ●\ ╲●︾︼ ●●
+ ●/—●/—\●﹉ ●\ ╱
+ \● \● \● \● ☆
+ ◥ ◥ ◥ ◥
+
diff --git a/pttbbs/sample/etc/chickens/d15 b/pttbbs/sample/etc/chickens/d15
new file mode 100644
index 00000000..2c305cb5
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d15
@@ -0,0 +1,11 @@
+ ● ● ●●\\
+ ● * ●● ︽●
+ ● * ▼ ●
+ ● * ●| ● ◣
+ ● * ●|╱ ●●︼/
+ ● ●* ● ● //●\ _______ \/
+ ●/ ●/ ●/_/ ●\ < ﹍/﹍/╲\><
+ ●/--●/--●\ ◥●\_\_ ╱○●▼/
+ ●\ ●\ ●\ ●●●Ω Ω Ω
+ ◥ ◥ ◥ ╱
+
diff --git a/pttbbs/sample/etc/chickens/d16 b/pttbbs/sample/etc/chickens/d16
new file mode 100644
index 00000000..2c305cb5
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d16
@@ -0,0 +1,11 @@
+ ● ● ●●\\
+ ● * ●● ︽●
+ ● * ▼ ●
+ ● * ●| ● ◣
+ ● * ●|╱ ●●︼/
+ ● ●* ● ● //●\ _______ \/
+ ●/ ●/ ●/_/ ●\ < ﹍/﹍/╲\><
+ ●/--●/--●\ ◥●\_\_ ╱○●▼/
+ ●\ ●\ ●\ ●●●Ω Ω Ω
+ ◥ ◥ ◥ ╱
+
diff --git a/pttbbs/sample/etc/chickens/d2 b/pttbbs/sample/etc/chickens/d2
new file mode 100644
index 00000000..b256f5e5
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d2
@@ -0,0 +1,11 @@
+ ▆▆▆▆▆▆▆
+ .
+ .
+ .
+ .
+ >●●∠ \★/ \★/
+ —●●- 喔? ◢▎ ◢▎
+ / OO \ ◣ ◣
+ \|/ ▎\|/ ▎ \|/
+  
+
diff --git a/pttbbs/sample/etc/chickens/d3 b/pttbbs/sample/etc/chickens/d3
new file mode 100644
index 00000000..c0dacb46
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d3
@@ -0,0 +1,16 @@
+ ▆▆▆▆▆▆▆▆
+ .
+ .
+ .
+ . 嘿嘿!dinner?
+ >●●<
+ —●●—
+ /☉☉\
+ \/ ??
+
+ >♁﹏﹏ \|/ \|/
+  
+
+
+
+
diff --git a/pttbbs/sample/etc/chickens/d4 b/pttbbs/sample/etc/chickens/d4
new file mode 100644
index 00000000..a11683f0
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d4
@@ -0,0 +1,12 @@
+ ▆▆▆▆▆▆▆▆
+ .
+ .
+ .
+ . 嘿嘿!BIG DINNER!!
+ —●●—
+ > ● <
+ /∩∩\ ???
+ ()
+ >●O● ﹏﹏
+  ^ ^ ^ 
+
diff --git a/pttbbs/sample/etc/chickens/d5 b/pttbbs/sample/etc/chickens/d5
new file mode 100644
index 00000000..30ca6a2c
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d5
@@ -0,0 +1,14 @@
+ ▆▆▆▆▆▆▆▆
+ .
+ .
+ .
+ \ . / ☆
+ ╲◢◣╱ ★
+ \—██—/
+ ☆ /—██—\ 倒吊太久會不會腦充血啊??
+ ╱**╲
+ / () \ ☆
+ ★
+
+
+
diff --git a/pttbbs/sample/etc/chickens/d6 b/pttbbs/sample/etc/chickens/d6
new file mode 100644
index 00000000..7e30c6b6
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d6
@@ -0,0 +1,12 @@
+
+ ●● 可怕嗎??
+ \ ● ● /
+ ╲_● ●_╱ 快閃!!!
+ _● ●_
+ \╱ _● ●_ ╲/ ◎●/
+ ╱ ● ● ╲ . . ●◎\
+ ╱‾●︵︵●‾╲ . .
+ / ◥◎◎◤ \ . .
+ ( ) . .
+ . . .
+
diff --git a/pttbbs/sample/etc/chickens/d7 b/pttbbs/sample/etc/chickens/d7
new file mode 100644
index 00000000..535e5503
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d7
@@ -0,0 +1,16 @@
+ .
+ . 陷阱???
+ ●●
+ \ ● ● /
+ ╲_● ●_╱ ▃▃◢▃▃▃◢▃
+ _● ●_ XXXXX◥XXXX
+ \╱ _● ●_ ╲/XXXXX \/ XX help﹏
+ ╱ ● ● ╲ XXXX ◎●︴XX▎
+ ╱‾●︵︵●‾╲ ◥XX ●◎ XX◤
+ / ◥∩∩◤ \ ◥XX﹌ XX ▎
+ ( ) ◥XXXXX◥
+ ◥XXX ▎╲∣/ \|╱
+
+
+
+
diff --git a/pttbbs/sample/etc/chickens/d8 b/pttbbs/sample/etc/chickens/d8
new file mode 100644
index 00000000..ed46f5e9
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d8
@@ -0,0 +1,14 @@
+
+ //╱ 流星!!
+ ☆
+ ● ●
+ ● ● ●●\\
+ ● ● ∩ 許個願吧!
+ ● ● ◣ 快快長大!!
+ ▅●▅●▅ ●▃︾
+ // // // \\
+ \| \| \| |/ \|/ \|╱ \|/
+  
+
+
+
diff --git a/pttbbs/sample/etc/chickens/d9 b/pttbbs/sample/etc/chickens/d9
new file mode 100644
index 00000000..409ae520
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/d9
@@ -0,0 +1,13 @@
+
+ // ╱
+ ●● ● //╱ 隕石??
+ ● Χ ● ★
+ ● Χ ● ●●\\
+ ● Χ ● >
+ ● ● ● ● ◣ 許願的代價?
+ ●/▅●▅●/▅● ︼︾
+ ●\ ●\ ●\ ●\
+ ◥ ◥ ◥ ◥ \|/ \|/ \|/
+  
+
+
diff --git a/pttbbs/sample/etc/chickens/deadth b/pttbbs/sample/etc/chickens/deadth
new file mode 100644
index 00000000..ff9b3687
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/deadth
@@ -0,0 +1,15 @@
+
+
+
+ ╭╮﹏﹏{{
+ ◥███◤
+ @`@`〝
+  "_ 
+ ▄▄
+ ぬ███ 
+ █ 
+  
+  
+  ╭┘ │ 
+ └─-┘
+ 嗚 死翹翹了 下次再來吧~~~~
diff --git a/pttbbs/sample/etc/chickens/e0 b/pttbbs/sample/etc/chickens/e0
new file mode 100644
index 00000000..b66f2972
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e0
@@ -0,0 +1,6 @@
+
+
+ ●●
+ ●●●
+ ●●
+
diff --git a/pttbbs/sample/etc/chickens/e1 b/pttbbs/sample/etc/chickens/e1
new file mode 100644
index 00000000..b66f2972
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e1
@@ -0,0 +1,6 @@
+
+
+ ●●
+ ●●●
+ ●●
+
diff --git a/pttbbs/sample/etc/chickens/e10 b/pttbbs/sample/etc/chickens/e10
new file mode 100644
index 00000000..72d37fd8
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e10
@@ -0,0 +1,12 @@
+
+ ◣◣◢◢
+ ◣◣◣◣◣◢
+ ●●●●●●●●
+  ●●●╳●●╳●●●
+  ●●▼▼▼▼▼▼▼●●
+  ●● ●●  ◤
+ ●●▲▲▲▲▲●● ●◤◤◤
+ ● ●●●●●●●●●● ◤●● ◤
+ ●●●●●●●●●●●●●●● ◤◤
+ ● ●●●●●●●●●●◤◤●
+  ●●●●●●●●●●●●●
diff --git a/pttbbs/sample/etc/chickens/e11 b/pttbbs/sample/etc/chickens/e11
new file mode 100644
index 00000000..1579e557
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e11
@@ -0,0 +1,14 @@
+
+ ◢◢◢
+ ◢◢◢◢◢◢
+  ●●●●●●◢◢◢  ◢
+  ●●◢◣●●●◢◢ ◎◎
+  ●●●●●●●●◢◢◎◎◎
+  ▼▼▼▼▼▼●●◢◎◎◎◎
+   ●●●◎◎
+  ● ▲▲▲▲▲▲●●●◢◢◢
+  ●●●●●●●●●●●●●◢◢ ◢
+ ●●●●●●●●●●◢◢ ◢◢
+ ●●●●●●●●●●●●●◣◣◣◢◢◢
+ ● ●●●●●●●●●●●●◣◣◢
+
diff --git a/pttbbs/sample/etc/chickens/e12 b/pttbbs/sample/etc/chickens/e12
new file mode 100644
index 00000000..0a05bc8a
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e12
@@ -0,0 +1,15 @@
+
+  ◎ ◎  ◣◣  ◎
+  ◣◣◣◣
+ ◣ ◣◣●●●●●  ◎ ◢◢
+ ◎◎  ◣●●●●●● ◢◢◢◢
+ ◎◎◎◣◣●●●●● ●●●●◢
+ ◎◎◎◎◣●●●●●●●● ◎ ●●●◢◢  ◢
+  ◎  ◎◎◣◣●●●●●◥◥◥◥ ●●●●●●◢  ◎◎
+ ◎◣◣◣●●●●● ◤◤●●●●◢◢◎◎  ◎
+ ◣◣◣●●●●●● ●●●●◢◢◎
+ ◣ ◣◣◣●●●●●●●◢ ◣●●●●●●◢◢
+ ◣◣ ◢◢◢●●●●●●●●●● ●●●●●●●●◢◢ ◢
+ ◣◣◢◢◢●●●●●●●●●● ●●●●●●●●◣◣◢◢
+
+
diff --git a/pttbbs/sample/etc/chickens/e13 b/pttbbs/sample/etc/chickens/e13
new file mode 100644
index 00000000..7fcee5e0
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e13
@@ -0,0 +1,14 @@
+
+ ◢◢◢  ◢
+ ◢◢◢◢◢◢  ◎◎◎
+  ●●●●●●◢◢◢ ◎◎◎◎
+   ●●╳●●●●◢◢  ◎◎◎◎◎
+   ●●●●●●●●◢◢◎◎◎◎◎
+  ▼▼▼▼▼▼●●◢◎◎◎◎◎◎
+  ●●● ●●●◎◎◎◎◎
+  ●  ●● ●●●◢◢◎◎
+  ●●● ▲▲▲▲▲▲▲●●●◢◢
+ ●●●●●●●●●●●◢◢ ◢◢
+ ●●●●●●●●●●●●●◣◣◣◢◢◢
+ ● ●●●●●●●●●●●●◣◣◢
+
diff --git a/pttbbs/sample/etc/chickens/e14 b/pttbbs/sample/etc/chickens/e14
new file mode 100644
index 00000000..5685534b
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e14
@@ -0,0 +1,13 @@
+
+ ◢◢◢ ◢
+  ◢◢◢◢◢  ◎◎
+ ●●●●●●◢◢  ◎◎◎
+ ●●●●●●●◢ ◎◎◎◎
+ ●●●●●●◢◢  ◎◎◎◎◎
+ ●●●●●●●●●◢◢ ◎◎◎◎◎
+ ◤◤◤◤◤◤●●●●◢◢◎◎◎◎
+ ●●●  ●●●●◢◢◎◎◎
+  ●● ● ●●  ●●●●◢◢◎◎
+ ●●● ●● ● ◣◣●●●●●●●●◢◢◢ ◢
+ ●● ●●● ●●●●●●●●●●●●●●●◣◣ ◢◢
+ ●● ●●●●●●●●●●●◣◢◢◢
diff --git a/pttbbs/sample/etc/chickens/e15 b/pttbbs/sample/etc/chickens/e15
new file mode 100644
index 00000000..b1c406be
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e15
@@ -0,0 +1,13 @@
+
+ ◣ ◣◣◣ 
+  ◎◎  ◣◣◣◣◣ 
+  ◎◎◎◎  ◣◣●●●●●● 
+  ◎◎◎◎◎  ◣●●●●●●●● 
+  ◎◎◎◎◎ ◣◣●●●●●●╳●● 
+ ◎◎◎◎  ◣●●●●◥◥◥◥◥◥ ●
+  ◎◎ ◣◣●●●●● ●●●●●
+ ◎◎◣◣◣●●●● ●● 
+  ◣  ◎◣◣◣●●●●●◢◢◢◢◢  ● 
+ ◣◣ ◣◣◣●●●●●●●●●● ● 
+ ◣◣ ◢◢◢●●●●●●●●●●●●●●
+  ◣◣◢◢◢●●●●●●●●●● 
diff --git a/pttbbs/sample/etc/chickens/e16 b/pttbbs/sample/etc/chickens/e16
new file mode 100644
index 00000000..c607a360
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e16
@@ -0,0 +1,14 @@
+
+ ◢◢◢ ◢
+  ◢◢◢◢◢ ●●◢  ◢
+ ●●●●●●◢◢●︽●◢ ◎◎
+ ●●●●●●●◢ ●●◢  ◎◎◎
+ ●●●●●●◢◢ ●◣  ◎◎◎◎
+ ●●●●●●●●●◢◢ ●◢ ◎◎◎◎
+  ◣ ◤◤◤●●●●●●●◢◢  ◎◎◎
+  ◣●● ● ●●●●●●◢◢  ◎◎◎◎
+  ◣●●● ◣ ● ●●●●●●◢◢◎◎◎
+◢◢●●●● ◣●● ● ● ◣●●●●●●●●●◢◢◢
+ ◣●︽● ●●●●●●●●●●●●●●●◣◣ ◢
+ ◢◢●●●● ● ●●●●●●●●●●●●◣◢◢
+
diff --git a/pttbbs/sample/etc/chickens/e2 b/pttbbs/sample/etc/chickens/e2
new file mode 100644
index 00000000..d975147c
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e2
@@ -0,0 +1,6 @@
+
+
+ ●●◢
+ ●●●◢
+ ●●●●◣◣
+
diff --git a/pttbbs/sample/etc/chickens/e3 b/pttbbs/sample/etc/chickens/e3
new file mode 100644
index 00000000..244f71d2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e3
@@ -0,0 +1,6 @@
+ ◢◢
+ ●●●◢
+ ●︽︽●◢
+ ●●●●◢◢
+ ●●●●●◣◣◢
+
diff --git a/pttbbs/sample/etc/chickens/e4 b/pttbbs/sample/etc/chickens/e4
new file mode 100644
index 00000000..d6b05b44
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e4
@@ -0,0 +1,8 @@
+
+ ◢◢◢
+ ●●●●◢
+ ●●●●●◢
+ ●●▼▼●◢◢
+ ●●▲●●●◢◢ ◢
+ ●●●●●●●◣◣◢
+
diff --git a/pttbbs/sample/etc/chickens/e5 b/pttbbs/sample/etc/chickens/e5
new file mode 100644
index 00000000..a78b2df8
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e5
@@ -0,0 +1,9 @@
+
+ ◢◢◢
+ ●●●●◢
+ ●◣●◢●◢◢
+ ●●●●●●◢
+ ●●▼▼▼●●◢◢
+ ●●▲▲●●●●◢◢ ◢
+ ●●●●●●●●◣◣◢◢◢
+
diff --git a/pttbbs/sample/etc/chickens/e6 b/pttbbs/sample/etc/chickens/e6
new file mode 100644
index 00000000..66af6938
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e6
@@ -0,0 +1,9 @@
+
+  ◢◢◢◢
+ ●●●●◢◢
+ ●︽●︽●●◢◢
+ ●●●●●●●◢
+ ●▼▼▼▼●●◢◢◢
+ ●●▲▲●●●●◢ ◢
+ ●●●●●●●●◣◣ ◢◢
+ ●●●●●●●●●◣◢◢
diff --git a/pttbbs/sample/etc/chickens/e7 b/pttbbs/sample/etc/chickens/e7
new file mode 100644
index 00000000..fc0b8270
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e7
@@ -0,0 +1,11 @@
+
+ ◣◣◣◣
+ ◣◣●●●●● 
+ ◣◣●●●●● 
+ ◣◣●●●●●●● 
+ ◣◣◣●●●●●●◥◥  ◎
+ ◣◣●●●●●  ◎
+ ◣◣●●●●●●●●◢
+ ◣ ◣◣●●●●●●●●● ◎
+ ◣◣◢◢●●●●●●●●●
+
diff --git a/pttbbs/sample/etc/chickens/e8 b/pttbbs/sample/etc/chickens/e8
new file mode 100644
index 00000000..6841430c
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e8
@@ -0,0 +1,11 @@
+
+ ◣◣
+ ◣◣◣◣
+ ◣◣●●●●●
+ ◣●◣●●◢●
+ ◣◣●●●●●●●
+ ◣◣●▼▼▼▼▼●
+ ◣◣◣●●▲▲▲▲●
+ ◣ ◣◣●●歈●●歈
+ ◣◣◢◢●●●●●●●●●
+ ◣◢◢●●●●●●●●●
diff --git a/pttbbs/sample/etc/chickens/e9 b/pttbbs/sample/etc/chickens/e9
new file mode 100644
index 00000000..1c475487
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/e9
@@ -0,0 +1,12 @@
+
+ ◣◣
+ ◣◣◣◣◣
+ ◣●●●●●
+ ◣●●●◥◤●●
+ ◣◣●●▼▼▼◥◥◥ 
+ ◣◣●● 
+ ◣ ◣◣●●●●▲▲▲▲▲▲ 
+ ◣ ◣◣●●●●●●●翦
+ ◣◣◣◢◢●●●●●●●●●●
+ ◣◣◢●●●●●●●●●●●
+
diff --git a/pttbbs/sample/etc/chickens/eat b/pttbbs/sample/etc/chickens/eat
new file mode 100644
index 00000000..03f3a578
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/eat
@@ -0,0 +1,14 @@
+
+
+
+
+ _._ /
+  ╮  ▁   ╮ ▼   ╭
+  () [≡ ###/  │ ()
+  ∥  ▕ ▔ ▔ ▏ ∥
+ │  ▕︽︽︽︽︽︽︽︽︽︽▏   │
+ ╰踛  矱    踛灨
+ ∥ ≡   ∥
+  ╭┴╮ ╭┴╮   ╭┴╮
+  ▔ ▔ ▔ ▔ ▔ ▔
+ 吃飯囉~~~~~~~~
diff --git a/pttbbs/sample/etc/chickens/f0 b/pttbbs/sample/etc/chickens/f0
new file mode 100644
index 00000000..36f55c21
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f0
@@ -0,0 +1,9 @@
+
+
+ ●●●
+ ● ●● ●
+ ●●●●●●
+ ●●●●●●
+ ●● ●●
+ ●● ●●
+ ●●●●
diff --git a/pttbbs/sample/etc/chickens/f1 b/pttbbs/sample/etc/chickens/f1
new file mode 100644
index 00000000..36f55c21
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f1
@@ -0,0 +1,9 @@
+
+
+ ●●●
+ ● ●● ●
+ ●●●●●●
+ ●●●●●●
+ ●● ●●
+ ●● ●●
+ ●●●●
diff --git a/pttbbs/sample/etc/chickens/f10 b/pttbbs/sample/etc/chickens/f10
new file mode 100644
index 00000000..b5c8367f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f10
@@ -0,0 +1,15 @@
+ ●● ●●
+ ●●●● ●●●●
+ ●●●●●●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ●
+ ● ● ● ●
+ ●● ●●●
+ ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/f11 b/pttbbs/sample/etc/chickens/f11
new file mode 100644
index 00000000..8646bce2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f11
@@ -0,0 +1,14 @@
+ ●●●●●●
+ ● ● ●●
+ ● ● ● ●●
+ ●● ●●●
+ ● ● ●●
+ ●●●●●●●● ●●
+ ● ●●
+ ● ● ● ●
+ ● ● ● ●
+ ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ● ●
+ ● ●
diff --git a/pttbbs/sample/etc/chickens/f12 b/pttbbs/sample/etc/chickens/f12
new file mode 100644
index 00000000..8646bce2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f12
@@ -0,0 +1,14 @@
+ ●●●●●●
+ ● ● ●●
+ ● ● ● ●●
+ ●● ●●●
+ ● ● ●●
+ ●●●●●●●● ●●
+ ● ●●
+ ● ● ● ●
+ ● ● ● ●
+ ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ● ●
+ ● ●
diff --git a/pttbbs/sample/etc/chickens/f13 b/pttbbs/sample/etc/chickens/f13
new file mode 100644
index 00000000..ba9d1d34
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f13
@@ -0,0 +1,14 @@
+ ●●●●
+ ●●●●●●●●
+ ●●●●●●●●●●
+ ●●●●●●●●●●●●
+ ● ●●●●●●●●●
+ ●● ● ●●
+ ●●●● ●
+ ● ●
+ ●● ●●
+ ● ●●●●●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●● ●●
diff --git a/pttbbs/sample/etc/chickens/f14 b/pttbbs/sample/etc/chickens/f14
new file mode 100644
index 00000000..ba9d1d34
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f14
@@ -0,0 +1,14 @@
+ ●●●●
+ ●●●●●●●●
+ ●●●●●●●●●●
+ ●●●●●●●●●●●●
+ ● ●●●●●●●●●
+ ●● ● ●●
+ ●●●● ●
+ ● ●
+ ●● ●●
+ ● ●●●●●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●● ●●
diff --git a/pttbbs/sample/etc/chickens/f15 b/pttbbs/sample/etc/chickens/f15
new file mode 100644
index 00000000..df160353
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f15
@@ -0,0 +1,15 @@
+ ●●●●●●
+ ● ● ●
+ ● ●● ●
+ ● ● ●
+ ●●● ●
+ ● ●
+ ● ●● ●
+ ● ● ●
+ ● ● ● ●
+ ● ● ●
+ ● ●
+ ● ●
+ ● ●● ●●●
+ ● ●● ●
+ ● ●
diff --git a/pttbbs/sample/etc/chickens/f16 b/pttbbs/sample/etc/chickens/f16
new file mode 100644
index 00000000..100d4260
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f16
@@ -0,0 +1,15 @@
+ ●●●●●●●
+ ● ●●●
+ ● ● ● ●●●
+ ●●●●●●● ●●
+ ● ● ●
+ ●●●●●●● ●
+ ● ● ●
+ ●●●●●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●● ● ●
+ ● ●●●●●● ●●
+ ● ●
+ ●●● ●●
diff --git a/pttbbs/sample/etc/chickens/f2 b/pttbbs/sample/etc/chickens/f2
new file mode 100644
index 00000000..6714c9d2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f2
@@ -0,0 +1,12 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/f3 b/pttbbs/sample/etc/chickens/f3
new file mode 100644
index 00000000..6714c9d2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f3
@@ -0,0 +1,12 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/f4 b/pttbbs/sample/etc/chickens/f4
new file mode 100644
index 00000000..723291ae
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f4
@@ -0,0 +1,13 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ●●● ●● ●●
+ ● ●
+ ● ●●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/f5 b/pttbbs/sample/etc/chickens/f5
new file mode 100644
index 00000000..723291ae
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f5
@@ -0,0 +1,13 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ●●● ●● ●●
+ ● ●
+ ● ●●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/f6 b/pttbbs/sample/etc/chickens/f6
new file mode 100644
index 00000000..d3edcf12
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f6
@@ -0,0 +1,14 @@
+
+
+
+ ●●●●●●
+ ● ● ●
+ ● ●● ●
+ ● ● ●
+ ●●● ●
+ ● ●
+ ● ●● ●
+ ● ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/f7 b/pttbbs/sample/etc/chickens/f7
new file mode 100644
index 00000000..d3edcf12
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f7
@@ -0,0 +1,14 @@
+
+
+
+ ●●●●●●
+ ● ● ●
+ ● ●● ●
+ ● ● ●
+ ●●● ●
+ ● ●
+ ● ●● ●
+ ● ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/f8 b/pttbbs/sample/etc/chickens/f8
new file mode 100644
index 00000000..b5c8367f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f8
@@ -0,0 +1,15 @@
+ ●● ●●
+ ●●●● ●●●●
+ ●●●●●●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ●
+ ● ● ● ●
+ ●● ●●●
+ ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/f9 b/pttbbs/sample/etc/chickens/f9
new file mode 100644
index 00000000..b5c8367f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/f9
@@ -0,0 +1,15 @@
+ ●● ●●
+ ●●●● ●●●●
+ ●●●●●●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ●
+ ● ● ● ●
+ ●● ●●●
+ ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/food b/pttbbs/sample/etc/chickens/food
new file mode 100644
index 00000000..bb686d2f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/food
@@ -0,0 +1,14 @@
+
+  
+   ▄▄▄  
+   ◤◤  
+   ELEVEN  
+   █  
+ █▃▃▃▃█
+ //////
+     
+  ((((((  ●●- > Marshakt
+  ▄▄▄ˍ ╰▼ ╯  妙天小廚師 
+ \-. >>
+ : 
+ 來買食物啦....
diff --git a/pttbbs/sample/etc/chickens/g0 b/pttbbs/sample/etc/chickens/g0
new file mode 100644
index 00000000..1ed07519
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g0
@@ -0,0 +1,5 @@
+
+ ●●
+ ●●●
+ ●●╯
+
diff --git a/pttbbs/sample/etc/chickens/g1 b/pttbbs/sample/etc/chickens/g1
new file mode 100644
index 00000000..1ed07519
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g1
@@ -0,0 +1,5 @@
+
+ ●●
+ ●●●
+ ●●╯
+
diff --git a/pttbbs/sample/etc/chickens/g10 b/pttbbs/sample/etc/chickens/g10
new file mode 100644
index 00000000..cf4c0f14
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g10
@@ -0,0 +1,13 @@
+
+ ◣ ◢  ◣ ◢  ◣ ◢
+ ●●●●●  ●●●●●  ●●●●●
+ ☉*♁☆ ● ●  ● ●  ● ●
+ ☆*☉** ● ╳ ╳ ●  ● ︽ ︽ ●  ● 〥 〥 ●
+ *♁*☉*  ● ●  ● ●  ● ●
+ *♁*☆   ● ●  ● ●  ● ●
+ 稙  ● ●  ● ● ● ●
+  ● ●  ● ●  ● ●
+  ● ●  ● ● ● ●
+  嘵 ●●●●●●  ●●●●●  ●●●●
+
+
diff --git a/pttbbs/sample/etc/chickens/g11 b/pttbbs/sample/etc/chickens/g11
new file mode 100644
index 00000000..0c6390f8
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g11
@@ -0,0 +1,15 @@
+
+  ◣ ◢
+ ●●●●●●
+ ● ● 
+  ●  ﹏ ﹏ ●
+ ● ═ ● ═ ●
+ ● ﹌  ●
+ ● ●
+ ● ●
+ ● ●
+ ● ●
+ ● ●
+  ●●●●●●● 
+
+
diff --git a/pttbbs/sample/etc/chickens/g12 b/pttbbs/sample/etc/chickens/g12
new file mode 100644
index 00000000..326c650d
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g12
@@ -0,0 +1,15 @@
+
+  ◣ ◢  ●●●●●●●
+ ●●●●●●●●●●●●●●
+ ● ●  ●●●●●●●
+ ● 〥 〥 ●  ●●●●●●
+ ● ═ ● ═ ●  ● ●●●●
+ ●  ∼  ● ●
+  ● ●  ●
+  ● ●  ●
+ ●●●●●●●● 
+ ●●●●●●●●
+  ●  ╳  ●
+ ●●●●●●●
+
+
diff --git a/pttbbs/sample/etc/chickens/g13 b/pttbbs/sample/etc/chickens/g13
new file mode 100644
index 00000000..0b6e8b53
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g13
@@ -0,0 +1,14 @@
+ ● ●
+ ●●●●●●   ◣ ◣
+  ● ●  ●●●●●
+ ●  ︽ ︽ ● ● ●
+  ● >●< ●  ● ︽ ︽ ●
+  ● ●  ●>●<●
+  ●●■●●  ●●●●
+  ●  ■  ●  ● ●
+   ●  ■ ●  ● ● 
+  ●  ▼  ●  ● ●
+  ● ●  ● ●
+  ●●●●●●●  ●●●●●●
+
+
diff --git a/pttbbs/sample/etc/chickens/g14 b/pttbbs/sample/etc/chickens/g14
new file mode 100644
index 00000000..12c177ea
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g14
@@ -0,0 +1,14 @@
+
+ ● ●
+ ●●●●●●  ◣ ◢
+ ● ●  ●●●●●
+ ● ● ● ● ● ●
+ ● > ● < ● ●●● 〥 〥 ●
+ ● ∼  ● ●● ●═◆═●
+ ◣ ◢  ═●●═  ●●  ●●●
+  ●●●●  ● ● ●● ● ●
+ ● ●  ● ●●●● ● ◣◢
+ ● ☉☉ ●  ● ●●● ●●◤◥
+ ● ●  ● ●●● ● ☉☉●
+ ●●●●●  ●●●●●●●●  ●●●●●●●●●●
+
diff --git a/pttbbs/sample/etc/chickens/g15 b/pttbbs/sample/etc/chickens/g15
new file mode 100644
index 00000000..05757960
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g15
@@ -0,0 +1,14 @@
+
+
+ ● ●
+ ●●●●●●●
+  ● ●●●●●  雙下巴都跑出來了呢!
+ ● # #  ●
+ ● ═●═  ●
+ ● ︵  ●
+ ● ●
+ ●  ︾  ●
+ ● ●●
+  ●●●●●●●●●●●●● ●●●●●●●
+
+
diff --git a/pttbbs/sample/etc/chickens/g16 b/pttbbs/sample/etc/chickens/g16
new file mode 100644
index 00000000..03e4ebaa
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g16
@@ -0,0 +1,14 @@
+
+ ●●
+ ╮ ● ● ●●
+ ●●● ╰╮● ●  ● ●
+ ●●●● ● ╰╮  ●● ●●● ●
+ ●●  (  ∥ ◤ ╰──╮ ● ● ●
+ ●  ●  ●●  ╰╮ ● ●  ●
+ ●●●  (  ∥ ◣ ● ● ╰╮● ●  ●  ●●●●
+ ●●  ● ●  ╰╮ ●  ●  ●●●●●
+ ●  │  ●  ●  ●●●●●●
+ ●  ╰╮● ●●  ●●●●●
+ ●●●●●●●●●●●● ╰─────────●●●●
+
+
diff --git a/pttbbs/sample/etc/chickens/g2 b/pttbbs/sample/etc/chickens/g2
new file mode 100644
index 00000000..032a3e61
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g2
@@ -0,0 +1,5 @@
+
+ ●●●
+ ●OO●
+ ●●●
+
diff --git a/pttbbs/sample/etc/chickens/g3 b/pttbbs/sample/etc/chickens/g3
new file mode 100644
index 00000000..5e65b34d
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g3
@@ -0,0 +1,6 @@
+
+ ◣ ◢
+ ●●●
+ ●OO●
+ ●●●●
+
diff --git a/pttbbs/sample/etc/chickens/g4 b/pttbbs/sample/etc/chickens/g4
new file mode 100644
index 00000000..5e8a7520
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g4
@@ -0,0 +1,6 @@
+
+ ◣ ◢
+ ●●●●
+ ●Ο Ο●
+ ●≡◆≡●
+ ●●●● 
diff --git a/pttbbs/sample/etc/chickens/g5 b/pttbbs/sample/etc/chickens/g5
new file mode 100644
index 00000000..7bd2bea7
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g5
@@ -0,0 +1,8 @@
+
+ ◣ ◢
+ ●●●●● 
+ ● Ω Ω ●
+ ● ≧◆≦ ●
+ ●●●●● 
+ ● ●
+
diff --git a/pttbbs/sample/etc/chickens/g6 b/pttbbs/sample/etc/chickens/g6
new file mode 100644
index 00000000..87477217
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g6
@@ -0,0 +1,10 @@
+
+ ◣ ◢
+ ●●●●●● 
+ ● 〥 〥 ●  矙
+ ● > ● < ● 
+ ●  ﹀ ● 
+ ●●●●●●●●
+ ●●●●●●●
+
+
diff --git a/pttbbs/sample/etc/chickens/g7 b/pttbbs/sample/etc/chickens/g7
new file mode 100644
index 00000000..08e1a435
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g7
@@ -0,0 +1,11 @@
+
+ ● ●
+ ●●●●●●
+ ● ●●●
+ ● 〥 〥  ●  ●
+ ● >●<  ●  ●● ●
+ ●  ∪  ● ● ●●
+ ● ● ●
+ ●●●●●●●●●
+ ●● ●●●●●
+
diff --git a/pttbbs/sample/etc/chickens/g8 b/pttbbs/sample/etc/chickens/g8
new file mode 100644
index 00000000..3de22b49
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g8
@@ -0,0 +1,14 @@
+
+ ●
+ ●
+ ◣ ◣ ●●● ●
+ ●●●●●●● ● ●
+ ● ●●
+ ● ◣ ◢ ●●
+ ● >●<  ●●
+ ● ◤ ◥ ●●
+ ● ◣ ◢ ●●◤◤
+ ●●●●●●●◤◤
+ ◤◤ ◤◤
+
+
diff --git a/pttbbs/sample/etc/chickens/g9 b/pttbbs/sample/etc/chickens/g9
new file mode 100644
index 00000000..c0274032
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/g9
@@ -0,0 +1,14 @@
+ ◢ ◢
+ ●●●● ●
+ ● ●  ●
+ ● ●  ● ●
+ ●═  ● ●
+ ●●● ●  ●
+ ●● ●● ● ● ●
+ ● ◢ ●  ● ●●
+ ● ◢■◢ ● ● ●
+ ● ◥■◥ ●  ● ●
+ ● ◥ ●  ●  ●
+ ●●●●●●●● ●●●
+
+
diff --git a/pttbbs/sample/etc/chickens/h0 b/pttbbs/sample/etc/chickens/h0
new file mode 100644
index 00000000..36f55c21
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h0
@@ -0,0 +1,9 @@
+
+
+ ●●●
+ ● ●● ●
+ ●●●●●●
+ ●●●●●●
+ ●● ●●
+ ●● ●●
+ ●●●●
diff --git a/pttbbs/sample/etc/chickens/h1 b/pttbbs/sample/etc/chickens/h1
new file mode 100644
index 00000000..36f55c21
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h1
@@ -0,0 +1,9 @@
+
+
+ ●●●
+ ● ●● ●
+ ●●●●●●
+ ●●●●●●
+ ●● ●●
+ ●● ●●
+ ●●●●
diff --git a/pttbbs/sample/etc/chickens/h10 b/pttbbs/sample/etc/chickens/h10
new file mode 100644
index 00000000..b5c8367f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h10
@@ -0,0 +1,15 @@
+ ●● ●●
+ ●●●● ●●●●
+ ●●●●●●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ●
+ ● ● ● ●
+ ●● ●●●
+ ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/h11 b/pttbbs/sample/etc/chickens/h11
new file mode 100644
index 00000000..8646bce2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h11
@@ -0,0 +1,14 @@
+ ●●●●●●
+ ● ● ●●
+ ● ● ● ●●
+ ●● ●●●
+ ● ● ●●
+ ●●●●●●●● ●●
+ ● ●●
+ ● ● ● ●
+ ● ● ● ●
+ ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ● ●
+ ● ●
diff --git a/pttbbs/sample/etc/chickens/h12 b/pttbbs/sample/etc/chickens/h12
new file mode 100644
index 00000000..8646bce2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h12
@@ -0,0 +1,14 @@
+ ●●●●●●
+ ● ● ●●
+ ● ● ● ●●
+ ●● ●●●
+ ● ● ●●
+ ●●●●●●●● ●●
+ ● ●●
+ ● ● ● ●
+ ● ● ● ●
+ ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ● ●
+ ● ●
diff --git a/pttbbs/sample/etc/chickens/h13 b/pttbbs/sample/etc/chickens/h13
new file mode 100644
index 00000000..ba9d1d34
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h13
@@ -0,0 +1,14 @@
+ ●●●●
+ ●●●●●●●●
+ ●●●●●●●●●●
+ ●●●●●●●●●●●●
+ ● ●●●●●●●●●
+ ●● ● ●●
+ ●●●● ●
+ ● ●
+ ●● ●●
+ ● ●●●●●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●● ●●
diff --git a/pttbbs/sample/etc/chickens/h14 b/pttbbs/sample/etc/chickens/h14
new file mode 100644
index 00000000..ba9d1d34
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h14
@@ -0,0 +1,14 @@
+ ●●●●
+ ●●●●●●●●
+ ●●●●●●●●●●
+ ●●●●●●●●●●●●
+ ● ●●●●●●●●●
+ ●● ● ●●
+ ●●●● ●
+ ● ●
+ ●● ●●
+ ● ●●●●●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●● ●●
diff --git a/pttbbs/sample/etc/chickens/h15 b/pttbbs/sample/etc/chickens/h15
new file mode 100644
index 00000000..df160353
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h15
@@ -0,0 +1,15 @@
+ ●●●●●●
+ ● ● ●
+ ● ●● ●
+ ● ● ●
+ ●●● ●
+ ● ●
+ ● ●● ●
+ ● ● ●
+ ● ● ● ●
+ ● ● ●
+ ● ●
+ ● ●
+ ● ●● ●●●
+ ● ●● ●
+ ● ●
diff --git a/pttbbs/sample/etc/chickens/h16 b/pttbbs/sample/etc/chickens/h16
new file mode 100644
index 00000000..100d4260
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h16
@@ -0,0 +1,15 @@
+ ●●●●●●●
+ ● ●●●
+ ● ● ● ●●●
+ ●●●●●●● ●●
+ ● ● ●
+ ●●●●●●● ●
+ ● ● ●
+ ●●●●●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●● ● ●
+ ● ●●●●●● ●●
+ ● ●
+ ●●● ●●
diff --git a/pttbbs/sample/etc/chickens/h2 b/pttbbs/sample/etc/chickens/h2
new file mode 100644
index 00000000..6714c9d2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h2
@@ -0,0 +1,12 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/h3 b/pttbbs/sample/etc/chickens/h3
new file mode 100644
index 00000000..6714c9d2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h3
@@ -0,0 +1,12 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/h4 b/pttbbs/sample/etc/chickens/h4
new file mode 100644
index 00000000..723291ae
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h4
@@ -0,0 +1,13 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ●●● ●● ●●
+ ● ●
+ ● ●●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/h5 b/pttbbs/sample/etc/chickens/h5
new file mode 100644
index 00000000..723291ae
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h5
@@ -0,0 +1,13 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ●●● ●● ●●
+ ● ●
+ ● ●●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/h6 b/pttbbs/sample/etc/chickens/h6
new file mode 100644
index 00000000..d3edcf12
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h6
@@ -0,0 +1,14 @@
+
+
+
+ ●●●●●●
+ ● ● ●
+ ● ●● ●
+ ● ● ●
+ ●●● ●
+ ● ●
+ ● ●● ●
+ ● ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/h7 b/pttbbs/sample/etc/chickens/h7
new file mode 100644
index 00000000..d3edcf12
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h7
@@ -0,0 +1,14 @@
+
+
+
+ ●●●●●●
+ ● ● ●
+ ● ●● ●
+ ● ● ●
+ ●●● ●
+ ● ●
+ ● ●● ●
+ ● ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/h8 b/pttbbs/sample/etc/chickens/h8
new file mode 100644
index 00000000..b5c8367f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h8
@@ -0,0 +1,15 @@
+ ●● ●●
+ ●●●● ●●●●
+ ●●●●●●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ●
+ ● ● ● ●
+ ●● ●●●
+ ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/h9 b/pttbbs/sample/etc/chickens/h9
new file mode 100644
index 00000000..b5c8367f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/h9
@@ -0,0 +1,15 @@
+ ●● ●●
+ ●●●● ●●●●
+ ●●●●●●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ●
+ ● ● ● ●
+ ●● ●●●
+ ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/hit b/pttbbs/sample/etc/chickens/hit
new file mode 100644
index 00000000..2fe4b736
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/hit
@@ -0,0 +1,14 @@
+
+
+
+
+
+ ◤ "  >< 
+  d▃  █  ﹌ 
+  ███ █ █ ff █
+  `` ▊ ●." █ █ ▊ █
+  ◢▇◥◣  ◢ ▇◣
+ .o  .o o, o,
+
+
+ 嘿 打架練身體..
diff --git a/pttbbs/sample/etc/chickens/i0 b/pttbbs/sample/etc/chickens/i0
new file mode 100644
index 00000000..40ac1217
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i0
@@ -0,0 +1,12 @@
+
+誕生囉∼
+
+
+  ╲ || ╱
+
+ ◢█◣
+ █▋█
+ █▊█
+ ◥█◤
+
+
diff --git a/pttbbs/sample/etc/chickens/i1 b/pttbbs/sample/etc/chickens/i1
new file mode 100644
index 00000000..189ef11f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i1
@@ -0,0 +1,11 @@
+
+狗蛋?
+
+
+
+
+ ▃▃▃
+ |=.=|
+ ╰w─
+ ◥█◤
+
diff --git a/pttbbs/sample/etc/chickens/i10 b/pttbbs/sample/etc/chickens/i10
new file mode 100644
index 00000000..6da5f43a
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i10
@@ -0,0 +1,13 @@
+
+我要成為帥帥狗!!
+
+
+ ◢██▇▅e
+ ▕\\─\\  ▌
+  ▼ 
+ ◥﹀ ◤
+ ◢▅▅▅█◣
+ | │ 
+ │ │ 
+ │ ♁ │ 
+  \ 
diff --git a/pttbbs/sample/etc/chickens/i11 b/pttbbs/sample/etc/chickens/i11
new file mode 100644
index 00000000..b8ad1b15
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i11
@@ -0,0 +1,12 @@
+
+去游泳~
+
+
+ ▄▆▇█▇▆▄
+ ▌ ∩ ∩  ▌
+ ◣ ▼ ◢
+ ◢▅▅▅◣
+ ▄| |▄
+ █ █ █
+  
+  
diff --git a/pttbbs/sample/etc/chickens/i12 b/pttbbs/sample/etc/chickens/i12
new file mode 100644
index 00000000..ffbab836
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i12
@@ -0,0 +1,13 @@
+
+招財狗
+ ▃▄▅▅▄▃
+ █ █ 汪!!
+ ▌ ● ●  ▌
+  ▼ 
+ ◥ ︶︶ ◤◢◣
+ ▄▄▄▄ █
+ ◢ ██
+  │ 
+  │ _ 
+ ◣ ̄ ̄╱ 
+  ̄ ̄ 
diff --git a/pttbbs/sample/etc/chickens/i13 b/pttbbs/sample/etc/chickens/i13
new file mode 100644
index 00000000..b435427b
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i13
@@ -0,0 +1,13 @@
+
+ ▃▄▅▅▄▃
+ █ █ 我來跳草裙舞~汪!
+ ▌ ︵ ︵  ▌
+  ▼ 
+ ◥ ︶︶ ◤
+ ▄▄▄▄
+ ◢__ ◣
+ _╮╮╮╲ ╮
+   ̄ ̄ 
+           
+ ▌▌▌▌▌▌▌
+ ( ( ( ▌▌▌▌▌▌▌ 
diff --git a/pttbbs/sample/etc/chickens/i14 b/pttbbs/sample/etc/chickens/i14
new file mode 100644
index 00000000..c1c8edd4
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i14
@@ -0,0 +1,14 @@
+
+
+ ▃▄▅▅▄▃ ??? (os:我沒有偷上網...)
+ █ █
+ ▌ ● ●  ▌
+◢█▌  ▼ 
+██▌ ◥ ◆ ◣
+██▌ ◥ ◣
+██▌ ▄▄▄▄▄e
+██▌ ◢ ╱ 
+◥█▌ _╱ ╱ 
+████◣ ╭╭ ╱ 
+█████ ◢______◤
+█████  ╲◣
diff --git a/pttbbs/sample/etc/chickens/i15 b/pttbbs/sample/etc/chickens/i15
new file mode 100644
index 00000000..df263a9e
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i15
@@ -0,0 +1,13 @@
+
+ ▃▄▅▅▄▃
+ █ █ 我是天下無敵狗!!!!!汪汪汪....
+ ▌ \ /  ▌
+  ▼ 
+ 冊 ◥ ▇ ◤ 冊
+ █ ▄▄▄▄ █
+ █████████
+ ██████
+ ██████
+ ██████
+ ██ ♁ ██
+ ██◤◥██
diff --git a/pttbbs/sample/etc/chickens/i16 b/pttbbs/sample/etc/chickens/i16
new file mode 100644
index 00000000..83aab985
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i16
@@ -0,0 +1,13 @@
+
+ ▃▄▅▅▄▃
+ █ █ 有我在...誰敢欺侮你...
+ ▌ == ==  ▌ ┼
+  ▼ 
+ ◥ `` ◤
+ ▄▄▄▄
+ ◢ ◣
+  
+ ◢█◣ ││ ◢█◣
+  │ ││ │ 
+ ◥ \ │ ││ │ / ◤
+  │ \︱/ \︱/ │ 
diff --git a/pttbbs/sample/etc/chickens/i2 b/pttbbs/sample/etc/chickens/i2
new file mode 100644
index 00000000..4fcadb84
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i2
@@ -0,0 +1,11 @@
+
+破蛋了~
+
+
+
+
+ ▃▄▃
+ |﹦﹦|▅▄▃▂
+ ▄▄▄▆▅▄▃
+
+
diff --git a/pttbbs/sample/etc/chickens/i3 b/pttbbs/sample/etc/chickens/i3
new file mode 100644
index 00000000..260025a8
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i3
@@ -0,0 +1,11 @@
+
+睜開眼睛了~
+
+
+
+
+ ▃▄▅▄▃ ▌
+  * *  ̄████
+ ◣˙◢████▌
+ d d d d
+
diff --git a/pttbbs/sample/etc/chickens/i4 b/pttbbs/sample/etc/chickens/i4
new file mode 100644
index 00000000..e4b1ebb3
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i4
@@ -0,0 +1,11 @@
+
+坐起來嘍~
+
+
+
+ ▄▅▆▅▄
+  ˙˙ 
+ ◣˙/◣
+ ▄─ 
+ ▄─ ◤
+
diff --git a/pttbbs/sample/etc/chickens/i5 b/pttbbs/sample/etc/chickens/i5
new file mode 100644
index 00000000..5ad09a22
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i5
@@ -0,0 +1,11 @@
+
+來~笑一個~
+
+
+
+ ▄▅▆▅▄
+ ︵ ︵
+ ◣▽/◣
+ ▄─ 
+ ▄─ ◤
+
diff --git a/pttbbs/sample/etc/chickens/i6 b/pttbbs/sample/etc/chickens/i6
new file mode 100644
index 00000000..8e4f58b9
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i6
@@ -0,0 +1,12 @@
+
+第一次戴上項圈~
+
+
+
+ ▄▅▆▆▅▄
+  ˙ ˙ 
+ ◣ ▼ ◢
+ ◢▆▆◣
+ ▌██▌
+ ▃ ×
+ ▌▌
diff --git a/pttbbs/sample/etc/chickens/i7 b/pttbbs/sample/etc/chickens/i7
new file mode 100644
index 00000000..2088fb47
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i7
@@ -0,0 +1,12 @@
+
+來玩球~
+
+
+
+ ▄▅▆▆▅▄
+  ╮╭ 
+ ◣ ▼ ◢
+ ◢▆▆▄▌
+ ▌██
+ ▃ ×▄e
+ ▌ ((( ●
diff --git a/pttbbs/sample/etc/chickens/i8 b/pttbbs/sample/etc/chickens/i8
new file mode 100644
index 00000000..f803840d
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i8
@@ -0,0 +1,12 @@
+
+我可以去上班嗎??
+
+
+
+ ▄▅▆▆▆▅▄
+  ˙ ˙ 
+ ◣ ▼ ◢
+ ▌▅▅▅▌
+ e▄█▇█▄e
+  炎  │
+ ▋ ▍ └
diff --git a/pttbbs/sample/etc/chickens/i9 b/pttbbs/sample/etc/chickens/i9
new file mode 100644
index 00000000..8ad4e1d1
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/i9
@@ -0,0 +1,12 @@
+
+好朋友~喝喝茶~
+
+
+ ▄▅▆▆▆▅▄ ▄▅▆▆▆▅▄
+ ▌ ︵ ︵  ▌ ▌ ︵ ︵  ▌
+ ◣ ▼ ◢ ◣ ▼ ◢
+  ▅▅▅◣ ╴╴Z ╴╴Z ◢▅▅▅◣
+ | | ▉ ▌▏▉ ▌▏ | |
+ └╮♁ │▄▉█▌█▏▉█▌█▏ | ♁ |
+   ̄  ▉█▌█▏▉█▌█▏ | |
+   ◥█◤  ◥█◤ ▋ ▋
diff --git a/pttbbs/sample/etc/chickens/j0 b/pttbbs/sample/etc/chickens/j0
new file mode 100644
index 00000000..0aeb9433
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j0
@@ -0,0 +1,8 @@
+
+
+
+ ●●
+ ●●● ▲
+ ●● ●●●
+
+ 有尾巴的惡魔卵
diff --git a/pttbbs/sample/etc/chickens/j1 b/pttbbs/sample/etc/chickens/j1
new file mode 100644
index 00000000..2f5324a9
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j1
@@ -0,0 +1,9 @@
+
+
+
+ ▲
+ ▼●●●
+ ▲ ●●●●
+ ●● ●●●
+ ●●●
+
diff --git a/pttbbs/sample/etc/chickens/j10 b/pttbbs/sample/etc/chickens/j10
new file mode 100644
index 00000000..ca0238c3
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j10
@@ -0,0 +1,12 @@
+ ▲
+ ●●
+ ●●●●●●▼
+ ●●◢●●●●
+ ●●●●●●●● ▲
+ ▼ ●●●●● ●●
+ ●●●● ●●●
+ ▲ ●●●● ●
+ ●●●●●●●● ●
+ ●●●●●● ●●●
+ ● ●
+ ●●● ●●●
diff --git a/pttbbs/sample/etc/chickens/j11 b/pttbbs/sample/etc/chickens/j11
new file mode 100644
index 00000000..67acda5c
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j11
@@ -0,0 +1,12 @@
+
+ 我有手了!嚇死你!
+ ▲ ▲
+ ● ●● ●● ●
+ ● ●●●●●●● ●
+ ● ●●●●●●●● ● ▲
+ ●●◣●●●◢●● ●●
+ ●●●●●●●● ●●●
+ ●▼▼▼▼▼● ● ●
+ ● ●●●●●● ●●●
+ ● ●
+ ●●● ●●●
diff --git a/pttbbs/sample/etc/chickens/j12 b/pttbbs/sample/etc/chickens/j12
new file mode 100644
index 00000000..f5368760
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j12
@@ -0,0 +1,11 @@
+
+ ▲ ▲
+ ● ●● ●● ●
+ ● ●●●●●●● ●
+ ● ●●●●●●●● ● ▲
+ ●●◣●●●◢●● ●●
+ ●●●●●●●● ●●●
+ ●▼▼▼▼▼● ● ●
+ ● ●●●●●● ●●●
+ ● ●
+ ●●● ●●●
diff --git a/pttbbs/sample/etc/chickens/j13 b/pttbbs/sample/etc/chickens/j13
new file mode 100644
index 00000000..22721b27
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j13
@@ -0,0 +1,12 @@
+ ● ● ▲ ▲
+ ●● ●● ●●
+ ● ●●●●●●●●●
+ ● ●●●●●●●●●●
+ ● ●●◣●●●●●◢●●
+ ● ●●●●●●●●●●●●
+ ● ●●▼▼▼▼▼▼▼▼▼●●
+ ●●●● ● ● ●
+▲●● ● ●▲▲▲▲▲▲▲▲▲● ●
+ ●●● ●●●●●●●●●● ●●
+ ●● ●● ● ●
+ ●●●● ●●●●
diff --git a/pttbbs/sample/etc/chickens/j14 b/pttbbs/sample/etc/chickens/j14
new file mode 100644
index 00000000..774d0ce2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j14
@@ -0,0 +1,13 @@
+
+ ▲ ▲
+ ●●● ●● ●● ●●●
+ ●● ● ●●●●●●●●● ● ●●
+ ▼▼ ●●●●●●●●●● ▼▼
+ ●●◣●●●●●◢●●
+ ●●●● ●●●●●●●●●●●● ●●●●
+ ● ●●▼▼▼▼▼▼▼▼▼●●
+ ●●●● ● ●
+ ▲●● ● ●▲▲▲▲▲▲▲▲▲●
+ ●●● ●●●●●●●●●●
+ ●● ●●
+ ●●●● ●●●●
diff --git a/pttbbs/sample/etc/chickens/j15 b/pttbbs/sample/etc/chickens/j15
new file mode 100644
index 00000000..774d0ce2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j15
@@ -0,0 +1,13 @@
+
+ ▲ ▲
+ ●●● ●● ●● ●●●
+ ●● ● ●●●●●●●●● ● ●●
+ ▼▼ ●●●●●●●●●● ▼▼
+ ●●◣●●●●●◢●●
+ ●●●● ●●●●●●●●●●●● ●●●●
+ ● ●●▼▼▼▼▼▼▼▼▼●●
+ ●●●● ● ●
+ ▲●● ● ●▲▲▲▲▲▲▲▲▲●
+ ●●● ●●●●●●●●●●
+ ●● ●●
+ ●●●● ●●●●
diff --git a/pttbbs/sample/etc/chickens/j16 b/pttbbs/sample/etc/chickens/j16
new file mode 100644
index 00000000..788ee97b
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j16
@@ -0,0 +1,13 @@
+ 吼!!!我是大魔王!!!
+ ▲ ▲
+ ●●●● ●● ●● ●●●●
+ ●●● ● ●●●●●●●●●●●●●● ● ●●●
+ ●●● ● ●●●●●●●●●●●●●●● ● ●●●
+ ▼▼▼ ●●◣●●●●●●●●●●◢●● ▼▼▼
+ ●●●●●●●●●●●●●●●●●
+ ●●▼▼▼▼▼▼▼▼▼▼▼▼▼▼●● ●●
+ ● ● ● ● ●●●
+ ● ● ●▲▲▲▲▲▲▲▲▲▲▲▲▲▲● ●●
+ ●●●● ●●●●●●●●●●●●●●● ●● ●●▼
+ ●● ●● ●●●●●●●●
+ ●●●●● ●●●●● ●
diff --git a/pttbbs/sample/etc/chickens/j2 b/pttbbs/sample/etc/chickens/j2
new file mode 100644
index 00000000..03ef58fc
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j2
@@ -0,0 +1,7 @@
+
+
+ ▲ ▲
+ ●●●●
+ ●●●●●
+ ● ●●●●
+ ▲●●●
diff --git a/pttbbs/sample/etc/chickens/j3 b/pttbbs/sample/etc/chickens/j3
new file mode 100644
index 00000000..593a726b
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j3
@@ -0,0 +1,10 @@
+
+ ▲▲
+ ●●●
+ ●●●● ▲
+ ●◢●◣● ●●
+ ●●●●● ●●●
+ ●●●●● ●
+ ●●●● ● ●
+ ●●
+
diff --git a/pttbbs/sample/etc/chickens/j4 b/pttbbs/sample/etc/chickens/j4
new file mode 100644
index 00000000..e2e31934
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j4
@@ -0,0 +1,9 @@
+
+
+ ▲ ▲
+ ●●●●● ▲
+ ●●●●●● ●●
+ ●●●●●●● ●●●
+ ●●●●●● ●
+ ●●●●● ●
+ ●●●●
diff --git a/pttbbs/sample/etc/chickens/j5 b/pttbbs/sample/etc/chickens/j5
new file mode 100644
index 00000000..2554d000
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j5
@@ -0,0 +1,12 @@
+ ▲ ▲
+ ●●●●●
+ ●●●●●●
+ ●●︽●︽●●
+ ●●●●●●
+ ●●●●●
+ ●
+ ●
+ ●
+ ●●●
+ ●●
+ ▼
diff --git a/pttbbs/sample/etc/chickens/j6 b/pttbbs/sample/etc/chickens/j6
new file mode 100644
index 00000000..79484d50
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j6
@@ -0,0 +1,9 @@
+
+
+ ▲ ▲
+ ▲ ●●●●●
+ ●● ●●●●●●
+ ●●● ●●◣●◢●●
+ ● ●●●●●●
+ ● ●●●●●
+ ●●●●
diff --git a/pttbbs/sample/etc/chickens/j7 b/pttbbs/sample/etc/chickens/j7
new file mode 100644
index 00000000..93ffcac6
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j7
@@ -0,0 +1,10 @@
+
+
+ ▲ ▲ ▲
+ ●●●●● ●●
+ ●●●●●● ●●●
+ ●●◣●◢●● ●
+ ●●●●●● ●
+ ●●●●● ●●●
+ ● ●
+ ●●● ●●●
diff --git a/pttbbs/sample/etc/chickens/j8 b/pttbbs/sample/etc/chickens/j8
new file mode 100644
index 00000000..c86b6f4a
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j8
@@ -0,0 +1,10 @@
+
+
+ ▲ ▲
+ ●●●●●▼ ●●
+ ●●●●●● ●●●
+ ●●◣●◢●● ●
+ ● ●●●●●● ●
+ ●● ●●●●● ●●●
+ ● ●
+ ●●●
diff --git a/pttbbs/sample/etc/chickens/j9 b/pttbbs/sample/etc/chickens/j9
new file mode 100644
index 00000000..a902e57c
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/j9
@@ -0,0 +1,11 @@
+
+ ▲ ▲
+ ●● ●●
+ ●●●●●●●
+ ●●●●●●●●
+ ▲ ●●◣●●●◢●●
+ ●● ●●●●●●●●
+ ●●● ●▼▼▼▼▼●
+ ● ● ●●●●●●
+ ●●● ● ●
+ ●●● ●●●
diff --git a/pttbbs/sample/etc/chickens/k0 b/pttbbs/sample/etc/chickens/k0
new file mode 100644
index 00000000..2c1f12a0
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k0
@@ -0,0 +1,13 @@
+
+(1)我要成為忍者!
+ ◢
+ ◢◢██◣
+ ◤◥◤◥█
+  ▌ ▌ ◥
+ ◣ ◢
+ ◣ ◥◤ ◢
+ █ ██ █
+ █ ██ █
+ ██
+ ◢◤◥◣
+
diff --git a/pttbbs/sample/etc/chickens/k1 b/pttbbs/sample/etc/chickens/k1
new file mode 100644
index 00000000..836976bd
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k1
@@ -0,0 +1,11 @@
+大師!請收我為徒吧!
+ ◢
+ ◢◢██◣
+ ◤◥◤◥█
+  ▌ ▌ ◥
+ ◣ ◢
+ ◣ ◥◤ ◢
+ █████
+ ██
+ ██
+ ◢◤◥◣
diff --git a/pttbbs/sample/etc/chickens/k10 b/pttbbs/sample/etc/chickens/k10
new file mode 100644
index 00000000..6bf8c59e
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k10
@@ -0,0 +1,13 @@
+忍者五術-心
+ ◢
+ ◢◢██◣
+ ◤◥◤◥█
+  ◣ ◢ ◤
+ ◥██◤
+ ◣ ◥◤ ◢
+ █◣ ◢█
+ ████
+ ████
+
+ ◢██◣
+ ◥██◤
diff --git a/pttbbs/sample/etc/chickens/k11 b/pttbbs/sample/etc/chickens/k11
new file mode 100644
index 00000000..bc2a19cc
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k11
@@ -0,0 +1,13 @@
+水土風水心...合體!!
+ ◢ ◣
+ ◢◢██◣ ◢██◣◣
+ ◤◥◤◥█ █◤◥◤◥
+  ◣ ◢ ◥ ◤ ◣ ◢ 
+ ◥██◤ ◥██◤
+ ██ ◤ ◢██ ██◣ ◥ ██
+ ◣ ◢ ◣ ◢
+ ██ ██
+ ███ ███
+ █▌ █▌
+ ◢ ◥ ◤ ◣
+ ◢ ◣
diff --git a/pttbbs/sample/etc/chickens/k12 b/pttbbs/sample/etc/chickens/k12
new file mode 100644
index 00000000..4cf65887
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k12
@@ -0,0 +1,12 @@
+合體後成為高級忍者,氣勢駭人!
+ ◢█◤
+ ◥◢███◣
+ ◤◥◤◥█◣
+  ◣ ◢ ◥█
+ ◥██◤ ◤
+ ◣ ◥ ◥◤ ◤ ◢
+ ◥ █◣ ◢█ ◤
+ ◥ █ ██ █ ◤
+ ◥◥ █ ███ █ ◤◤
+ ◥◥ ███ ◤◤
+ ◥◥◥ ██ ◤◤◤
diff --git a/pttbbs/sample/etc/chickens/k13 b/pttbbs/sample/etc/chickens/k13
new file mode 100644
index 00000000..faa26334
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k13
@@ -0,0 +1,12 @@
+速度超快,你看得到嗎?
+ ◢█◤
+ ◥◢██████◤ ◤
+ ◤◥◤◥██◤ ◤
+  ◣ ◢ ◤
+ ◥██◤
+ ███◥◥◤ ◤███ █
+ ◣ ◢
+ ██
+ ██ █ █
+ ██ █ █
+ ██ █ █
diff --git a/pttbbs/sample/etc/chickens/k14 b/pttbbs/sample/etc/chickens/k14
new file mode 100644
index 00000000..4d875ce4
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k14
@@ -0,0 +1,12 @@
+這是我自創的武器-十字刀!
+ ◢█◤
+ ◥◢███◣
+ ◤◥◤◥█◣
+ ◢  ◣ ◢ ◥█ ◣
+ █ ◥██◤ ◤ █
+ ◥███◣◥ ◥ ◤ ◤◢███◤
+ ███ ◣ ◥◤ ◢ ███
+ ◤ ██ ◥
+ ███
+ ███
+ ██
diff --git a/pttbbs/sample/etc/chickens/k15 b/pttbbs/sample/etc/chickens/k15
new file mode 100644
index 00000000..d2b7f59b
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k15
@@ -0,0 +1,14 @@
+成為終極之-白忍!
+ ◢███◤
+ ◣ ████◣ ◢
+ ◢ ◢◤◥◤◥█◣ ◣
+ ◣  ◣ ◢ ██ ◢
+ ◣◢ ◥██◤█◤ ◣◢
+ ◥ ◥ ◤ ◤◤
+ █ ████ ◣ ◥◤ ◢ ████ █
+ █ ███ █
+ █ ███ █
+ █ ████ █
+ ████
+創 世 奇 俠 敗 腳 下 寰 宇 武 典 握 手 中
+ 天 下 無 敵 第 一 人 哈!
diff --git a/pttbbs/sample/etc/chickens/k16 b/pttbbs/sample/etc/chickens/k16
new file mode 100644
index 00000000..d2b7f59b
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k16
@@ -0,0 +1,14 @@
+成為終極之-白忍!
+ ◢███◤
+ ◣ ████◣ ◢
+ ◢ ◢◤◥◤◥█◣ ◣
+ ◣  ◣ ◢ ██ ◢
+ ◣◢ ◥██◤█◤ ◣◢
+ ◥ ◥ ◤ ◤◤
+ █ ████ ◣ ◥◤ ◢ ████ █
+ █ ███ █
+ █ ███ █
+ █ ████ █
+ ████
+創 世 奇 俠 敗 腳 下 寰 宇 武 典 握 手 中
+ 天 下 無 敵 第 一 人 哈!
diff --git a/pttbbs/sample/etc/chickens/k2 b/pttbbs/sample/etc/chickens/k2
new file mode 100644
index 00000000..5ed4647f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k2
@@ -0,0 +1,10 @@
+皇帝召見,成為正式忍者!
+ ◢
+ ◢◢██◣
+ ◤◥◤◥█
+  ▌ ▌ ◥
+ ◣ ◢
+ ◣ ◥◤ ◢
+ █ █ ██
+ █ █ █
+ ◤█ ◥◣
diff --git a/pttbbs/sample/etc/chickens/k3 b/pttbbs/sample/etc/chickens/k3
new file mode 100644
index 00000000..aa8a6519
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k3
@@ -0,0 +1,11 @@
+什麼?只要蒙面就可以了?
+ ◢
+ ◢◢██◣
+ ◤◥◤◥█
+  ▌ ▌ ◥
+ ◥██◤
+ ◣ ◥◤ ◢
+ ██ ██ ██
+ ██
+ ██
+ ◢◤◥◣
diff --git a/pttbbs/sample/etc/chickens/k4 b/pttbbs/sample/etc/chickens/k4
new file mode 100644
index 00000000..b7e0c1c8
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k4
@@ -0,0 +1,12 @@
+哈!變成中級忍者了,好神氣!
+ ◢
+ ◢◢██◣
+ ◤◥◤◥█
+  ◣ ◢ ◥
+ █ ◥██◤ █
+ █◣ ◥◤ ◢█
+ ◣ ◢
+ ◢██◣
+ █◤◥█
+ █ █
+ ◢ ◣
diff --git a/pttbbs/sample/etc/chickens/k5 b/pttbbs/sample/etc/chickens/k5
new file mode 100644
index 00000000..120b52ac
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k5
@@ -0,0 +1,12 @@
+最簡單的-隱身術...
+  ◢
+ ◢◢██◣
+ ◤◥◤◥█
+  ◣ ◢ ◥
+ ◥██◤
+  █ █ 
+  ◢◤ 
+  █ █◤ 
+  █ █◤ 
+  █ ● █◤ ◢█◤ 
+  █ ████◤ 
diff --git a/pttbbs/sample/etc/chickens/k6 b/pttbbs/sample/etc/chickens/k6
new file mode 100644
index 00000000..0d869ccc
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k6
@@ -0,0 +1,13 @@
+忍者五術-火
+ ◢
+ ◢◢██◣
+ ◤◥◤◥█
+  ◣ ◢ ◥
+ ◣ ◥██◤ ◢
+ ██◣ ◥◤ ◢██
+ ◣ ◢
+ ██
+ ◣ ███ ◢
+ ◣ ██ ◢
+ ◣ ◣◣ ◥◤ ◢◢ ◢
+ ◣◣◣◢◣◢◢◢
diff --git a/pttbbs/sample/etc/chickens/k7 b/pttbbs/sample/etc/chickens/k7
new file mode 100644
index 00000000..fb24da23
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k7
@@ -0,0 +1,14 @@
+忍者五術-土
+ ◢
+ ◢◢██◣
+ ◣ ◤◥◤◥█◢
+ ◣ ◣ ◢ ◥
+ █◥██◤█
+ ◣ ◥◤ ◢
+ ◣ ◢
+ ██
+ ███
+ ██
+ ◣ ◥◤ ◢
+ ◣◥◤◢
+ ◣◢
diff --git a/pttbbs/sample/etc/chickens/k8 b/pttbbs/sample/etc/chickens/k8
new file mode 100644
index 00000000..4e73a12c
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k8
@@ -0,0 +1,10 @@
+忍者五術-風
+ ◢
+ ◢◢██◣
+ ◢◣ ◤◥◤◥█ ◢◣
+ ◢██◣  ◣ ◢ ◥ ◢██◣
+ ◢████◣ ◥██◤ ◢████◣
+ ◥█◣ ◥◤ ◢█◤
+ █ ◣ ◢ █
+ █ ██ █
+ ◤ ◥
diff --git a/pttbbs/sample/etc/chickens/k9 b/pttbbs/sample/etc/chickens/k9
new file mode 100644
index 00000000..cda73fd8
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/k9
@@ -0,0 +1,11 @@
+忍者五術-水
+  ● ◢ ──── 
+  ● ◢◢██◣ ── 
+  ● ◤◥◤◥█◤ ── 
+  ● ◣ ◢  ─── 
+  ◥██◤██ ── 
+  ██◣◥◤ ◢◤ ──── 
+  ◣ ◢█ ◢█◥ ───
+  █████ ─── 
+  ███ ───── 
+  ◥ ─── 
diff --git a/pttbbs/sample/etc/chickens/kiss b/pttbbs/sample/etc/chickens/kiss
new file mode 100644
index 00000000..fdf155ee
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/kiss
@@ -0,0 +1,14 @@
+
+
+  oo$$$$oooo ooo$$$$$$oo
+  o$$$$$$$$$$$$$$$$$$$$$$$$$$o
+  o$$$$$$$$$$$$$$$$$$$$$$$$$$$$o
+  $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
+  $$$$$$$$$$$$$$$$$$$$$$$$$$$$$"
+  "$$$$$$$$$$$$$$$$$$$$$$$$$$$"
+  "$$$$$$$$$$$$$$$$$$$$$$$$$"
+  "$$$$$$$$$$$$$$$$$$$$$"
+  ""$$$$$$$$$$$$$$$$"
+  ""$$$$$$$$$""
+  ""$"" 
+ 來...親一個!!
diff --git a/pttbbs/sample/etc/chickens/l0 b/pttbbs/sample/etc/chickens/l0
new file mode 100644
index 00000000..36f55c21
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l0
@@ -0,0 +1,9 @@
+
+
+ ●●●
+ ● ●● ●
+ ●●●●●●
+ ●●●●●●
+ ●● ●●
+ ●● ●●
+ ●●●●
diff --git a/pttbbs/sample/etc/chickens/l1 b/pttbbs/sample/etc/chickens/l1
new file mode 100644
index 00000000..36f55c21
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l1
@@ -0,0 +1,9 @@
+
+
+ ●●●
+ ● ●● ●
+ ●●●●●●
+ ●●●●●●
+ ●● ●●
+ ●● ●●
+ ●●●●
diff --git a/pttbbs/sample/etc/chickens/l10 b/pttbbs/sample/etc/chickens/l10
new file mode 100644
index 00000000..b5c8367f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l10
@@ -0,0 +1,15 @@
+ ●● ●●
+ ●●●● ●●●●
+ ●●●●●●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ●
+ ● ● ● ●
+ ●● ●●●
+ ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/l11 b/pttbbs/sample/etc/chickens/l11
new file mode 100644
index 00000000..8646bce2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l11
@@ -0,0 +1,14 @@
+ ●●●●●●
+ ● ● ●●
+ ● ● ● ●●
+ ●● ●●●
+ ● ● ●●
+ ●●●●●●●● ●●
+ ● ●●
+ ● ● ● ●
+ ● ● ● ●
+ ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ● ●
+ ● ●
diff --git a/pttbbs/sample/etc/chickens/l12 b/pttbbs/sample/etc/chickens/l12
new file mode 100644
index 00000000..8646bce2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l12
@@ -0,0 +1,14 @@
+ ●●●●●●
+ ● ● ●●
+ ● ● ● ●●
+ ●● ●●●
+ ● ● ●●
+ ●●●●●●●● ●●
+ ● ●●
+ ● ● ● ●
+ ● ● ● ●
+ ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ● ●
+ ● ●
diff --git a/pttbbs/sample/etc/chickens/l13 b/pttbbs/sample/etc/chickens/l13
new file mode 100644
index 00000000..ba9d1d34
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l13
@@ -0,0 +1,14 @@
+ ●●●●
+ ●●●●●●●●
+ ●●●●●●●●●●
+ ●●●●●●●●●●●●
+ ● ●●●●●●●●●
+ ●● ● ●●
+ ●●●● ●
+ ● ●
+ ●● ●●
+ ● ●●●●●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●● ●●
diff --git a/pttbbs/sample/etc/chickens/l14 b/pttbbs/sample/etc/chickens/l14
new file mode 100644
index 00000000..ba9d1d34
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l14
@@ -0,0 +1,14 @@
+ ●●●●
+ ●●●●●●●●
+ ●●●●●●●●●●
+ ●●●●●●●●●●●●
+ ● ●●●●●●●●●
+ ●● ● ●●
+ ●●●● ●
+ ● ●
+ ●● ●●
+ ● ●●●●●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●● ●●
diff --git a/pttbbs/sample/etc/chickens/l15 b/pttbbs/sample/etc/chickens/l15
new file mode 100644
index 00000000..df160353
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l15
@@ -0,0 +1,15 @@
+ ●●●●●●
+ ● ● ●
+ ● ●● ●
+ ● ● ●
+ ●●● ●
+ ● ●
+ ● ●● ●
+ ● ● ●
+ ● ● ● ●
+ ● ● ●
+ ● ●
+ ● ●
+ ● ●● ●●●
+ ● ●● ●
+ ● ●
diff --git a/pttbbs/sample/etc/chickens/l16 b/pttbbs/sample/etc/chickens/l16
new file mode 100644
index 00000000..100d4260
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l16
@@ -0,0 +1,15 @@
+ ●●●●●●●
+ ● ●●●
+ ● ● ● ●●●
+ ●●●●●●● ●●
+ ● ● ●
+ ●●●●●●● ●
+ ● ● ●
+ ●●●●●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●● ● ●
+ ● ●●●●●● ●●
+ ● ●
+ ●●● ●●
diff --git a/pttbbs/sample/etc/chickens/l2 b/pttbbs/sample/etc/chickens/l2
new file mode 100644
index 00000000..6714c9d2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l2
@@ -0,0 +1,12 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/l3 b/pttbbs/sample/etc/chickens/l3
new file mode 100644
index 00000000..6714c9d2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l3
@@ -0,0 +1,12 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/l4 b/pttbbs/sample/etc/chickens/l4
new file mode 100644
index 00000000..723291ae
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l4
@@ -0,0 +1,13 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ●●● ●● ●●
+ ● ●
+ ● ●●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/l5 b/pttbbs/sample/etc/chickens/l5
new file mode 100644
index 00000000..723291ae
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l5
@@ -0,0 +1,13 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ●●● ●● ●●
+ ● ●
+ ● ●●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/l6 b/pttbbs/sample/etc/chickens/l6
new file mode 100644
index 00000000..d3edcf12
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l6
@@ -0,0 +1,14 @@
+
+
+
+ ●●●●●●
+ ● ● ●
+ ● ●● ●
+ ● ● ●
+ ●●● ●
+ ● ●
+ ● ●● ●
+ ● ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/l7 b/pttbbs/sample/etc/chickens/l7
new file mode 100644
index 00000000..d3edcf12
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l7
@@ -0,0 +1,14 @@
+
+
+
+ ●●●●●●
+ ● ● ●
+ ● ●● ●
+ ● ● ●
+ ●●● ●
+ ● ●
+ ● ●● ●
+ ● ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/l8 b/pttbbs/sample/etc/chickens/l8
new file mode 100644
index 00000000..b5c8367f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l8
@@ -0,0 +1,15 @@
+ ●● ●●
+ ●●●● ●●●●
+ ●●●●●●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ●
+ ● ● ● ●
+ ●● ●●●
+ ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/l9 b/pttbbs/sample/etc/chickens/l9
new file mode 100644
index 00000000..b5c8367f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/l9
@@ -0,0 +1,15 @@
+ ●● ●●
+ ●●●● ●●●●
+ ●●●●●●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ●
+ ● ● ● ●
+ ●● ●●●
+ ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/m0 b/pttbbs/sample/etc/chickens/m0
new file mode 100644
index 00000000..36f55c21
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m0
@@ -0,0 +1,9 @@
+
+
+ ●●●
+ ● ●● ●
+ ●●●●●●
+ ●●●●●●
+ ●● ●●
+ ●● ●●
+ ●●●●
diff --git a/pttbbs/sample/etc/chickens/m1 b/pttbbs/sample/etc/chickens/m1
new file mode 100644
index 00000000..36f55c21
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m1
@@ -0,0 +1,9 @@
+
+
+ ●●●
+ ● ●● ●
+ ●●●●●●
+ ●●●●●●
+ ●● ●●
+ ●● ●●
+ ●●●●
diff --git a/pttbbs/sample/etc/chickens/m10 b/pttbbs/sample/etc/chickens/m10
new file mode 100644
index 00000000..b5c8367f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m10
@@ -0,0 +1,15 @@
+ ●● ●●
+ ●●●● ●●●●
+ ●●●●●●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ●
+ ● ● ● ●
+ ●● ●●●
+ ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/m11 b/pttbbs/sample/etc/chickens/m11
new file mode 100644
index 00000000..8646bce2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m11
@@ -0,0 +1,14 @@
+ ●●●●●●
+ ● ● ●●
+ ● ● ● ●●
+ ●● ●●●
+ ● ● ●●
+ ●●●●●●●● ●●
+ ● ●●
+ ● ● ● ●
+ ● ● ● ●
+ ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ● ●
+ ● ●
diff --git a/pttbbs/sample/etc/chickens/m12 b/pttbbs/sample/etc/chickens/m12
new file mode 100644
index 00000000..8646bce2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m12
@@ -0,0 +1,14 @@
+ ●●●●●●
+ ● ● ●●
+ ● ● ● ●●
+ ●● ●●●
+ ● ● ●●
+ ●●●●●●●● ●●
+ ● ●●
+ ● ● ● ●
+ ● ● ● ●
+ ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ● ●
+ ● ●
diff --git a/pttbbs/sample/etc/chickens/m13 b/pttbbs/sample/etc/chickens/m13
new file mode 100644
index 00000000..ba9d1d34
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m13
@@ -0,0 +1,14 @@
+ ●●●●
+ ●●●●●●●●
+ ●●●●●●●●●●
+ ●●●●●●●●●●●●
+ ● ●●●●●●●●●
+ ●● ● ●●
+ ●●●● ●
+ ● ●
+ ●● ●●
+ ● ●●●●●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●● ●●
diff --git a/pttbbs/sample/etc/chickens/m14 b/pttbbs/sample/etc/chickens/m14
new file mode 100644
index 00000000..ba9d1d34
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m14
@@ -0,0 +1,14 @@
+ ●●●●
+ ●●●●●●●●
+ ●●●●●●●●●●
+ ●●●●●●●●●●●●
+ ● ●●●●●●●●●
+ ●● ● ●●
+ ●●●● ●
+ ● ●
+ ●● ●●
+ ● ●●●●●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●● ●●
diff --git a/pttbbs/sample/etc/chickens/m15 b/pttbbs/sample/etc/chickens/m15
new file mode 100644
index 00000000..df160353
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m15
@@ -0,0 +1,15 @@
+ ●●●●●●
+ ● ● ●
+ ● ●● ●
+ ● ● ●
+ ●●● ●
+ ● ●
+ ● ●● ●
+ ● ● ●
+ ● ● ● ●
+ ● ● ●
+ ● ●
+ ● ●
+ ● ●● ●●●
+ ● ●● ●
+ ● ●
diff --git a/pttbbs/sample/etc/chickens/m16 b/pttbbs/sample/etc/chickens/m16
new file mode 100644
index 00000000..100d4260
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m16
@@ -0,0 +1,15 @@
+ ●●●●●●●
+ ● ●●●
+ ● ● ● ●●●
+ ●●●●●●● ●●
+ ● ● ●
+ ●●●●●●● ●
+ ● ● ●
+ ●●●●●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●● ● ●
+ ● ●●●●●● ●●
+ ● ●
+ ●●● ●●
diff --git a/pttbbs/sample/etc/chickens/m2 b/pttbbs/sample/etc/chickens/m2
new file mode 100644
index 00000000..6714c9d2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m2
@@ -0,0 +1,12 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/m3 b/pttbbs/sample/etc/chickens/m3
new file mode 100644
index 00000000..6714c9d2
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m3
@@ -0,0 +1,12 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/m4 b/pttbbs/sample/etc/chickens/m4
new file mode 100644
index 00000000..723291ae
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m4
@@ -0,0 +1,13 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ●●● ●● ●●
+ ● ●
+ ● ●●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/m5 b/pttbbs/sample/etc/chickens/m5
new file mode 100644
index 00000000..723291ae
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m5
@@ -0,0 +1,13 @@
+
+
+
+ ●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●● ●
+ ●●● ●● ●●
+ ● ●
+ ● ●●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/m6 b/pttbbs/sample/etc/chickens/m6
new file mode 100644
index 00000000..d3edcf12
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m6
@@ -0,0 +1,14 @@
+
+
+
+ ●●●●●●
+ ● ● ●
+ ● ●● ●
+ ● ● ●
+ ●●● ●
+ ● ●
+ ● ●● ●
+ ● ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/m7 b/pttbbs/sample/etc/chickens/m7
new file mode 100644
index 00000000..d3edcf12
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m7
@@ -0,0 +1,14 @@
+
+
+
+ ●●●●●●
+ ● ● ●
+ ● ●● ●
+ ● ● ●
+ ●●● ●
+ ● ●
+ ● ●● ●
+ ● ● ●
+ ● ●
+ ● ●
+ ●●●●●●
diff --git a/pttbbs/sample/etc/chickens/m8 b/pttbbs/sample/etc/chickens/m8
new file mode 100644
index 00000000..b5c8367f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m8
@@ -0,0 +1,15 @@
+ ●● ●●
+ ●●●● ●●●●
+ ●●●●●●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ●
+ ● ● ● ●
+ ●● ●●●
+ ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/m9 b/pttbbs/sample/etc/chickens/m9
new file mode 100644
index 00000000..b5c8367f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/m9
@@ -0,0 +1,15 @@
+ ●● ●●
+ ●●●● ●●●●
+ ●●●●●●●●●●●
+ ● ●
+ ● ● ● ●
+ ● ●
+ ● ●●● ●
+ ● ●
+ ● ● ● ●
+ ●● ●●●
+ ● ●
+ ● ●
+ ● ●●● ●
+ ● ● ●
+ ●
diff --git a/pttbbs/sample/etc/chickens/medicine b/pttbbs/sample/etc/chickens/medicine
new file mode 100644
index 00000000..2bde35ed
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/medicine
@@ -0,0 +1,14 @@
+
+
+
+[1;36 
+ 
+  ▁▁▁▃  ▁▁▁▃
+ ══☉ ══◎
+ ◤ ◥ ◤ ◥
+  ╭─────鞷w────╮
+  │ pepper   salt │
+ │◥◤  ◥◤│
+ \薋嘵/
+
+ 吃藥囉~~~
diff --git a/pttbbs/sample/etc/chickens/n0 b/pttbbs/sample/etc/chickens/n0
new file mode 100644
index 00000000..05d456bd
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n0
@@ -0,0 +1,12 @@
+ ▄▅▇▇▇▅▄
+ ▕ . ∵ ▏
+ ▕ ◢ ◆ ◣ ▏
+ ▕  ◤◥◤◥  ▏
+ ▕  ︶ ︶  ▏
+ ▕ ◥" ︶ "◤.▏
+ ▕ ◢ 非 ◣ ▏
+ ▕ ◥◢│◣◤ ▏
+ ▕ . ◣│◢ . ▏
+ ▕ ∵. . ▏
+ ███████
+ ████████
diff --git a/pttbbs/sample/etc/chickens/n1 b/pttbbs/sample/etc/chickens/n1
new file mode 100644
index 00000000..05d456bd
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n1
@@ -0,0 +1,12 @@
+ ▄▅▇▇▇▅▄
+ ▕ . ∵ ▏
+ ▕ ◢ ◆ ◣ ▏
+ ▕  ◤◥◤◥  ▏
+ ▕  ︶ ︶  ▏
+ ▕ ◥" ︶ "◤.▏
+ ▕ ◢ 非 ◣ ▏
+ ▕ ◥◢│◣◤ ▏
+ ▕ . ◣│◢ . ▏
+ ▕ ∵. . ▏
+ ███████
+ ████████
diff --git a/pttbbs/sample/etc/chickens/n10 b/pttbbs/sample/etc/chickens/n10
new file mode 100644
index 00000000..6bbb4100
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n10
@@ -0,0 +1,12 @@
+ -------- ╱╲ ╱╲
+ ------ ▕◢ █ ◣▏
+ -------- █◤▄◥█
+ -- ◥ ◣ > < ◢ ◤
+ --- ◥ ◢◣" ▼ "◢◣ ◤
+ ---- ◢█◥ J ◤ ▄
+ ---- ▄ ◤ ◥◥█◤
+ ---------- ▄▄▄ ◤
+ -------- ◢◥█◤◣
+ ------- ◣ ◤ ◥◢
+ ------ █▌ █▌
+ --- ◢ ◥ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/n11 b/pttbbs/sample/etc/chickens/n11
new file mode 100644
index 00000000..939572c3
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n11
@@ -0,0 +1,12 @@
+ ╱╲ ╱╲
+ 幹 ▕◢ █ ◣▏ 幹
+ █◤▄◥█
+ 幹◥ ◣ ╲ ╱ ◢ ◤幹
+ ◥ ◢◣" ▲ "◢◣ ◤
+ 幹 ▄ ◥ J ◤ ▄ 幹
+ ◥█◤◤ ◥◥█◤
+ 幹 ◥ ▄▄▄ ◤ 幹
+ ◢◥█◤◣
+ 幹 ◣◤ ◥◢ 幹
+ █▌ █▌
+ 幹 ◢ ◥ ◤ ◣ 幹
diff --git a/pttbbs/sample/etc/chickens/n12 b/pttbbs/sample/etc/chickens/n12
new file mode 100644
index 00000000..900740cf
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n12
@@ -0,0 +1,12 @@
+ ╱╲ ╱╲
+ ▕◢ █ ◣▏
+ █◤▄◥█
+ ◥█◣ • • ◢█◤
+ ◥ ◢◣" • "◢◣ ◤
+ ▄ ◥███◤ ▄
+ ◥█◤◤ ◥◥█◤
+ ◥ ▄▄▄ ◤
+ ◢◥█◤◣
+ ◣◤●◥◢
+ █▌ █▌
+ ◢ ◥ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/n13 b/pttbbs/sample/etc/chickens/n13
new file mode 100644
index 00000000..5fdba18f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n13
@@ -0,0 +1,12 @@
+ ╱╲ ╱╲
+ ▕◢ █ ◣▏
+ █◤▄◥█
+ ◥ ◣ ╲ ╱ ◢ ◤
+ ◥ ◢◣" ︵ "◢◣ ◤╭─╮
+ ▄ ◥ J ◤ █▌│●│
+ ◥█◤◤ ◥ ◤ ╰─╯
+ ◥ ▄▄▄
+ ◢◥█◤◣
+ ◣◤ ◥◢
+ █▌ █▌
+ ◢ ◥ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/n14 b/pttbbs/sample/etc/chickens/n14
new file mode 100644
index 00000000..980f6117
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n14
@@ -0,0 +1,12 @@
+ ╱╲ ╱╲
+ ▕◢ █ ◣▏
+ █◤▄◥█
+ ◥ ◣ ╲ ╱ ◢ ◤
+ ◥ ◢◣" 0 "◢◣ ◤╲│╱
+ ◥  ◥ J ◤  ▌─○─
+ █ ◤ ◥ ◤ ╱│╲
+ ▄▄▄
+ ◢ ◥█◤ ◣
+ ◣◤ ◥◢
+ █▌ █▌
+ ◢ ◥ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/n15 b/pttbbs/sample/etc/chickens/n15
new file mode 100644
index 00000000..9a46c040
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n15
@@ -0,0 +1,12 @@
+ ◣◣▲◢◢
+ ◥ ◣ ㄈ ◢ ◤
+ ◥ ≡ ● ≡ ◤
+ ◥█◣ ╱ ╲ ◢█◤
+ ◥ ◢◣" ︵ "◢◣ ◤
+ ◥███◤
+ ◥█◤ ◥█◤
+  ▄▄▄ 
+ ◢◥█◤◣
+ ◣◤ ◥◢
+ █▌ █▌
+ ◢ ◥ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/n16 b/pttbbs/sample/etc/chickens/n16
new file mode 100644
index 00000000..9f932fa5
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n16
@@ -0,0 +1,12 @@
+ ╱╲ ╱╲
+ ▕◢ █ ◣▏
+   █◤▄◥█  
+ ◥◥◣ ︵ ︵ ◢◤◤
+ ◥ ◥◣" ▼ "◢◤ ◤
+ ◢ J ◣
+ ◤ ◥
+ ▄▄▄
+  ◢◥█◤◣
+ ◣◤ ◥◢
+ █▌ █▌
+ ◢ ◥ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/n2 b/pttbbs/sample/etc/chickens/n2
new file mode 100644
index 00000000..4c706ff5
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n2
@@ -0,0 +1,11 @@
+ ◢ ◆ ◣
+  ◤◥◤◥ 
+  ● ●  ◣
+ ◥" ▼ "◤  
+ ◢ ◥ ◤ ◤
+    ◥◤ 
+ ◥  ▄▄▄
+ ◢◥█◤◣
+ ◣◤ ◥◢
+ █▌ █▌
+ ◢ ◥ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/n3 b/pttbbs/sample/etc/chickens/n3
new file mode 100644
index 00000000..62f06de1
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n3
@@ -0,0 +1,11 @@
+ ◢ ◆ ◣
+  ◤◥◤◥ 
+  ● ︵ 
+ ◢◣" ▼ "◢◣
+ ◢◥ J ◤◣
+ █ ◤ ◥ █
+ ◥  ▄▄▄  ◤
+ ◢◥█◤◣
+ ◣◤ ◥◢
+ █▌ █▌
+ ◢ ◥ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/n4 b/pttbbs/sample/etc/chickens/n4
new file mode 100644
index 00000000..982240c1
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n4
@@ -0,0 +1,12 @@
+ ╱╲ ╱╲
+ ▕◢ █ ◣▏
+ █◤▄◥█
+  ● ● 
+ ◢◣" ︶ "◢◣
+ ◢◥ J ◤◣
+ █ ◤ ◥ █
+ ◥  ▄▄▄  ◤
+ ◢◥█◤◣
+ ◣◤ ◥◢
+ █▌ █▌
+ ◢ ◥ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/n5 b/pttbbs/sample/etc/chickens/n5
new file mode 100644
index 00000000..339533e8
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n5
@@ -0,0 +1,12 @@
+ ╱╲ ╱╲
+ ▕◢ █ ◣▏
+ █◤▄◥█
+  ● ● 
+ ◢◣" ▼ "◢◣
+ ▄ ◥ J ◤ ▄
+ ◥█◤◤ ◥◥█◤
+ ◥ ▄▄▄ ◤
+ ◢◥█◤◣
+ ◣◤ ◥◢
+ █▌ █▌
+ ◢ ◥ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/n6 b/pttbbs/sample/etc/chickens/n6
new file mode 100644
index 00000000..29fabf26
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n6
@@ -0,0 +1,12 @@
+ ╱╲ ╱╲
+ ▕◢ █ ◣▏
+ █◤▄◥█
+ ◥ ◣ ︵ ︵ ◢ ◤
+ ◥ ◢◣" ▼ "◢◣ ◤
+ ▄ ◥ J ◤ ▄
+ ◥█◤◤ ◥◥█◤
+ ◥ ▄▄▄ ◤
+ ◢◥█◤◣
+ ◣◤ ◥◢
+ █▌ █▌
+ ◢ ◥ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/n7 b/pttbbs/sample/etc/chickens/n7
new file mode 100644
index 00000000..9965bed3
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n7
@@ -0,0 +1,12 @@
+ ╱╲ ╱╲
+ ▕◢ █ ◣▏
+ █◤▄◥█
+ ◥ ◣ ◣ ◢ ◢ ◤
+ ◥ ◢◣" ▼ "◢◣ ◤
+ ◢◥ J ◤   
+ ◥█ ◤ ◥ ◤
+ ▄█▄▄▄▄▄▄ 
+ ◢◥◥◤◥◤◤
+ ◣◤ ◥◢
+ █▌ █▌
+ ◢ ◥ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/n8 b/pttbbs/sample/etc/chickens/n8
new file mode 100644
index 00000000..665ef99b
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n8
@@ -0,0 +1,12 @@
+ ╱╲ ╱╲
+ ▕◢ █ ◣▏
+ █◤▄◥█
+ ◥ ◣ ︵ ︵ ◢ ◤
+ ◥ ◢◣*˙*◢◣ ◤
+ ◢◥ J ◤◣
+ ◥█ ◤ ◥ █◤
+ ◥  ▄▄▄  ◤
+ ◢◥█◤◣
+ ◣◤█◥◢
+ █▌█ █▌
+ ◢ ◥ █ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/n9 b/pttbbs/sample/etc/chickens/n9
new file mode 100644
index 00000000..c9b79651
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/n9
@@ -0,0 +1,12 @@
+  ╱╲ ╱╲ 
+  ▕◢ █ ◣▏ 
+  █◤▄◥█ 
+ ◥ ◣ @ @ ◢ ◤
+ ◥ ◢◣" 0 "◢◣ ◤
+ ▄ ◥ J ◤ ▄
+ ◥█◤◤ ◥◥█◤
+ ◥ ▄▄▄ ◤
+  ◢◥█◤◣ 
+   ◣ ◤ ◥ ◢  
+  █▌ █▌ 
+ ◢ ◥ ◤ ◣
diff --git a/pttbbs/sample/etc/chickens/nofood b/pttbbs/sample/etc/chickens/nofood
new file mode 100644
index 00000000..d784c9e0
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/nofood
@@ -0,0 +1,18 @@
+
+ \ / ◤ /◥ ●●●
+ — ● —  ◤◥  ●●●●
+ / \ ●-●─  ●●●●
+  ﹉█ ●●●
+ █ ii◤  █
+ █ ◤◥ˍ/█◥
+ ████ iii █ 
+ ◥ ◥█◤
+ ◥ ◤◥  ███
+  ◥◥█●███
+ ☉ ☉ ◥◥◤◥ ◤
+  ◥    ◥ ▁▂▃▄
+     ▃    
+   ◤    
+  ◤█  
+  ████ ████ ,,.....
+ 餓過頭了 離家出走...
diff --git a/pttbbs/sample/etc/chickens/nohp b/pttbbs/sample/etc/chickens/nohp
new file mode 100644
index 00000000..474f9c63
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/nohp
@@ -0,0 +1,16 @@
+
+ ▆███████▆
+ i██████████
+ i███████████
+ ▍ i██ ██ ███
+ ▍▍▍ ii█ █ i█i
+▍▍▍▍▍ i█ i i█
+▍▍▍▍▍ i▁ ° i ° ▁█
+████i f █▅██▅█ f
+████i ii█▂█▂█i█
+ ███i i n▌▋▋▌▊ █
+ ██i i █
+ o▌ █ █
+ o▌ d mm▌▌▌▌█ ▂
+ o▌ d▃▃ ██████ ▃▃▃
+ 體力(HP)耗盡 死掉了 :~~
diff --git a/pttbbs/sample/etc/chickens/nosatis b/pttbbs/sample/etc/chickens/nosatis
new file mode 100644
index 00000000..c8495275
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/nosatis
@@ -0,0 +1,6 @@
+
+哼
+
+我不滿意你這個爛保母
+
+Byebye
diff --git a/pttbbs/sample/etc/chickens/o0 b/pttbbs/sample/etc/chickens/o0
new file mode 100644
index 00000000..ce313964
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o0
@@ -0,0 +1,12 @@
+  │
+ │ 
+ \   ╱
+ \ ▕▎  /
+ ◤ ◣
+ ╴╴╴  ▍ ╴╴╴
+  ▍
+ ◣ ◢
+  ╱ ╲
+ / │ ╲
+ │
+
diff --git a/pttbbs/sample/etc/chickens/o1 b/pttbbs/sample/etc/chickens/o1
new file mode 100644
index 00000000..ce313964
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o1
@@ -0,0 +1,12 @@
+  │
+ │ 
+ \   ╱
+ \ ▕▎  /
+ ◤ ◣
+ ╴╴╴  ▍ ╴╴╴
+  ▍
+ ◣ ◢
+  ╱ ╲
+ / │ ╲
+ │
+
diff --git a/pttbbs/sample/etc/chickens/o10 b/pttbbs/sample/etc/chickens/o10
new file mode 100644
index 00000000..8b5a7f2e
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o10
@@ -0,0 +1,12 @@
+ ◢ ◣
+  ◤◤◤◥ 
+ 呀!  > < 
+  ▼   哈!
+     
+ ●▃d ▲▲ d▃●
+   ◥◤  
+  *** 
+ █ █
+  ▃ ◤ ◥ ▃ 
+  
+
diff --git a/pttbbs/sample/etc/chickens/o11 b/pttbbs/sample/etc/chickens/o11
new file mode 100644
index 00000000..d1038280
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o11
@@ -0,0 +1,12 @@
+ ◢ ◣  鵂s┬┬
+  岷  ◤◤◤◥   礜q┼┼┤
+  哈   ● ●   礜q┼┼┤
+ 岷  ▼    礜r┴┴
+ 哈       
+ ●▃d ▲▲ d▃●
+ ┼┼┼   ◥◤    
+ ┼┼┼  ***    
+ █ █  
+ █ ◥▅▅ 岷
+ ▋  ◥ 哈
+ ◢▋
diff --git a/pttbbs/sample/etc/chickens/o12 b/pttbbs/sample/etc/chickens/o12
new file mode 100644
index 00000000..6eeb2b11
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o12
@@ -0,0 +1,12 @@
+ ◢ ◣
+  ◤◤◤◥ 
+  ● ●  我要去上學囉∼
+  ︶  
+  ◢▃◣  \ 
+ ●▃▃ ◤◥ ▃▃● * 
+     ╱ 
+  ▅▅▅ 
+  ◢███◣ 
+  ▋ ◥▅▅
+  ▋ ◥
+ ◢▋
diff --git a/pttbbs/sample/etc/chickens/o13 b/pttbbs/sample/etc/chickens/o13
new file mode 100644
index 00000000..3a0751cc
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o13
@@ -0,0 +1,12 @@
+ ◢███◣
+ i◤◤◤◥i
+ i● ︽i
+  ︶  今天的晚餐就交給我吧∼
+ ◢▃◣
+ ◢i█i◣
+ ▌。 。▌
+  ▌ ◎  ▌ 
+    ̄  
+  
+  
+  
diff --git a/pttbbs/sample/etc/chickens/o14 b/pttbbs/sample/etc/chickens/o14
new file mode 100644
index 00000000..44e5b4ec
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o14
@@ -0,0 +1,12 @@
+ ◢屆閒◣
+  ◤◤◤◥ 
+  ● ● 
+  ︶  
+  ◣▄◢  可以教我跳舞嗎?
+ ◢★◣
+ ▌   
+    
+ ◢﹒﹒﹒﹒﹒◣
+ ◇◇◇◇◇◇◇
+  ╴ ╴ 
+  ▼ ▼
diff --git a/pttbbs/sample/etc/chickens/o15 b/pttbbs/sample/etc/chickens/o15
new file mode 100644
index 00000000..7e6b6402
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o15
@@ -0,0 +1,11 @@
+ ◢ ◣
+  ◤◤◤◥ 
+  > ● 
+  •   咦?
+  ▇ ▇ 
+  ▌Ψ▌ ◣
+ ◥███◤ ◣
+ ◢████◣ 
+  ◥█████◤ ◤
+  ◥◣◥▆▆◣
+  ◥◤ ◥
diff --git a/pttbbs/sample/etc/chickens/o16 b/pttbbs/sample/etc/chickens/o16
new file mode 100644
index 00000000..1ee3801f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o16
@@ -0,0 +1,11 @@
+ ◢ ◣
+  ◤◤◤◥ 
+  ● ● 
+  ︶   全世界我最喜歡*s唷...
+  ▇ ▇ 
+  ▌Ψ▌ ◣
+ ◥███◤ ◣
+ ◢████◣ 
+  ◥█████◤ ◤
+  ◥◣◥▆▆◣
+  ◥◤ ◥
diff --git a/pttbbs/sample/etc/chickens/o2 b/pttbbs/sample/etc/chickens/o2
new file mode 100644
index 00000000..9161372e
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o2
@@ -0,0 +1,12 @@
+  ▃▄▅▇▇▇▅▄▃
+ ▕ . . ▏
+ ▕ ∵    . ▏
+ ┌────▕ ▕▎  ▏────┐
+ ┌───▕ : ◤ ◣  ▏───┐
+ ┌── ▕  ▍  :▏ ──┐
+ ┌─▕ .  ▍ ∵ ▏─┐
+ ─▕ ◣ ◢  ▏─
+ ▕ ‥  . ▏
+ ▕ ∴ . ∵ . ▏
+ █████████
+ ██████████
diff --git a/pttbbs/sample/etc/chickens/o3 b/pttbbs/sample/etc/chickens/o3
new file mode 100644
index 00000000..112fc914
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o3
@@ -0,0 +1,12 @@
+  ▃▄▅▇▇▇▅▄▃
+ ▕ . . ▏
+ ▕ ∵◢ ◣ . ▏
+ ┌────▕  ◤◤◤◥   ▏────┐
+ ┌───▕ : ︾ ︾   ▏───┐
+ ┌── ▕  ﹋   :▏ ──┐
+ ┌─▕ .   ▇非▇  ∵ ▏─┐ 
+ ─▕ ◤│◥ ▏─
+ ▕ ‥ ◣│◢  . ▏
+ ▕ ∴ . ∵ . ▏
+ █████████
+ ██████████
diff --git a/pttbbs/sample/etc/chickens/o4 b/pttbbs/sample/etc/chickens/o4
new file mode 100644
index 00000000..9e0fe255
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o4
@@ -0,0 +1,11 @@
+
+ ◢ ◣
+  ◤◤◤◥ 
+  ● ● 
+  ﹋  
+  ▇ ▇  
+ ◢█●●█◣
+ ▆▆▆
+ ███
+ ▋▍
+ ▼▼
diff --git a/pttbbs/sample/etc/chickens/o5 b/pttbbs/sample/etc/chickens/o5
new file mode 100644
index 00000000..d38b64bd
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o5
@@ -0,0 +1,11 @@
+
+ ◢ ◣
+  ◤◤◤◥ 
+  ︵ ︵  你今天也好嗎?
+  ▼  
+  ▇ ▇  
+ ◢█●●█◣
+ ▆▆▆
+ ███
+ ▋▍
+ ▼▼
diff --git a/pttbbs/sample/etc/chickens/o6 b/pttbbs/sample/etc/chickens/o6
new file mode 100644
index 00000000..f92eb54b
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o6
@@ -0,0 +1,10 @@
+ ◢ *◣
+  ◤◤◤◥ 
+  ︽ ︽  @
+  ▼    │
+  │ ▇e▄●
+  x  
+ x 
+ e....  *s是大好人,謝謝你
+ ▋▍
+ ▼▼
diff --git a/pttbbs/sample/etc/chickens/o7 b/pttbbs/sample/etc/chickens/o7
new file mode 100644
index 00000000..123a2eef
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o7
@@ -0,0 +1,12 @@
+ ◢
+ █ ◢◣
+  岷 
+  ◣  ──
+  ◤◤◤◥  │▲▲│
+  ︽ ︽   < ◥◤│
+  ︶    ── 
+  \ ◣ ◢ 
+ \╴◢ ◣ 
+ ▊    ≡  ▍
+ ██  ██ 
+ ◥ ◤
diff --git a/pttbbs/sample/etc/chickens/o8 b/pttbbs/sample/etc/chickens/o8
new file mode 100644
index 00000000..637aae53
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o8
@@ -0,0 +1,12 @@
+ ◢███◣
+ i◤◤◤◥i
+ i● ●i
+  ︶  
+  ▃  
+ ◢ ◤◥ ◣
+ ▌   ▌
+ ▌● ●▌
+ ◢███◣
+ ▋▍
+ ▋▍
+ ◢▋▍◣
diff --git a/pttbbs/sample/etc/chickens/o9 b/pttbbs/sample/etc/chickens/o9
new file mode 100644
index 00000000..b0b2cf1a
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/o9
@@ -0,0 +1,12 @@
+ ◢ ◣
+  Ξ ◥◣
+ ◢ ◣ ◤
+  ◤◤◤◥ 
+  ● ● 
+  ο   別小看我唷∼
+ ◢▃◣
+ ◢ ◤◥ ▄▄▍
+ m 
+ ◢█◣▄ ◥◣◥▅▅▅
+ ███ ◥▅▅ ◥
+ ◥█◤  ◥
diff --git a/pttbbs/sample/etc/chickens/oo b/pttbbs/sample/etc/chickens/oo
new file mode 100644
index 00000000..e316b055
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/oo
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+  λ
+  ●●
+ ●●●
+ ●●
+ ● 
+
+ 吃大補丸解除疲勞吧
diff --git a/pttbbs/sample/etc/chickens/read b/pttbbs/sample/etc/chickens/read
new file mode 100644
index 00000000..e4060357
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/read
@@ -0,0 +1,14 @@
+
+
+
+
+
+ ▃▃▃▃
+   |/ __
+  ▁▆▂ ▇ ▕::::▏
+  歈稙
+  │ = │ = │
+  ├──────┼───┤
+  │ │ = │
+ │      ├───┤
+ 用功時間囉.....
diff --git a/pttbbs/sample/etc/chickens/sell b/pttbbs/sample/etc/chickens/sell
new file mode 100644
index 00000000..e253e3d0
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/sell
@@ -0,0 +1,23 @@
+
+  *  ︱  *
+  \ ︱ /  
+ |   \ | /  * 
+  \|/  \ │  /  
+  --も--   ◢█████◣ 
+  /|\  █◤\│/◥█ 
+  |  - - -----◥-─*─-█----- - - 
+  /│\ █  * 
+ *  / | ◢◤ 
+  /{㎜★㎜}\ 
+  /  (▄▄)  \ 
+  *  /  ▆▆  \ 
+  ██ 
+  │  ██  *    | 
+  \ │ /  ██ \|/ 
+  /|\  ██  -----┼----- 
+  ……(※)……  ██ /|\ 
+ \|/  ██  | 
+ / │ \  ██ 
+  *  │  ()  * 
+ ▔▔ 終於解脫了 ^_^....
+
diff --git a/pttbbs/sample/etc/chickens/toofat b/pttbbs/sample/etc/chickens/toofat
new file mode 100644
index 00000000..b20a502e
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/toofat
@@ -0,0 +1,17 @@
+
+
+
+
+ .-., ,.-.
+ '-. /:::\\ //:::\ .-'
+ '-.\|':':' `"` ':':'|/.-'
+ `-./`. .-=-. .-=-. .`\.-`
+ /=- / | \ -=\
+ ; | | | ;
+ |=-.|______|______|.-=|
+ |== \ 0 /_\ 0 / ==|
+ |= /'---( )---'\ =|
+ \ \: .'. :/ /
+ `\= '--` `--' =/'
+ `-=._ _.=-'
+ `"""`  肥胖過度..撐死了..
diff --git a/pttbbs/sample/etc/chickens/tootired b/pttbbs/sample/etc/chickens/tootired
new file mode 100644
index 00000000..a1f0429f
--- /dev/null
+++ b/pttbbs/sample/etc/chickens/tootired
@@ -0,0 +1,15 @@
+
+
+ ┼┼┼┼┼┼┼┼┼ ▁▂▃ ┼┼┼┼┼
+ ┼┼┼┼┼▄█▄ ◆◆▕ ◤ ◥◥┼
+ ┼┼┼┼/  ☉☉  ◥◤  oo  \┼
+ ▃__◤ ∼◥▆▂▌◤ o▏  ◥◤ ┼
+  ▔◥ ∼◤◥◥◥█▕O▎▌ ▃▃┼
+ -------◥◥◥◥◥◥█o◤ █● ◤█
+  ◥____◥▆▆◥◥ ≡≡≡≡▌▌▌▌▌▌
+ ◢ ◥█歈◤ ▆▆▊▃ 
+ ▄▄ ◥◥ ◥ ▆█ ▊█ █ ▊ 
+ ▌ ◥◥ ◥ █  ▊ ▌█ ▊█ 
+ ▌ ◢◤ ◥◣◤ ▊ ▌█ ◥ 
+
+ 操勞過度.....累倒了......
diff --git a/pttbbs/sample/etc/domain_name_query.cidr b/pttbbs/sample/etc/domain_name_query.cidr
new file mode 100644
index 00000000..ecfc35b3
--- /dev/null
+++ b/pttbbs/sample/etc/domain_name_query.cidr
@@ -0,0 +1,299 @@
+# '#'自之後可以加註解
+#
+# 下面這'@'行不可拿掉
+# 查無此住址
+@@@@@@@@@@@@@@ ----------
+
+# 255.255.255.255 各人專屬請放這邊
+
+# 學校 或 團體 群組請由小到大排列
+140.112.28.0/22 台大資訊系
+140.112.90.0/23 台大資訊系
+140.112.3.0/23 台大計算機中心
+140.112.6.0/24 台大撥接/ADSL
+140.112.7.0/24 台大計中PC室
+140.112.8.0/24 台大計算機中心
+140.112.10.0/23 台大土木系
+140.112.12.0/23 台大土木系
+140.112.14.0/24 台大機械系
+140.112.15.0/24 台大志鴻館
+140.112.16.0/24 台大工綜
+140.112.17.0/24 台大電機系
+140.112.18.0/23 台大電機系
+140.112.20.0/23 台大電機系
+140.112.41.0/24 台大電機系
+140.112.22.0/23 台大化工系
+140.112.25.0/24 台大撥接/ADSL
+140.112.26.0/23 台大工科系
+140.112.36.0/24 台大工工所(工綜)
+140.112.38.0/23 台大應力所
+140.112.40.0/24 台大城鄉所
+140.112.41.0/24 台大電機系
+140.112.42.0/24 台大電機舊館
+140.112.42.0/24 台大電機舊館
+140.112.43.0/24 台大機械系
+140.112.44.0/24 台大機械系
+140.112.46.0/24 台大機械系
+140.112.50.0/24 台大數學館
+140.112.52.0/24 台大物理系
+140.112.54.0/24 台大化學系
+140.112.56.0/24 台大地質系
+140.112.58.0/24 台大動物系
+140.112.60.255/25 台大植物系
+140.112.60.0/24 台大植微系
+140.112.61.0/24 台大植研大樓
+140.112.62.0/24 台大心理系
+140.112.64.0/24 台大地理系
+140.112.65.0/24 台大全球變遷/凝態中心
+140.112.66.0/24 台大大氣系
+140.112.67.0/24 台大大氣系
+140.112.68.0/23 台大海洋所
+140.112.70.0/24 台大漁科所
+140.112.72.0/24 台大生化所
+140.112.74.0/24 台大農藝系
+140.112.75.0/24 台大生統教室
+140.112.76.0/24 台大生工系
+140.112.78.0/24 台大農化系
+140.112.80.0/24 台大植微系
+140.112.81.0/24 台大昆蟲館
+140.112.82.0/24 台大森林系
+140.112.84.0/24 台大畜產系
+140.112.86.0/24 台大農經系
+140.112.88.0/24 台大園藝系
+140.112.89.0/24 台大工廠
+140.112.92.0/24 台大農推系
+140.112.94.0/24 台大生機系
+140.112.96.0/24 台大獸醫系
+140.112.99.0/24 台大衛生組
+140.112.100.0/24 台大植病系養蟲館
+140.112.106.0/24 台大資管系
+140.112.108.0/24 台大管院計中
+140.112.110.0/24 台大管院計中
+140.112.120.0/24 台大醫學校區
+140.112.122.0/24 台大醫學院
+140.112.136.0/24 台大公衛系
+140.112.141.0/24 台大中文系/外文系
+140.112.142.0/24 台大歷史系/藝術史所
+140.112.143.0/24 台大哲學系/農業陳列館
+140.112.145.0/24 台大闈場及普通教室
+140.112.146.0/24 台大日文系/戲劇所/語言所
+140.112.150.0/24 台大法學院/法圖
+140.112.153.0/24 台大新聞所
+140.112.155.0/24 台大國家發展所
+140.112.156.0/24 台大社會系
+140.112.157.0/24 台大社會系
+140.112.160.0/24 台大行政大樓
+140.112.169.0/24 台大研二小套房
+140.112.170.0/24 台大研二中套房
+140.112.171.0/24 台大研二大套房
+140.112.173.0/24 台大女八快餐店
+140.112.178.0/24 台大學生住宿服務組
+140.112.181.0/24 台大管理學院
+140.112.182.0/24 台大工綜
+140.112.183.0/24 台大農業自動化教室
+140.112.193.0/24 台大女九紗棉坊
+140.112.195.0/24 台大數化舍
+140.112.201.0/24 台大東亞文明研究中心
+140.112.204.0/24 台大女四小木屋
+140.112.205.0/24 台大ADSL
+140.112.211.0/24 台大男四舍
+140.112.212.0/24 台大男二館
+140.112.213.0/24 台大男二館
+140.112.214.0/24 台大男四舍
+140.112.215.0/24 台大男16舍
+140.112.216.0/24 台大女三甜蜜窩
+140.112.217.0/24 台大水源宿舍
+140.112.218.0/24 台大水源宿舍
+140.112.220.0/24 台大女一小閨房
+140.112.221.0/24 台大女一大閨房
+140.112.222.0/24 台大女二手工舖
+140.112.223.0/24 台大女三甜蜜窩
+140.112.224.0/24 台大女七成衣場
+140.112.225.0/24 台大女五針織室
+140.112.226.0/24 台大女六婚紗廊
+140.112.227.0/24 台大女七成衣場
+140.112.228.0/24 台大女二手工舖
+140.112.229.0/24 台大國青宿舍
+140.112.231.0/24 台大國青宿舍
+140.112.232.0/24 台大國青宿舍
+140.112.233.0/24 台大研一舍
+140.112.234.0/24 台大研一舍
+140.112.239.0/24 台大男一窩
+140.112.240.0/23 台大男一窩
+140.112.242.0/24 台大男一窩
+140.112.242.0/24 台大男一窩
+140.112.243.0/24 台大男三屋
+140.112.244.0/24 台大男三屋
+140.112.245.0/24 台大男五房
+140.112.246.0/24 台大男五房
+140.112.247.0/24 台大男六眷
+140.112.248.0/24 台大男六眷
+140.112.249.0/24 台大男七窯
+140.112.250.0/24 台大男七窯
+140.112.251.0/24 台大男八別墅
+140.112.252.0/24 台大男八別墅
+140.112.253.0/24 台大男八別墅
+140.112.0.0/16 台灣大學
+
+140.109.0.0/16 中央研究院
+140.111.76.0/24 宜大男生宿舍
+140.111.78.0/24 宜大女生宿舍
+140.111.73.0/24 國立宜蘭大學
+140.111.79.0/24 國立宜蘭大學
+140.111.0.0/16 教育部
+140.113.0.0/16 交通大學
+140.114.0.0/16 清華大學
+140.115.81.0/24 中央大學管理學院
+140.115.83.0/24 中央大學管理學院
+140.115.84.0/24 中央大學管理學院
+140.115.85.0/24 中央大學產經所
+140.115.0.0/16 中央大學
+140.116.0.0/16 成功大學
+140.117.0.0/16 中山大學
+140.118.0.0/16 台灣科技大學
+140.119.0.0/16 政治大學
+140.120.0.0/16 中興大學
+140.121.0.0/16 海洋大學
+140.122.0.0/16 台灣師範大學
+140.123.0.0/16 中正大學
+140.124.0.0/16 台北科技大學
+140.125.0.0/16 雲林科技大學
+140.126.2.0/23 中華大學
+140.126.32.0/20 新竹師範學院
+140.127.112.0/24 高雄應用科技大學
+140.127.113.0/24 高雄應用科技大學
+140.127.114.0/24 高雄應用科技大學
+140.127.150.0/24 高雄應用科技大學
+140.127.38.0/24 高雄師範大學
+140.127.45.0/24 高雄師範大學
+140.127.54.0/24 高雄師範大學
+140.127.81.0/21 屏東教育大學
+140.127.64.0/18 高雄師範大學
+140.127.128.0/19 義守大學
+140.127.0.0/19 高雄大學
+140.128.61.0/24 中國醫藥大學
+140.128.136.0/24 中山醫學大學
+140.128.138.0/24 中山醫學大學
+140.128.71.0/24 勤益技術學院
+140.128.72.0/20 勤益技術學院
+140.128.80.0/19 勤益技術學院
+140.128.0.0/16 東海大學
+140.129.37.0/24 大同大學
+140.129.38.0/24 大同大學
+140.129.39.0/24 大同大學
+140.129.40.0/24 大同大學
+140.129.41.0/24 大同大學
+140.129.42.0/24 大同大學
+140.129.56.0/24 陽明大學
+140.129.57.0/24 陽明大學
+140.129.58.0/24 陽明大學
+140.129.59.0/24 陽明大學
+140.129.60.0/24 陽明大學
+140.129.61.0/24 陽明大學
+140.129.62.0/24 陽明大學
+140.129.63.0/24 陽明大學
+140.129.68.0/24 陽明大學牙醫館
+140.129.79.0/24 陽明大學
+140.129.164.0/24 陽明大學
+140.129.165.0/24 陽明大學
+140.130.0.0/16 嘉義大學
+140.131.15.0/20 龍華科技大學
+140.131.16.0/24 龍華科技大學
+140.131.17.0/24 龍華科技大學
+140.131.18.0/24 龍華科技大學
+140.131.19.0/24 龍華科技大學
+140.131.20.0/24 龍華科技大學
+140.131.30.0/24 台灣藝術大學
+140.131.0.0/16 銘傳大學
+140.132.0.0/16 中正理工學院
+140.134.0.0/16 逢甲大學
+140.135.0.0/16 中原大學
+140.136.0.0/16 輔仁大學
+140.137.0.0/16 文化大學
+140.138.0.0/16 元智大學
+163.13.0.0/16 淡江大學
+163.14.0.0/16 東吳大學
+163.15.0.0/16 高雄醫學大學
+163.24.241.0/20 屏東教育大學
+163.25.0.0/16 長庚大學
+192.83.191.0/24 義守大學
+192.192.35.0/24 台北大學
+192.192.36.0/24 台北大學
+192.192.90.0/24 國防大學國防醫學院
+192.192.197.0/24 長榮大學
+192.192.198.0/24 長榮大學
+192.192.199.0/24 長榮大學
+203.64.3.0/24 國立藝術學院
+203.68.128.0/17 台北大學
+203.71.112.0/22 長榮大學
+203.71.116.0/23 長榮大學
+210.60.180.0/20 義守大學
+210.70.145.0/24 長榮大學
+210.70.146.0/23 長榮大學
+210.70.148.0/22 長榮大學
+210.70.152.0/21 長榮大學
+210.70.160.0/20 長榮大學
+210.70.176.0/21 長榮大學
+210.240.172.0/24 台東大學
+203.64.26.0/24 建國中學
+210.71.78.0/24 建國中學
+210.59.53.0/24 國立中和高中
+210.60.107.0/24 國立台中一中
+
+59.104.0.0/15 SeedNet
+59.112.0.0/16 HiNet
+61.13.0.0/16 英普達寬頻
+61.16.0.0/16 英普達寬頻
+61.30.0.0/16 台灣固網
+61.31.0.0/16 台灣固網
+61.56.128.0/20 So-net
+61.59.0.0/16 種子寬頻
+61.62.0.0/16 So-net
+61.63.160.0/19 @corner
+61.63.192.0/18 @corner
+61.64.0.0/16 So-net
+61.70.0.0/16 Giga超網路
+61.71.0.0/16 Giga超網路
+61.216.0.0/13 HiNet寬頻
+61.224.0.0/13 HiNet
+139.175.0.0/16 SeedNet
+163.30.0.0/16 HiNet
+163.31.0.0/16 HiNet
+163.32.0.0/16 HiNet
+168.95.0.0/16 HiNet
+202.178.0.0/16 東森寬頻
+203.64.26.0/24 建國中學
+203.73.0.0/16 種子寬頻
+203.133.0.0/16 Giga超網路
+203.203.0.0/16 Giga寬頻
+203.204.0.0/16 Giga寬頻
+210.58.0.0/16 東森寬頻
+210.64.0.0/16 種子寬頻
+210.66.0.0/16 種子寬頻
+210.68.0.0/16 種子寬頻
+210.71.78.0/24 建國中學
+211.74.128.0/17 種子寬頻
+211.75.0.0/17 HiNet
+211.76.32.0/19 聯宇寬頻
+211.78.0.0/16 台灣固網
+210.85.0.0/16 東森寬頻
+210.59.53.0/24 國立中和高中
+210.60.107.0/24 國立台中一中
+210.201.0.0/16 亞太線上
+210.202.0.0/16 東森寬頻
+210.208.0.0/16 SayHiNet無限撥接
+211.74.0.0/24 種子寬頻
+218.160.0.0/12 HiNet寬頻
+218.32.0.0/16 速博網
+218.34.0.0/15 亞太寬頻
+211.78.0.0/16 台灣固網
+218.184.0.0/16 東森寬頻
+218.187.0.0/16 亞太線上
+219.68.0.0/14 Giga超網路
+219.80.0.0/16 台灣固網
+219.81.0.0/16 台灣固網
+219.91.0.0/17 亞太線上
+220.129.0.0/16 HiNet
+220.130.0.0/15 HiNet
+220.132.0.0/14 HiNet
+220.136.0.0/13 HiNet
diff --git a/pttbbs/sample/etc/expire.conf b/pttbbs/sample/etc/expire.conf
new file mode 100644
index 00000000..9cb8340d
--- /dev/null
+++ b/pttbbs/sample/etc/expire.conf
@@ -0,0 +1,4 @@
+# board days max min
+# ---------------------------------------
+cvslog 999 9999
+asciiart 50 3000
diff --git a/pttbbs/sample/etc/feast b/pttbbs/sample/etc/feast
new file mode 100644
index 00000000..e4ea9199
--- /dev/null
+++ b/pttbbs/sample/etc/feast
@@ -0,0 +1,48 @@
+01 01 元旦放假
+01 02 元旦隔天不放假
+01 11 司法節
+01 23 自由日
+02 14 情人節
+02 28 二二八紀念
+03 01 兵役節
+03 05 童軍節
+03 12 植樹節
+03 14 白色情人節
+03 17 國醫節
+03 21 氣象節
+03 29 青年節
+04 01 愚人節
+04 04 婦幼節
+04 04 兒童節
+04 05 清明節
+04 21 創站週年慶
+05 01 勞動節
+05 04 文藝節
+06 03 禁煙節
+06 09 鐵路節
+06 06 工程師節
+06 15 警察節
+07 01 公路節
+07 03 合作節
+07 04 美國獨立紀念日
+07 11 航海節
+07 14 法國國慶
+08 08 八八父親節
+08 14 空軍節
+09 01 記者節
+09 03 軍人節
+09 09 狗狗節
+09 18 九一八事變
+09 28 教師節
+10 10 國慶日
+10 21 華僑節
+10 25 台灣光復節
+10 31 蔣公誕辰
+11 11 工業節
+11 12 國父誕辰
+11 15 台灣大學校慶
+12 01 世界愛滋日
+12 10 世界人權節
+12 13 三商週年慶
+12 24 X'masEve
+12 25 聖誕節
diff --git a/pttbbs/sample/etc/goodbye b/pttbbs/sample/etc/goodbye
new file mode 100644
index 00000000..38f8aac9
--- /dev/null
+++ b/pttbbs/sample/etc/goodbye
@@ -0,0 +1,19 @@
+再見再見再見再見再見再見再見再見再見再見再見再見再見再見再再再見再見再見再見再
+再見再見再見再見再見再見再見再見再見再見再見再見再見再見再見再見再見再見再見再
+再見再見再見再見再見再見再見再見再見再見再見再見再見再見再再再見再見再見再見再
+再見再見再見再見再見再見再見再見再見再見再見再見再見再見再再再見再見再見再見再
+再見再見再見再見再見再見再見再見再見再見再見再見再見再見再再再見再見再見再見再
+再見再見再見再見再見再見再見再見再見再見再見再見再見再見再再再見再見再見再見再
+再見再見再見再見再見再見再見再見再見再見再見再見再見再見再再再見再見再見再見再
+再見再見再見再見再見┌─────────────┐再見再見再再見再見再見再見再
+再見再見再見再見再見│ 請仔細回憶您的密碼,  │─┐再見再再見再見再見再見再
+再見再見再見再見再見│ 如果真的忘記密碼了,  │ │再見再再見再見再見再見再
+再見再見再見再見再見│ 請用[new]重新註冊吧! │ │再見再再見再見再見再見再
+再見再見再見再見再見│ 亂踹密碼會留下記錄喔。 │ │再見再再見再見再見再見再
+再見再見再見再見再見└─────────────┘ │再見再再見再見再見再見再
+再見再見再見再見再見再見└─────────────┘再見再再見再見再見再見再
+再見再見再見再見再見再見再見再見再見再見再見再見再見再見再再再見再見再見再見再
+再見再見再見再見再見再見再見再見再見再見再見再見再見再見再再再見再見再見再見再
+如果您發現您的ID不見了, 而且不是因為太久沒來被砍者
+請通知站務管理員, 切勿重新new 原來的ID, 否則所有記
+錄和金錢將會歸零
diff --git a/pttbbs/sample/etc/register b/pttbbs/sample/etc/register
new file mode 100644
index 00000000..5772eb2c
--- /dev/null
+++ b/pttbbs/sample/etc/register
@@ -0,0 +1,17 @@
+┌─────────────────────────────────────┐
+│ 請參考下列的範例填寫您的註冊資料: │
+├───────────────────┬─────────────────┤
+│請輸入代號: piggy │ 輸入您想用的英文代號 │
+│請設定密碼: I'mPig │ 請設定密碼,不過螢幕上應該看不見 │
+│請檢查密碼: I'mPig │ 請再次輸入密碼,以示確定 │
+│ │ Kermit 使用者請打 vt320 │
+│您的暱稱: 豬年生的美眉 │ 您發表文章用的筆名或綽號 │
+│真實姓名: 朱曉媚 │ 您的真實姓名 │
+│目前住址: 新竹縣子虛鄉烏有村543號 │ 您目前的聯絡地址(詳細填寫) │
+│網路郵件地址: whoami@cs.ntnu.edu.tw │ 您的 E-Mail address │
+└───────────────────┴─────────────────┘
+ ※ 在本站註冊者,代表願意尊從本站制度與站規,否則請離開。
+ ※ 代號 [ID] 至少要兩個字,不宜採用數字。
+ ※ 密碼 至少要四個字,不可與 [ID] 雷同,不宜全部小寫。
+ ※ 請確實按照規定詳細填寫,否則不予通過!
+ ※ 以上資料缺一不可,敬請合作!
diff --git a/pttbbs/sample/etc/registered b/pttbbs/sample/etc/registered
new file mode 100644
index 00000000..1b52a301
--- /dev/null
+++ b/pttbbs/sample/etc/registered
@@ -0,0 +1,21 @@
+ 歈歈
+ 僓僓 審核通過 
+◇── 裺裺────────────────────────◇
+
+ 嗨嗨,*s你好:
+
+ 我親自幫你審核通過囉 ^o^
+
+ 若還不能Post請重新login一次 ^_^ (要換身份囉)
+
+
+
+ 祝 使用愉快
+
+ 記得常常來玩喔.... ^_^
+
+
+
+
+ 站長~~
+◇────────────────────────────────────◇
diff --git a/pttbbs/sample/etc/registeredmail b/pttbbs/sample/etc/registeredmail
new file mode 100644
index 00000000..255f2708
--- /dev/null
+++ b/pttbbs/sample/etc/registeredmail
@@ -0,0 +1,21 @@
+ 歈歈
+ 僓僓 EMail認證通過 
+◇── 裺裺────────────────────────◇
+
+ 嗨嗨,*s你好:
+
+ 歡迎加入 ptt2 的行列 ^o^
+
+ 若還不能Post請重新login一次 ^_^ (要換身份囉)
+
+
+
+ 祝 使用愉快
+
+ 記得常常來玩喔.... ^_^
+
+
+
+
+ 站長~~
+◇────────────────────────────────────◇
diff --git a/pttbbs/sample/etc/registermail b/pttbbs/sample/etc/registermail
new file mode 100644
index 00000000..b08a7218
--- /dev/null
+++ b/pttbbs/sample/etc/registermail
@@ -0,0 +1,27 @@
+Message below is written in Chinese/Big5.
+If you cannot read Chinese/Big5, skip to English section.
+-------------------------------------------------------------
+親愛的使用者您好:
+
+ 歡迎您到 ptt2 註冊,
+ 請直接到 (U)ser -> (R)egister 填入您的認證碼
+ (在主旨上的 [ ] 括號內,共 13 個字)
+ 即可通過認證取得所有功能喔~ :)
+ 如果有須要服務的地方,
+ 請到 SYSOP板, 我們會竭誠為您服務~ :)
+
+ ptt站長群敬上
+
+注意: 若您並沒有至 ptt2 註冊, 請直接刪除這封信.
+-------------------------------------------------------------
+Dear user:
+ Please go PTT BBS (U)ser -> (R)egister and input
+ your verification code (which is 13 characters
+ on subject and quoted by square brackets - [ ] )
+ If you have any problem, go contact us at SYSOP.
+
+ PTT Admins
+
+Warning: if you did not request a registeration, please
+ delete this mail directly.
+-------------------------------------------------------------
diff --git a/pttbbs/sample/etc/sysop b/pttbbs/sample/etc/sysop
new file mode 100644
index 00000000..d0276ccd
--- /dev/null
+++ b/pttbbs/sample/etc/sysop
@@ -0,0 +1,6 @@
+in2: 程設站長/root
+clifflu: 程設站長
+CChess: 看板總管
+windsheep: 看板總管
+ltlmouse: 帳號總管
+Sunicer: 帳號總管
diff --git a/pttbbs/sample/etc/today_boring b/pttbbs/sample/etc/today_boring
new file mode 100644
index 00000000..12e39ce2
--- /dev/null
+++ b/pttbbs/sample/etc/today_boring
@@ -0,0 +1,22 @@
+ [ 牡羊時 ]
+ [ 牡羊時 ]
+ [ 金牛時 ]
+ [ 金牛時 ]
+ [ 雙子時 ]
+ [ 雙子時 ]
+ [ 巨蟹時 ]
+ [ 巨蟹時 ]
+ [ 獅子時 ]
+ [ 獅子時 ]
+ [ 處女時 ]
+ [ 處女時 ]
+ [ 天秤時 ]
+ [ 天秤時 ]
+ [ 雙魚時 ]
+ [ 雙魚時 ]
+ [ 射手時 ]
+ [ 射手時 ]
+ [ 魔羯時 ]
+ [ 魔羯時 ]
+ [ 水瓶時 ]
+ [ 水瓶時 ]
diff --git a/pttbbs/sample/etc/ve.hlp b/pttbbs/sample/etc/ve.hlp
new file mode 100644
index 00000000..9db9dd94
--- /dev/null
+++ b/pttbbs/sample/etc/ve.hlp
@@ -0,0 +1,127 @@
+ 【一般指令】
+ ^X,F10 檔案處理 ^L 重新顯示畫面
+ ^V 切換ANSI色彩 ^Z,F1 顯示本求助畫面
+ ^Q 不存檔離開 ESC-R 切換自動辨識中文移動
+ 【游標移動指令】
+ ← 往後移動一格 ^A,Home 移到此行開頭
+ → 往前移動一格 ^E,End 移到此行結尾
+ ↑,^P 往上移動一行 (ESC-,) 移到檔案開頭
+ ↓ 往下移動一行 (ESC-.),^T 移到檔案結尾
+ ^B,PgUp 往上移動一頁 ^F,PgDn 往下移動一頁
+ ^S,F3 尋找字串 (ESC-L),F5 跳至指定行
+ (ESC-n) 再往後找 (ESC-p) 再往前找
+ (ESC-]) 尋找對稱括弧 (ESC-x) 回到前一位置
+ (ESC-f) 往前一字 (ESC-b) 後退一字
+ 【刪除插入指令】
+ ^D,Del 刪除目前的字元 ^H,BS 刪除前一個字元
+ ^K 刪除游標之後至行尾 ^Y 刪除目前這行
+ ^O,Ins 切換 插入/覆蓋 ^G 插入圖片文字庫
+ (ESC-d) 刪除一字 (ESC-0~9) 貼上暫存檔 0-9
+
+ 【ANSI│黑紅綠黃藍紫靛白】
+
+ 前景│3031323334353637
+ 背景│4041424344454647
+ Ctrl-V: 切換 ANSI-color 模式,「所見即所得」,可立即編修
+ Ctrl-C: 「直覺式」插入彩色碼,可輕易為文字「著色」 (in ANSI-color mode)
+ 支援 ANSI color 編輯模式... 不只是預覽而已
+ 同時, ctrl-C 在 ANSI mode 下可以輸入顏色...
+ 有三種模式....
+ w 代表 白字
+ wb 代表白字黑底
+ 1wb 代表高亮白字黑底..
+ 支援 overwrite & insert , 可輕易編輯類似 Welcome 畫面
+ 因某種原因, 取消在 ansi editing 下的 backspace功能:P
+ 【區塊處理命令】
+ (ESC-l), (ESC-SPACE) 設定標記區
+ ^W Cut, 把標記區剪至暫存檔 0
+ (ESC-c) Copy, 把標記區拷貝至暫存檔 0
+ 或者再按一次(ESC-SPACE)確定標記區範圍,確定範圍後:
+ 0: Cut, 把標記區剪至暫存檔 0
+ 5: Copy, 把標記區拷貝至暫存檔 5
+ 6-9: Cut or Copy
+ q: 取消
+ 當需要刪「很多」行時,試試 ESC-SPC 吧 :>
+ 若需要把文章堛滿u部分」取出時,ESC-SPC 也適用。
+ (ESC-0/5) Paste, 直接貼上暫存檔 0/5
+ (ESC-6-9) 貼上暫存檔 6-9 ,貼上前先預覽
+ (ESC-j) 將標記區往左移
+ (ESC-k) 將標記區往右移
+ (ESC-u) 取消標記區
+ 【特殊指令】
+ ^U 輸入 ESC 碼(以 * 表示) ^C 還原/設定 ANSI 色彩
+ (ESC-y) 救回誤刪行 ^_(Ctrl-/),(ESC--) 復原目前行
+ (ESC-A) 切換ANSI彩色模式 (ESC-I) 切換縮排(indent)模式
+ (ESC-P) 切換注音直接打模式 (ESC-R) 切換直接(raw)輸入模式
+ ^R 顯示上次扣應訊息 (ESC-U),F8 使用者列表
+ (ESC-i) 進入發呆模式
+
+
+> -------------------------------------------------------------------------- <
+
+ ASCⅡ上色基礎教學
+
+ <<<<<<<<<<<<<<<<<<<<<<<< $ 教 學 開 始 $ >>>>>>>>>>>>>>>>>>>>>>>>>>>>
+  首先,告訴你一件最重要的事,在往後的教學中所有的 * 號都是按 Esc 鍵
+  兩次產生的,以其他方式,如:直接按 * 號,所產生的都無法執行。
+  對了, * 號產生的方式在聯工是按 Esc 兩次,但在其他地方可就不一定了
+  ,可能是 Ctrl + U 產生的,總之,若不清楚,按 Ctrl + Z 線上輔助說明看看
+  就知道了,反正,現在的你,應該是只有在聯工上玩吧!所以,就以聯工的方式
+  教你,以後有到別站時,再教你吧!
+  先告訴你一些基本的色彩控制參數:(記住!m 一定是小寫!!)
+ 參 數 色彩 舉 例 說 明
+ --------- ------ ----------------------------------------------
+  ############ 以下為暗色系列的前景色彩 ############
+ *[0;30m  黑色  *[0;30;47m測試一下 ==> 測試一下
+ *[0;31m  紅色  *[0;31m測試一下 ==> 測試一下
+ *[0;32m  綠色  *[0;32m測試一下 ==> 測試一下
+ *[0;33m  土棕色 *[0;33m測試一下 ==> 測試一下
+ *[0;34m  深藍  *[0;34m測試一下 ==> 測試一下
+ *[0;35m  紫色  *[0;35m測試一下 ==> 測試一下
+ *[0;36m  淺藍  *[0;36m測試一下 ==> 測試一下
+ *[0;37m  白色  *[0;37m測試一下 ==> 測試一下
+  ############ 以下為亮色系列的前景色彩 ############
+ *[1;30m  深灰色 *[1;30m測試一下 ==> 測試一下
+ *[1;31m  亮紅色 *[1;31m測試一下 ==> 測試一下
+ *[1;32m  亮綠色 *[1;32m測試一下 ==> 測試一下
+ *[1;33m  黃色  *[1;33m測試一下 ==> 測試一下
+ *[1;34m  亮深藍 *[1;34m測試一下 ==> 測試一下
+ *[1;35m  亮紫色 *[1;35m測試一下 ==> 測試一下
+ *[1;36m  亮淺藍 *[1;36m測試一下 ==> 測試一下
+ *[1;37m  亮白色 *[1;37m測試一下 ==> 測試一下
+  ############## 以 下 為 背 景 色 彩 ##############
+  ############# 所有文字顏色皆以黃色顯示 ###########
+ *[1;33;40m 黑色  *[1;33;40m測試一下 ==> 測試一下
+ *[1;33;41m 紅色  *[1;33;41m測試一下 ==> 測試一下
+ *[1;33;42m 綠色  *[1;33;42m測試一下 ==> 測試一下
+ *[1;33;43m 棕色  *[1;33;43m測試一下 ==> 測試一下
+ *[1;33;44m 深藍  *[1;33;44m測試一下 ==> 測試一下
+ *[1;33;45m 紫色  *[1;33;45m測試一下 ==> 測試一下
+ *[1;33;46m 淺藍  *[1;33;46m測試一下 ==> 測試一下
+ *[1;33;47m 白色  *[1;33;47m測試一下 ==> 測試一下
+  以上的所有舉例皆是修飾過後的結果,若真照舉例寫,不但沒達到原先預期
+  效果,反而可能會導致天下大亂。前景(即文字部份)和背景(即底色)的修飾方式
+  不一樣,先說前景的修飾方式。
+  再舉個例吧!將 "測試一下" 上色,之後的都不上色,若 *[1;33m測試一下
+  這種寫法,其後面所有的文字都會變成黃色:
+ 
+ *[1;33m測試一下xxxx ==> 測試一下xxxx
+  會產生如上結果,若要將 xxxx 變回原色,則可:
+ 
+ *[1;33m測試一下*[mxxxx ==> 測試一下xxxx
+  加個 *[m 或 *[0m 就可以解決,但若想把 xxxx 變成其他顏色,則:
+ 
+ *[1;33m測試一下*[0;36mxxxx ==> 測試一下xxxx
+  總之,在想改變色彩的部份加上想改變的色彩參數;在想停止上色的地方加
+  上 *[m 或 *[0m 即可!
+  以上,為前景色的修飾方式,接著,說說背景色的修飾方式,說完就下課了
+  唷!
+  背景色的寫法很奇怪,以下舉例說明一下:
+ 
+ *[1;33;43m測試一下*[m ==> 測試一下
+ *[1;33;43m 測試一下*[m ==>  測試一下
+ *[1;33;43m 測試一下 *[m ==>  測試一下 
+  它會因為你參數的位置不同,而產生不同的結果,以此特性,將背景色多加活
+  用,你也能做出出色的簽名檔、計畫檔....等。
+  好啦!下課囉!開始做些試驗吧!看看有哪裡還不懂的,再告訴我!^_^
+ <<<<<<<<<<<<<<<<<<<<<<<<<<<<< $ E N D $ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
diff --git a/pttbbs/sample/innd/Makefile b/pttbbs/sample/innd/Makefile
new file mode 100644
index 00000000..28f297c2
--- /dev/null
+++ b/pttbbs/sample/innd/Makefile
@@ -0,0 +1,9 @@
+BBSHOME?=$(HOME)
+TARGET=$(BBSHOME)/innd/
+FILES=bbsname.bbs newsfeeds.bbs nodelist.bbs ntu.active ncmperm.bbs
+
+all:
+
+install:
+ install -d $(TARGET)
+ install -c -m 644 $(FILES) $(TARGET)
diff --git a/pttbbs/sample/innd/bbsname.bbs b/pttbbs/sample/innd/bbsname.bbs
new file mode 100644
index 00000000..72db534d
--- /dev/null
+++ b/pttbbs/sample/innd/bbsname.bbs
@@ -0,0 +1 @@
+ptt2
diff --git a/pttbbs/sample/innd/ncmperm.bbs b/pttbbs/sample/innd/ncmperm.bbs
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/pttbbs/sample/innd/ncmperm.bbs
diff --git a/pttbbs/sample/innd/newsfeeds.bbs b/pttbbs/sample/innd/newsfeeds.bbs
new file mode 100644
index 00000000..c32b8042
--- /dev/null
+++ b/pttbbs/sample/innd/newsfeeds.bbs
@@ -0,0 +1,3 @@
+# newsgroups board news server
+#------------------------------------- -------------- -----------
+#tw.bbs.test test ntu
diff --git a/pttbbs/sample/innd/nodelist.bbs b/pttbbs/sample/innd/nodelist.bbs
new file mode 100644
index 00000000..d5784970
--- /dev/null
+++ b/pttbbs/sample/innd/nodelist.bbs
@@ -0,0 +1,4 @@
+# name hostname & domainname full name
+#------- ---------------------------------- ------------
+#ptt2 ptt2.csie.ntu.edu.tw IHAVE(7777) Ptt2
+#ntu news.ntu.edu.tw POST(119) NTU News Server
diff --git a/pttbbs/sample/innd/ntu.active b/pttbbs/sample/innd/ntu.active
new file mode 100644
index 00000000..3825d9ea
--- /dev/null
+++ b/pttbbs/sample/innd/ntu.active
@@ -0,0 +1 @@
+tw.bbs.chat 0000000000 0000000000 y
diff --git a/pttbbs/sample/pttbbs.conf b/pttbbs/sample/pttbbs.conf
new file mode 100644
index 00000000..6541dbd1
--- /dev/null
+++ b/pttbbs/sample/pttbbs.conf
@@ -0,0 +1,235 @@
+/* $Id: pttbbs.conf,v 1.14 2003/07/06 03:41:08 in2 Exp $ */
+/* 請注意! 這個檔案是批踢踢實業坊(telnet://ptt2.cc)的設定值,
+ * 這個設定在硬體資源足夠的前題下, 可以提供給上萬個人同時在線上. 若您的硬
+ * 體資源並不足夠, 也不須負荷這麼多註冊人數/看板/上線人數, 請您務必要將相
+ * 關設定值改小, 否則將會使用掉極為大量的記憶體.
+ */
+/* 定義 BBS 站名位址 */
+#define BBSNAME "新批踢踢" /* 中文站名 */
+#define BBSENAME "PTT2" /* 英文站名 */
+#define MYHOSTNAME "ptt2.cc" /* 網路位址 */
+#define MYIP "140.112.30.143" /* IP位址 */
+
+/* 為減少假 email 利用 source 算出註冊碼, 我們改用新的公式。
+ * 下面這個是起始的種子值,請改成任意字串 (1~13 chars) */
+#define REGCODE_MAGIC "pttbbs"
+
+/* 想減低系統安全性、讓人易於釣魚騙資料請開啟 */
+//#define LOW_SECURITY
+
+/* 定義系統資訊 */
+#define BBSUSER "bbs"
+#define BBSUID 9999
+#define BBSGID 99
+
+/* 最大編輯行數, 以防有惡意使用者 post 巨大文章 */
+#define MAX_EDIT_LINE 2048
+
+/* 若定義, 則可以免費隱形 */
+#define HAVE_FREECLOAK 1
+
+/* 可以設定多重進站畫面 */
+#define MULTI_WELCOME_LOGIN
+
+/* 最大 CPU負荷, 超過的時候將拒絕 login */
+#define MAX_CPULOAD (400)
+
+/* 最多註冊人數, 每個人會用掉 21 bytes 的 shared-memory */
+#define MAX_USERS (150000)
+
+/* 最多同時上線人數, 每個人會用掉 3456 bytes 的 shared-memory */
+#define MAX_ACTIVE (4096)
+
+/* 最大開板個數, 每個會用掉 6420 bytes 的 shared-memory */
+#define MAX_BOARD (8192)
+
+/* 主題式閱讀搜尋範圍,文章多可試著加大,但小心對效能影響 */
+#define THREAD_SEARCH_RANGE (500)
+
+/* 幫忙寄信的 server, 一般設成自己(即ip: 127.0.0.1)就可以 */
+#define RELAY_SERVER_IP "127.0.0.1"
+
+/* 抬頭色彩 */
+#define TITLE_COLOR "\033[0;1;37;46m"
+
+/* 若定義, 則所有編輯文章最下方都會加入編輯來源.
+ 否則只有 SYSOP板會加入來源 */
+//#define ALL_REEDIT_LOG
+
+/* 定義看板好友名單將會在幾秒鐘後失效強迫重載 */
+#define HBFLexpire (432000)
+
+/* 定義是否使用外籍使用者註冊
+ 及外國人最長居留時間,之後需向站方申請永久居留權 */
+//#define FOREIGN_REG
+//#define FOREIGN_REG_DAY 30
+
+/* 板主可以按大寫 H切換隱形與否 */
+#define BMCHS
+
+/* 水球整理, 看板備份等等外部程式 */
+#define OUTJOBSPOOL
+
+/* 若定義, 則不能舉辦賭盤 */
+#define NO_GAMBLE
+
+/* 可動態透過 GLOBALVAR[9]調整使用者上限 */
+#define DYMAX_ACTIVE
+
+/* 程式最多可以跑多久 (in min) 因為有的時候會出現跑不停的 process */
+#define CPULIMIT 1
+
+/* 若定義, 若程式失敗, 會等待 86400 秒以讓 gdb來 attach */
+#define DEBUGSLEEP
+
+/* 若定義, 在轉寄位址輸入錯誤時會有讓使用者回報訊息的提示 */
+/* 這個選項存在的原因是因為有部份使用者信誓旦旦說他們沒打錯但看不出程式錯誤 */
+//#define DEBUG_FWDADDRERR
+
+/* 若定義, 用一個奇怪的數字來檢查我的最愛和看板列表是否錯誤 */
+#define MEM_CHECK 0x98761234
+
+/* 若定義, 則以此為版名提供全站文摘 */
+#define GLOBAL_DIGEST "PttDigest"
+
+/* 若定義, 則全站所有五子棋/象棋棋譜都會紀錄在此板 */
+//#define GLOBAL_FIVECHESS_LOG "PttFive"
+//#define GLOBAL_CCHESS_LOG "PttCChess"
+
+/* 若定義, 則可在外部 (shmctl cmsignal) 要求將 mbbsd將 zapbuf 釋放掉.
+ 會使用非正規的記憶體要求函式. (目前只在 FreeBSD上測試過)
+ !!請注意!!
+ 除非您確切知道這個能能在做什麼並且有須要,
+ 否則請不要打開這個功能!! */
+//#define CRITICAL_MEMORY
+
+/* 設定最大可再買幾封信箱 (default: 1000) */
+#define MAX_EXKEEPMAIL (1000)
+
+/* 對於 port 23的, 會預先 fork 幾隻出來. 如此在系統負荷高的時候,
+ 仍可有好的上站率 */
+//#define PRE_FORK 10
+
+/* 若定義, 則由 shmctl utmpsortd 將 time(NULL) 寫入 SHM->GV2.e.now,
+ 則不須每個 mbbsd都自己透過 time(NULL) 取得時間, 導致大量的 system call.
+ 須要加跑 shmctl timed 來提供時間 */
+//#define OUTTA_TIMER
+
+/* 若定義, 則開啟正體中文轉 簡體中文/UTF-8 的功能 */
+//#define CONVERT
+
+/* 若定義, 則啟動 bbs中連至 BLOG 的 interface
+ 請參閱 pttbbs/blog/ */
+//#define BLOGDB_HOST "10.1.1.1"
+//#define BLOGDB_USER "USER"
+//#define BLOGDB_PASSWD "PASSWORD"
+//#define BLOGDB_DB "DATABASE"
+//#define BLOGDB_PORT 3306
+//#define BLOGDB_SOCK NULL
+
+/* 若定義, 則在文章列表的時候不同日期會標上不同顏色 */
+//#define COLORDATE
+
+/* 若定義, 在使用者註冊之前, 會先顯示出該檔案, 經使用者確認後才能註冊 */
+//#define HAVE_USERAGREEMENT "etc/UserAgreement"
+
+/* DBCS Aware: 讓游標不會跑到 DBCS trailing bytes 上 */
+//#define DBCSAWARE
+/* 因為 DBCS 要先偵測,所以可以利用指定下面的時間來判斷使用者有否偵測過
+ * 請換成你真正上線的時間 (time_t) */
+//#define DBCSAWARE_UPGRADE_STARTTIME (0)
+
+/* "不"使用新式的 pmore (piaip's more) 代替舊式 bug 抓不完的 more */
+//#define USE_TRADITIONAL_MORE
+
+/* 使用 textlen 欄位來確定文章實際長度,避免蓋掉推文等。 */
+// #define USE_TEXTLEN
+
+/* 使用 rfork()取代 fork() . 目前只在 FreeBSD上有效 */
+//#define USE_RFORK
+
+/* 使用 HUGETLB shared memory . 目前只在 Linux 上有效 */
+//#define USE_HUGETLB
+
+/* 在某些平台之下, shared-memory規定需要為一定的 aligned size,
+ 如在 linux x86_64 下使用 HUGETLB 時需為 4MB aligned,
+ 而在 linux ia64 下使用 HUGETLB時需為 256MB aligned.
+ 單位為 bytes */
+//#define SHMALIGNEDSIZE (1048576*4) // 4MB for x86_64
+
+/* 讓過於熱門或被鬧的版冷靜, SHM 會變大一些些 */
+#define USE_COOLDOWN
+
+/* 若定義, 則在刪除看板文章的時候, 僅會在 .DIR 中標明, 並不會將該資料
+ 從 .DIR 中拿掉. 可以避免多項問題 (尤其是熱門看板一堆推薦及編輯時)
+ 須配合使用 (尚未完成) */
+//#define SAFE_ARTICLE_DELETE
+
+/* 若定義, 則在傳送水球的時候, 不會直接 kill 該程序. 理論上可以減少大
+ 量的系統負和 */
+//#define NOKILLWATERBALL
+
+/* 若定義, 則在系統超過負荷的時候, 新接的連線會留住 OVERLOADBLOCKFDS
+ 這麼多個 fd , 以避免使用者狂連造成更大的負荷 (default: 0) */
+//#define OVERLOADBLOCKFDS 128
+
+/* 若定義, 則 SYSOP帳號並不會自動加上站長權限.
+ 在第一次啟動時, 您並不能定義 (否則就拿不到站長權了) .
+ 而在設定完成後, 若您站長帳號並不叫做 SYSOP,
+ 則可透過 NO_SYSOP_ACCOUNT 關閉該帳號, 以避免安全問題發生. */
+//#define NO_SYSOP_ACCOUNT
+
+/* 若定義, 則熱門看板列表會改用 shmctl utmpsortd 來計算, 而不是每
+ 個使用者自己算. 在站上會同時有很多人同時跑去看熱門看板的時候用.
+ 若站上並不會一瞬間很多人跑去看熱門看板, 會得到反效果. */
+//#define HOTBOARDCACHE 128
+
+/* 在轉信時附上的時區. 若在台灣, 中國大陸等地, 用預設的即可. */
+//#define INNTIMEZONE "+0800 (CST)"
+
+/* 大學聯考查榜系統 */
+//#define HAVE_JCEE (1)
+
+/* 開啟小天使小主人功能 */
+//#define PLAY_ANGEL
+
+/* 若定義, 則使用舊式推文 */
+#define OLDRECOMMEND
+
+/* 若定義, 則 guest 可推文,格式變為 IP+日期 */
+#define GUESTRECOMMEND
+
+/* 定義幾秒內算快速推文 */
+#define FASTRECMD_LIMIT (90)
+
+/* 若定義, 可設定轉錄自動在原文留下記錄 */
+#define USE_AUTOCPLOG
+
+/* 若定義, 新板設定自動開記錄,不過 USE_AUTOCPLOG 還是要開才有用 */
+#define DEFAULT_AUTOCPLOG
+
+/* 贈送信箱 */
+//#define ADD_EXMAILBOX 100
+
+/* 如果是在 IA32 底下的話, 可以定義 IA32 以取得記憶體統計資訊 */
+//#define IA32
+
+/* 如果 time_t 是 8 bytes的話 (如 X86_64) */
+//#define TIMET64
+
+/* 使用 cacheserver, 在外部運算好友資料, 如果您確定這個在做什麼才開啟 */
+//#define OUTTACACHE
+//#define OUTTACACHEHOST "192.168.0.1"
+//#define OUTTACACHEPORT 5120
+/* 在 cacheserver 上面擋掉狂上下站的使用者 */
+//#define NOFLOODING
+
+/* 若定義, 則不允許註冊 guest */
+//#define NO_GUEST_ACCOUNT_REG
+
+/* 若定義, 則每篇發文會寫到 log/post , 可以給分析或張爸魔一類的程式用 */
+//#define LOGPOST
+
+/* 前進站畫面 */
+#define INSCREEN \
+"前進站畫面 (請至 pttbbs.conf 修改您的前進站畫面)"
diff --git a/pttbbs/sample/pttbbs.sh b/pttbbs/sample/pttbbs.sh
new file mode 100755
index 00000000..d8b87f6b
--- /dev/null
+++ b/pttbbs/sample/pttbbs.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# $Id$
+# 請注意!這個檔案將以 root 的權限執行!
+# 預設使用 bbs這個帳號,安裝目錄為 /home/bbs。
+
+case "$1" in
+start)
+ # 初始化 shared-memory, 載入 uhash, utmpsortd, timed(if necessary)
+ # 如果使用 USE_HUGETLB 的話請用 root 跑 shmctl init
+ /usr/bin/su -fm bbs -c '/home/bbs/bin/shmctl init'
+
+ # 寄信至站外
+ /usr/bin/su -fm bbs -c /home/bbs/bin/outmail &
+
+ # 轉信
+ /usr/bin/su -fm bbs -c /home/bbs/innd/innbbsd &
+
+ # 啟動 port 23 (port 23須使用 root 才能進行 bind ) 以其他
+ /home/bbs/bin/bbsctl start
+
+ # 提示
+ echo -n ' mbbsd'
+ ;;
+stop)
+ /usr/bin/killall outmail
+ /usr/bin/killall innbbsd
+ /usr/bin/killall mbbsd
+ /usr/bin/killall shmctl
+ /bin/sleep 2; /usr/bin/killall shmctl
+ ;;
+*)
+ echo "Usage: `basename $0` {start|stop}" >&2
+ ;;
+esac
+
+exit 0
diff --git a/pttbbs/sample/pttbbs_minimal.conf b/pttbbs/sample/pttbbs_minimal.conf
new file mode 100644
index 00000000..1536dfd0
--- /dev/null
+++ b/pttbbs/sample/pttbbs_minimal.conf
@@ -0,0 +1,17 @@
+/* Primary Settings MUST BE CHANGED */
+#define BBSNAME "批踢踢實業坊" /* 中文站名 */
+#define BBSENAME "PTT" /* 英文站名 */
+#define MYHOSTNAME "ptt.csie.ntu.edu.tw" /* 網路位址 */
+#define MYIP "140.112.30.142" /* IP位址 */
+#define REGCODE_MAGIC "changeMe" /* 註冊亂數種子 */
+
+/* 下面是較常改的設定,不改也行 */
+/* Performance Settings */
+#define MAX_USERS (15000) /* 最高註冊人數 */
+#define MAX_ACTIVE (512) /* 最多同時上站人數 */
+
+/* Misc Settings */
+#define MEM_CHECK (0) /* 設為 0 代表非測試中 */
+
+/* vim:ft=c
+ */
diff --git a/pttbbs/sample/rc.local b/pttbbs/sample/rc.local
new file mode 100644
index 00000000..c266f9bf
--- /dev/null
+++ b/pttbbs/sample/rc.local
@@ -0,0 +1,7 @@
+#!/bin/sh
+# WARNING: this script is run by root!!
+
+/usr/bin/su bbsadm -c /home/bbs/bin/shmctl init
+/usr/bin/su bbsadm -c /home/bbs/bin/outmail&
+/usr/bin/su bbsadm -c /home/bbs/innd/innbbsd 7777
+/home/bbs/bin/bbsctl start
diff --git a/pttbbs/staticweb/INSTALL b/pttbbs/staticweb/INSTALL
new file mode 100644
index 00000000..2eadd296
--- /dev/null
+++ b/pttbbs/staticweb/INSTALL
@@ -0,0 +1,43 @@
+這篇文章介紹如何使用 web版精華區, 文章的版號及最後編修時間是:
+$Id$
+
+1.安裝好下列的東西, 我們並同時列上 FreeBSD ports內的目錄:
+ apache /usr/ports/www/apache13/
+ perl /usr/ports/lang/perl5.8/
+ mod_perl /usr/ports/www/mod_perl/
+
+ 以及下列的 module
+ Template /usr/ports/www/p5-Template-Toolkit/
+ MD5 /usr/ports/security/p5-MD5/
+ Data::Serializer /usr/ports/devel/p5-Data-Serializer/
+ OurNet::FuzzyIndex (還沒有進 ports, 請用 cpan 裝)
+
+2.
+須要三個目錄, 一個是放置 cgi程式的地方, 一個放置實際資料. 一個放置
+編譯過的 template , 其中放置編譯過的 template 目錄須要是 apache 可
+以寫入的.
+將 pttbbs/staticweb/* 拷貝至放置 cgi程式的目錄內.
+修改 /home/bbs/bin/LocalVars.pm , 將放置實際資料的目錄寫給 $MANDATA ,
+將放置編譯過 template 的目錄給 $MANCACHE. 這兩個請都使用絕對路徑.
+
+3.
+使用 pttbbs/staticweb/manbuild.pl 來將當前的精華區建成資料庫.
+usage: manbuild.pl [-n] [BoardName1/DB1 [BoardName2/DB2 [...]]]
+其中 -n 表示不建立用來搜尋的索引檔, 後面請加要建立的看板名稱.
+產生好後請將 *.db, *.idx移至 $MANDATA 中, 並且確認該檔案是 apache
+可讀.
+
+4.
+執行
+ pttbbs/util/boardlist > boardlist.pm
+再將 boardlist.pm 移入程式目錄.
+
+5.
+設定 apache , 使用 mod_perl , 並開啟該目錄的 ExecCGI權限, 如:
+ <Directory "/home/bbs/web/cgi">
+ Options ExecCGI
+
+ # 下面兩行是使用 mod_perl 的.
+ AddHandler perl-script .pl
+ PerlHandler Apache::Registry
+ </Directory>
diff --git a/pttbbs/staticweb/article.html b/pttbbs/staticweb/article.html
new file mode 100644
index 00000000..8c1a8260
--- /dev/null
+++ b/pttbbs/staticweb/article.html
@@ -0,0 +1,18 @@
+[% INCLUDE header.html %]
+<table width="75%" align="center"><tr><td>
+看板: <a href="/man.pl/[% brdname %]/[% IF gb %]?gb=1[% END %]">[% brdname %]</a><br />
+<a href="./[% IF gb %]?gb=1[% END %]">回上頁</a><br />
+<hr />
+</tr></td><tr><td>
+<font size=+2><pre>
+[% content %]
+</pre></font>
+</td></tr><tr><td>
+<hr>
+<a href="./[% IF gb %]?gb=1[% END %]">回上頁</a><br />
+<a href="http://ptt.cc">批踢踢實業坊</a>
+</td></tr>
+</table>
+
+</body>
+</html>
diff --git a/pttbbs/staticweb/b2g.pm b/pttbbs/staticweb/b2g.pm
new file mode 100644
index 00000000..7b3b8ddf
--- /dev/null
+++ b/pttbbs/staticweb/b2g.pm
@@ -0,0 +1,13990 @@
+package b2g;
+use Exporter;
+use vars qw(%b2g);
+
+%b2g = (
+' ' => '﹛',
+',' => 'ㄛ',
+'、' => '﹜',
+'。' => '﹝',
+'.' => 'ㄝ',
+'•' => '﹞',
+';' => '˙',
+':' => 'ㄩ',
+'?' => 'ˋ',
+'!' => 'ㄐ',
+'︰' => 'ㄩ',
+'…' => '#',
+'‥' => '“',
+'﹐' => 'ㄛ',
+'、' => '﹜',
+'﹒' => 'ㄝ',
+'·' => '﹞',
+'﹔' => '˙',
+'﹕' => 'ㄩ',
+'﹖' => 'ˋ',
+'﹗' => 'ㄐ',
+'|' => '',
+'–' => '求',
+'︱' => '',
+'—' => '〞',
+'︳' => '估',
+'╴' => '',
+'︴' => '佐',
+'﹏\' => '姊',
+'(' => 'ㄗ',
+')' => 'ㄘ',
+'︵' => 'ㄗ',
+'︶' => '艮',
+'{' => '',
+'}' => '',
+'︷' => '佞',
+'︸' => '伴',
+'〔' => '〃',
+'〕' => '○',
+'︹' => '色',
+'︺' => '艾',
+'【' => '▽',
+'】' => '▼',
+'︻' => '佇',
+'︼' => '佗',
+'《' => '▲',
+'》' => '◎',
+'︽' => '行',
+'︾' => '衣',
+'〈' => '●',
+'〉' => '△',
+'︿' => '_',
+'﹀' => 'ˍ',
+'「' => '☆',
+'」' => '★',
+'﹁' => '西',
+'﹂' => '阡',
+'『' => '◇',
+'』' => '◆',
+'﹃' => '串',
+'﹄' => '亨',
+'﹙' => 'ㄗ',
+'﹚' => 'ㄘ',
+'﹛' => '',
+'﹜' => '',
+'﹝' => '〃',
+'﹞' => '○',
+'‘' => '&',
+'’' => '*',
+'“' => '※',
+'”' => '§',
+'〝' => '',
+'〞' => 'ㄑ',
+'‵' => '',
+'′' => '∩',
+'#' => 'ㄒ',
+'&' => 'ㄕ',
+'*' => 'ㄙ',
+'※' => '↗',
+'§' => '∫',
+'〃' => '”',
+'○' => '♀',
+'●' => '♂',
+'△' => '→',
+'▲' => '↖',
+'◎' => '♁',
+'☆' => '∵',
+'★' => '∴',
+'◇' => '☉',
+'◆' => '↑',
+'□' => '↓',
+'■' => '←',
+'▽' => '',
+'▼' => '',
+'㊣' => '呼',
+'℅' => '沁',
+'‾' => '‘',
+' ̄' => '',
+'_' => '',
+'ˍ' => 'ㄜ',
+'﹉' => '姑',
+'﹊' => '姆',
+'﹍' => '始',
+'﹎' => '姓',
+'﹋' => '姐',
+'﹌' => '姍',
+'﹟' => 'ㄒ',
+'﹠' => 'ㄕ',
+'﹡' => 'ㄙ',
+'+' => 'ㄚ',
+'-' => 'ㄜ',
+'×' => '℅',
+'÷' => '‾',
+'±' => '㊣',
+'√' => '﹟',
+'<' => 'ˉ',
+'>' => 'ˇ',
+'=' => 'ˊ',
+'≦' => '≒',
+'≧' => '≡',
+'≠' => '≧',
+'∞' => '﹢',
+'≒' => '沌',
+'≡' => '√',
+'﹢' => '',
+'﹣' => '',
+'﹤' => '',
+'﹥' => '',
+'﹦' => '',
+'∼' => '‵',
+'∩' => '﹎',
+'∪' => '﹍',
+'⊥' => '﹠',
+'∠' => '+',
+'∟' => '沐',
+'⊿' => '沒',
+'㏒' => '咎',
+'㏑' => '命',
+'∫' => '÷',
+'∮' => '±',
+'∵' => '﹣',
+'∴' => '﹤',
+'♀' => '﹦',
+'♂' => '﹥',
+'♁' => '',
+'☉' => '×',
+'↑' => '∥',
+'↓' => '∣',
+'←' => '↘',
+'→' => '↙',
+'↖' => '沉',
+'↗' => '沅',
+'↙' => '汪',
+'↘' => '沛',
+'∥' => '′',
+'∣' => '',
+'/' => 'ㄞ',
+'\' => '',
+'/' => 'ㄞ',
+'\' => '',
+'$' => '∠',
+'¥' => 'ㄓ',
+'〒' => '',
+'¢' => '⊿',
+'£' => '㏒',
+'%' => 'ㄔ',
+'@' => '',
+'℃' => '⊥',
+'℉' => '沈',
+'﹩' => '',
+'﹪' => '',
+'﹫' => '',
+'㏕' => '固',
+'㎜' => '呶',
+'㎝' => '和',
+'㎞' => '咚',
+'㏎' => '咋',
+'㎡' => '呢',
+'㎎' => '咐',
+'㎏' => '呱',
+'㏄' => '周',
+'°' => '∼',
+'兙' => '',
+'兛' => '',
+'兞' => '',
+'兝\' => '',
+'兡' => '',
+'兣' => '',
+'嗧' => '',
+'瓩' => '',
+'糎' => '嘻',
+'▁' => '肝',
+'▂' => '肘',
+'▃' => '肛',
+'▄' => '肚',
+'▅' => '育',
+'▆' => '良',
+'▇' => '芒',
+'█' => '',
+'▏' => '',
+'▎' => '',
+'▍' => '',
+'▌' => '',
+'▋' => '',
+'▊' => '',
+'▉' => '',
+'┼' => '拈',
+'┴' => '拂',
+'┬' => '房',
+'┤' => '怕',
+'├' => '念',
+'▔' => '',
+'─' => '岸',
+'│' => '岫',
+'▕' => '',
+'┌' => '庚',
+'┐' => '庖',
+'└' => '弩',
+'┘' => '彼',
+'╭' => '秀',
+'╮' => '禿',
+'╰' => '系',
+'╯' => '究',
+'═' => '/',
+'╞' => '忿',
+'╪' => '押',
+'╡' => '怡',
+'◢' => '',
+'◣' => '',
+'◥' => '',
+'◤' => '',
+'╱' => '罕',
+'╲' => '肖',
+'╳' => '肓',
+'0' => 'ㄟ',
+'1' => 'ㄠ',
+'2' => 'ㄡ',
+'3' => 'ㄢ',
+'4' => 'ㄣ',
+'5' => 'ㄤ',
+'6' => 'ㄥ',
+'7' => 'ㄦ',
+'8' => 'ㄧ',
+'9' => 'ㄨ',
+'Ⅰ' => 'i',
+'Ⅱ' => 'j',
+'Ⅲ' => 'k',
+'Ⅳ' => 'l',
+'Ⅴ' => 'm',
+'Ⅵ' => 'n',
+'Ⅶ' => 'o',
+'Ⅷ' => 'p',
+'Ⅸ' => 'q',
+'Ⅹ' => 'r',
+'〡' => '咖',
+'〢' => '呸',
+'〣' => '咕',
+'〤' => '咀',
+'〥' => '呻',
+'〦' => '呷',
+'〧' => '咄',
+'〨' => '咒',
+'〩' => '恅',
+'十' => '坋',
+'卄' => '`',
+'卅' => '埵',
+'A' => '',
+'B' => '',
+'C' => '',
+'D' => '',
+'E' => '',
+'F' => '',
+'G' => '',
+'H' => '',
+'I' => '',
+'J' => '',
+'K' => '',
+'L' => '',
+'M' => '',
+'N' => '',
+'O' => '',
+'P' => '',
+'Q' => '',
+'R' => '',
+'S' => '',
+'T' => '',
+'U' => '',
+'V' => '',
+'W' => '',
+'X' => '',
+'Y' => '',
+'Z' => '',
+'a' => '',
+'b' => '',
+'c' => '',
+'d' => '',
+'e' => '',
+'f' => '',
+'g' => '',
+'h' => '',
+'i' => '',
+'j' => '',
+'k' => '',
+'l' => '',
+'m' => '',
+'n' => '',
+'o' => '',
+'p' => '',
+'q' => '',
+'r' => '',
+'s' => '',
+'t' => '',
+'u' => '',
+'v' => '',
+'w' => '',
+'x' => '',
+'y' => '',
+'z' => '',
+'Α' => '式',
+'Β' => '弛',
+'Γ' => '忙',
+'Δ' => '忖',
+'Ε' => '戎',
+'Ζ' => '戌',
+'Η' => '戍',
+'Θ' => '成',
+'Ι' => '扣',
+'Κ' => '扛',
+'Λ' => '托',
+'Μ' => '收',
+'Ν' => '早',
+'Ξ' => '旨',
+'Ο' => '旬',
+'Π' => '旭',
+'Ρ' => '曲',
+'Σ' => '曳',
+'Τ' => '有',
+'Υ' => '朽',
+'Φ' => '朴',
+'Χ' => '朱',
+'Ψ' => '朵',
+'Ω' => '次',
+'α\' => '汐',
+'β' => '汕',
+'γ' => '污',
+'δ' => '汛',
+'ε' => '汍',
+'ζ' => '汎',
+'η' => '灰',
+'θ' => '牟',
+'ι' => '牝',
+'κ' => '百',
+'λ' => '竹',
+'μ' => '米',
+'ν' => '糸',
+'ξ' => '缶',
+'ο' => '羊',
+'π' => '羽',
+'ρ' => '老',
+'σ' => '考',
+'τ' => '而',
+'υ' => '耒',
+'φ' => '耳',
+'χ' => '聿',
+'ψ' => '肉',
+'ω' => '肋',
+'ㄅ' => '乳',
+'ㄆ' => '事',
+'ㄇ' => '些',
+'ㄈ' => '亞',
+'ㄉ' => '享',
+'ㄊ' => '京',
+'ㄋ' => '佯',
+'ㄌ' => '依',
+'ㄍ' => '侍',
+'ㄎ' => '佳',
+'ㄏ' => '使',
+'ㄐ' => '佬',
+'ㄑ' => '供',
+'ㄒ' => '例',
+'ㄓ' => '來',
+'ㄔ' => '侃',
+'ㄕ' => '佰',
+'ㄖ' => '併',
+'ㄗ' => '侈',
+'ㄘ' => '佩',
+'ㄙ' => '佻',
+'ㄚ' => '侖',
+'ㄛ' => '佾',
+'ㄜ' => '侏',
+'ㄝ' => '侑',
+'ㄞ' => '佺',
+'ㄟ' => '兔',
+'ㄠ' => '兒',
+'ㄡ' => '兕',
+'ㄢ' => '兩',
+'ㄣ' => '具',
+'ㄤ' => '其',
+'ㄥ' => '典',
+'ㄦ' => '冽',
+'ㄧ' => '函',
+'ㄨ' => '刻',
+'ㄩ' => '券',
+'˙' => '步',
+'ˉ' => '‘',
+'ˊ' => '杓',
+'ˇ' => '’',
+'ˋ' => '',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'一' => '珨',
+'乙' => '眣',
+'丁' => '間',
+'七' => 'ほ',
+'乃' => '騰',
+'九' => '嬝',
+'了' => '賸',
+'二' => '媼',
+'人' => '',
+'儿' => '嫁',
+'入' => '',
+'八' => '匐',
+'几' => '撓',
+'刀' => '絮',
+'刁' => '街',
+'力' => '薯',
+'匕' => '堸',
+'十' => '坋',
+'卜' => '眺',
+'又' => '衱',
+'三' => '',
+'下' => '狟',
+'丈' => '桾',
+'上' => '奻',
+'丫' => '挩',
+'丸' => '侳',
+'凡' => '歇',
+'久' => '壅',
+'么\' => '繫',
+'也' => '珩',
+'乞' => 'ゎ',
+'于' => '衾',
+'亡' => '厗',
+'兀' => '堧',
+'刃' => '',
+'勺' => '屺',
+'千' => 'ロ',
+'叉' => '脫',
+'口' => '諳',
+'土' => '芩',
+'士' => '尪',
+'夕' => '浀',
+'大' => '湮',
+'女' => '躓',
+'子' => '赽',
+'孑' => '篊',
+'孓' => '箵',
+'寸' => '渡',
+'小' => '苤',
+'尢' => '痼',
+'尸' => '坌',
+'山' => '刓',
+'川' => '捶',
+'工' => '馱',
+'己' => '撩',
+'已' => '眒',
+'巳' => '侒',
+'巾' => '踫',
+'干' => '補',
+'廾' => '甝',
+'弋' => '蒏',
+'弓' => '僮',
+'才' => '符',
+'丑' => '堯',
+'丐' => '堣',
+'不' => '祥',
+'中' => '笢',
+'丰' => '猿',
+'丹' => '竣',
+'之' => '眳',
+'尹' => '窇',
+'予' => '軑',
+'云' => '堁',
+'井' => '凝',
+'互' => '誑',
+'五' => '拻',
+'亢' => '蕩',
+'仁' => '',
+'什' => '妦',
+'仃' => '崹',
+'仆' => 'ど',
+'仇' => '喫',
+'仍' => '',
+'今' => '踏',
+'介' => '賡',
+'仄' => '媃',
+'元' => '啋',
+'允' => '埰',
+'內' => '囀',
+'六' => '鞠',
+'兮' => '殽',
+'公' => '鼠',
+'冗' => '',
+'凶' => '倜',
+'分' => '煦',
+'切' => 'з',
+'刈' => '尌',
+'勻' => '埱',
+'勾' => '僑',
+'勿' => '昦',
+'化' => '趙',
+'匹' => 'ぁ',
+'午' => '敁',
+'升' => '汔',
+'卅' => '埵',
+'卞' => '勗',
+'厄' => '塌',
+'友' => '衭',
+'及' => '摯',
+'反' => '毀',
+'壬' => '',
+'天' => '毞',
+'夫' => '痲',
+'太' => '怮',
+'夭' => '堬',
+'孔' => '謂',
+'少' => '屾',
+'尤' => '蚧',
+'尺' => '喜',
+'屯' => '迋',
+'巴' => '匙',
+'幻' => '酵',
+'廿' => '堨',
+'弔' => '裂',
+'引' => '竘',
+'心' => '陑',
+'戈' => '資',
+'戶' => '誧',
+'手' => '忒',
+'扎' => '崨',
+'支' => '盓',
+'文' => '恅',
+'斗' => '須',
+'斤' => '踝',
+'方' => '源',
+'日' => '',
+'曰' => '堇',
+'月' => '堎',
+'木' => '躂',
+'欠' => 'Й',
+'止' => '砦',
+'歹' => '渦',
+'毋' => '拺',
+'比' => '掀',
+'毛' => '禱',
+'氏' => '庌',
+'水' => '阨',
+'火' => '鳶',
+'爪' => '蛈',
+'父' => '虜',
+'爻' => '堻',
+'片' => 'え',
+'牙' => '挴',
+'牛' => '籟',
+'犬' => '',
+'王' => '卼',
+'丙' => '梡',
+'世' => '岍',
+'丕' => '塈',
+'且' => 'й',
+'丘' => '⑧',
+'主' => '翋',
+'乍' => '敓',
+'乏' => '椰',
+'乎' => '綱',
+'以' => '眕',
+'付' => '葆',
+'仔' => '豝',
+'仕' => '帊',
+'他' => '坻',
+'仗' => '梋',
+'代' => '測',
+'令' => '鍔',
+'仙' => '珈',
+'仞' => '嵀',
+'充' => '喃',
+'兄' => '倗',
+'冉' => '',
+'冊' => '聊',
+'冬' => '隄',
+'凹' => '側',
+'出' => '堤',
+'凸' => '芧',
+'刊' => '膳',
+'加' => '樓',
+'功\' => '髡',
+'包' => '婦',
+'匆' => '棍',
+'北' => '控',
+'匝' => '婧',
+'仟' => 'ヰ',
+'半' => '圉',
+'卉' => '雒',
+'卡' => '縐',
+'占' => '梩',
+'卯' => '簾',
+'卮' => '奡',
+'去' => '',
+'可' => '褫',
+'古' => '嘉',
+'右' => '衵',
+'召' => '欸',
+'叮' => '閎',
+'叩' => '萰',
+'叨' => '葍',
+'叼' => '蛤',
+'司' => '侗',
+'叵' => '媝',
+'叫' => '請',
+'另' => '鍚',
+'只' => '硐',
+'史' => '妢',
+'叱' => '蒆',
+'台' => '怢',
+'句' => '曆',
+'叭' => '務',
+'叻' => '葽',
+'四' => '侐',
+'囚' => '⑵',
+'外' => '俋',
+'央' => '栝',
+'失' => '囮',
+'奴' => '贖',
+'奶' => '騷',
+'孕' => '婕',
+'它' => '坳',
+'尼' => '攝',
+'巨' => '操',
+'巧' => 'б',
+'左' => '酘',
+'市' => '庈',
+'布' => '票',
+'平' => 'す',
+'幼' => '衿',
+'弁' => '袲',
+'弘' => '精',
+'弗' => '艇',
+'必' => '斛',
+'戊' => '昡',
+'打' => '湖',
+'扔' => '',
+'扒' => '勒',
+'扑' => 'で',
+'斥' => '喇',
+'旦' => '筒',
+'朮' => '扲',
+'本' => '掛',
+'未' => '帤',
+'末' => '藺',
+'札' => '崥',
+'正' => '淏',
+'母' => '譫',
+'民' => '鏍',
+'氐' => '媯',
+'永' => '蚗',
+'汁' => '眴',
+'汀' => '矷',
+'氾' => '滓',
+'犯' => '溢',
+'玄' => '哱',
+'玉' => '迶',
+'瓜' => '圖',
+'瓦' => '俓',
+'甘' => '裘',
+'生' => '汜',
+'用' => '蚚',
+'甩' => '辿',
+'田' => '泬',
+'由' => '蚕',
+'甲' => '樅',
+'申' => '扠',
+'疋' => '鼀',
+'白' => '啞',
+'皮' => '々',
+'皿' => '鏤',
+'目' => '醴',
+'矛' => '穫',
+'矢' => '妐',
+'石' => '坒',
+'示' => '尨',
+'禾' => '睽',
+'穴' => '悃',
+'立' => '蕾',
+'丞' => '堜',
+'丟' => '隍',
+'乒' => 'さ',
+'乓' => '籤',
+'乩' => '媕',
+'亙' => '堥',
+'交' => '蝠',
+'亦' => '砫',
+'亥' => '漸',
+'仿' => '溘',
+'伉' => '惉',
+'伙' => '鳴',
+'伊' => '畛',
+'伕' => '痲',
+'伍' => '斪',
+'伐' => '極',
+'休' => '倎',
+'伏' => '睦',
+'仲' => '笯',
+'件' => '璃',
+'任' => '',
+'仰' => '欯',
+'仳' => '幄',
+'份' => '爺',
+'企' => 'わ',
+'伋' => '',
+'光' => '嫖',
+'兇' => '倜',
+'兆' => '欳',
+'先' => '珂',
+'全' => '',
+'共' => '僕',
+'再' => '婬',
+'冰' => '梨',
+'列' => '蹈',
+'刑' => '倢',
+'划' => '赫',
+'刎' => '尰',
+'刖' => '踾',
+'劣' => '輾',
+'匈' => '倧',
+'匡' => '選',
+'匠' => '蔔',
+'印' => '荂',
+'危' => '峉',
+'吉' => '憚',
+'吏' => '環',
+'同' => '肮',
+'吊' => '裂',
+'吐' => '苂',
+'吁' => '郚',
+'吋' => '渡',
+'各' => '跪',
+'向' => '砃',
+'名' => '靡',
+'合' => '磁',
+'吃' => '勛',
+'后' => '綴',
+'吆' => '葴',
+'吒\' => '葚',
+'因' => '秪',
+'回' => '隙',
+'囝' => '麀',
+'圳' => '詀',
+'地' => '華',
+'在' => '婓',
+'圭' => '寧',
+'圬' => '訹',
+'圯' => '詄',
+'圩' => '詍',
+'夙' => '渼',
+'多' => '嗣',
+'夷' => '痁',
+'夸' => '蹂',
+'妄' => '咥',
+'奸' => '潮',
+'妃' => '漦',
+'好' => '疑',
+'她' => '坴',
+'如' => '',
+'妁' => '潁',
+'字' => '趼',
+'存' => '湔',
+'宇' => '迻',
+'守' => '忐',
+'宅' => '晙',
+'安' => '假',
+'寺' => '侁',
+'尖' => '潑',
+'屹' => '砣',
+'州' => '笣',
+'帆' => '楞',
+'并' => '甜',
+'年' => '爛',
+'式' => '宒',
+'弛' => '啼',
+'忙' => '疆',
+'忖' => '瞁',
+'戎' => '',
+'戌' => '剚',
+'戍' => '旴',
+'成' => '傖',
+'扣' => '諶',
+'扛' => '蕈',
+'托' => '迖',
+'收' => '彶',
+'早' => '婌',
+'旨' => '祤',
+'旬' => '悎',
+'旭' => '哢',
+'曲' => '⑻',
+'曳' => '珝',
+'有' => '衄',
+'朽' => '冓',
+'朴' => 'は',
+'朱' => '紾',
+'朵' => '嗡',
+'次' => '棒',
+'此' => '森',
+'死' => '侚',
+'氖' => '騫',
+'汝' => '',
+'汗' => '犒',
+'汙' => '拹',
+'江' => '蔬',
+'池' => '喀',
+'汐' => '洢',
+'汕' => '囟',
+'污' => '拹',
+'汛' => '挬',
+'汍' => '',
+'汎' => '滓',
+'灰' => '閡',
+'牟' => '觸',
+'牝' => '膷',
+'百' => '啃',
+'竹' => '罣',
+'米' => '譙',
+'糸' => '鏻',
+'缶' => '騞',
+'羊' => '栺',
+'羽' => '迼',
+'老' => '橾',
+'考' => '蕉',
+'而' => '奧',
+'耒' => '鼩',
+'耳' => '嫉',
+'聿' => '穛',
+'肉' => '',
+'肋' => '澀',
+'肌' => '慼',
+'臣' => '頃',
+'自' => '赻',
+'至' => '祫',
+'臼' => '彊',
+'舌' => '忕',
+'舛' => '漍',
+'舟' => '笸',
+'艮' => '轘',
+'色' => '伎',
+'艾' => '鬲',
+'虫' => '單',
+'血' => '悛',
+'行' => '俴',
+'衣' => '畟',
+'西' => '昹',
+'阡' => '筀',
+'串' => '揹',
+'亨' => '箋',
+'位' => '弇',
+'住' => '蛂',
+'佇' => '悹',
+'佗' => '晬',
+'佞' => '惌',
+'伴' => '圈',
+'佛' => '痰',
+'何' => '睡',
+'估' => '嘛',
+'佐' => '酚',
+'佑' => '衶',
+'伽' => '暀',
+'伺' => '侜',
+'伸' => '扥',
+'佃' => '菔',
+'佔' => '梩',
+'似' => '侔',
+'但' => '筍',
+'佣' => '荈',
+'作' => '釬',
+'你' => '斕',
+'伯' => '皎',
+'低' => '腴',
+'伶' => '鍥',
+'余' => '豻',
+'佝' => '愔',
+'佈' => '票',
+'佚' => '惄',
+'兌' => '募',
+'克' => '親',
+'免' => '轎',
+'兵' => '條',
+'冶' => '珣',
+'冷' => '濮',
+'別' => '梗',
+'判' => '瓚',
+'利' => '瞳',
+'刪' => '刉',
+'刨' => '蘸',
+'劫' => '誶',
+'助' => '翑',
+'努' => '贗',
+'劬' => '蛨',
+'匣' => '牰',
+'即' => '撈',
+'卵' => '覲',
+'吝' => '醞',
+'吭\' => '諮',
+'吞' => '迒',
+'吾' => '挓',
+'否' => '瘁',
+'呎' => '喜',
+'吧' => '勘',
+'呆' => '渭',
+'呃' => '萺',
+'吳' => '挔',
+'呈' => '傘',
+'呂' => '臍',
+'君' => '澱',
+'吩' => '煜',
+'告' => '豢',
+'吹' => '斯',
+'吻' => '恉',
+'吸' => '柲',
+'吮' => '丳',
+'吵' => '陶',
+'吶' => '霰',
+'吠' => '溴',
+'吼' => '綾',
+'呀' => '挼',
+'吱' => '眹',
+'含' => '漪',
+'吟' => '窉',
+'听' => '泭',
+'囪' => '棋',
+'困' => '嬪',
+'囤' => '嗓',
+'囫' => '僔',
+'坊' => '溶',
+'坑' => '諧',
+'址' => '硊',
+'坍' => '怌',
+'均' => '歙',
+'坎' => '臻',
+'圾' => '僵',
+'坐' => '釴',
+'坏' => '輓',
+'圻' => '詒',
+'壯' => '袕',
+'夾' => '標',
+'妝' => '衒',
+'妒' => '催',
+'妨' => '溥',
+'妞' => '璊',
+'妣' => '澒',
+'妙' => '鏝',
+'妖' => '毦',
+'妍' => '潾',
+'妤' => '璆',
+'妓' => '樣',
+'妊' => '',
+'妥' => '邰',
+'孝' => '苠',
+'孜' => '谻',
+'孚' => '篎',
+'孛' => '媄',
+'完' => '俇',
+'宋' => '冼',
+'宏' => '粽',
+'尬' => '痸',
+'局' => '擁',
+'屁' => 'い',
+'尿' => '續',
+'尾' => '帣',
+'岐' => '嶊',
+'岑' => '嶍',
+'岔' => '舶',
+'岌' => '嵺',
+'巫' => '拵',
+'希' => '洷',
+'序' => '唗',
+'庇' => '敏',
+'床' => '散',
+'廷' => '祂',
+'弄' => '讀',
+'弟' => '萊',
+'彤' => '肸',
+'形' => '倛',
+'彷' => '摴',
+'役' => '砢',
+'忘' => '咭',
+'忌' => '暴',
+'志' => '祩',
+'忍' => '',
+'忱' => '鹿',
+'快' => '辦',
+'忸' => '碭',
+'忪' => '碪',
+'戒' => '賭',
+'我' => '扂',
+'抄' => '陪',
+'抗' => '蕨',
+'抖' => '順',
+'技' => '撮',
+'扶' => '痴',
+'抉' => '橄',
+'扭' => '聾',
+'把' => '參',
+'扼' => '塭',
+'找' => '梑',
+'批' => '蠶',
+'扳' => '售',
+'抒' => '忻',
+'扯' => '雀',
+'折' => '殏',
+'扮' => '啁',
+'投' => '芘',
+'抓' => '蚰',
+'抑' => '眚',
+'抆' => '^',
+'改' => '蜊',
+'攻' => '馴',
+'攸' => '惎',
+'旱' => '熊',
+'更' => '載',
+'束' => '旰',
+'李' => '燠',
+'杏' => '倬',
+'材' => '第',
+'村' => '游',
+'杜' => '債',
+'杖' => '桱',
+'杞' => '頧',
+'杉' => '冱',
+'杆' => '裝',
+'杠' => '話',
+'杓' => '頛',
+'杗' => 'n',
+'步' => '祭',
+'每' => '藩',
+'求' => '⑴',
+'汞' => '僖',
+'沙' => '伈',
+'沁' => 'ц',
+'沈' => '朻',
+'沉' => '麥',
+'沅' => '蜠',
+'沛' => '驛',
+'汪' => '匽',
+'決' => '樵',
+'沐' => '蜲',
+'汰' => '怑',
+'沌' => '蜭',
+'汨' => '蜼',
+'沖' => '喳',
+'沒' => '羶',
+'汽' => 'イ',
+'沃' => '挋',
+'汲' => '撲',
+'汾' => '煆',
+'汴' => '蜺',
+'沆' => '蜵',
+'汶' => '蜱',
+'沍' => '渃',
+'沔\' => '蜪',
+'沘' => 'a',
+'沂' => '疺',
+'灶' => '婜',
+'灼' => '觖',
+'災' => '婐',
+'灸' => '奮',
+'牢' => '檣',
+'牡' => '警',
+'牠' => '坳',
+'狄' => '菸',
+'狂' => '遼',
+'玖' => '墾',
+'甬' => '薿',
+'甫' => '蒂',
+'男' => '鹹',
+'甸' => '菟',
+'皂' => '婂',
+'盯' => '閒',
+'矣' => '眑',
+'私' => '佌',
+'秀' => '凅',
+'禿' => '芮',
+'究' => '噶',
+'系' => '炵',
+'罕' => '滷',
+'肖' => '苳',
+'肓' => '蹅',
+'肝' => '裕',
+'肘' => '紵',
+'肛' => '誇',
+'肚' => '傳',
+'育' => '郤',
+'良' => '謎',
+'芒' => '璽',
+'芋' => '郠',
+'芍' => '屼',
+'見' => '獗',
+'角' => '褒',
+'言' => '晟',
+'谷' => '嗷',
+'豆' => '飪',
+'豕' => '鼮',
+'貝' => '探',
+'赤' => '喪',
+'走' => '軗',
+'足' => '逋',
+'身' => '旯',
+'車' => '陬',
+'辛' => '釓',
+'辰' => '魚',
+'迂' => '衯',
+'迆' => '暲',
+'迅' => '捃',
+'迄' => 'ア',
+'巡' => '挐',
+'邑' => '眧',
+'邢' => '俵',
+'邪' => '訄',
+'邦' => '堊',
+'那' => '饒',
+'酉' => '衃',
+'釆' => '粒',
+'里' => '爵',
+'防' => '滅',
+'阮' => '',
+'阱' => '筘',
+'阪' => '筅',
+'阬' => '諧',
+'並' => '甜',
+'乖' => '墊',
+'乳' => '',
+'事' => '岈',
+'些' => '虳',
+'亞' => '捚',
+'享' => '砅',
+'京' => '儔',
+'佯' => '栮',
+'依' => '甡',
+'侍' => '帎',
+'佳' => '槽',
+'使' => '妏',
+'佬' => '檗',
+'供' => '鼎',
+'例' => '瞰',
+'來' => '懂',
+'侃' => '朁',
+'佰' => '唱',
+'併' => '甜',
+'侈' => '喂',
+'佩' => '驚',
+'佻' => '椄',
+'侖' => '贅',
+'佾' => '棓',
+'侏' => '椌',
+'侑' => '晪',
+'佺' => '',
+'兔' => '芤',
+'兒' => '嫁',
+'兕' => '渽',
+'兩' => '謗',
+'具' => '撿',
+'其' => 'む',
+'典' => '萎',
+'冽' => '渮',
+'函' => '滲',
+'刻' => '覦',
+'券' => '',
+'刷' => '芃',
+'刺' => '棧',
+'到' => '善',
+'刮' => '團',
+'制' => '秶',
+'剁' => '嗥',
+'劾' => '衈',
+'劻' => '',
+'卒' => '逑',
+'協' => '衪',
+'卓' => '袗',
+'卑' => '掠',
+'卦' => '寑',
+'卷' => '橙',
+'卸' => '迠',
+'卹' => '哧',
+'取' => '',
+'叔' => '忴',
+'受' => '忳',
+'味' => '庤',
+'呵' => '瘉',
+'咖' => '縉',
+'呸' => '邏',
+'咕' => '嗾',
+'咀' => '擋',
+'呻' => '扚',
+'呷' => '菙',
+'咄' => '葟',
+'咒' => '紸',
+'咆' => '臢',
+'呼' => '網',
+'咐' => '蛻',
+'呱' => '葋',
+'呶' => '葰',
+'和' => '睿',
+'咚' => '葂',
+'呢' => '儸',
+'周' => '笚',
+'咋' => '捰',
+'命' => '韜',
+'咎' => '憑',
+'固' => '嘐',
+'垃' => '嶼',
+'坷' => '螃',
+'坪' => 'ざ',
+'坩' => '詑',
+'坡' => 'ぞ',
+'坦' => '拊',
+'坤' => '壑',
+'坼\' => '豟',
+'夜' => '珗',
+'奉' => '畸',
+'奇' => 'も',
+'奈' => '鰓',
+'奄' => '栟',
+'奔' => '掉',
+'妾' => '瑼',
+'妻' => 'ぺ',
+'委' => '巹',
+'妹' => '藤',
+'妮' => '屬',
+'姑' => '嘔',
+'姆' => '譟',
+'姐' => '賬',
+'姍' => '璈',
+'始' => '宎',
+'姓' => '俷',
+'姊' => '璇',
+'妯' => '璅',
+'妳' => '斕',
+'姒' => '璁',
+'姅' => '',
+'孟' => '譁',
+'孤' => '嗽',
+'季' => '撫',
+'宗' => '跁',
+'定' => '隅',
+'官' => '夥',
+'宜' => '皊',
+'宙' => '紺',
+'宛' => '剄',
+'尚' => '奾',
+'屈' => '⑽',
+'居' => '懈',
+'屆' => '趣',
+'岷' => '廕',
+'岡' => '詳',
+'岸' => '偉',
+'岩' => '旂',
+'岫' => '廑',
+'岱' => '廗',
+'岳' => '埬',
+'帘' => '螫',
+'帚' => '紽',
+'帖' => '泃',
+'帕' => '鰻',
+'帛' => '盔',
+'帑' => '僰',
+'幸' => '倷',
+'庚' => '軾',
+'店' => '虛',
+'府' => '葬',
+'底' => '菁',
+'庖' => '瑲',
+'延' => '晊',
+'弦' => '玾',
+'弧' => '說',
+'弩' => '殢',
+'往' => '厘',
+'征' => '涽',
+'彿' => '痰',
+'彼' => '捨',
+'忝' => '蓇',
+'忠' => '笳',
+'忽' => '綺',
+'念' => '癩',
+'忿' => '牒',
+'怏' => '碥',
+'怔' => '涺',
+'怯' => 'к',
+'怵' => '硾',
+'怖' => '窕',
+'怪' => '墅',
+'怕' => '鷓',
+'怡' => '禊',
+'性' => '俶',
+'怩' => '碬',
+'怫' => '碢',
+'怛' => '碞',
+'或' => '麼',
+'戕' => '蜛',
+'房' => '滇',
+'戾' => '懤',
+'所' => '垀',
+'承' => '創',
+'拉' => '嶺',
+'拌' => '啗',
+'拄' => '羝',
+'抿' => '鏘',
+'拂' => '痳',
+'抹' => '蘑',
+'拒' => '擇',
+'招' => '桸',
+'披' => '蠹',
+'拓' => '阹',
+'拔' => '匿',
+'拋' => '纔',
+'拈' => '灌',
+'抨' => '髑',
+'抽' => '喲',
+'押' => '挹',
+'拐' => '塹',
+'拙' => '袛',
+'拇' => '譬',
+'拍' => '鼴',
+'抵' => '萋',
+'拚' => '皙',
+'抱' => '惕',
+'拘' => '憶',
+'拖' => '迍',
+'拗' => '皵',
+'拆' => '莞',
+'抬' => '怬',
+'拎' => '醜',
+'放' => '溫',
+'斧' => '葦',
+'於' => '衾',
+'旺' => '咺',
+'昔' => '昺',
+'易' => '眢',
+'昌' => '荻',
+'昆' => '壎',
+'昂' => '偕',
+'明' => '隴',
+'昀' => '篔',
+'昏' => '餉',
+'昕' => '篹',
+'昊' => '篕',
+'昇' => '汔',
+'服' => '督',
+'朋' => '攬',
+'杭' => '獐',
+'枋' => '駔',
+'枕' => '淠',
+'東' => '陲',
+'果' => '彆',
+'杳' => '餖',
+'杷' => '駎',
+'枇' => '餑',
+'枝' => '皉',
+'林' => '輿',
+'杯' => '戚',
+'杰' => '豌',
+'板' => '啣',
+'枉' => '厖',
+'松' => '侂',
+'析' => '昴',
+'杵' => '駜',
+'枚' => '繹',
+'枓' => '',
+'杼' => '駉',
+'杪' => '餔',
+'杲' => '篚',
+'欣' => '釔',
+'武' => '挕',
+'歧' => 'ゃ',
+'歿\' => '殪',
+'氓' => '疇',
+'氛' => '煬',
+'泣' => 'ゥ',
+'注' => '蛁',
+'泳' => '蚞',
+'沱' => '裮',
+'泌' => '蹼',
+'泥' => '懾',
+'河' => '碩',
+'沽' => '嘗',
+'沾' => '桭',
+'沼' => '梌',
+'波' => '疏',
+'沫' => '蘊',
+'法' => '楊',
+'泓' => '裼',
+'沸' => '煩',
+'泄' => '邿',
+'油' => '蚐',
+'況' => '錶',
+'沮' => '據',
+'泗' => '蜑',
+'泅' => '⑷',
+'泱' => '蜰',
+'沿' => '朓',
+'治' => '笥',
+'泡' => '邐',
+'泛' => '滓',
+'泊' => '眼',
+'沬' => 'i',
+'泯' => '裶',
+'泜' => '',
+'泖' => '裱',
+'泠' => '裧',
+'炕' => '蕃',
+'炎' => '朒',
+'炒' => '陷',
+'炊' => '普',
+'炙' => '笵',
+'爬' => '鰾',
+'爭' => '淰',
+'爸' => '啄',
+'版' => '唳',
+'牧' => '鐘',
+'物' => '昜',
+'狀' => '袨',
+'狎' => '摥',
+'狙' => '憾',
+'狗' => '僩',
+'狐' => '緒',
+'玩' => '俙',
+'玨' => '谾',
+'玟' => '諙',
+'玫' => '繭',
+'玥' => '則',
+'甽' => '峽',
+'疝' => '謰',
+'疙' => '貲',
+'疚' => '憊',
+'的' => '腔',
+'盂' => '衴',
+'盲' => '瓣',
+'直' => '眻',
+'知' => '眭',
+'矽' => '朏',
+'社' => '扦',
+'祀' => '擫',
+'祁' => 'り',
+'秉' => '梂',
+'秈' => '覹',
+'空' => '諾',
+'穹' => '騇',
+'竺' => '鬊',
+'糾' => '壁',
+'罔' => '嵙',
+'羌' => 'Ф',
+'羋' => '娷',
+'者' => '氪',
+'肺' => '煎',
+'肥' => '滔',
+'肢' => '眱',
+'肱' => '蹁',
+'股' => '嘖',
+'肫' => '踰',
+'肩' => '潛',
+'肴' => '躽',
+'肪' => '溝',
+'肯' => '諫',
+'臥' => '拏',
+'臾' => '籈',
+'舍' => '忔',
+'芳' => '滂',
+'芝' => '皏',
+'芙' => '傰',
+'芭' => '剪',
+'芽' => '捁',
+'芟' => '嗊',
+'芹' => 'т',
+'花' => '豪',
+'芬' => '煉',
+'芥' => '賣',
+'芯' => '郋',
+'芸' => '傺',
+'芣' => '釁',
+'芰' => '僋',
+'芾' => '傱',
+'芷' => '剺',
+'虎' => '誥',
+'虱' => '坉',
+'初' => '場',
+'表' => '桶',
+'軋' => '崏',
+'迎' => '茩',
+'返' => '殿',
+'近' => '輪',
+'邵' => '幵',
+'邸' => '菕',
+'邱' => '⑨',
+'邶' => '缿',
+'采' => '粒',
+'金' => '踢',
+'長' => '酗',
+'門' => '藷',
+'阜' => '虞',
+'陀' => '邲',
+'阿' => '陝',
+'阻' => '郯',
+'附' => '蜇',
+'陂' => '粨',
+'隹' => '鶹',
+'雨' => '迾',
+'青' => 'ч',
+'非' => '準',
+'亟' => '婼',
+'亭' => '秅',
+'亮' => '謠',
+'信' => '陓',
+'侵' => 'н',
+'侯' => '綜',
+'便' => '晞',
+'俠' => '狨',
+'俑' => '椓',
+'俏' => 'ё',
+'保' => '悵',
+'促' => '棻',
+'侶' => '舊',
+'俘' => '睞',
+'俟' => '椐',
+'俊' => '縑',
+'俗' => '匋',
+'侮' => '斿',
+'俐' => '瞬',
+'俄' => '塘',
+'係' => '炵',
+'俚' => '棫',
+'俎' => '殔',
+'俞\' => '貤',
+'侷' => '擁',
+'兗' => '湢',
+'冒' => '簸',
+'冑' => '遶',
+'冠' => '夢',
+'剎' => '价',
+'剃' => '殀',
+'削' => '祅',
+'前' => 'ヶ',
+'剌' => '嵋',
+'剋' => '親',
+'則' => '寀',
+'勇' => '蚋',
+'勉' => '辭',
+'勃' => '痕',
+'勁' => '麩',
+'匍' => '湇',
+'南' => '鰍',
+'卻' => '',
+'厚' => '綠',
+'叛' => '竊',
+'咬' => '狶',
+'哀' => '飢',
+'咨' => '訰',
+'哎' => '陞',
+'哉' => '婭',
+'咸' => '玶',
+'咦' => '葇',
+'咳' => '褥',
+'哇' => '阺',
+'哂' => '葯',
+'咽' => '捗',
+'咪' => '蛷',
+'品' => 'こ',
+'哄' => '箏',
+'哈' => '慇',
+'咯' => '罹',
+'咫' => '槶',
+'咱' => '婤',
+'咻' => '萫',
+'咩' => '蜄',
+'咧' => '萻',
+'咿' => '葠',
+'囿' => '僨',
+'垂' => '晶',
+'型' => '倰',
+'垠' => '跇',
+'垣' => '圊',
+'垢' => '兢',
+'城' => '傑',
+'垮' => '踹',
+'垓' => '跍',
+'奕' => '瘏',
+'契' => 'ゑ',
+'奏' => '軠',
+'奎' => '錫',
+'奐' => '蛩',
+'姜' => '蔽',
+'姘' => '瘞',
+'姿' => '訬',
+'姣' => '瘥',
+'姨' => '盉',
+'娃' => '俅',
+'姥' => '檐',
+'姪' => '硍',
+'姚' => '狾',
+'姦' => '潮',
+'威' => '哏',
+'姻' => '窆',
+'孩' => '滯',
+'宣' => '哫',
+'宦' => '鄞',
+'室' => '弅',
+'客' => '諦',
+'宥' => '撊',
+'封' => '猾',
+'屎' => '妧',
+'屏' => 'そ',
+'屍' => '坌',
+'屋' => '挌',
+'峙' => '秸',
+'峒' => '廒',
+'巷' => '砏',
+'帝' => '著',
+'帥' => '邟',
+'帟' => '',
+'幽' => '蚅',
+'庠' => '瑮',
+'度' => '僅',
+'建' => '膘',
+'弈' => '畹',
+'弭' => '殦',
+'彥' => '栫',
+'很' => '竭',
+'待' => '渾',
+'徊' => '輔',
+'律' => '薺',
+'徇' => '摲',
+'後' => '綴',
+'徉' => '摳',
+'怒' => '躑',
+'思' => '佷',
+'怠' => '窗',
+'急' => '摹',
+'怎' => '崋',
+'怨' => '埳',
+'恍' => '鉼',
+'恰' => 'ョ',
+'恨' => '管',
+'恢' => '閥',
+'恆' => '箝',
+'恃' => '庍',
+'恬' => '泮',
+'恫' => '雯',
+'恪' => '耤',
+'恤' => '哧',
+'扁' => '晦',
+'拜' => '問',
+'挖' => '阼',
+'按' => '偌',
+'拼' => 'ぐ',
+'拭' => '岋',
+'持' => '厥',
+'拮' => '盝',
+'拽' => '蚹',
+'指' => '硌',
+'拱' => '僭',
+'拷' => '蕭',
+'拯' => '淂',
+'括' => '嬤',
+'拾' => '夆',
+'拴' => '邥',
+'挑' => '泔',
+'挂' => '境',
+'政' => '淉',
+'故' => '嘟',
+'斫' => '簀',
+'施' => '囥',
+'既' => '暫',
+'春' => '景',
+'昭' => '桻',
+'映' => '茬',
+'昧' => '藪',
+'是' => '岆',
+'星' => '陎',
+'昨' => '酖',
+'昱' => '篘',
+'昤' => '`',
+'曷' => '篢',
+'柿' => '岏',
+'染' => '',
+'柱' => '翐',
+'柔' => '',
+'某' => '議',
+'柬' => '潤',
+'架' => '殤',
+'枯\' => '豫',
+'柵' => '掑',
+'柩' => '駌',
+'柯' => '螞',
+'柄' => '梟',
+'柑' => '裡',
+'枴' => '塹',
+'柚' => '髲',
+'查' => '脤',
+'枸' => '魴',
+'柏' => '啡',
+'柞' => '酓',
+'柳' => '霞',
+'枰' => '骳',
+'柙' => '髫',
+'柢' => '魱',
+'柝' => '魆',
+'柒' => 'ま',
+'歪' => '俉',
+'殃' => '栴',
+'殆' => '渺',
+'段' => '僇',
+'毒' => '馮',
+'毗' => '讒',
+'氟' => '睛',
+'泉' => '',
+'洋' => '栥',
+'洲' => '粔',
+'洪' => '粹',
+'流' => '霜',
+'津' => '踩',
+'洌' => '銫',
+'洱' => '媽',
+'洞' => '韌',
+'洗' => '炴',
+'活' => '魂',
+'洽' => 'ヨ',
+'派' => '巖',
+'洶' => '倵',
+'洛' => '醫',
+'泵' => '掙',
+'洹' => '銦',
+'洧' => '銚',
+'洸' => '',
+'洩' => '邿',
+'洮' => '銢',
+'洵' => '鉽',
+'洎' => '銎',
+'洫' => '銂',
+'炫' => '嚃',
+'為' => '峈',
+'炳' => '殺',
+'炬' => '暹',
+'炯' => '噯',
+'炭' => '抰',
+'炸' => '旍',
+'炮' => '蘿',
+'炤' => '桽',
+'爰' => '趧',
+'牲' => '汊',
+'牯' => '臲',
+'牴' => '萋',
+'狩' => '暠',
+'狠' => '端',
+'狡' => '複',
+'玷' => '賥',
+'珊' => '仴',
+'玻' => '產',
+'玲' => '鍍',
+'珍' => '湴',
+'珀' => '賙',
+'玳' => '賟',
+'甚' => '朼',
+'甭' => '授',
+'畏' => '庢',
+'界' => '賜',
+'畎' => '謚',
+'畋' => '豏',
+'疫' => '砮',
+'疤' => '匏',
+'疥' => '赭',
+'疢' => '烘',
+'疣' => '譇',
+'癸' => '對',
+'皆' => '諂',
+'皇' => '銘',
+'皈' => '藃',
+'盈' => '荅',
+'盆' => '髓',
+'盃' => '戚',
+'盅' => '笤',
+'省' => '吽',
+'盹' => '臩',
+'相' => '眈',
+'眉' => '羹',
+'看' => '艘',
+'盾' => '嗎',
+'盼' => '曬',
+'眇' => '艛',
+'矜' => '鼪',
+'砂' => '仱',
+'研' => '旃',
+'砌' => 'を',
+'砍' => '興',
+'祆' => '擤',
+'祉' => '擨',
+'祈' => 'ら',
+'祇' => '發',
+'禹' => '迿',
+'禺' => '堮',
+'科' => '褪',
+'秒' => '鏃',
+'秋' => '⑦',
+'穿' => '援',
+'突' => '芼',
+'竿' => '裊',
+'竽' => '鬎',
+'籽' => '豽',
+'紂' => '聤',
+'紅' => '綻',
+'紀' => '槨',
+'紉' => '',
+'紇' => '聧',
+'約' => '埮',
+'紆' => '翨',
+'缸' => '詰',
+'美' => '藝',
+'羿' => '邍',
+'耄' => '諴',
+'耐' => '騵',
+'耍' => '芄',
+'耑' => '蚳',
+'耶' => '珖',
+'胖' => '纖',
+'胥' => '鼖',
+'胚' => '鑣',
+'胃' => '庛',
+'胄' => '遶',
+'背' => '掖',
+'胡' => '綸',
+'胛' => '輷',
+'胎' => '怚',
+'胞' => '婉',
+'胤' => '媟',
+'胝' => '鄳',
+'致' => '祡',
+'舢' => '纁',
+'苧' => '嗀',
+'范' => '毓',
+'茅' => '矇',
+'苣' => '傸',
+'苛' => '螟',
+'苦' => '賴',
+'茄' => 'и',
+'若' => '',
+'茂' => '簿',
+'茉' => '嗩',
+'苒\' => '嗖',
+'苗' => '醮',
+'英' => '荎',
+'茁' => '袌',
+'苜' => '嗕',
+'苔' => '怞',
+'苑' => '埸',
+'苞' => '婁',
+'苓' => '嗙',
+'苟' => '僎',
+'苯' => '掃',
+'茆' => '塓',
+'虐' => '酈',
+'虹' => '箇',
+'虻' => '繺',
+'虺' => '繷',
+'衍' => '栲',
+'衫' => '劦',
+'要' => '猁',
+'觔' => '踐',
+'計' => '數',
+'訂' => '隆',
+'訃' => '蜈',
+'貞' => '淔',
+'負' => '蛹',
+'赴' => '萼',
+'赳' => '鐙',
+'趴' => '鰱',
+'軍' => '濂',
+'軌' => '寢',
+'述' => '扴',
+'迦' => '暪',
+'迢' => '泧',
+'迪' => '舜',
+'迥' => '暰',
+'迭' => '詞',
+'迫' => 'つ',
+'迤' => '暲',
+'迨' => '樀',
+'郊' => '蝦',
+'郎' => '檔',
+'郁' => '郙',
+'郃' => '磁',
+'酋' => '⑶',
+'酊' => '鏺',
+'重' => '笭',
+'閂' => '蒛',
+'限' => '癹',
+'陋' => '穠',
+'陌' => '襤',
+'降' => '蔥',
+'面' => '醱',
+'革' => '賂',
+'韋' => '峇',
+'韭' => '壇',
+'音' => '秞',
+'頁' => '珜',
+'風' => '瑞',
+'飛' => '滄',
+'食' => '妘',
+'首' => '忑',
+'香' => '眅',
+'乘' => '傚',
+'亳' => '渫',
+'倌' => '椔',
+'倍' => '捷',
+'倣' => '溘',
+'俯' => '萱',
+'倦' => '樸',
+'倥' => '棸',
+'俸' => '棳',
+'倩' => '棡',
+'倖' => '倷',
+'倆' => '薨',
+'值' => '硉',
+'借' => '質',
+'倚' => '眓',
+'倒' => '給',
+'們' => '蠅',
+'俺' => '偃',
+'倀' => '徥',
+'倔' => '橡',
+'倨' => '棐',
+'俱' => '整',
+'倡' => '釩',
+'個' => '跺',
+'候' => '緊',
+'倘' => '昈',
+'俳' => '棌',
+'修' => '党',
+'倭' => '椑',
+'倪' => '懼',
+'俾' => '棯',
+'倫' => '豐',
+'倉' => '累',
+'兼' => '潭',
+'冤' => '啀',
+'冥' => '琱',
+'冢' => '琭',
+'凍' => '雲',
+'凌' => '錘',
+'准' => '袧',
+'凋' => '蛞',
+'剖' => 'て',
+'剜' => '嵑',
+'剔' => '枌',
+'剛' => '試',
+'剝' => '婀',
+'匪' => '溪',
+'卿' => 'ы',
+'原' => '埻',
+'厝' => '媩',
+'叟' => '袹',
+'哨' => '巟',
+'唐' => '昄',
+'唁' => '栵',
+'唷' => '遄',
+'哼' => '箕',
+'哥' => '貊',
+'哲' => '殍',
+'唆' => '坭',
+'哺' => '硫',
+'唔' => '蜁',
+'哩' => '薇',
+'哭' => '豭',
+'員' => '埜',
+'唉' => '隻',
+'哮' => '祄',
+'哪' => '闡',
+'哦' => '韃',
+'唧' => '裍',
+'唇' => '晾',
+'哽' => '蜉',
+'唏' => '裖',
+'圃' => 'ば',
+'圄' => '僳',
+'埂' => '飽',
+'埔' => 'の',
+'埋' => '鎚',
+'埃' => '除',
+'堉' => '',
+'夏' => '狦',
+'套' => '杶',
+'奘' => '痷',
+'奚' => '瘃',
+'娑' => '瘨',
+'娘' => '矓',
+'娜' => '饑',
+'娟' => '樽',
+'娛' => '軓',
+'娓' => '皜',
+'姬' => '憫',
+'娠' => '朾',
+'娣' => '瘛',
+'娩' => '邊',
+'娥' => '塔',
+'娌' => '瘝',
+'娉\' => '瘜',
+'孫' => '呤',
+'屘' => '',
+'宰' => '婟',
+'害' => '漲',
+'家' => '模',
+'宴' => '栯',
+'宮' => '僧',
+'宵' => '秖',
+'容' => '',
+'宸' => '撌',
+'射' => '扞',
+'屑' => '邾',
+'展' => '桯',
+'屐' => '樦',
+'峭' => 'е',
+'峽' => '狤',
+'峻' => '澡',
+'峪' => '郥',
+'峨' => '塞',
+'峰' => '瑕',
+'島' => '絢',
+'崁' => '',
+'峴' => '嵾',
+'差' => '船',
+'席' => '炟',
+'師' => '呇',
+'庫' => '踱',
+'庭' => '穸',
+'座' => '釱',
+'弱' => '',
+'徒' => '芺',
+'徑' => '噤',
+'徐' => '剢',
+'恙' => '磽',
+'恣' => '礂',
+'恥' => '喝',
+'恐' => '謁',
+'恕' => '芊',
+'恭' => '鳩',
+'恩' => '塋',
+'息' => '洘',
+'悄' => 'Ь',
+'悟' => '昳',
+'悚' => '膉',
+'悍' => '熒',
+'悔' => '際',
+'悌' => '膌',
+'悅' => '埼',
+'悖' => '聜',
+'扇' => '圮',
+'拳' => '',
+'挈' => '蕓',
+'拿' => '鏽',
+'捎' => '孖',
+'挾' => '衩',
+'振' => '淥',
+'捕' => '眸',
+'捂' => '拰',
+'捆' => '嬰',
+'捏' => '羼',
+'捉' => '袙',
+'挺' => '穻',
+'捐' => '曇',
+'挽' => '侺',
+'挪' => '鑑',
+'挫' => '渥',
+'挨' => '陘',
+'捍' => '煽',
+'捌' => '副',
+'效' => '虴',
+'敉' => '觷',
+'料' => '蹋',
+'旁' => '籥',
+'旅' => '藏',
+'時' => '奀',
+'晉' => '輩',
+'晏' => '縒',
+'晃' => '銜',
+'晒' => '伄',
+'晌' => '妅',
+'晅' => 't',
+'晁' => '糑',
+'書' => '抎',
+'朔' => '侇',
+'朕' => '錞',
+'朗' => '檄',
+'校' => '苺',
+'核' => '瞄',
+'案' => '偶',
+'框' => '遺',
+'桓' => '遘',
+'根' => '跦',
+'桂' => '屢',
+'桔' => '諛',
+'栩' => '鼏',
+'梳' => '忯',
+'栗' => '璦',
+'桌' => '袤',
+'桑' => '氿',
+'栽' => '娵',
+'柴' => '莘',
+'桐' => '糽',
+'桀' => '鴅',
+'格' => '跡',
+'桃' => '朊',
+'株' => '絁',
+'桅' => '峖',
+'栓' => '邡',
+'栘' => '',
+'桁' => '鳻',
+'殊' => '忷',
+'殉' => '捖',
+'殷' => '秜',
+'氣' => 'ァ',
+'氧' => '欬',
+'氨' => '停',
+'氦' => '漱',
+'氤' => '貐',
+'泰' => '怍',
+'浪' => '檢',
+'涕' => '欥',
+'消' => '秏',
+'涇' => '裻',
+'浦' => 'ひ',
+'浸' => '輞',
+'海' => '漆',
+'浙' => '涳',
+'涓' => '銝',
+'浬' => '爵',
+'涉' => '扡',
+'浮' => '腹',
+'浚' => '縛',
+'浴' => '唌',
+'浩' => '瘋',
+'涌' => '蚇',
+'涊' => '',
+'浹' => '鉹',
+'涅' => '蠡',
+'浥' => '',
+'涔' => '銋',
+'烊' => '噿',
+'烘' => '箸',
+'烤' => '蕪',
+'烙' => '歜',
+'烈' => '轄',
+'烏' => '拫',
+'爹' => '註',
+'特' => '杻',
+'狼' => '曖',
+'狹' => '狫',
+'狽' => '捧',
+'狸' => '燥',
+'狷' => '朄',
+'玆' => '觕',
+'班' => '啤',
+'琉' => '闋',
+'珮\' => '驚',
+'珠' => '紩',
+'珪' => '寧',
+'珞' => '踠',
+'畔' => '欐',
+'畝' => '譯',
+'畜' => '唒',
+'畚' => '褁',
+'留' => '隱',
+'疾' => '撞',
+'病' => '瓷',
+'症' => '痌',
+'疲' => 'ゞ',
+'疳' => '謯',
+'疽' => '懊',
+'疼' => '构',
+'疹' => '淟',
+'痂' => '謶',
+'疸' => '謾',
+'皋' => '詭',
+'皰' => '謥',
+'益' => '祔',
+'盍' => '轀',
+'盎' => '偵',
+'眩' => '悈',
+'真' => '淩',
+'眠' => '蹺',
+'眨' => '掁',
+'矩' => '撻',
+'砰' => '體',
+'砧' => '涷',
+'砸' => '婞',
+'砝' => '簎',
+'破' => 'ぢ',
+'砷' => '扙',
+'砥' => '簃',
+'砭' => '篿',
+'砠' => '訟',
+'砟' => '簂',
+'砲' => '蘿',
+'祕' => '贈',
+'祐' => '衶',
+'祠' => '檖',
+'祟' => '呧',
+'祖' => '逌',
+'神' => '朸',
+'祝' => '蛅',
+'祗' => '檍',
+'祚' => '旚',
+'秤' => '勝',
+'秣' => '濷',
+'秧' => '栔',
+'租' => '逤',
+'秦' => 'п',
+'秩' => '窏',
+'秘' => '贈',
+'窄' => '晜',
+'窈' => '髜',
+'站' => '桴',
+'笆' => '動',
+'笑' => '虷',
+'粉' => '煨',
+'紡' => '溺',
+'紗' => '伝',
+'紋' => '恇',
+'紊' => '恌',
+'素' => '匼',
+'索' => '坰',
+'純' => '曾',
+'紐' => '臟',
+'紕' => '蝣',
+'級' => '撰',
+'紜' => '蝖',
+'納' => '馨',
+'紙' => '祧',
+'紛' => '煌',
+'缺' => '',
+'罟' => '赯',
+'羔' => '詬',
+'翅' => '喔',
+'翁' => '恟',
+'耆' => '糔',
+'耘' => '埧',
+'耕' => '較',
+'耙' => '曼',
+'耗' => '瘧',
+'耽' => '窖',
+'耿' => '飾',
+'胱' => '鄶',
+'脂' => '眲',
+'胰' => '疿',
+'脅' => '赲',
+'胭' => '醐',
+'胴' => '醓',
+'脆' => '毯',
+'胸' => '倠',
+'胳' => '賄',
+'脈' => '闕',
+'能' => '夔',
+'脊' => '撕',
+'胼' => '錧',
+'胯' => '輯',
+'臭' => '堪',
+'臬' => '糮',
+'舀' => '狳',
+'舐' => '鬋',
+'航' => '瑤',
+'舫' => '臛',
+'舨' => '聹',
+'般' => '啜',
+'芻' => '蛬',
+'茫' => '瓊',
+'荒' => '酸',
+'荔' => '璩',
+'荊' => '麾',
+'茸' => '',
+'荐' => '熱',
+'草' => '翌',
+'茵' => '秮',
+'茴' => '塛',
+'荏' => '嫇',
+'茲' => '觕',
+'茹' => '',
+'茶' => '脰',
+'茗' => '媱',
+'荀' => '媸',
+'茱' => '堽',
+'茨' => '棕',
+'荃' => '嫋',
+'虔' => '繶',
+'蚊' => '恞',
+'蚪' => '羷',
+'蚓' => '翽',
+'蚤' => '婩',
+'蚩' => '翾',
+'蚌' => '培',
+'蚣' => '羆',
+'蚜' => '捘',
+'衰' => '迉',
+'衷' => '笪',
+'袁' => '圇',
+'袂' => '鯁',
+'衽' => '鯃',
+'衹' => '硐',
+'記' => '暮',
+'訐' => '琣',
+'討' => '枒',
+'訌' => '琝',
+'訕' => '琩',
+'訊' => '捅',
+'託' => '迖',
+'訓' => '捄',
+'訖' => 'ウ',
+'訏' => '郚',
+'訑' => '',
+'豈' => 'ろ',
+'豺' => '荸',
+'豹\' => '悸',
+'財' => '笙',
+'貢' => '僚',
+'起' => 'れ',
+'躬' => '鼓',
+'軒' => '唄',
+'軔' => '澼',
+'軏' => '',
+'辱' => '',
+'送' => '冞',
+'逆' => '欄',
+'迷' => '譎',
+'退' => '豖',
+'迺' => '騰',
+'迴' => '隙',
+'逃' => '枅',
+'追' => '袚',
+'逅' => '樆',
+'迸' => '掬',
+'邕' => '諅',
+'郡' => '縣',
+'郝' => '甄',
+'郢' => '菻',
+'酒' => '嬴',
+'配' => '饜',
+'酌' => '袓',
+'釘' => '隊',
+'針' => '渀',
+'釗' => '醙',
+'釜' => '葵',
+'釙' => '醛',
+'閃' => '匢',
+'院' => '埏',
+'陣' => '淝',
+'陡' => '飧',
+'陛' => '旎',
+'陝' => '匟',
+'除' => '壺',
+'陘' => '粡',
+'陞' => '汔',
+'隻' => '硐',
+'飢' => '慰',
+'馬' => '鎮',
+'骨' => '嘎',
+'高' => '詢',
+'鬥' => '須',
+'鬲' => '堛',
+'鬼' => '寤',
+'乾' => 'ヲ',
+'偺' => '',
+'偽' => '帢',
+'停' => '礿',
+'假' => '樑',
+'偃' => '棼',
+'偌' => '椇',
+'做' => '酕',
+'偉' => '帡',
+'健' => '翩',
+'偶' => '髒',
+'偎' => '椊',
+'偕' => '棨',
+'偵' => '淈',
+'側' => '耜',
+'偷' => '芚',
+'偏' => 'ぇ',
+'倏' => '楰',
+'偯' => '',
+'偭' => '',
+'兜' => '項',
+'冕' => '轔',
+'凰' => '銖',
+'剪' => '熟',
+'副' => '萵',
+'勒' => '毚',
+'務' => '昢',
+'勘' => '膨',
+'動' => '雄',
+'匐' => '湉',
+'匏' => '痾',
+'匙' => '啻',
+'匿' => '曩',
+'區' => '⑹',
+'匾' => '寋',
+'參' => '統',
+'曼' => '霤',
+'商' => '妀',
+'啪' => '鱉',
+'啦' => '徽',
+'啄' => '袎',
+'啞' => '挳',
+'啡' => '溜',
+'啃' => '諱',
+'啊' => '陛',
+'唱' => '釭',
+'啖' => '遉',
+'問' => '恀',
+'啕' => '覛',
+'唯' => '峔',
+'啤' => 'ヾ',
+'唸' => '癩',
+'售' => '忮',
+'啜' => '鄖',
+'唬' => '誨',
+'啣' => '玴',
+'唳' => '鄏',
+'啁' => '覅',
+'啗' => '',
+'圈' => '',
+'國' => '弊',
+'圉' => '僪',
+'域' => '郖',
+'堅' => '澄',
+'堊' => '覘',
+'堆' => '剽',
+'埠' => '硎',
+'埤' => '軷',
+'基' => '價',
+'堂' => '斻',
+'堵' => '黑',
+'執' => '硒',
+'培' => '鑠',
+'夠' => '劂',
+'奢' => '异',
+'娶' => '',
+'婁' => '礎',
+'婉' => '剉',
+'婦' => '蜀',
+'婪' => '懋',
+'婀' => '皝',
+'娼' => '瞏',
+'婢' => '瞉',
+'婚' => '駁',
+'婆' => 'ち',
+'婊' => '皛',
+'孰' => '抔',
+'寇' => '諼',
+'寅' => '窌',
+'寄' => '敵',
+'寂' => '敷',
+'宿' => '咑',
+'密' => '躇',
+'尉' => '徆',
+'專' => '蚳',
+'將' => '蔚',
+'屠' => '芡',
+'屜' => '歾',
+'屝' => '',
+'崇' => '喟',
+'崆' => '慳',
+'崎' => 'ゅ',
+'崛' => '慒',
+'崖' => '捔',
+'崢' => '彃',
+'崑' => '壎',
+'崩' => '推',
+'崔' => '殖',
+'崙' => '贅',
+'崤\' => '慞',
+'崧' => '愬',
+'崗' => '詣',
+'巢' => '陴',
+'常' => '都',
+'帶' => '湍',
+'帳' => '梛',
+'帷' => '寣',
+'康' => '艙',
+'庸' => '蚢',
+'庶' => '旵',
+'庵' => '甂',
+'庾' => '甃',
+'張' => '桲',
+'強' => 'Ч',
+'彗' => '樄',
+'彬' => '梃',
+'彩' => '粗',
+'彫' => '蛐',
+'得' => '腕',
+'徙' => '摦',
+'從' => '植',
+'徘' => '龔',
+'御' => '郘',
+'徠' => '摵',
+'徜' => '撦',
+'恿' => '蚆',
+'患' => '遞',
+'悉' => '洃',
+'悠' => '蚙',
+'您' => '蠟',
+'惋' => '俬',
+'悴' => '蓂',
+'惦' => '蛟',
+'悽' => 'ぼ',
+'情' => '①',
+'悻' => '蒗',
+'悵' => '瞃',
+'惜' => '洇',
+'悼' => '翕',
+'惘' => '蒟',
+'惕' => '枔',
+'惆' => '蒺',
+'惟' => '峏',
+'悸' => '撢',
+'惚' => '蓎',
+'惇' => '嗟',
+'戚' => 'べ',
+'戛' => '磡',
+'扈' => '擯',
+'掠' => '謨',
+'控' => '諷',
+'捲' => '橙',
+'掖' => '珒',
+'探' => '抻',
+'接' => '諉',
+'捷' => '豎',
+'捧' => '癱',
+'掘' => '橢',
+'措' => '渠',
+'捱' => '睧',
+'掩' => '栚',
+'掉' => '裁',
+'掃' => '禸',
+'掛' => '境',
+'捫' => '痶',
+'推' => '芢',
+'掄' => '謬',
+'授' => '忨',
+'掙' => '淴',
+'採' => '粒',
+'掬' => '碇',
+'排' => '齬',
+'掏' => '昅',
+'掀' => '玅',
+'捻' => '瓔',
+'捩' => '碔',
+'捨' => '忔',
+'捺' => '睔',
+'敝' => '敔',
+'敖' => '偷',
+'救' => '寰',
+'教' => '諒',
+'敗' => '啖',
+'啟' => 'ゐ',
+'敏' => '鏗',
+'敘' => '唦',
+'敕' => '賰',
+'敔' => '',
+'斜' => '訇',
+'斛' => '蘡',
+'斬' => '梮',
+'族' => '逜',
+'旋' => '唅',
+'旌' => '儥',
+'旎' => '儢',
+'晝' => '絅',
+'晚' => '俀',
+'晤' => '昵',
+'晨' => '鹵',
+'晦' => '靼',
+'晞' => '',
+'曹' => '羚',
+'勗' => '袺',
+'望' => '咡',
+'梁' => '褽',
+'梯' => '枍',
+'梢' => '奿',
+'梓' => '儚',
+'梵' => '鼐',
+'桿' => '裝',
+'桶' => '肭',
+'梱' => '嬰',
+'梧' => '挀',
+'梗' => '馳',
+'械' => '迮',
+'梃' => '鳷',
+'棄' => 'ィ',
+'梭' => '坲',
+'梆' => '埠',
+'梅' => '繩',
+'梔' => '魃',
+'條' => '沭',
+'梨' => '燧',
+'梟' => '駓',
+'梡' => 'p',
+'梂' => 'W',
+'欲' => '郗',
+'殺' => '伀',
+'毫' => '瑭',
+'毬' => '⑩',
+'氫' => 'щ',
+'涎' => '珇',
+'涼' => '褸',
+'淳' => '晷',
+'淙' => '靿',
+'液' => '珘',
+'淡' => '筏',
+'淌' => '昃',
+'淤' => '袃',
+'添' => '氝',
+'淺' => 'Ё',
+'清' => 'ь',
+'淇' => '靽',
+'淋' => '邀',
+'涯' => '挭',
+'淑' => '抃',
+'涮' => '颭',
+'淞' => '靾',
+'淹' => '敊',
+'涸' => '碣',
+'混' => '髦',
+'淵' => '唻',
+'淅' => '靺',
+'淒' => 'ぼ',
+'渚' => '靘',
+'涵' => '滬',
+'淚\' => '濡',
+'淫' => '窋',
+'淘' => '杬',
+'淪' => '蹙',
+'深' => '旮',
+'淮' => '輕',
+'淨' => '噱',
+'淆' => '秎',
+'淄' => '谹',
+'涪' => '腺',
+'淬' => '氬',
+'涿' => '鞀',
+'淦' => '鞄',
+'烹' => '鱔',
+'焉' => '挸',
+'焊' => '爾',
+'烽' => '琿',
+'烯' => '洬',
+'爽' => '邠',
+'牽' => 'ラ',
+'犁' => '營',
+'猜' => '笨',
+'猛' => '襖',
+'猖' => '荼',
+'猓' => '滹',
+'猙' => '淭',
+'率' => '薹',
+'琅' => '斃',
+'琊' => '趜',
+'球' => '⑩',
+'理' => '燴',
+'現' => '珋',
+'琍' => '薛',
+'瓠' => '藄',
+'瓶' => 'せ',
+'瓷' => '棟',
+'甜' => '泫',
+'產' => '莉',
+'略' => '謹',
+'畦' => 'や',
+'畢' => '救',
+'異' => '祑',
+'疏' => '抌',
+'痔' => '筇',
+'痕' => '窩',
+'疵' => '棺',
+'痊' => '',
+'痍' => '謤',
+'皎' => '藂',
+'盔' => '錳',
+'盒' => '碟',
+'盛' => '呏',
+'眷' => '樺',
+'眾' => '笲',
+'眼' => '桉',
+'眶' => '醒',
+'眸' => '薠',
+'眺' => '沷',
+'硫' => '闈',
+'硃' => '紾',
+'硎' => '簆',
+'祥' => '矨',
+'票' => 'き',
+'祭' => '撬',
+'移' => '痄',
+'窒' => '笰',
+'窕' => '鬈',
+'笠' => '鯜',
+'笨' => '捫',
+'笛' => '萃',
+'第' => '菴',
+'符' => '睫',
+'笙' => '鯔',
+'笞' => '鯚',
+'笮' => '鯗',
+'粒' => '薜',
+'粗' => '棉',
+'粕' => 'づ',
+'絆' => '堅',
+'絃' => '玾',
+'統' => '苀',
+'紮' => '崨',
+'紹' => '庄',
+'紼' => '蝔',
+'絀' => '蝛',
+'細' => '牉',
+'紳' => '朹',
+'組' => '郪',
+'累' => '濛',
+'終' => '笝',
+'紲' => '蟡',
+'紱' => '蝳',
+'缽' => '異',
+'羞' => '冔',
+'羚' => '鍋',
+'翌' => '秬',
+'翎' => '酃',
+'習' => '炾',
+'耜' => '齕',
+'聊' => '謐',
+'聆' => '壚',
+'脯' => '葫',
+'脖' => '盛',
+'脣' => '晾',
+'脫' => '迕',
+'脩' => '党',
+'脰' => '',
+'脤' => '',
+'舂' => '籇',
+'舵' => '嗆',
+'舷' => '珫',
+'舶' => '盒',
+'船' => '摒',
+'莎' => '伔',
+'莞' => '搛',
+'莘' => '揧',
+'荸' => '搣',
+'莢' => '樊',
+'莖' => '墨',
+'莽' => '癟',
+'莫' => '蘆',
+'莒' => '塙',
+'莊' => '蚽',
+'莓' => '摁',
+'莉' => '獲',
+'莠' => '搰',
+'荷' => '盡',
+'荻' => '搋',
+'荼' => '搊',
+'莆' => 'な',
+'莧' => '剻',
+'處' => '揭',
+'彪' => '梵',
+'蛇' => '彴',
+'蛀' => '翇',
+'蚶' => '聸',
+'蛄' => '臗',
+'蚵' => '臕',
+'蛆' => '⑺',
+'蛋' => '粥',
+'蚱' => '藫',
+'蚯' => '藱',
+'蛉' => '藭',
+'術' => '扲',
+'袞' => '渿',
+'袈' => '蘌',
+'被' => '掩',
+'袒' => '抳',
+'袖' => '凈',
+'袍' => '蠱',
+'袋' => '渝',
+'覓' => '贊',
+'規' => '寞',
+'訪' => '溼',
+'訝' => '捑',
+'訣' => '機',
+'訥' => '瓻',
+'許\' => '勍',
+'設' => '扢',
+'訟' => '冾',
+'訛' => '塚',
+'訢' => '釔',
+'豉' => '鐒',
+'豚' => '錟',
+'販' => '毽',
+'責' => '孮',
+'貫' => '嫗',
+'貨' => '億',
+'貪' => '怜',
+'貧' => 'げ',
+'赧' => '鐇',
+'赦' => '忏',
+'趾' => '硅',
+'趺' => '劗',
+'軛' => '濎',
+'軟' => '',
+'這' => '涴',
+'逍' => '槱',
+'通' => '籵',
+'逗' => '飯',
+'連' => '蟀',
+'速' => '厒',
+'逝' => '岒',
+'逐' => '紨',
+'逕' => '暯',
+'逞' => '剩',
+'造' => '婖',
+'透' => '芵',
+'逢' => '瑙',
+'逖' => '槤',
+'逛' => '嫣',
+'途' => '芴',
+'部' => '窒',
+'郭' => '廖',
+'都' => '飲',
+'酗' => '厞',
+'野' => '珧',
+'釵' => '鎃',
+'釦' => '諶',
+'釣' => '袱',
+'釧' => '醝',
+'釭' => '榙',
+'釩' => '楣',
+'閉' => '敕',
+'陪' => '顯',
+'陵' => '鍬',
+'陳' => '麻',
+'陸' => '翻',
+'陰' => '秝',
+'陴' => '絧',
+'陶' => '枎',
+'陷' => '疪',
+'陬' => '絓',
+'雀' => '',
+'雪' => '悕',
+'雩' => '鬺',
+'章' => '梒',
+'竟' => '器',
+'頂' => '階',
+'頃' => '②',
+'魚' => '赶',
+'鳥' => '纏',
+'鹵' => '簣',
+'鹿' => '繒',
+'麥' => '闔',
+'麻' => '鎊',
+'傢' => '模',
+'傍' => '奢',
+'傅' => '葭',
+'備' => '掘',
+'傑' => '豌',
+'傀' => '錚',
+'傖' => '徫',
+'傘' => '氶',
+'傚' => '虴',
+'最' => '郔',
+'凱' => '翮',
+'割' => '賃',
+'剴' => '嵁',
+'創' => '斐',
+'剩' => '呁',
+'勞' => '櫛',
+'勝' => '吨',
+'勛' => '悗',
+'博' => '痔',
+'厥' => '婽',
+'啻' => '鉥',
+'喀' => '縝',
+'喧' => '唈',
+'啼' => '杽',
+'喊' => '滌',
+'喝' => '瘓',
+'喘' => '揚',
+'喂' => '庣',
+'喜' => '炰',
+'喪' => '犮',
+'喔' => '鉊',
+'喇' => '嶽',
+'喋' => '鄔',
+'喃' => '鄎',
+'喳' => '崍',
+'單' => '等',
+'喟' => '鈰',
+'唾' => '阽',
+'喲' => '荋',
+'喚' => '遢',
+'喻' => '郟',
+'喬' => 'Я',
+'喱' => '酮',
+'啾' => '鈺',
+'喉' => '綰',
+'喫' => '勛',
+'喙' => '鉆',
+'圍' => '峓',
+'堯' => '牶',
+'堪' => '膩',
+'場' => '部',
+'堤' => '腓',
+'堰' => '桋',
+'報' => '惆',
+'堡' => '惜',
+'堝' => '跏',
+'堠' => '靬',
+'壹' => '瓞',
+'壺' => '綿',
+'奠' => '蛙',
+'婷' => '磌',
+'媚' => '藥',
+'婿' => '哤',
+'媒' => '羸',
+'媛' => '磏',
+'媧' => '瘣',
+'孳' => '箹',
+'孱' => '槴',
+'寒' => '漁',
+'富' => '蜓',
+'寓' => '唲',
+'寐' => '藕',
+'尊' => '郬',
+'尋' => '扆',
+'就' => '憩',
+'嵌' => 'И',
+'嵐' => '嵹',
+'崴' => '慬',
+'嵇' => '燿',
+'巽' => '毰',
+'幅' => '盟',
+'帽' => '簽',
+'幀' => '痋',
+'幃' => '僤',
+'幾' => '撓',
+'廊' => '檀',
+'廁' => '翎',
+'廂' => '眃',
+'廄\' => '學',
+'弼' => '氀',
+'彭' => '鱖',
+'復' => '葩',
+'循' => '悜',
+'徨' => '摎',
+'惑' => '鼻',
+'惡' => '填',
+'悲' => '扈',
+'悶' => '蟻',
+'惠' => '需',
+'愜' => '舕',
+'愣' => '蒹',
+'惺' => '倇',
+'愕' => '蒫',
+'惰' => '嗉',
+'惻' => '禕',
+'惴' => '蒴',
+'慨' => '耨',
+'惱' => '齣',
+'愎' => '蓍',
+'惶' => '鉻',
+'愉' => '赸',
+'愀' => '蓁',
+'愒' => '',
+'戟' => '磢',
+'扉' => '擩',
+'掣' => '雩',
+'掌' => '梪',
+'描' => '鏡',
+'揀' => '滕',
+'揩' => '翰',
+'揉' => '',
+'揆' => '碖',
+'揍' => '軡',
+'插' => '脣',
+'揣' => '揮',
+'提' => '枑',
+'握' => '挍',
+'揖' => '瓴',
+'揭' => '課',
+'揮' => '閨',
+'捶' => '晰',
+'援' => '堔',
+'揪' => '噢',
+'換' => '遙',
+'摒' => '碀',
+'揚' => '栨',
+'揹' => '掖',
+'敞' => '釣',
+'敦' => '嗟',
+'敢' => '詫',
+'散' => '汃',
+'斑' => '唯',
+'斐' => '麭',
+'斯' => '佴',
+'普' => 'ぱ',
+'晰' => '朐',
+'晴' => 'ю',
+'晶' => '儒',
+'景' => '劓',
+'暑' => '扻',
+'智' => '秷',
+'晾' => '謊',
+'晷' => '縟',
+'曾' => '崠',
+'替' => '杸',
+'期' => 'ぶ',
+'朝' => '陳',
+'棺' => '塽',
+'棕' => '趹',
+'棠' => '昉',
+'棘' => '憔',
+'棗' => '娹',
+'椅' => '眛',
+'棟' => '集',
+'棵' => '螢',
+'森' => '伬',
+'棧' => '梬',
+'棹' => '噮',
+'棒' => '堵',
+'棲' => 'へ',
+'棣' => '擐',
+'棋' => 'め',
+'棍' => '幔',
+'植' => '眵',
+'椒' => '蔆',
+'椎' => '袢',
+'棉' => '蹬',
+'棚' => '麟',
+'楮' => '匴',
+'棻' => '',
+'款' => '遴',
+'欺' => 'ぷ',
+'欽' => 'м',
+'殘' => '紹',
+'殖' => '硈',
+'殼' => '褲',
+'毯' => '抮',
+'氮' => '答',
+'氯' => '薰',
+'氬' => '貒',
+'港' => '誠',
+'游' => '蚔',
+'湔' => '馻',
+'渡' => '傾',
+'渲' => '馺',
+'湧' => '蚇',
+'湊' => '椎',
+'渠' => '',
+'渥' => '駂',
+'渣' => '崦',
+'減' => '熬',
+'湛' => '梲',
+'湘' => '盻',
+'渤' => '眾',
+'湖' => '綬',
+'湮' => '餂',
+'渭' => '弮',
+'渦' => '恦',
+'湯' => '抸',
+'渴' => '褡',
+'湍' => '苃',
+'渺' => '鏈',
+'測' => '聆',
+'湃' => '囌',
+'渝' => '趵',
+'渾' => '骰',
+'滋' => '訞',
+'溉' => '裙',
+'渙' => '鄘',
+'湎' => '餀',
+'湣' => '裶',
+'湄' => '馽',
+'湲' => '',
+'湩' => '',
+'湟' => '馜',
+'焙' => '捱',
+'焚' => '煞',
+'焦' => '蝴',
+'焰' => '栭',
+'無' => '拸',
+'然' => '',
+'煮' => '羜',
+'焜' => 'j',
+'牌' => '齪',
+'犄' => '艗',
+'犀' => '洉',
+'猶' => '蚝',
+'猥' => '漇',
+'猴' => '綽',
+'猩' => '倅',
+'琺' => '楨',
+'琪' => '踮',
+'琳' => '轅',
+'琢' => '袬',
+'琥' => '踖',
+'琵\' => '讓',
+'琶' => '鷗',
+'琴' => 'р',
+'琯' => '奪',
+'琛' => '銵',
+'琦' => '踛',
+'琨' => '踑',
+'甥' => '汏',
+'甦' => '劼',
+'畫' => '賒',
+'番' => '楓',
+'痢' => '薄',
+'痛' => '芫',
+'痣' => '謻',
+'痙' => '噸',
+'痘' => '飩',
+'痞' => 'あ',
+'痠' => '呫',
+'登' => '腎',
+'發' => '楷',
+'皖' => '侹',
+'皓' => '薳',
+'皴' => '鼫',
+'盜' => '聒',
+'睏' => '嬪',
+'短' => '傻',
+'硝' => '祌',
+'硬' => '茞',
+'硯' => '栱',
+'稍' => '尕',
+'稈' => '解',
+'程' => '最',
+'稅' => '阭',
+'稀' => '洁',
+'窘' => '噬',
+'窗' => '敦',
+'窖' => '諸',
+'童' => '肵',
+'竣' => '縈',
+'等' => '脹',
+'策' => '習',
+'筆' => '捩',
+'筐' => '遲',
+'筒' => '芠',
+'答' => '湘',
+'筍' => '囹',
+'筋' => '踐',
+'筏' => '楔',
+'筑' => '耟',
+'粟' => '厔',
+'粥' => '粖',
+'絞' => '褊',
+'結' => '賦',
+'絨' => '',
+'絕' => '橈',
+'紫' => '豜',
+'絮' => '哳',
+'絲' => '佪',
+'絡' => '釐',
+'給' => '跤',
+'絢' => '悀',
+'絰' => '',
+'絳' => '蝑',
+'善' => '囡',
+'翔' => '矧',
+'翕' => '酁',
+'耋' => '嚧',
+'聒' => '壛',
+'肅' => '咈',
+'腕' => '勂',
+'腔' => 'У',
+'腋' => '珚',
+'腑' => '葉',
+'腎' => '朳',
+'脹' => '梠',
+'腆' => '沶',
+'脾' => 'ゝ',
+'腌' => '錣',
+'腓' => '錒',
+'腴' => '錁',
+'舒' => '戺',
+'舜' => '侅',
+'菩' => 'ぬ',
+'萃' => '楢',
+'菸' => '楱',
+'萍' => 'じ',
+'菠' => '略',
+'菅' => '楪',
+'萋' => '暐',
+'菁' => '敯',
+'華' => '貌',
+'菱' => '鎂',
+'菴' => 'C',
+'著' => '翍',
+'萊' => '應',
+'菰' => '楗',
+'萌' => '蠍',
+'菌' => '歷',
+'菽' => '暊',
+'菲' => '滑',
+'菊' => '擅',
+'萸' => '晸',
+'萎' => '峸',
+'萄' => '曶',
+'菜' => '粕',
+'萇' => '剼',
+'菔' => '楟',
+'菟' => '椸',
+'虛' => '剞',
+'蛟' => '藜',
+'蛙' => '陃',
+'蛭' => '藬',
+'蛔' => '閤',
+'蛛' => '絇',
+'蛤' => '跟',
+'蛐' => '藸',
+'蛞' => '藟',
+'街' => '誰',
+'裁' => '笛',
+'裂' => '蹊',
+'袱' => '舅',
+'覃' => '嬾',
+'視' => '弝',
+'註' => '蛁',
+'詠' => '蚑',
+'評' => 'ぜ',
+'詞' => '棵',
+'証' => '痐',
+'詁' => '甯',
+'詔' => '痧',
+'詛' => '逡',
+'詐' => '晥',
+'詆' => '畬',
+'訴' => '咂',
+'診' => '淖',
+'訶' => '畯',
+'詖' => '',
+'象' => '砓',
+'貂' => '蘣',
+'貯' => '翏',
+'貼' => '泂',
+'貳' => '楚',
+'貽' => '縥',
+'賁' => '縖',
+'費' => '煤',
+'賀' => '種',
+'貴' => '幛',
+'買' => '鎗',
+'貶' => '晨',
+'貿' => '籀',
+'貸' => '湃',
+'越' => '埣',
+'超' => '閉',
+'趁' => '傢',
+'跎' => '巋',
+'距' => '擒',
+'跋' => '區',
+'跚\' => '孈',
+'跑' => '變',
+'跌' => '視',
+'跛' => '廱',
+'跆' => '懽',
+'軻' => '潞',
+'軸' => '粣',
+'軼' => '澞',
+'辜' => '匱',
+'逮' => '滋',
+'逵' => '槿',
+'週' => '笚',
+'逸' => '砯',
+'進' => '輛',
+'逶' => '槬',
+'鄂' => '塢',
+'郵' => '蚘',
+'鄉' => '盺',
+'郾' => '蛘',
+'酣' => '漕',
+'酥' => '匊',
+'量' => '講',
+'鈔' => '陵',
+'鈕' => '聽',
+'鈣' => '裟',
+'鈉' => '飄',
+'鈞' => '氅',
+'鈍' => '嗦',
+'鈐' => '鍘',
+'鈇' => '榥',
+'鈑' => '鍼',
+'閔' => '蓖',
+'閏' => '',
+'開' => '羲',
+'閑' => '玿',
+'間' => '潔',
+'閒' => '玿',
+'閎' => '蒨',
+'隊' => '勦',
+'階' => '論',
+'隋' => '呬',
+'陽' => '栠',
+'隅' => '趶',
+'隆' => '癒',
+'隍' => '絏',
+'陲' => '絖',
+'隄' => '腓',
+'雁' => '栜',
+'雅' => '捇',
+'雄' => '倯',
+'集' => '摩',
+'雇' => '嗶',
+'雯' => '鰫',
+'雲' => '堁',
+'韌' => '',
+'項' => '砐',
+'順' => '佼',
+'須' => '剕',
+'飧' => '漈',
+'飪' => '熂',
+'飯' => '溯',
+'飩' => '熀',
+'飲' => '窊',
+'飭' => '煻',
+'馮' => '瑛',
+'馭' => '啈',
+'黃' => '酴',
+'黍' => '抈',
+'黑' => '窪',
+'亂' => '觴',
+'傭' => '荈',
+'債' => '晢',
+'傲' => '偭',
+'傳' => '換',
+'僅' => '躺',
+'傾' => 'ъ',
+'催' => '殼',
+'傷' => '夼',
+'傻' => '伂',
+'傯' => '椗',
+'僇' => 'J',
+'剿' => '誼',
+'剷' => '莓',
+'剽' => '嵕',
+'募' => '躁',
+'勦' => '誼',
+'勤' => 'с',
+'勢' => '岊',
+'勣' => '憎',
+'匯' => '颯',
+'嗟' => '鉞',
+'嗨' => '鉖',
+'嗓' => '氻',
+'嗦' => '鉰',
+'嗎' => '鎘',
+'嗜' => '岓',
+'嗇' => '媊',
+'嗑' => '鉧',
+'嗣' => '佸',
+'嗤' => '閟',
+'嗯' => '鉣',
+'嗚' => '挎',
+'嗡' => '恂',
+'嗅' => '凊',
+'嗆' => 'М',
+'嗥' => '鉐',
+'嗉' => '鉏',
+'園' => '埶',
+'圓' => '埴',
+'塞' => '',
+'塑' => '呿',
+'塘' => '攽',
+'塗' => '芨',
+'塚' => '琭',
+'塔' => '坢',
+'填' => '沓',
+'塌' => '坵',
+'塭' => '恔',
+'塊' => '輸',
+'塢' => '昶',
+'塒' => '跜',
+'塋' => '塨',
+'奧' => '兜',
+'嫁' => '毆',
+'嫉' => '撐',
+'嫌' => '珃',
+'媾' => '磎',
+'媽' => '鎔',
+'媼' => '碻',
+'媳' => '炱',
+'嫂' => '肊',
+'媲' => '磈',
+'嵩' => '慡',
+'嵯' => '慺',
+'幌' => '銨',
+'幹' => '補',
+'廉' => '螳',
+'廈' => '狪',
+'弒' => '葑',
+'彙' => '颯',
+'徬' => '籥',
+'微' => '峚',
+'愚' => '豇',
+'意' => '砩',
+'慈' => '椅',
+'感' => '覜',
+'想' => '砑',
+'愛' => '乾',
+'惹' => '',
+'愁' => '啾',
+'愈' => '郛',
+'慎' => '氘',
+'慌' => '酷',
+'慄' => '璦',
+'慍' => '蒬',
+'愾' => '睾',
+'愴' => '碲',
+'愧\' => '壕',
+'愍' => '磲',
+'愆' => '磼',
+'愷' => '禔',
+'戡' => '磟',
+'戢' => '磭',
+'搓' => '湊',
+'搾' => '掍',
+'搞' => '詻',
+'搪' => '斨',
+'搭' => '減',
+'搽' => '舵',
+'搬' => '唸',
+'搏' => '疵',
+'搜' => '刲',
+'搔' => '犰',
+'損' => '囷',
+'搶' => 'Ш',
+'搖' => '牷',
+'搗' => '絲',
+'搆' => '凳',
+'敬' => '噹',
+'斟' => '涬',
+'新' => '陔',
+'暗' => '做',
+'暉' => '縡',
+'暇' => '狊',
+'暈' => '婠',
+'暖' => '轡',
+'暄' => '縠',
+'暘' => 'D',
+'暍' => '',
+'會' => '頗',
+'榔' => '曙',
+'業' => '珛',
+'楚' => '奠',
+'楷' => '翱',
+'楠' => '撉',
+'楔' => '虼',
+'極' => '憤',
+'椰' => '珙',
+'概' => '衙',
+'楊' => '栦',
+'楨' => '鳺',
+'楫' => '擙',
+'楞' => '濕',
+'楓' => '瑯',
+'楹' => '暻',
+'榆' => '衼',
+'楝' => '擛',
+'楣' => '暽',
+'楛' => '',
+'歇' => '衁',
+'歲' => '呡',
+'毀' => '障',
+'殿' => '蛔',
+'毓' => '媢',
+'毽' => '謔',
+'溢' => '祛',
+'溯' => '咁',
+'滓' => '貥',
+'溶' => '',
+'滂' => '儰',
+'源' => '埭',
+'溝' => '僱',
+'滇' => '菲',
+'滅' => '鏢',
+'溥' => '魠',
+'溘' => '髣',
+'溼' => '坁',
+'溺' => '櫺',
+'溫' => '恲',
+'滑' => '賑',
+'準' => '袧',
+'溜' => '闊',
+'滄' => '終',
+'滔' => '昑',
+'溪' => '洈',
+'溧' => '魡',
+'溴' => '麧',
+'煎' => '澆',
+'煙' => '捈',
+'煩' => '歲',
+'煤' => '繳',
+'煉' => '褻',
+'照' => '桽',
+'煜' => '嬥',
+'煬' => '儩',
+'煦' => '懠',
+'煌' => '銓',
+'煥' => '鄙',
+'煞' => '伢',
+'煆' => '牬',
+'煨' => '嬲',
+'煖' => '轡',
+'爺' => '玼',
+'牒' => '赮',
+'猷' => '歖',
+'獅' => '囧',
+'猿' => '堀',
+'猾' => '賓',
+'瑯' => '斃',
+'瑚' => '綢',
+'瑕' => '閬',
+'瑟' => '阞',
+'瑞' => '',
+'瑁' => '鋆',
+'琿' => '踥',
+'瑙' => '閫',
+'瑛' => '踕',
+'瑜' => '銴',
+'當' => '絞',
+'畸' => '儈',
+'瘀' => '贀',
+'痰' => '拑',
+'瘁' => '氮',
+'痲' => '鎊',
+'痱' => '貗',
+'痺' => '敘',
+'痿' => '贄',
+'痴' => '博',
+'痳' => '鎊',
+'盞' => '桮',
+'盟' => '襠',
+'睛' => '齒',
+'睫' => '豬',
+'睦' => '釋',
+'睞' => '薋',
+'督' => '飭',
+'睹' => '亂',
+'睪' => '媞',
+'睬' => '笞',
+'睜' => '淊',
+'睥' => '謖',
+'睨' => '薞',
+'睢' => '謘',
+'矮' => '鬥',
+'碎' => '呯',
+'碰' => '癲',
+'碗' => '俖',
+'碘' => '菊',
+'碌' => '繕',
+'碉' => '蛛',
+'硼' => '黴',
+'碑' => '戛',
+'碓' => '顈',
+'硿' => '',
+'祺' => '檉',
+'祿' => '罈',
+'禁' => '輦',
+'萬' => '勀',
+'禽' => 'ф',
+'稜' => '濩',
+'稚' => '窔',
+'稠' => '喱',
+'稔' => '獶',
+'稟' => '渳',
+'稞\' => '燽',
+'窟' => '貓',
+'窠' => '鬅',
+'筷' => '輳',
+'節' => '誹',
+'筠' => '鶀',
+'筮' => '鵸',
+'筧' => '鯫',
+'粱' => '覬',
+'粳' => '冀',
+'粵' => '埡',
+'經' => '冪',
+'絹' => '橫',
+'綑' => '嬪',
+'綁' => '堂',
+'綏' => '呦',
+'絛' => '昐',
+'置' => '离',
+'罩' => '欶',
+'罪' => '郫',
+'署' => '扰',
+'義' => '砱',
+'羨' => '畈',
+'群' => '',
+'聖' => '吤',
+'聘' => 'ご',
+'肆' => '佹',
+'肄' => '砨',
+'腱' => '錎',
+'腰' => '殈',
+'腸' => '釵',
+'腥' => '倞',
+'腮' => '',
+'腳' => '褐',
+'腫' => '笫',
+'腹' => '號',
+'腺' => '甮',
+'腦' => '齟',
+'舅' => '憲',
+'艇' => '竻',
+'蒂' => '菱',
+'葷' => '餌',
+'落' => '邈',
+'萱' => '楘',
+'葵' => '鋼',
+'葦' => '峟',
+'葫' => '綵',
+'葉' => '珔',
+'葬' => '婛',
+'葛' => '賅',
+'萼' => '椴',
+'萵' => '搦',
+'葡' => 'に',
+'董' => '雁',
+'葩' => '楀',
+'葭' => '楁',
+'葆' => '楩',
+'虞' => '訒',
+'虜' => '簡',
+'號' => '瘍',
+'蛹' => '蚍',
+'蜓' => '藘',
+'蜈' => '藢',
+'蜇' => '藯',
+'蜀' => '抁',
+'蛾' => '圓',
+'蛻' => '虭',
+'蜂' => '瑚',
+'蜃' => '藦',
+'蜆' => '罋',
+'蜊' => '蠀',
+'衙' => '捙',
+'裟' => '蠙',
+'裔' => '砡',
+'裙' => '',
+'補' => '硃',
+'裘' => '藽',
+'裝' => '蚾',
+'裡' => '爵',
+'裊' => '蘅',
+'裕' => '啥',
+'裒' => '渜',
+'覜' => '沷',
+'解' => '賤',
+'詫' => '莎',
+'該' => '蜆',
+'詳' => '砆',
+'試' => '彸',
+'詩' => '坅',
+'詰' => '痤',
+'誇' => '蹂',
+'詼' => '痗',
+'詣' => '祏',
+'誠' => '剴',
+'話' => '趕',
+'誅' => '紻',
+'詭' => '察',
+'詢' => '戙',
+'詮' => '盚',
+'詬' => '皒',
+'詹' => '梐',
+'詻' => '',
+'訾' => '鬗',
+'詨' => '',
+'豢' => '遛',
+'貊' => '蘜',
+'貉' => '碧',
+'賊' => '崞',
+'資' => '訧',
+'賈' => '樂',
+'賄' => '鞅',
+'貲' => '罃',
+'賃' => '醣',
+'賂' => '繡',
+'賅' => '罻',
+'跡' => '慫',
+'跟' => '躲',
+'跨' => '輻',
+'路' => '繚',
+'跳' => '泐',
+'跺' => '嗅',
+'跪' => '嶇',
+'跤' => '灃',
+'跦' => '胾',
+'躲' => '嗚',
+'較' => '誕',
+'載' => '婥',
+'軾' => '澮',
+'輊' => '澺',
+'辟' => '斬',
+'農' => '觼',
+'運' => '堍',
+'遊' => '蚔',
+'道' => '耋',
+'遂' => '咘',
+'達' => '湛',
+'逼' => '排',
+'違' => '峊',
+'遐' => '槲',
+'遇' => '郣',
+'遏' => '塊',
+'過' => '徹',
+'遍' => '梢',
+'遑' => '槾',
+'逾' => '貣',
+'遁' => '嗜',
+'鄒' => '軜',
+'鄗' => '輈',
+'酬' => '喚',
+'酪' => '檠',
+'酩' => '鶪',
+'釉' => '衲',
+'鈷' => '鎏',
+'鉗' => 'ヵ',
+'鈸' => '鍗',
+'鈽' => '鍹',
+'鉀' => '槭',
+'鈾\' => '蚎',
+'鉛' => 'レ',
+'鉋' => '蘸',
+'鉤' => '像',
+'鉑' => '痊',
+'鈴' => '鍊',
+'鉉' => '鍡',
+'鉍' => '鍣',
+'鉅' => '鍇',
+'鈹' => '鎀',
+'鈿' => '鍱',
+'鉚' => '穩',
+'閘' => '掅',
+'隘' => '偺',
+'隔' => '路',
+'隕' => '埩',
+'雍' => '蚨',
+'雋' => '鶬',
+'雉' => '濻',
+'雊' => '螅',
+'雷' => '濘',
+'電' => '萇',
+'雹' => '悻',
+'零' => '錨',
+'靖' => '噪',
+'靴' => '悒',
+'靶' => '匾',
+'預' => '啎',
+'頑' => '侻',
+'頓' => '嗨',
+'頊' => '趠',
+'頒' => '唬',
+'頌' => '佮',
+'飼' => '侞',
+'飴' => '熆',
+'飽' => '悼',
+'飾' => '庉',
+'馳' => '喊',
+'馱' => '邴',
+'馴' => '拲',
+'髡' => '孍',
+'鳩' => '藋',
+'麂' => '灛',
+'鼎' => '隋',
+'鼓' => '嘆',
+'鼠' => '扷',
+'僧' => '仵',
+'僮' => '椕',
+'僥' => '衝',
+'僖' => '棴',
+'僭' => '椆',
+'僚' => '豁',
+'僕' => 'ど',
+'像' => '砉',
+'僑' => 'а',
+'僱' => '嗶',
+'僎' => 'Q',
+'僩' => 'g',
+'兢' => '黎',
+'凳' => '脾',
+'劃' => '赫',
+'劂' => '崳',
+'匱' => '寍',
+'厭' => '栖',
+'嗾' => '雎',
+'嘀' => '雺',
+'嘛' => '鎰',
+'嘗' => '郭',
+'嗽' => '刱',
+'嘔' => '驍',
+'嘆' => '抩',
+'嘉' => '樁',
+'嘍' => '銃',
+'嘎' => '蜃',
+'嗷' => '鉬',
+'嘖' => '裞',
+'嘟' => '鉠',
+'嘈' => '閛',
+'嘐' => 'E',
+'嗶' => '萳',
+'團' => '芶',
+'圖' => '芞',
+'塵' => '鳥',
+'塾' => '觝',
+'境' => '噫',
+'墓' => '贏',
+'墊' => '菜',
+'塹' => 'З',
+'墅' => '旲',
+'塽' => 'u',
+'壽' => '忭',
+'夥' => '漞',
+'夢' => '襞',
+'夤' => '漡',
+'奪' => '嗤',
+'奩' => '榃',
+'嫡' => '菅',
+'嫦' => '禢',
+'嫩' => '囂',
+'嫗' => '濆',
+'嫖' => '禜',
+'嫘' => '禛',
+'嫣' => '禡',
+'孵' => '痿',
+'寞' => '蠕',
+'寧' => '譴',
+'寡' => '塾',
+'寥' => '賺',
+'實' => '妗',
+'寨' => '朘',
+'寢' => 'х',
+'寤' => '撱',
+'察' => '舷',
+'對' => '勤',
+'屢' => '藍',
+'嶄' => '楖',
+'嶇' => '嶉',
+'幛' => '嶀',
+'幣' => '啟',
+'幕' => '躉',
+'幗' => '僠',
+'幔' => '嶂',
+'廓' => '尷',
+'廖' => '蹉',
+'弊' => '斜',
+'彆' => '梗',
+'彰' => '桼',
+'徹' => '章',
+'慇' => '秜',
+'愿' => '堋',
+'態' => '怓',
+'慷' => '蕊',
+'慢' => '鞣',
+'慣' => '嫦',
+'慟' => '禋',
+'慚' => '紼',
+'慘' => '絀',
+'慵' => '蒱',
+'截' => '諍',
+'撇' => 'ぎ',
+'摘' => '晡',
+'摔' => '豸',
+'撤' => '雪',
+'摸' => '類',
+'摟' => '禮',
+'摺' => '腄',
+'摑' => '睭',
+'摧' => '殘',
+'搴' => '摨',
+'摭' => '稢',
+'摻' => '莖',
+'敲' => 'Ы',
+'斡' => '扃',
+'旗' => 'よ',
+'旖' => '儠',
+'暢' => '釧',
+'暨' => '轚',
+'暝\' => '縜',
+'榜' => '埤',
+'榨' => '掍',
+'榕' => '橝',
+'槁' => '樲',
+'榮' => '',
+'槓' => '話',
+'構' => '凳',
+'榛' => '暺',
+'榷' => '',
+'榻' => '朣',
+'榫' => '樴',
+'榴' => '闌',
+'槐' => '跼',
+'槍' => 'Л',
+'榭' => '橦',
+'槌' => '曈',
+'榦' => '補',
+'槃' => '攫',
+'榣' => 'l',
+'歉' => 'К',
+'歌' => '貉',
+'氳' => '賮',
+'漳' => '桫',
+'演' => '栳',
+'滾' => '幗',
+'漓' => '燬',
+'滴' => '舒',
+'漩' => '噈',
+'漾' => '歭',
+'漠' => '蠔',
+'漬' => '赹',
+'漏' => '穢',
+'漂' => 'か',
+'漢' => '犖',
+'滿' => '雛',
+'滯' => '笴',
+'漆' => 'ぽ',
+'漱' => '杇',
+'漸' => '膝',
+'漲' => '梀',
+'漣' => '蟆',
+'漕' => '儋',
+'漫' => '鞦',
+'漯' => '僽',
+'澈' => '竟',
+'漪' => '勱',
+'滬' => '誚',
+'漁' => '趷',
+'滲' => '汆',
+'滌' => '萍',
+'滷' => '簣',
+'熔' => '',
+'熙' => '昝',
+'煽' => '刐',
+'熊' => '倱',
+'熄' => '洠',
+'熒' => '茷',
+'爾' => '嫌',
+'犒' => '蕍',
+'犖' => '媻',
+'獄' => '郜',
+'獐' => '滽',
+'瑤' => '毤',
+'瑣' => '坱',
+'瑪' => '鎖',
+'瑰' => '孵',
+'瑭' => '閰',
+'甄' => '淢',
+'疑' => '疶',
+'瘧' => '鑄',
+'瘍' => '桍',
+'瘋' => '瑁',
+'瘉' => '郛',
+'瘓' => '遝',
+'盡' => '鴃',
+'監' => '潼',
+'瞄' => '鏑',
+'睽' => '謋',
+'睿' => '謑',
+'睡' => '阯',
+'磁' => '棠',
+'碟' => '詠',
+'碧' => '捺',
+'碳' => '抯',
+'碩' => '侀',
+'碣' => '繇',
+'禎' => '檁',
+'福' => '腦',
+'禍' => '儀',
+'種' => '笱',
+'稱' => '備',
+'窪' => '俍',
+'窩' => '恮',
+'竭' => '賠',
+'端' => '傷',
+'管' => '奪',
+'箕' => '凜',
+'箋' => '潦',
+'筵' => '鶄',
+'算' => '呾',
+'箝' => '鶅',
+'箔' => '痍',
+'箏' => '鵱',
+'箸' => '鵰',
+'箇' => '跺',
+'箄' => '靴',
+'粹' => '氯',
+'粽' => '譭',
+'精' => '儕',
+'綻' => '梏',
+'綰' => '蝥',
+'綜' => '軘',
+'綽' => '朝',
+'綾' => '蝐',
+'綠' => '蟯',
+'緊' => '踡',
+'綴' => '袟',
+'網' => '厙',
+'綱' => '詼',
+'綺' => '蝎',
+'綢' => '喙',
+'綿' => '蹴',
+'綵' => '粗',
+'綸' => '蹣',
+'維' => '峎',
+'緒' => '唚',
+'緇' => '蝏',
+'綬' => '蝺',
+'罰' => '楠',
+'翠' => '港',
+'翡' => '醵',
+'翟' => '菠',
+'聞' => '恓',
+'聚' => '擄',
+'肇' => '欷',
+'腐' => '葛',
+'膀' => '基',
+'膏' => '詮',
+'膈' => '錴',
+'膊' => '眷',
+'腿' => '虯',
+'膂' => '錂',
+'臧' => '穈',
+'臺' => '怢',
+'與' => '迵',
+'舔' => '泙',
+'舞' => '敃',
+'艋' => '藾',
+'蓉' => '',
+'蒿' => '楑',
+'蓆' => '炟',
+'蓄' => '匎',
+'蒙' => '蟹',
+'蒞' => '搯',
+'蒲' => 'ね',
+'蒜' => '呺',
+'蓋\' => '裔',
+'蒸' => '淛',
+'蓀' => '搘',
+'蓓' => '楜',
+'蒐' => '彳',
+'蒼' => '紳',
+'蓑' => '坯',
+'蓊' => '楏',
+'蜿' => '襚',
+'蜜' => '蹲',
+'蜻' => '蟷',
+'蜢' => '襗',
+'蜥' => '蠌',
+'蜴' => '蟿',
+'蜘' => '眯',
+'蝕' => '妠',
+'蜷' => '襢',
+'蜩' => '蠂',
+'裳' => '奷',
+'褂' => '墓',
+'裴' => '鑤',
+'裹' => '彰',
+'裸' => '邃',
+'製' => '秶',
+'裨' => '鵋',
+'褚' => '鵊',
+'裯' => '峮',
+'誦' => '刵',
+'誌' => '祩',
+'語' => '逄',
+'誣' => '挏',
+'認' => '',
+'誡' => '趟',
+'誓' => '岉',
+'誤' => '昫',
+'說' => '佽',
+'誥' => '睅',
+'誨' => '餃',
+'誘' => '袀',
+'誑' => '睊',
+'誚' => '睍',
+'誧' => '惃',
+'豪' => '瑰',
+'貍' => '燥',
+'貌' => '簷',
+'賓' => '梅',
+'賑' => '罺',
+'賒' => '弚',
+'赫' => '禎',
+'趙' => '梊',
+'趕' => '裒',
+'跼' => '擁',
+'輔' => '落',
+'輒' => '濏',
+'輕' => 'ш',
+'輓' => '侺',
+'辣' => '彌',
+'遠' => '堈',
+'遘' => '樔',
+'遜' => '挶',
+'遣' => 'Е',
+'遙' => '猀',
+'遞' => '菰',
+'遢' => '槷',
+'遝' => '穖',
+'遛' => '槧',
+'鄙' => '捻',
+'鄘' => '颩',
+'鄞' => '蛓',
+'酵' => '談',
+'酸' => '呫',
+'酷' => '蹄',
+'酴' => '鶨',
+'鉸' => '蝓',
+'銀' => '窅',
+'銅' => '肣',
+'銘' => '霧',
+'銖' => '霘',
+'鉻' => '跳',
+'銓' => '鞡',
+'銜' => '玴',
+'銨' => '鴽',
+'鉼' => '綦',
+'銑' => '炡',
+'閡' => '碳',
+'閨' => '寨',
+'閩' => '關',
+'閣' => '跨',
+'閥' => '概',
+'閤' => '跨',
+'隙' => '炩',
+'障' => '梤',
+'際' => '暱',
+'雌' => '棘',
+'雒' => '鶱',
+'需' => '剒',
+'靼' => '鱁',
+'鞅' => '鰼',
+'韶' => '屻',
+'頗' => 'だ',
+'領' => '鍰',
+'颯' => '鴘',
+'颱' => '怢',
+'餃' => '褓',
+'餅' => '欲',
+'餌' => '媾',
+'餉' => '熁',
+'駁' => '眶',
+'骯' => '偎',
+'骰' => '鷋',
+'髦' => '巘',
+'魁' => '錄',
+'魂' => '骯',
+'鳴' => '霪',
+'鳶' => '藎',
+'鳳' => '瘀',
+'麼' => '繫',
+'鼻' => '掏',
+'齊' => 'ょ',
+'億' => '砬',
+'儀' => '痀',
+'僻' => 'ぃ',
+'僵' => '蔗',
+'價' => '歎',
+'儂' => '棬',
+'儈' => '辨',
+'儉' => '潟',
+'儅' => '絞',
+'凜' => '鄹',
+'劇' => '曄',
+'劈' => '衢',
+'劉' => '隸',
+'劍' => '膛',
+'劊' => '幣',
+'勰' => '裗',
+'厲' => '癆',
+'嘮' => '蜎',
+'嘻' => '柁',
+'嘹' => '靳',
+'嘲' => '陸',
+'嘿' => '稱',
+'嘴' => '郲',
+'嘩' => '貍',
+'噓' => '剟',
+'噎' => '珥',
+'噗' => '靷',
+'噴' => '驗',
+'嘶' => '侄',
+'嘯' => '苭',
+'嘰' => '葧',
+'墀' => '鳦',
+'墟' => '剡',
+'增' => '崝',
+'墳' => '煥',
+'墜' => '袡',
+'墮' => '園',
+'墩' => '勢',
+'墦\' => '',
+'奭' => ']',
+'嬉' => '稹',
+'嫻' => '瘚',
+'嬋' => '瞈',
+'嫵' => '澇',
+'嬌' => '蝙',
+'嬈' => '甈',
+'寮' => '撘',
+'寬' => '遵',
+'審' => '机',
+'寫' => '迡',
+'層' => '脯',
+'履' => '薩',
+'嶝' => '戫',
+'嶔' => '',
+'幢' => '敢',
+'幟' => '秺',
+'幡' => '嶆',
+'廢' => '煙',
+'廚' => '報',
+'廟' => '鏜',
+'廝' => '媌',
+'廣' => '嫘',
+'廠' => '釦',
+'彈' => '粟',
+'影' => '荌',
+'德' => '肅',
+'徵' => '摞',
+'慶' => '④',
+'慧' => '雌',
+'慮' => '藉',
+'慝' => '礅',
+'慕' => '躅',
+'憂' => '蚡',
+'慼' => 'べ',
+'慰' => '怷',
+'慫' => '佫',
+'慾' => '郗',
+'憧' => '蒧',
+'憐' => '蟒',
+'憫' => '鏨',
+'憎' => '崚',
+'憬' => '蓐',
+'憚' => '筋',
+'憤' => '猷',
+'憔' => '蒝',
+'憮' => '瞅',
+'戮' => '職',
+'摩' => '藻',
+'摯' => '祪',
+'摹' => '纂',
+'撞' => '袉',
+'撲' => 'で',
+'撈' => '檜',
+'撐' => '傅',
+'撰' => '蚴',
+'撥' => '畢',
+'撓' => '鼯',
+'撕' => '侉',
+'撩' => '謄',
+'撒' => '',
+'撮' => '湧',
+'播' => '畦',
+'撫' => '葷',
+'撚' => '瓔',
+'撬' => 'г',
+'撙' => '艉',
+'撢' => '筆',
+'撳' => '碡',
+'敵' => '菩',
+'敷' => '痱',
+'數' => '杅',
+'暮' => '贍',
+'暫' => '婃',
+'暴' => '惟',
+'暱' => '糒',
+'樣' => '欴',
+'樟' => '桷',
+'槨' => '擗',
+'樁' => '蛃',
+'樞' => '忺',
+'標' => '梓',
+'槽' => '羞',
+'模' => '耀',
+'樓' => '瞼',
+'樊' => '榆',
+'槳' => '蔑',
+'樂' => '氈',
+'樅' => '駏',
+'槭' => '樨',
+'樑' => '褽',
+'歐' => '韁',
+'歎' => '抩',
+'殤' => '毈',
+'毅' => '砳',
+'毆' => '饕',
+'漿' => '蓮',
+'潼' => '噉',
+'澄' => '割',
+'潑' => 'た',
+'潦' => '購',
+'潔' => '賞',
+'澆' => '蝸',
+'潭' => '抾',
+'潛' => 'Д',
+'潸' => '噁',
+'潮' => '陰',
+'澎' => '鱗',
+'潺' => '噆',
+'潰' => '壓',
+'潤' => '',
+'澗' => '膚',
+'潘' => '攣',
+'滕' => '鋿',
+'潯' => '銆',
+'潠' => '',
+'潟' => '籅',
+'熟' => '抇',
+'熬' => '偏',
+'熱' => '',
+'熨' => '寲',
+'牖' => '趥',
+'犛' => '膧',
+'獎' => '蔣',
+'獗' => '漹',
+'瑩' => '茖',
+'璋' => '靚',
+'璃' => '薛',
+'瑾' => '隤',
+'璀' => '霅',
+'畿' => '諗',
+'瘠' => '韙',
+'瘩' => '渤',
+'瘟' => '恔',
+'瘤' => '雖',
+'瘦' => '忡',
+'瘡' => '敞',
+'瘢' => '韗',
+'皚' => '馬',
+'皺' => '紶',
+'盤' => '攫',
+'瞎' => '牊',
+'瞇' => '譜',
+'瞌' => '謏',
+'瞑' => '謒',
+'瞋' => '淪',
+'磋' => '渲',
+'磅' => '執',
+'確' => '',
+'磊' => '濠',
+'碾' => '犧',
+'磕' => '融',
+'碼' => '鎢',
+'磐' => '攪',
+'稿' => '詨',
+'稼' => '歐',
+'穀\' => '嗷',
+'稽' => '儉',
+'稷' => '艟',
+'稻' => '翔',
+'窯' => '狺',
+'窮' => '⑥',
+'箭' => '璋',
+'箱' => '眊',
+'範' => '毓',
+'箴' => '鶇',
+'篆' => '蚼',
+'篇' => 'う',
+'篁' => '麔',
+'箠' => '憸',
+'篌' => '麑',
+'糊' => '緇',
+'締' => '萌',
+'練' => '褶',
+'緯' => '帠',
+'緻' => '祡',
+'緘' => '澎',
+'緬' => '邋',
+'緝' => '憬',
+'編' => '晤',
+'緣' => '埽',
+'線' => '盄',
+'緞' => '剷',
+'緩' => '遣',
+'綞' => '蝬',
+'緙' => '蝻',
+'緲' => '蝧',
+'緹' => '蝢',
+'罵' => '鎬',
+'罷' => '啦',
+'羯' => '蠖',
+'翩' => '醳',
+'耦' => '勷',
+'膛' => '旼',
+'膜' => '臚',
+'膝' => '洏',
+'膠' => '蝶',
+'膚' => '痺',
+'膘' => '桿',
+'蔗' => '涫',
+'蔽' => '敖',
+'蔚' => '庰',
+'蓮' => '虧',
+'蔬' => '忣',
+'蔭' => '秭',
+'蔓' => '雞',
+'蔑' => '鏖',
+'蔣' => '蔓',
+'蔡' => '絆',
+'蔔' => '眺',
+'蓬' => '鷥',
+'蔥' => '棣',
+'蓿' => '煚',
+'蔆' => '鎂',
+'螂' => '襛',
+'蝴' => '維',
+'蝶' => '評',
+'蝠' => '襝',
+'蝦' => '牬',
+'蝸' => '恘',
+'蝨' => '坉',
+'蝙' => '譀',
+'蝗' => '銀',
+'蝌' => '覈',
+'蝓' => '觶',
+'衛' => '怹',
+'衝' => '喳',
+'褐' => '福',
+'複' => '葩',
+'褒' => '婪',
+'褓' => '鵒',
+'褕' => '',
+'褊' => '鵟',
+'誼' => '祓',
+'諒' => '謝',
+'談' => '抶',
+'諄' => '袘',
+'誕' => '筑',
+'請' => '③',
+'諸' => '絊',
+'課' => '諺',
+'諉' => '矞',
+'諂' => '硤',
+'調' => '覃',
+'誰' => '阰',
+'論' => '蹦',
+'諍' => '睆',
+'誶' => '硥',
+'誹' => '溧',
+'諛' => '矬',
+'豌' => '俁',
+'豎' => '旳',
+'豬' => '紿',
+'賠' => '靨',
+'賞' => '奼',
+'賦' => '董',
+'賤' => '獎',
+'賬' => '梖',
+'賭' => '傭',
+'賢' => '玵',
+'賣' => '闖',
+'賜' => '棹',
+'質' => '窐',
+'賡' => '疐',
+'赭' => '鐎',
+'趟' => '昋',
+'趣' => '',
+'踫' => '菋',
+'踐' => '犛',
+'踝' => '灉',
+'踢' => '杺',
+'踏' => '怳',
+'踩' => '笮',
+'踟' => '灅',
+'踡' => '襢',
+'踞' => '擔',
+'躺' => '旻',
+'輝' => '閩',
+'輛' => '謙',
+'輟' => '瞗',
+'輩' => '捲',
+'輦' => '澿',
+'輪' => '謫',
+'輜' => '磝',
+'輞' => '澸',
+'輥' => '幕',
+'適' => '巠',
+'遮' => '殑',
+'遨' => '槮',
+'遭' => '婈',
+'遷' => 'ヮ',
+'鄰' => '邁',
+'鄭' => '痑',
+'鄧' => '腌',
+'鄱' => '蛚',
+'醇' => '智',
+'醉' => '郳',
+'醋' => '棚',
+'醃' => '錣',
+'鋅' => '郈',
+'銻' => '枟',
+'銷' => '种',
+'鋪' => 'と',
+'銬' => '鍙',
+'鋤' => '堝',
+'鋁' => '臏',
+'銳' => '',
+'銼' => '黿',
+'鋒' => '瑟',
+'鋇' => '接',
+'鋰' => '黈',
+'銲' => '爾',
+'閭' => '蓏',
+'閱\' => '堐',
+'霄' => '祋',
+'霆' => '鰝',
+'震' => '涾',
+'霉' => '羅',
+'靠' => '蕞',
+'鞍' => '偽',
+'鞋' => '衧',
+'鞏' => '僥',
+'頡' => '礡',
+'頫' => '萱',
+'頜' => '礜',
+'颳' => '團',
+'養' => '欱',
+'餓' => '塒',
+'餒' => '囁',
+'餘' => '牄',
+'駝' => '邯',
+'駐' => '蚺',
+'駟' => '糌',
+'駛' => '妡',
+'駑' => '緪',
+'駕' => '毅',
+'駒' => '戰',
+'駙' => '糋',
+'骷' => '鷐',
+'髮' => '楷',
+'髯' => '蠯',
+'鬧' => '齡',
+'魅' => '黰',
+'魄' => 'っ',
+'魷' => '鼘',
+'魯' => '糧',
+'鴆' => '藅',
+'鴉' => '捋',
+'鴃' => '蘠',
+'麩' => '鐐',
+'麾' => '欏',
+'黎' => '燮',
+'墨' => '蘋',
+'齒' => '喘',
+'儒' => '',
+'儘' => '鴃',
+'儔' => '棱',
+'儐' => '棝',
+'儕' => '棜',
+'冀' => '播',
+'冪' => '蹶',
+'凝' => '覽',
+'劑' => '撙',
+'劓' => '崽',
+'勳' => '悗',
+'噙' => '頍',
+'噫' => '馰',
+'噹' => '絞',
+'噩' => '堿',
+'噤' => '馯',
+'噸' => '勣',
+'噪' => '婑',
+'器' => 'ん',
+'噥' => '蛺',
+'噱' => '馲',
+'噯' => '鉎',
+'噬' => '岕',
+'噢' => '頏',
+'噶' => '蜂',
+'壁' => '族',
+'墾' => '謀',
+'壇' => '抭',
+'壅' => '觛',
+'奮' => '煖',
+'嬝' => '蘅',
+'嬴' => '湋',
+'學' => '悝',
+'寰' => '敺',
+'導' => '絳',
+'彊' => 'Ч',
+'憲' => '疧',
+'憑' => 'ず',
+'憩' => '磹',
+'憊' => '措',
+'懍' => '蒢',
+'憶' => '砪',
+'憾' => '熄',
+'懊' => '冕',
+'懈' => '邽',
+'戰' => '桵',
+'擅' => '卍',
+'擁' => '茧',
+'擋' => '結',
+'撻' => '怊',
+'撼' => '熙',
+'據' => '擂',
+'擄' => '簞',
+'擇' => '寁',
+'擂' => '濯',
+'操' => '紱',
+'撿' => '潯',
+'擒' => 'у',
+'擔' => '童',
+'撾' => '恄',
+'整' => '淕',
+'曆' => '盪',
+'曉' => '窀',
+'暹' => '橀',
+'曄' => '糐',
+'曇' => '篥',
+'暸' => '',
+'樽' => '樼',
+'樸' => 'は',
+'樺' => '鳹',
+'橙' => '傀',
+'橫' => '筵',
+'橘' => '橖',
+'樹' => '攷',
+'橄' => '橪',
+'橢' => '邳',
+'橡' => '砎',
+'橋' => 'Э',
+'橇' => 'Щ',
+'樵' => '橯',
+'機' => '儂',
+'橈' => '魬',
+'歙' => '鴩',
+'歷' => '盪',
+'氅' => '諰',
+'濂' => '憟',
+'澱' => '蛭',
+'澡' => '婰',
+'濃' => '襯',
+'澤' => '屙',
+'濁' => '觙',
+'澧' => '憓',
+'澳' => '凰',
+'激' => '慾',
+'澹' => '憯',
+'澶' => '憭',
+'澦' => '',
+'澠' => '靻',
+'澴' => '',
+'熾' => '喋',
+'燉' => '嚓',
+'燐' => '避',
+'燒' => '尥',
+'燈' => '腑',
+'燕' => '桏',
+'熹' => '懥',
+'燎' => '豳',
+'燙' => '昍',
+'燜' => '壔',
+'燃' => '',
+'燄' => '栭',
+'獨' => '黃',
+'璜' => '隢',
+'璣' => '諃',
+'璘' => '苣',
+'璟' => '茂',
+'璞\' => '鞊',
+'瓢' => 'が',
+'甌' => '穇',
+'甍' => '歈',
+'瘴' => '梉',
+'瘸' => '',
+'瘺' => '蹞',
+'盧' => '竅',
+'盥' => '邅',
+'瞠' => '謇',
+'瞞' => '離',
+'瞟' => '謕',
+'瞥' => 'く',
+'磨' => '艦',
+'磚' => '蚸',
+'磬' => '縺',
+'磧' => '縳',
+'禦' => '郘',
+'積' => '儅',
+'穎' => '荓',
+'穆' => '鐃',
+'穌' => '龒',
+'穋' => '搾',
+'窺' => '錢',
+'篙' => '誅',
+'簑' => '坯',
+'築' => '耟',
+'篤' => '鬷',
+'篛' => '鵩',
+'篡' => '欺',
+'篩' => '伓',
+'篦' => '齀',
+'糕' => '詹',
+'糖' => '昒',
+'縊' => '褑',
+'縑' => '褎',
+'縈' => '楂',
+'縛' => '蛾',
+'縣' => '瓮',
+'縞' => '褆',
+'縝' => '褘',
+'縉' => '褗',
+'縐' => '蝘',
+'罹' => '蹌',
+'羲' => '襦',
+'翰' => '熔',
+'翱' => '倏',
+'翮' => '鐋',
+'耨' => '嚭',
+'膳' => '吇',
+'膩' => '櫻',
+'膨' => '壩',
+'臻' => '淶',
+'興' => '倓',
+'艘' => '刳',
+'艙' => '組',
+'蕊' => '',
+'蕙' => '犍',
+'蕈' => '犌',
+'蕨' => '犑',
+'蕩' => '絕',
+'蕃' => '猻',
+'蕉' => '蓿',
+'蕭' => '祊',
+'蕪' => '拶',
+'蕞' => '犎',
+'螃' => '韟',
+'螟' => '難',
+'螞' => '鎳',
+'螢' => '茤',
+'融' => '',
+'衡' => '算',
+'褪' => '虮',
+'褲' => '踴',
+'褥' => '',
+'褫' => '鵚',
+'褡' => '鵌',
+'親' => 'о',
+'覦' => '膵',
+'諦' => '硞',
+'諺' => '桎',
+'諫' => '硭',
+'諱' => '颱',
+'謀' => '覺',
+'諜' => '証',
+'諧' => '迣',
+'諮' => '硢',
+'諾' => '霾',
+'謁' => '硪',
+'謂' => '彖',
+'諷' => '當',
+'諭' => '硰',
+'諳' => '硨',
+'諶' => '硜',
+'諼' => '硩',
+'豫' => '唹',
+'豭' => '啷',
+'貓' => '癡',
+'賴' => '懇',
+'蹄' => '枃',
+'踱' => '礱',
+'踴' => '蚖',
+'蹂' => '籓',
+'踹' => '癪',
+'踵' => '矐',
+'輻' => '盞',
+'輯' => '憮',
+'輸' => '怀',
+'輳' => '磩',
+'辨' => '望',
+'辦' => '域',
+'遵' => '郩',
+'遴' => '樈',
+'選' => '恁',
+'遲' => '喧',
+'遼' => '賽',
+'遺' => '疻',
+'鄴' => '罥',
+'醒' => '倳',
+'錠' => '陽',
+'錶' => '桶',
+'鋸' => '撾',
+'錳' => '襟',
+'錯' => '渣',
+'錢' => 'ヴ',
+'鋼' => '詩',
+'錫' => '柈',
+'錄' => '翹',
+'錚' => '鵃',
+'錐' => '袪',
+'錦' => '踞',
+'錡' => '',
+'錕' => '嚙',
+'錮' => '奰',
+'錙' => '幭',
+'閻' => '晑',
+'隧' => '呣',
+'隨' => '呴',
+'險' => '玸',
+'雕' => '蛐',
+'霎' => '鰨',
+'霑' => '桭',
+'霖' => '遽',
+'霍' => '齊',
+'霓' => '巍',
+'霏' => '鰣',
+'靛' => '萄',
+'靜' => '噙',
+'靦' => '沶',
+'鞘' => 'в',
+'頰' => '槳',
+'頸' => '勳',
+'頻' => 'け',
+'頷' => '禰',
+'頭' => '芛',
+'頹' => '虰',
+'頤' => '疰',
+'餐\' => '絃',
+'館' => '奩',
+'餞' => '膜',
+'餛' => '牓',
+'餡' => '畇',
+'餚' => '躽',
+'駭' => '漣',
+'駢' => '縃',
+'駱' => '醬',
+'骸' => '滿',
+'骼' => '鷩',
+'髻' => '戁',
+'髭' => '戃',
+'鬨' => '箏',
+'鮑' => '惚',
+'鴕' => '迗',
+'鴣' => '薱',
+'鴦' => '栒',
+'鴨' => '捊',
+'鴒' => '鍔',
+'鴛' => '唭',
+'默' => '蘇',
+'黔' => 'ン',
+'龍' => '韓',
+'龜' => '實',
+'優' => '蚥',
+'償' => '野',
+'儡' => '濤',
+'儲' => '揣',
+'勵' => '療',
+'嚎' => '瑪',
+'嚀' => '萭',
+'嚐' => '郭',
+'嚅' => '骫',
+'嚇' => '狣',
+'嚏' => '杹',
+'壕' => '瑣',
+'壓' => '揤',
+'壑' => '詎',
+'壎' => '跕',
+'嬰' => '茪',
+'嬪' => '磄',
+'嬤' => '箷',
+'孺' => '',
+'尷' => '瘐',
+'屨' => '歑',
+'嶼' => '适',
+'嶺' => '鍛',
+'嶽' => '埬',
+'嶸' => '慓',
+'幫' => '堆',
+'彌' => '譆',
+'徽' => '閣',
+'應' => '茼',
+'懂' => '雅',
+'懇' => '諜',
+'懦' => '鑒',
+'懋' => '礄',
+'戲' => '牁',
+'戴' => '渴',
+'擎' => 'э',
+'擊' => '僻',
+'擘' => '諲',
+'擠' => '撥',
+'擰' => '禳',
+'擦' => '笠',
+'擬' => '攜',
+'擱' => '賊',
+'擢' => '萿',
+'擭' => 'N',
+'斂' => '螻',
+'斃' => '教',
+'曙' => '扺',
+'曖' => '縎',
+'檀' => '抴',
+'檔' => '紫',
+'檄' => '洐',
+'檢' => '潰',
+'檜' => '鴈',
+'櫛' => '駘',
+'檣' => '橑',
+'橾' => '癲',
+'檗' => '歕',
+'檐' => '橎',
+'檠' => '橐',
+'歜' => 'b',
+'殮' => '氃',
+'毚' => '',
+'氈' => '梇',
+'濘' => '籠',
+'濱' => '梆',
+'濟' => '撳',
+'濠' => '憍',
+'濛' => '蟹',
+'濤' => '旽',
+'濫' => '斂',
+'濯' => '慦',
+'澀' => '优',
+'濬' => '縛',
+'濡' => '憒',
+'濩' => 'C',
+'濕' => '坁',
+'濮' => '憪',
+'濰' => '峆',
+'燧' => '徾',
+'營' => '茠',
+'燮' => '袸',
+'燦' => '細',
+'燥' => '孲',
+'燭' => '羕',
+'燬' => '障',
+'燴' => '領',
+'燠' => '幬',
+'爵' => '橋',
+'牆' => 'Х',
+'獰' => '襬',
+'獲' => '鳳',
+'璩' => '鞈',
+'環' => '遠',
+'璦' => '閮',
+'璨' => '鞎',
+'癆' => '謽',
+'療' => '谿',
+'癌' => '骨',
+'盪' => '絕',
+'瞳' => '肏',
+'瞪' => '腆',
+'瞰' => '謍',
+'瞬' => '侘',
+'瞧' => 'Ю',
+'瞭' => '賸',
+'矯' => '衛',
+'磷' => '避',
+'磺' => '鉸',
+'磴' => '罾',
+'磯' => '穚',
+'礁' => '螂',
+'禧' => '檞',
+'禪' => '檟',
+'穗' => '呠',
+'窿' => '騁',
+'簇' => '楮',
+'簍' => '穡',
+'篾' => '齖',
+'篷' => '囑',
+'簌' => '齍',
+'篠' => '鵽',
+'糠' => '蕙',
+'糜' => '譚',
+'糞' => '獅',
+'糢' => '耀',
+'糟' => '媎',
+'糙' => '缽',
+'糝' => '趮',
+'縮' => '坫',
+'績' => '憎',
+'繆' => '觭',
+'縷\' => '藐',
+'縲' => '覣',
+'繃' => '掄',
+'縫' => '瑜',
+'總' => '軞',
+'縱' => '軝',
+'繅' => '觰',
+'繁' => '楛',
+'縴' => '玹',
+'縹' => '覢',
+'繈' => '麌',
+'縵' => '覤',
+'縿' => '',
+'縯' => '栳',
+'罄' => '騔',
+'翳' => '鐓',
+'翼' => '秫',
+'聱' => '嬽',
+'聲' => '汒',
+'聰' => '棲',
+'聯' => '薊',
+'聳' => '侕',
+'臆' => '砵',
+'臃' => '虓',
+'膺' => '瘊',
+'臂' => '旋',
+'臀' => '迓',
+'膿' => '襲',
+'膽' => '筐',
+'臉' => '螺',
+'膾' => '醑',
+'臨' => '還',
+'舉' => '撼',
+'艱' => '潸',
+'薪' => '郇',
+'薄' => '情',
+'蕾' => '濟',
+'薜' => '瑑',
+'薑' => '蔽',
+'薔' => 'Ц',
+'薯' => '扱',
+'薛' => '悁',
+'薇' => '瑄',
+'薨' => '獉',
+'薊' => '撒',
+'虧' => '鋸',
+'蟀' => '饇',
+'蟑' => '饈',
+'螳' => '颿',
+'蟒' => '譕',
+'蟆' => '鞳',
+'螫' => '顜',
+'螻' => '譈',
+'螺' => '蹟',
+'蟈' => '蠈',
+'蟋' => '颽',
+'褻' => '湝',
+'褶' => '麎',
+'襄' => '盷',
+'褸' => '鵔',
+'褽' => '浺',
+'覬' => '膦',
+'謎' => '譏',
+'謗' => '娶',
+'謙' => 'ヱ',
+'講' => '蔡',
+'謊' => '銑',
+'謠' => '狴',
+'謝' => '郅',
+'謄' => '杴',
+'謐' => '稊',
+'豁' => '魁',
+'谿' => '洈',
+'豳' => '搫',
+'賺' => '蚻',
+'賽' => '',
+'購' => '劃',
+'賸' => '呁',
+'賻' => '聬',
+'趨' => '⑸',
+'蹉' => '礯',
+'蹋' => '怗',
+'蹈' => '絡',
+'蹊' => '纇',
+'轄' => '牮',
+'輾' => '梫',
+'轂' => '麇',
+'轅' => '埢',
+'輿' => '豗',
+'避' => '旌',
+'遽' => '槦',
+'還' => '遜',
+'邁' => '闐',
+'邂' => '槻',
+'邀' => '肂',
+'鄹' => '蛝',
+'醣' => '麙',
+'醞' => '奜',
+'醜' => '堯',
+'鍍' => '傲',
+'鎂' => '臘',
+'錨' => '礙',
+'鍵' => '瑩',
+'鍊' => '',
+'鍥' => '幮',
+'鍋' => '廓',
+'錘' => '晴',
+'鍾' => '瀎',
+'鍬' => 'Ъ',
+'鍛' => '傯',
+'鍰' => '懪',
+'鍚' => '',
+'鍔' => '懭',
+'闊' => '屨',
+'闋' => '蜨',
+'闌' => '擊',
+'闈' => '蒯',
+'闆' => '啣',
+'隱' => '笐',
+'隸' => '薔',
+'雖' => '呥',
+'霜' => '邞',
+'霞' => '牳',
+'鞠' => '懍',
+'韓' => '澈',
+'顆' => '衡',
+'颶' => '鴢',
+'餵' => '庣',
+'騁' => '勞',
+'駿' => '縞',
+'鮮' => '珅',
+'鮫' => '巑',
+'鮪' => '孋',
+'鮭' => '囋',
+'鴻' => '箄',
+'鴿' => '賈',
+'麋' => '玂',
+'黏' => '薴',
+'點' => '萸',
+'黜' => '籦',
+'黝' => '纕',
+'黛' => '籧',
+'鼾' => '襳',
+'齋' => '晛',
+'叢' => '椒',
+'嚕' => '頎',
+'嚮' => '砃',
+'壙' => '詗',
+'壘' => '濫',
+'嬸' => '朿',
+'彝' => '眝',
+'懣' => '禫',
+'戳' => '期',
+'擴' => '孺',
+'擲' => '祣',
+'擾' => '',
+'攆' => '瓖',
+'擺\' => '啊',
+'擻' => '剆',
+'擷' => '腡',
+'斷' => '剿',
+'曜' => '縢',
+'朦' => '錪',
+'檳' => '樾',
+'檬' => '蟾',
+'櫃' => '嶄',
+'檻' => '熨',
+'檸' => '襪',
+'櫂' => '噮',
+'檮' => '',
+'檯' => '怢',
+'歟' => '鴥',
+'歸' => '寥',
+'殯' => '澣',
+'瀉' => '郕',
+'瀋' => '韎',
+'濾' => '薦',
+'瀆' => '鞃',
+'濺' => '膠',
+'瀑' => 'ふ',
+'瀏' => '銡',
+'燻' => '悇',
+'燼' => '輜',
+'燾' => '懧',
+'燸' => '^',
+'獷' => '搿',
+'獵' => '轂',
+'璧' => '韏',
+'璿' => '霂',
+'甕' => '怤',
+'癖' => '騉',
+'癘' => '謳',
+'癒' => '郛',
+'瞽' => '謆',
+'瞿' => '鶭',
+'瞻' => '桹',
+'瞼' => '薣',
+'礎' => '插',
+'禮' => '獰',
+'穡' => '艞',
+'穢' => '韶',
+'穠' => '',
+'竄' => '欽',
+'竅' => 'ж',
+'簫' => '鵿',
+'簧' => '銅',
+'簪' => '穮',
+'簞' => '鶂',
+'簣' => '鵨',
+'簡' => '潠',
+'糧' => '襄',
+'織' => '眽',
+'繕' => '圪',
+'繞' => '',
+'繚' => '諏',
+'繡' => '凎',
+'繒' => '諆',
+'繙' => '楹',
+'罈' => '抭',
+'翹' => 'д',
+'翻' => '楹',
+'職' => '眥',
+'聶' => '蘗',
+'臍' => 'ゆ',
+'臏' => '錤',
+'舊' => '導',
+'藏' => '紲',
+'薩' => '',
+'藍' => '懦',
+'藐' => '鏟',
+'藉' => '賢',
+'薰' => '瑐',
+'薺' => '媵',
+'薹' => '瑀',
+'薦' => '熱',
+'蟯' => '藗',
+'蟬' => '莽',
+'蟲' => '單',
+'蟠' => '騚',
+'覆' => '葡',
+'覲' => '膰',
+'觴' => '蘩',
+'謨' => '祳',
+'謹' => '輝',
+'謬' => '韻',
+'謫' => '稃',
+'豐' => '猿',
+'贅' => '袑',
+'蹙' => '齙',
+'蹣' => '纊',
+'蹦' => '採',
+'蹤' => '趿',
+'蹟' => '慫',
+'蹕' => '櫼',
+'軀' => '⑼',
+'轉' => '蛌',
+'轍' => '殌',
+'邇' => '暷',
+'邃' => '槼',
+'邈' => '樍',
+'醫' => '瓟',
+'醬' => '蓬',
+'釐' => '濰',
+'鎔' => '',
+'鎊' => '夠',
+'鎖' => '坶',
+'鎢' => '挃',
+'鎳' => '蠢',
+'鎮' => '淜',
+'鎬' => '訾',
+'鎰' => '擼',
+'鎘' => '擽',
+'鎚' => '晴',
+'鎗' => 'Л',
+'闔' => '蝫',
+'闖' => '斑',
+'闐' => '蝀',
+'闕' => '蜮',
+'離' => '燭',
+'雜' => '娸',
+'雙' => '邧',
+'雛' => '堠',
+'雞' => '憐',
+'霤' => '闊',
+'鞣' => '鷛',
+'鞦' => '⑦',
+'鞭' => '晝',
+'韹' => '',
+'額' => '塗',
+'顏' => '晇',
+'題' => '枙',
+'顎' => '穧',
+'顓' => '穨',
+'颺' => '栨',
+'餾' => '闆',
+'餿' => '犕',
+'餽' => '嚏',
+'餮' => '劙',
+'馥' => '藆',
+'騎' => 'る',
+'髁' => '鷙',
+'鬃' => '跂',
+'鬆' => '侂',
+'魏' => '庥',
+'魎' => '鼲',
+'魍' => '齫',
+'鯊' => '灕',
+'鯉' => '牆',
+'鯽' => '灗',
+'鯈' => '',
+'鯀' => '氍',
+'鵑' => '暸',
+'鵝' => '塑',
+'鵠' => '蟪',
+'黠\' => '艬',
+'鼕' => '',
+'鼬' => '蠲',
+'儳' => '莫',
+'嚥' => '捗',
+'壞' => '輓',
+'壟' => '瞽',
+'壢' => '詅',
+'寵' => '唾',
+'龐' => '籣',
+'廬' => '簧',
+'懲' => '凱',
+'懷' => '輒',
+'懶' => '擱',
+'懵' => '蒔',
+'攀' => '戀',
+'攏' => '瞿',
+'曠' => '錠',
+'曝' => 'ぴ',
+'櫥' => '堰',
+'櫝' => '噰',
+'櫚' => '曀',
+'櫓' => '橠',
+'瀛' => '摮',
+'瀟' => '僶',
+'瀨' => '噘',
+'瀚' => '憳',
+'瀝' => '薑',
+'瀕' => '梭',
+'瀘' => '蜚',
+'爆' => '惇',
+'爍' => '佶',
+'牘' => '赬',
+'犢' => '馭',
+'獸' => '忤',
+'獺' => '怴',
+'璽' => '踣',
+'瓊' => '⑤',
+'瓣' => '國',
+'疇' => '喻',
+'疆' => '蔭',
+'癟' => '械',
+'癡' => '博',
+'矇' => '蟹',
+'礙' => '鬼',
+'禱' => '絰',
+'穫' => '鳳',
+'穩' => '恛',
+'簾' => '螫',
+'簿' => '移',
+'簸' => '穭',
+'簽' => 'ワ',
+'簷' => '橎',
+'籀' => '籉',
+'繫' => '炵',
+'繭' => '潺',
+'繹' => '秠',
+'繩' => '汋',
+'繪' => '餅',
+'羅' => '蹕',
+'繳' => '褕',
+'羶' => '錌',
+'羹' => '輊',
+'羸' => '湑',
+'臘' => '幫',
+'藩' => '楫',
+'藝' => '眙',
+'藪' => '瑒',
+'藕' => '驕',
+'藤' => '枘',
+'藥' => '狻',
+'藷' => '',
+'蟻' => '眐',
+'蠅' => '茯',
+'蠍' => '衎',
+'蟹' => '郱',
+'蟾' => '騤',
+'襠' => '鮸',
+'襟' => '踟',
+'襖' => '偯',
+'襞' => '蠐',
+'譁' => '貍',
+'譜' => 'び',
+'識' => '妎',
+'證' => '痐',
+'譚' => '抪',
+'譎' => '竦',
+'譏' => '憧',
+'譆' => '柁',
+'譙' => '窙',
+'贈' => '崌',
+'贊' => '婝',
+'蹼' => '纆',
+'蹲' => '匯',
+'躇' => '堡',
+'蹶' => '纋',
+'蹬' => '腋',
+'蹺' => '欂',
+'蹴' => '罍',
+'轔' => '磪',
+'轎' => '諄',
+'辭' => '棗',
+'邊' => '晚',
+'邋' => '槫',
+'醱' => 'づ',
+'醮' => '黥',
+'鏡' => '噩',
+'鏑' => '櫆',
+'鏟' => '莓',
+'鏃' => '檽',
+'鏈' => '蟈',
+'鏜' => '曛',
+'鏝' => '曘',
+'鏖' => '玃',
+'鏢' => '曚',
+'鏍' => '櫅',
+'鏘' => '懖',
+'鏤' => '懫',
+'鏗' => '麍',
+'鏨' => '鹺',
+'關' => '壽',
+'隴' => '瞻',
+'難' => '麵',
+'霪' => '鰩',
+'霧' => '昲',
+'靡' => '證',
+'韜' => '頨',
+'韻' => '婘',
+'類' => '濬',
+'願' => '堋',
+'顛' => '菌',
+'颼' => '鴐',
+'饅' => '雜',
+'饉' => '獍',
+'騖' => '緟',
+'騙' => 'ぉ',
+'鬍' => '綸',
+'鯨' => '儘',
+'鯧' => '瓘',
+'鯖' => '灒',
+'鯛' => '癭',
+'鶉' => '蟭',
+'鵡' => '蟤',
+'鵲' => '',
+'鵪' => '蟜',
+'鵬' => '灞',
+'麒' => '玁',
+'麗' => '璨',
+'麓' => '織',
+'麴' => '鐨',
+'勸' => '',
+'嚨' => '颶',
+'嚷' => '',
+'嚶' => '隑',
+'嚴' => '旆',
+'嚼' => '蝗',
+'壤' => '',
+'孀\' => '篋',
+'孃' => '矓',
+'孽' => '蘭',
+'寶' => '惘',
+'巉' => 'f',
+'懸' => '唑',
+'懺' => '睼',
+'攘' => '',
+'攔' => '戴',
+'攙' => '莢',
+'曦' => '縋',
+'朧' => '輮',
+'櫬' => '暾',
+'瀾' => '擠',
+'瀰' => '譆',
+'瀲' => '劋',
+'爐' => '簪',
+'獻' => '瓬',
+'瓏' => '貏',
+'癢' => '欭',
+'癥' => '痌',
+'礦' => '鄴',
+'礪' => '簋',
+'礬' => '楝',
+'礫' => '癌',
+'竇' => '鬄',
+'競' => '噥',
+'籌' => '喉',
+'籃' => '擎',
+'籍' => '戮',
+'糯' => '霽',
+'糰' => '芶',
+'辮' => '梯',
+'繽' => '褉',
+'繼' => '樟',
+'纂' => '郴',
+'罌' => '騜',
+'耀' => '珓',
+'臚' => '輹',
+'艦' => '耦',
+'藻' => '婍',
+'藹' => '高',
+'蘑' => '罌',
+'藺' => '毼',
+'蘆' => '竄',
+'蘋' => 'し',
+'蘇' => '劼',
+'蘊' => '堄',
+'蠔' => '罊',
+'蠕' => '',
+'襤' => '鵘',
+'覺' => '橇',
+'觸' => '揖',
+'議' => '祜',
+'譬' => 'ぅ',
+'警' => '劑',
+'譯' => '祒',
+'譟' => '婑',
+'譫' => '筊',
+'贏' => '荇',
+'贍' => '厊',
+'躉' => '齠',
+'躁' => '婇',
+'躅' => '羻',
+'躂' => '怳',
+'醴' => '黦',
+'釋' => '庋',
+'鐘' => '笘',
+'鐃' => '閷',
+'鏽' => '凄',
+'闡' => '莠',
+'霰' => '鰡',
+'飄' => 'お',
+'饒' => '',
+'饑' => '慰',
+'馨' => '黹',
+'騫' => '撟',
+'騰' => '枆',
+'騷' => '玊',
+'騵' => '矄',
+'鰓' => '',
+'鰍' => '籗',
+'鹹' => '玶',
+'麵' => '醱',
+'黨' => '絨',
+'鼯' => '蠮',
+'齟' => '鶼',
+'齣' => '堤',
+'齡' => '鍵',
+'儷' => '棖',
+'儸' => '蹕',
+'囁' => '鉯',
+'囀' => '裐',
+'囂' => '秕',
+'夔' => '淼',
+'屬' => '扽',
+'巍' => '峞',
+'懼' => '曉',
+'懾' => '扤',
+'攝' => '扜',
+'攜' => '觓',
+'斕' => '黖',
+'曩' => '縏',
+'櫻' => '茛',
+'欄' => '戲',
+'櫺' => '凞',
+'殲' => '漿',
+'灌' => '嫩',
+'爛' => '擭',
+'犧' => '枺',
+'瓖' => '',
+'瓔' => '雓',
+'癩' => '餺',
+'矓' => '輮',
+'籐' => '枘',
+'纏' => '莊',
+'續' => '哿',
+'羼' => '殥',
+'蘗' => '瓾',
+'蘭' => '擘',
+'蘚' => '瑎',
+'蠣' => '艤',
+'蠢' => '替',
+'蠡' => '騠',
+'蠟' => '嶸',
+'襪' => '侲',
+'襬' => '缹',
+'覽' => '擬',
+'譴' => 'Ж',
+'護' => '誘',
+'譽' => '酐',
+'贓' => '婒',
+'躊' => '喬',
+'躍' => '埲',
+'躋' => '欀',
+'轟' => '箔',
+'辯' => '梁',
+'醺' => '鼰',
+'鐮' => '蟑',
+'鐳' => '濱',
+'鐵' => '沺',
+'鐺' => '隰',
+'鐸' => '鍎',
+'鐲' => '瀍',
+'鐫' => '擸',
+'闢' => '斬',
+'霸' => '啪',
+'霹' => '羈',
+'露' => '繞',
+'響' => '砒',
+'顧' => '嘈',
+'顥' => '簬',
+'饗' => '龢',
+'驅' => '',
+'驃' => '羭',
+'驀' => '楋',
+'騾' => '邇',
+'髏\' => '鷖',
+'魔' => '藹',
+'魑' => '龕',
+'鰭' => '驒',
+'鰥' => '髐',
+'鶯' => '搡',
+'鶴' => '禍',
+'鷂' => '蠁',
+'鶸' => '',
+'麝' => '癰',
+'黯' => '蘾',
+'鼙' => '亃',
+'齜' => '鷊',
+'齦' => '鷏',
+'齧' => '蘚',
+'儼' => '椏',
+'儻' => '棈',
+'囈' => '蔇',
+'囊' => '黨',
+'囉' => '蹕',
+'孿' => '蟠',
+'巔' => '摛',
+'巒' => '蟬',
+'彎' => '俔',
+'懿' => '亄',
+'攤' => '怉',
+'權' => '',
+'歡' => '辣',
+'灑' => '',
+'灘' => '戽',
+'玀' => '滮',
+'瓤' => '',
+'疊' => '詁',
+'癮' => '颸',
+'癬' => '悢',
+'禳' => '檇',
+'籠' => '餵',
+'籟' => '竷',
+'聾' => '顆',
+'聽' => '泭',
+'臟' => '婄',
+'襲' => '炷',
+'襯' => '傍',
+'觼' => '',
+'讀' => '黍',
+'贖' => '抏',
+'贗' => '媏',
+'躑' => '爙',
+'躓' => '灆',
+'轡' => '閜',
+'酈' => '菄',
+'鑄' => '翉',
+'鑑' => '牖',
+'鑒' => '牖',
+'霽' => '鰜',
+'霾' => '鶷',
+'韃' => '鰷',
+'韁' => '誸',
+'顫' => '荷',
+'饕' => '壨',
+'驕' => '蝨',
+'驍' => '緗',
+'髒' => '婄',
+'鬚' => '剕',
+'鱉' => '梱',
+'鰱' => '攢',
+'鰾' => '鬻',
+'鰻' => '魕',
+'鷓' => '蟝',
+'鷗' => '顫',
+'鼴' => '蠳',
+'齬' => '鶾',
+'齪' => '鷅',
+'龔' => '麂',
+'囌' => '劼',
+'巖' => '旂',
+'戀' => '蟋',
+'攣' => '蟲',
+'攫' => '樹',
+'攪' => '蝌',
+'曬' => '伄',
+'欐' => '',
+'瓚' => '頞',
+'竊' => 'л',
+'籤' => 'ワ',
+'籣' => '蓓',
+'籥' => '殗',
+'纓' => '荍',
+'纖' => '玹',
+'纔' => '符',
+'臢' => '醺',
+'蘸' => '梣',
+'蘿' => '蹤',
+'蠱' => '嘍',
+'變' => '曹',
+'邐' => '槸',
+'邏' => '軀',
+'鑣' => '瀔',
+'鑠' => '鍷',
+'鑤' => '蘸',
+'靨' => '媜',
+'顯' => '珆',
+'饜' => '儽',
+'驚' => '儐',
+'驛' => '緛',
+'驗' => '桄',
+'髓' => '咍',
+'體' => '极',
+'髑' => '麶',
+'鱔' => '鱄',
+'鱗' => '邂',
+'鱖' => '鰿',
+'鷥' => '薷',
+'麟' => '矔',
+'黴' => '羅',
+'囑' => '翊',
+'壩' => '商',
+'攬' => '擦',
+'灞' => '撅',
+'癱' => '戔',
+'癲' => '騍',
+'矗' => '提',
+'罐' => '嫡',
+'羈' => '蹇',
+'蠶' => '紮',
+'蠹' => '騧',
+'衢' => '摋',
+'讓' => '',
+'讒' => '莒',
+'讖' => '笻',
+'艷' => '栻',
+'贛' => '該',
+'釀' => '籐',
+'鑪' => '簪',
+'靂' => '魒',
+'靈' => '鍾',
+'靄' => '鰤',
+'韆' => 'ロ',
+'顰' => '糬',
+'驟' => '紬',
+'鬢' => '斖',
+'魘' => '鼳',
+'鱟' => '囆',
+'鷹' => '茈',
+'鷺' => '襑',
+'鹼' => '潘',
+'鹽' => '敆',
+'鼇' => '驉',
+'齷' => '鷃',
+'齲' => '',
+'廳' => '泆',
+'欖' => '擳',
+'灣' => '俜',
+'籬' => '燦',
+'籮' => '轍',
+'蠻' => '雙',
+'觀' => '夤',
+'躡\' => '糲',
+'釁' => '陊',
+'鑲' => '眄',
+'鑰' => '埥',
+'顱' => '簫',
+'饞' => '莫',
+'髖' => '鷕',
+'鬣' => '欑',
+'黌' => '毲',
+'灤' => '覆',
+'矚' => '羛',
+'讚' => '婝',
+'鑷' => '蠣',
+'韉' => '鰽',
+'驢' => '聶',
+'驥' => '翪',
+'纜' => '擢',
+'讜' => '祲',
+'躪' => '耰',
+'釅' => '鶡',
+'鑽' => '郰',
+'鑾' => '鷍',
+'鑼' => '轉',
+'鱷' => '穱',
+'鱸' => '齤',
+'黷' => '蘹',
+'豔' => '栻',
+'鑿' => '娾',
+'鸚' => '蟨',
+'爨' => '憵',
+'驪' => '緺',
+'鬱' => '郙',
+'鸛' => '襉',
+'鸞' => '蟢',
+'籲' => '郚',
+'ヾ' => 'K',
+'ゝ' => 'L',
+'ゞ' => 'M',
+'々' => 'N',
+'ぁ' => 'O',
+'あ' => 'P',
+'ぃ' => 'Q',
+'い' => 'R',
+'ぅ' => 'S',
+'う' => 'T',
+'ぇ' => '〣',
+'え' => '〤',
+'ぉ' => '〥',
+'お' => '〦',
+'か' => '〧',
+'が' => '〨',
+'き' => '〩',
+'ぎ' => '十',
+'く' => '卄',
+'ぐ' => '卅',
+'け' => '仃',
+'げ' => '仆',
+'こ' => '仇',
+'ご' => '仍',
+'さ' => '今',
+'ざ' => '介',
+'し' => '仄',
+'じ' => '元',
+'す' => '允',
+'ず' => '內',
+'せ' => '媦',
+'ぜ' => '堹',
+'そ' => '公',
+'ぞ' => '湅',
+'た' => '崱',
+'だ' => '琡',
+'ち' => '渻',
+'ぢ' => '湆',
+'っ' => '勻',
+'つ' => '筄',
+'づ' => '袽',
+'て' => '熇',
+'で' => '撗',
+'と' => '誾',
+'ど' => '誻',
+'な' => '嫘',
+'に' => '袾',
+'ぬ' => '樉',
+'ね' => '摓',
+'の' => '篞',
+'は' => '拸',
+'ば' => '謪',
+'ぱ' => '天',
+'ひ' => '夫',
+'び' => '薔',
+'ぴ' => '熇',
+'ふ' => '',
+'ぶ' => '少',
+'ぷ' => '尤',
+'へ' => '尺',
+'べ' => '屯',
+'ぺ' => 'ㄑ',
+'ほ' => '幻',
+'ぼ' => '〝',
+'ぽ' => '﹞',
+'ま' => '引',
+'み' => '心',
+'む' => '',
+'め' => '',
+'も' => '手',
+'ゃ' => '丑',
+'や' => '丐',
+'ゅ' => '不',
+'ゆ' => '中',
+'ょ' => '丰',
+'よ' => '丹',
+'ら' => '之',
+'り' => '尹',
+'る' => '予',
+'れ' => '云',
+'ろ' => '井',
+'ゎ' => '互',
+'わ' => '五',
+'ゐ' => '亢',
+'ゑ' => '仁',
+'を' => '什',
+'ん' => '仃',
+'ァ' => '仆',
+'ア' => '仇',
+'ィ' => '仍',
+'イ' => '今',
+'ゥ' => '介',
+'ウ' => '仄',
+'ェ' => '元',
+'エ' => '允',
+'ォ' => '內',
+'オ' => '六',
+'カ' => '兮',
+'ガ' => '公',
+'キ' => '冗',
+'ギ' => '凶',
+'ク' => '分',
+'グ' => '切',
+'ケ' => '刈',
+'ゲ' => '勻',
+'コ' => '勾',
+'ゴ' => '勿',
+'サ' => '化',
+'ザ' => '匹',
+'シ' => '午',
+'ジ' => '升',
+'ス' => '卅',
+'ズ' => '卞',
+'セ' => '厄',
+'ゼ' => '友',
+'ソ' => '及',
+'ゾ' => '反',
+'タ' => '壬',
+'ダ' => '天',
+'チ' => '夫',
+'ヂ' => '太',
+'ッ' => '夭',
+'ツ\' => '孔',
+'ヅ' => '少',
+'テ' => '尤',
+'デ' => '尺',
+'ト' => '屯',
+'ド' => '巴',
+'ナ' => '幻',
+'ニ' => '廿',
+'ヌ' => '弔',
+'ネ' => '引',
+'ノ' => '心',
+'ハ' => '戈',
+'バ' => '戶',
+'パ' => '手',
+'ヒ' => '扎',
+'ビ' => '支',
+'ピ' => '文',
+'フ' => '斗',
+'ブ' => '斤',
+'プ' => '方',
+'ヘ' => '日',
+'ベ' => '曰',
+'ペ' => '月',
+'ホ' => '木',
+'ボ' => '欠',
+'ポ' => '止',
+'マ' => '歹',
+'ミ' => '毋',
+'ム' => '比',
+'メ' => '毛',
+'モ' => '氏',
+'ャ' => '央',
+'ヤ' => '失',
+'ュ' => '奴',
+'ユ' => '奶',
+'ョ' => '孕',
+'ヨ' => '它',
+'ラ' => '尼',
+'リ' => '巨',
+'ル' => '巧',
+'レ' => '左',
+'ロ' => '市',
+'ヮ' => '布',
+'ワ' => '平',
+'ヰ' => '幼',
+'ヱ' => '弁',
+'ヲ' => '弘',
+'ン' => '弗',
+'ヴ' => '必',
+'ヵ' => '戊',
+'ヶ' => '打',
+'Д' => '扔',
+'Е' => '扒',
+'Ё' => '扑',
+'Ж' => '斥',
+'З' => '旦',
+'И' => '朮',
+'Й' => '本',
+'К' => '未',
+'Л' => '末',
+'М' => '札',
+'У' => '正',
+'Ф' => '母',
+'Х' => '民',
+'Ц' => '氐',
+'Ч' => '永',
+'Ш' => '汁',
+'Щ' => '汀',
+'Ъ' => '氾',
+'Ы' => '犯',
+'Ь' => '玄',
+'Э' => '玉',
+'Ю' => '瓜',
+'Я' => '瓦',
+'а' => '甘',
+'б' => '生',
+'в' => '用',
+'г' => '甩',
+'д' => '田',
+'е' => '由',
+'ё' => '甲',
+'ж' => '申',
+'з' => '疋',
+'и' => '白',
+'й' => '皮',
+'к' => '皿',
+'л' => '目',
+'м' => '矛',
+'н' => '矢',
+'о' => '石',
+'п' => '示',
+'р' => '禾',
+'с' => '穴',
+'т' => '立',
+'у' => '丞',
+'ф' => '丟',
+'х' => '乒',
+'ц' => '乓',
+'ч' => '乩',
+'ш' => '亙',
+'щ' => '交',
+'ъ' => '亦',
+'ы' => '亥',
+'ь' => '仿',
+'э' => '伉',
+'ю' => '伙',
+'я' => '伊',
+'①' => '伕',
+'②' => '伍',
+'③' => '伐',
+'④' => '休',
+'⑤' => '伏',
+'⑥' => '仲',
+'⑦' => '件',
+'⑧' => '任',
+'⑨' => '仰',
+'⑩' => '仳',
+'⑴' => '均',
+'⑵' => '坎',
+'⑶' => '圾',
+'⑷' => '坐',
+'⑸' => '坏',
+'⑹' => '圻',
+'⑺' => '漼',
+'⑻' => '夾',
+'⑼' => '妝',
+'⑽' => '淊',
+'' => '妨',
+'' => '妞',
+'@' => '妣',
+'A' => '妙',
+'B' => '譪',
+'C' => '妍',
+'D' => '妤',
+'E' => '妓',
+'F' => '妊',
+'G' => '妥',
+'H' => '孝',
+'I' => '孜',
+'J' => '孚',
+'K' => '嘍',
+'L' => '完',
+'M' => '宋',
+'N' => '宏',
+'O' => '尬',
+'P' => '局',
+'Q' => '屁',
+'R' => '尿',
+'S' => '尾',
+'T' => '╰',
+'U' => '╰',
+'V' => '忌',
+'W' => '志',
+'X' => '忍',
+'Y' => '忱',
+'Z' => '快',
+'[' => '忸',
+'\\' => '忪',
+']' => '戒',
+'^' => '我',
+'_' => '抄',
+'`' => '抗',
+'a' => '抖',
+'b' => '技',
+'c' => '扶',
+'d' => '抉',
+'e' => '雰',
+'f' => '把',
+'g' => '扼',
+'h' => '兜',
+'i' => '批',
+'j' => '扳',
+'k' => '抒',
+'l' => '扯',
+'m' => '折',
+'n' => '扮',
+'o' => '投',
+'p' => '抓',
+'q' => '抑',
+'r' => '抆',
+'s' => '改',
+'t' => '攻',
+'u' => '攸',
+'v' => '跺',
+'w' => '駃',
+'x' => 'X',
+'y' => '豱',
+'z' => '禿',
+'{' => '系',
+'|' => '絰',
+'}' => '寎',
+'~' => '蜡',
+'' => '斲',
+'' => '厙',
+'' => '儴',
+'' => '畟',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => 'х',
+'' => '↓',
+'' => '↓',
+'' => '慖',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '☆',
+'' => '帚',
+'' => '*',
+'' => '§',
+'' => '↓',
+'' => '∮',
+'' => '↓',
+'' => '蕫',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '嵂',
+'' => '湆',
+'' => '筌',
+'' => '亶',
+'' => '痻',
+'' => '摠',
+'' => '鶜',
+'' => '瘔',
+'' => '蜡',
+'' => '蝃',
+'' => '斲',
+'' => '篞',
+'' => '憼',
+'' => '擣',
+'' => '穜',
+'' => '鷟',
+'' => '鼱',
+'' => '懰',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'' => '檶',
+'' => '獑',
+'' => '↓',
+'' => '↓',
+'' => '↓',
+'乂' => 'V',
+'乜' => '媬',
+'凵' => '袶',
+'匚' => '媓',
+'厂' => '釦',
+'万' => '勀',
+'丌' => '堞',
+'乇' => '堭',
+'亍' => '堙',
+'囗' => '鳧',
+'兀' => '堧',
+'屮' => '氂',
+'彳' => '摝',
+'丏' => 'D',
+'冇' => '',
+'与' => '迵',
+'丮' => 'M',
+'亓' => '媮',
+'仂' => '崸',
+'仉' => '嵉',
+'仈' => '',
+'冘' => '',
+'勼' => '',
+'卬' => 'n',
+'厹' => '',
+'圠' => 'L',
+'夃' => '',
+'夬' => '',
+'尐\' => '',
+'巿' => '',
+'旡' => '拸',
+'殳' => '麈',
+'毌' => '',
+'气' => 'ァ',
+'爿' => '蜙',
+'丱' => 'O',
+'丼' => 'S',
+'仨' => '崼',
+'仜' => '',
+'仩' => '',
+'仡' => '崲',
+'仝' => '欹',
+'仚' => '',
+'刌' => 'Y',
+'匜' => 'F',
+'卌' => 'c',
+'圢' => 'N',
+'圣' => '吤',
+'夗' => '',
+'夯' => '獄',
+'宁' => '譴',
+'宄' => '撜',
+'尒' => '嫌',
+'尻' => '樏',
+'屴' => '',
+'屳' => '',
+'帄' => '',
+'庀' => '瑳',
+'庂' => '',
+'忉' => '皸',
+'戉' => '鍕',
+'扐' => 'A',
+'氕' => '諨',
+'氶' => '',
+'汃' => '',
+'氿' => '',
+'氻' => '',
+'犮' => '',
+'犰' => '摐',
+'玊' => '俊',
+'禸' => '軸',
+'肊' => '砵',
+'阞' => '瑿',
+'伎' => '撚',
+'优' => '蚥',
+'伬' => '',
+'仵' => '徦',
+'伔' => '',
+'仱' => '',
+'伀' => '碪',
+'价' => '歎',
+'伈' => '',
+'伝' => '換',
+'伂' => '',
+'伅' => '',
+'伢' => '幁',
+'伓' => '',
+'伄' => '',
+'仴' => '',
+'伒' => '',
+'冱' => '渃',
+'刓' => '\\',
+'刉' => 'W',
+'刐' => '[',
+'劦' => '',
+'匢' => 'I',
+'匟' => '蕃',
+'卍' => 'd',
+'厊' => '|',
+'吇' => '',
+'囡' => '黽',
+'囟' => '媔',
+'圮' => '詘',
+'圪' => '詙',
+'圴' => 'V',
+'夼' => '畷',
+'妀' => 'j',
+'奼' => '瘙',
+'妅' => 'k',
+'奻' => 'f',
+'奾' => 'h',
+'奷' => 'd',
+'奿' => 'i',
+'孖' => 'I',
+'尕' => '箾',
+'尥' => '痹',
+'屼' => '',
+'屺' => '嶁',
+'屻' => '',
+'屾' => '',
+'巟' => 'x',
+'幵' => '',
+'庄' => '蚽',
+'异' => '祑',
+'弚' => 'w',
+'彴' => '',
+'忕' => '',
+'忔' => '',
+'忏' => '睼',
+'扜' => 'G',
+'扞' => '煽',
+'扤' => 'N',
+'扡' => '迍',
+'扦' => 'リ',
+'扢' => 'M',
+'扙' => 'E',
+'扠' => '脫',
+'扚' => 'F',
+'扥' => 'O',
+'旯' => '篧',
+'旮' => '篣',
+'朾' => 'b',
+'朹' => '_',
+'朸' => '^',
+'朻' => '`',
+'机' => '儂',
+'朿' => 'c',
+'朼' => 'a',
+'朳' => '[',
+'氘' => '諿',
+'汆' => '殙',
+'汒' => '',
+'汜' => '蝁',
+'汏' => '',
+'汊' => '蜾',
+'汔' => '蜬',
+'汋' => '',
+'汌' => '',
+'灱' => '',
+'牞' => '',
+'犴' => '摿',
+'犵' => '',
+'玎' => '諘',
+'甪' => '宸',
+'癿' => '逅',
+'穵' => '阼',
+'网' => '厙',
+'艸' => '翌',
+'艼' => '驟',
+'芀' => '鬢',
+'艽' => '傽',
+'艿' => '傿',
+'虍' => '糪',
+'襾' => '',
+'邙' => '絩',
+'邗' => '絫',
+'邘' => '',
+'邛' => '絒',
+'邔' => '',
+'阢' => '筎',
+'阤' => '邲',
+'阠' => '璚',
+'阣' => '砣',
+'佖' => '',
+'伻' => '',
+'佢\' => '',
+'佉' => '',
+'体' => '极',
+'佤' => '彘',
+'伾' => '',
+'佧' => '惢',
+'佒' => '',
+'佟' => '晼',
+'佁' => '',
+'佘' => '欿',
+'伭' => '',
+'伳' => '',
+'伿' => '',
+'佡' => '',
+'冏' => '',
+'冹' => '',
+'刜' => '_',
+'刞' => '`',
+'刡' => 'b',
+'劭' => '蛑',
+'劮' => '',
+'匉' => '體',
+'卣' => '寊',
+'卲' => 'p',
+'厎' => '簃',
+'厏' => '~',
+'吰' => '',
+'吷' => '',
+'吪' => '塚',
+'呔' => '葞',
+'呅' => '',
+'吙' => '',
+'吜' => '',
+'吥' => '',
+'吘' => '',
+'吽' => '',
+'呏' => '',
+'呁' => '',
+'吨' => '勣',
+'吤' => '',
+'呇' => '',
+'囮' => '',
+'囧' => '',
+'囥' => '',
+'坁' => '^',
+'坅' => 'a',
+'坌' => '覕',
+'坉' => 'd',
+'坋' => 'e',
+'坒' => 'f',
+'夆' => '',
+'奀' => 'C',
+'妦' => '~',
+'妘' => 'u',
+'妠' => '{',
+'妗' => '獢',
+'妎' => 'o',
+'妢' => '}',
+'妐' => 'q',
+'妏' => 'p',
+'妧' => '',
+'妡' => '|',
+'宎' => 'a',
+'宒' => 'd',
+'尨' => '',
+'尪' => '',
+'岍' => '嵷',
+'岏' => '',
+'岈' => '嶈',
+'岋' => '',
+'岉' => '',
+'岒' => '',
+'岊' => '嵿',
+'岆' => '',
+'岓' => '',
+'岕' => '',
+'巠' => 'y',
+'帊' => '',
+'帎' => '',
+'庋' => '瑵',
+'庉' => '',
+'庌' => '',
+'庈' => '',
+'庍' => '',
+'弅' => 'k',
+'弝' => 'y',
+'彸' => '',
+'彶' => '',
+'忒' => '蒍',
+'忑' => '檓',
+'忐' => '檎',
+'忭' => '碴',
+'忨' => '',
+'忮' => '瞂',
+'忳' => '',
+'忡' => '瞀',
+'忤' => '睯',
+'忣' => '摹',
+'忺' => '',
+'忯' => '',
+'忷' => '',
+'忻' => '陏',
+'怀' => '輒',
+'忴' => '',
+'戺' => '',
+'抃' => '皙',
+'抌' => 'b',
+'抎' => 'd',
+'抏' => 'e',
+'抔' => 'g',
+'抇' => '_',
+'扱' => 'Q',
+'扻' => 'X',
+'扺' => 'W',
+'扰' => '',
+'抁' => 'Z',
+'抈' => '`',
+'扷' => 'U',
+'扽' => 'Y',
+'扲' => 'R',
+'扴' => 'S',
+'攷' => '蕉',
+'旰' => '篝',
+'旴' => 'B',
+'旳' => 'A',
+'旲' => '@',
+'旵' => 'C',
+'杅' => 'f',
+'杇' => '訹',
+'杙' => 'p',
+'杕' => 'm',
+'杌' => '頠',
+'杈' => '颲',
+'杝' => 's',
+'杍' => 'j',
+'杚' => 'q',
+'杋' => 'i',
+'毐' => '',
+'氙' => '諯',
+'氚' => '諻',
+'汸' => 'P',
+'汧' => 'F',
+'汫' => 'G',
+'沄' => 'V',
+'沋' => 'Y',
+'沏' => 'み',
+'汱' => 'L',
+'汯' => 'K',
+'汩' => '蜒',
+'沚' => 'b',
+'汭' => 'I',
+'沇' => 'W',
+'沕' => '^',
+'沜' => 'c',
+'汦' => 'E',
+'汳' => 'M',
+'汥' => 'D',
+'汻\' => '銊',
+'沎' => '[',
+'灴' => '',
+'灺' => '',
+'牣' => '',
+'犿' => '',
+'犽' => '',
+'狃' => '摫',
+'狆' => '',
+'狁' => '摙',
+'犺' => '',
+'狅' => '',
+'玕' => '俞\',
+'玗' => '侷',
+'玓' => '俚',
+'玔' => '醝',
+'玒' => '係',
+'町' => '謜',
+'甹' => '屐',
+'疔' => '謧',
+'疕' => '浹',
+'皁' => '婂',
+'礽' => '痣',
+'耴' => '歸',
+'肕' => '騎',
+'肙' => '鬃',
+'肐' => '賄',
+'肒' => '餮',
+'肜' => '蹀',
+'芐' => '嗝',
+'芏' => '僆',
+'芅' => '鷹',
+'芎' => '傴',
+'芑' => '僈',
+'芓' => '齲',
+'芊' => '傮',
+'芃' => '鱟',
+'芄' => '僊',
+'豸' => '蘟',
+'迉' => '',
+'辿' => '煰',
+'邟' => '',
+'邡' => '絟',
+'邥' => '',
+'邞' => '',
+'邧' => '',
+'邠' => '',
+'阰' => '瘳',
+'阨' => '塌',
+'阯' => '硊',
+'阭' => '瘱',
+'丳' => 'P',
+'侘' => '',
+'佼' => '椪',
+'侅' => '',
+'佽' => '',
+'侀' => '',
+'侇' => '',
+'佶' => '晱',
+'佴' => '晹',
+'侉' => '晲',
+'侄' => '硍',
+'佷' => '',
+'佌' => '',
+'侗' => '雇',
+'佪' => '輔',
+'侚' => '',
+'佹' => '',
+'侁' => '',
+'佸' => '',
+'侐' => '',
+'侜' => '',
+'侔' => '棪',
+'侞' => '',
+'侒' => '',
+'侂' => '',
+'侕' => '',
+'佫' => '',
+'佮' => '',
+'冞' => '',
+'冼' => '湞',
+'冾' => '',
+'刵' => 'n',
+'刲' => 'l',
+'刳' => '嵃',
+'剆' => 's',
+'刱' => '斐',
+'劼' => '',
+'匊' => '碇',
+'匋' => '',
+'匼' => '\\',
+'厒' => '',
+'厔' => '',
+'咇' => '',
+'呿' => '',
+'咁' => '',
+'咑' => '',
+'咂' => '葅',
+'咈' => '',
+'呫' => '',
+'呺' => '',
+'呾' => '',
+'呥' => '',
+'呬' => '',
+'呴' => '',
+'呦' => '萹',
+'咍' => '',
+'呯' => '',
+'呡' => '',
+'呠' => '',
+'咘' => '',
+'呣' => '',
+'呧' => '畬',
+'呤' => '萯',
+'囷' => '',
+'囹' => '僗',
+'坯' => '矗',
+'坲' => 'u',
+'坭' => '貺',
+'坫' => '詌',
+'坱' => 't',
+'坰' => 's',
+'坶' => '貾',
+'垀' => '~',
+'坵' => '⑧',
+'坻' => '貁',
+'坳' => '貰',
+'坴' => 'v',
+'坢' => 'm',
+'坨' => '貀',
+'坽' => '{',
+'夌' => '',
+'奅' => 'E',
+'妵' => '',
+'妺' => '',
+'姏' => '',
+'姎' => '',
+'妲' => '瑽',
+'姌' => '',
+'姁' => '',
+'妶' => '',
+'妼' => '',
+'姃' => '',
+'姖' => '',
+'妱' => '',
+'妽' => '',
+'姀' => '',
+'姈' => '',
+'妴' => '',
+'姇' => '',
+'孢' => '糅',
+'孥' => '箯',
+'宓' => '撋',
+'宕' => '撏',
+'屄' => '',
+'屇' => '',
+'岮' => 'A',
+'岤\' => '',
+'岠' => '',
+'岵' => '幘',
+'岯' => 'B',
+'岨' => '',
+'岬' => '廘',
+'岟' => '',
+'岣' => '廎',
+'岭' => '鍛',
+'岢' => '幙',
+'岪' => '@',
+'岧' => '',
+'岝' => '',
+'岥' => 'ぞ',
+'岶' => 'F',
+'岰' => 'C',
+'岦' => '',
+'帗' => '',
+'帔' => '僬',
+'帙' => '僓',
+'弨' => '',
+'弢' => '|',
+'弣' => '}',
+'弤' => '~',
+'彔' => '翹',
+'徂' => '摶',
+'彾' => '鍥',
+'彽' => '',
+'忞' => '',
+'忥' => '',
+'怭' => 'P',
+'怦' => '碫',
+'怙' => '碨',
+'怲' => 'T',
+'怋' => 'B',
+'怴' => 'V',
+'怊' => '碤',
+'怗' => 'G',
+'怳' => '鉼',
+'怚' => 'I',
+'怞' => 'J',
+'怬' => 'O',
+'怢' => 'L',
+'怍' => '碠',
+'怐' => 'D',
+'怮' => 'Q',
+'怓' => 'F',
+'怑' => '﹜',
+'怌' => '輒',
+'怉' => 'A',
+'怜' => '蟒',
+'戔' => '磣',
+'戽' => '懨',
+'抭' => '狳',
+'抴' => '蚹',
+'拑' => 'ヵ',
+'抾' => '|',
+'抪' => 'p',
+'抶' => 'x',
+'拊' => '痽',
+'抮' => 'r',
+'抳' => 'v',
+'抯' => 's',
+'抻' => '痵',
+'抩' => 'o',
+'抰' => 't',
+'抸' => 'z',
+'攽' => '',
+'斨' => '',
+'斻' => '',
+'昉' => 'P',
+'旼' => 'G',
+'昄' => 'L',
+'昒' => 'U',
+'昈' => 'O',
+'旻' => 'F',
+'昃' => '篨',
+'昋' => 'Q',
+'昍' => 'R',
+'昅' => 'M',
+'旽' => 'H',
+'昑' => 'T',
+'昐' => 'S',
+'曶' => '',
+'朊' => '踼',
+'枅' => '',
+'杬' => 'z',
+'枎' => '',
+'枒' => '挩',
+'杶' => '~',
+'杻' => '',
+'枘' => '餗',
+'枆' => '',
+'构' => '凳',
+'杴' => '珌',
+'枍' => '',
+'枌' => '',
+'杺' => '',
+'枟' => '抴',
+'枑' => '',
+'枙' => '',
+'枃' => '',
+'杽' => '',
+'极' => '憤',
+'杸' => '',
+'杹' => '',
+'枔' => '',
+'欥' => '',
+'殀' => '堬',
+'歾' => '殪',
+'毞' => '蝣',
+'氝' => '',
+'沓' => '穖',
+'泬' => '',
+'泫' => '裺',
+'泮' => '裾',
+'泙' => '',
+'沶' => 'n',
+'泔' => '蜧',
+'沭' => '蜸',
+'泧' => '',
+'沷' => 'o',
+'泐' => '蜦',
+'泂' => 's',
+'沺' => 'p',
+'泃' => 't',
+'泆' => 'u',
+'泭' => '',
+'泲' => '',
+'泒' => '}',
+'泝' => '',
+'沴' => '咁',
+'沊' => 'X',
+'沝' => 'd',
+'沀' => 'U',
+'泞' => '籠',
+'泀' => 'q',
+'洰' => '',
+'泍' => 'y',
+'泇' => 'v',
+'沰' => 'k',
+'泹' => '',
+'泏' => '{',
+'泩' => '',
+'泑' => '|',
+'炔' => '',
+'炘' => '',
+'炅' => '篪',
+'炓' => '',
+'炆' => '',
+'炄' => '',
+'炑' => '',
+'炖' => '嚓',
+'炂' => '',
+'炚' => '',
+'炃' => '',
+'牪' => '',
+'狖\' => '',
+'狋' => '',
+'狘' => '',
+'狉' => '',
+'狜' => '昇',
+'狒' => '敳',
+'狔' => '',
+'狚' => '',
+'狌' => '倅',
+'狑' => '',
+'玤' => '剋',
+'玡' => '趜',
+'玭' => '南',
+'玦' => '勇',
+'玢' => '誽',
+'玠' => '削',
+'玬' => '匍',
+'玝' => '剎',
+'瓝' => '',
+'瓨' => '',
+'甿' => '疇',
+'畀' => '謓',
+'甾' => '諀',
+'疌' => '浚',
+'疘' => '誇',
+'皯' => '',
+'盳' => '崔',
+'盱' => '臅',
+'盰' => '崩',
+'盵' => '崙',
+'矸' => '窾',
+'矼' => '蛉',
+'矹' => '蛋',
+'矻' => '蚯',
+'矺' => '蚱',
+'矷' => '蛆',
+'祂' => '痘',
+'礿' => '痙',
+'秅' => '週',
+'穸' => '騅',
+'穻' => '',
+'竻' => '',
+'籵' => '褂',
+'糽' => '幢',
+'耵' => '嚪',
+'肏' => '餾',
+'肮' => '偎',
+'肣' => '魏',
+'肸' => '鵝',
+'肵' => '鯀',
+'肭' => '踿',
+'舠' => '彎',
+'芠' => '觀',
+'苀' => '矚',
+'芫' => '僁',
+'芚' => '籬',
+'芘' => '凗',
+'芛' => '籮',
+'芵' => '顱',
+'芧' => '鑲',
+'芮' => '剸',
+'芼' => '黌',
+'芞' => '蠻',
+'芺' => '髖',
+'芴' => '嗌',
+'芨' => '僄',
+'芡' => '嗐',
+'芩' => '嗛',
+'苂' => '讚',
+'芤' => '嗔',
+'苃' => '嗏',
+'芶' => '饞',
+'芢' => '躡\',
+'虰' => '沴',
+'虯' => '繵',
+'虭' => '泒',
+'虮' => '繸',
+'豖' => '傌',
+'迒' => '',
+'迋' => '',
+'迓' => '斳',
+'迍' => '',
+'迖' => '',
+'迕' => '暵',
+'迗' => '',
+'邲' => '',
+'邴' => '絎',
+'邯' => '漯',
+'邳' => '缾',
+'邰' => '菺',
+'阹' => '瘲',
+'阽' => '粢',
+'阼' => '粞',
+'阺' => '瘰',
+'陃' => '瞚',
+'俍' => 'Z',
+'俅' => '棷',
+'俓' => '\\',
+'侲' => 'E',
+'俉' => 'W',
+'俋' => 'X',
+'俁' => '棤',
+'俔' => ']',
+'俜' => '棶',
+'俙' => '`',
+'侻' => 'M',
+'侳' => 'F',
+'俛' => '萱',
+'俇' => 'U',
+'俖' => '_',
+'侺' => 'L',
+'俀' => 'Q',
+'侹' => 'K',
+'俬' => 'h',
+'剄' => '崷',
+'剉' => '黿',
+'勀' => '',
+'勂' => '',
+'匽' => ']',
+'卼' => 't',
+'厗' => '',
+'厖' => '',
+'厙' => '媋',
+'厘' => '濰',
+'咺' => 'I',
+'咡' => '',
+'咭' => '葒',
+'咥' => 'A',
+'哏' => '蛖',
+'哃' => 'L',
+'茍' => '',
+'咷' => '覛',
+'咮' => 'B',
+'哖' => 'P',
+'咶' => 'F',
+'哅' => 'M',
+'哆' => '嗑',
+'咠' => '',
+'呰' => '隊',
+'咼' => '葃',
+'咢' => '@',
+'咾' => 'K',
+'呲' => '葨',
+'哞' => '蛵',
+'咰' => 'C',
+'垵' => '跅',
+'垞' => '',
+'垟' => '',
+'垤' => '貵',
+'垌' => '趄',
+'垗' => '',
+'垝' => '',
+'垛' => '嗯',
+'垔' => '雱',
+'垘' => '',
+'垏' => '',
+'垙' => '',
+'垥\' => '',
+'垚' => '',
+'垕' => '',
+'壴' => '',
+'复' => '葩',
+'奓' => 'L',
+'姡' => '',
+'姞' => '',
+'姮' => '禢',
+'娀' => '',
+'姱' => '',
+'姝' => '甇',
+'姺' => '',
+'姽' => '',
+'姼' => '',
+'姶' => '',
+'姤' => '',
+'姲' => '',
+'姷' => '晪',
+'姛' => '',
+'姩' => '',
+'姳' => '',
+'姵' => '',
+'姠' => '',
+'姾' => '',
+'姴' => '',
+'姭' => '',
+'宨' => 'i',
+'屌' => '',
+'峐' => 'Y',
+'峘' => '`',
+'峌' => 'U',
+'峗' => '_',
+'峋' => '彄',
+'峛' => 'b',
+'峞' => 'e',
+'峚' => 'a',
+'峉' => 'S',
+'峇' => 'Q',
+'峊' => 'T',
+'峖' => '^',
+'峓' => '[',
+'峔' => '\\',
+'峏' => 'X',
+'峈' => 'R',
+'峆' => 'P',
+'峎' => 'W',
+'峟' => 'f',
+'峸' => 'w',
+'巹' => '筈',
+'帡' => '',
+'帢' => '',
+'帣' => '',
+'帠' => '',
+'帤' => '',
+'庰' => '',
+'庤' => '',
+'庢' => '',
+'庛' => '',
+'庣' => '',
+'庥' => '瑧',
+'弇' => 'm',
+'弮' => '',
+'彖' => '樘',
+'徆' => '',
+'怷' => 'X',
+'怹' => 'Z',
+'恔' => 'k',
+'恲' => 'y',
+'恞' => 'q',
+'恅' => '`',
+'恓' => 'j',
+'恇' => 'b',
+'恉' => '祤',
+'恛' => 'o',
+'恌' => '╰',
+'恀' => '^',
+'恂' => '禓',
+'恟' => 'r',
+'怤' => 'N',
+'恄' => '_',
+'恘' => 'n',
+'恦' => 'v',
+'恮' => 'w',
+'扂' => '',
+'扃' => '懞',
+'拏' => '',
+'挍' => '',
+'挋' => '',
+'拵' => '',
+'挎' => '踵',
+'挃' => '',
+'拫' => '',
+'拹' => '',
+'挏' => '',
+'挌' => '',
+'拸' => '',
+'拶' => '睟',
+'挀' => '',
+'挓' => '',
+'挔' => '',
+'拺' => '',
+'挕' => '',
+'拻' => '',
+'拰' => '',
+'敁' => '',
+'敃' => '',
+'斪' => '',
+'斿' => '',
+'昶' => '篟',
+'昡' => ']',
+'昲' => 'h',
+'昵' => '糒',
+'昜' => '[',
+'昦' => 'a',
+'昢' => '^',
+'昳' => 'i',
+'昫' => '懠',
+'昺' => '殺',
+'昝' => '篜',
+'昴' => '篫',
+'昹' => 'l',
+'昮' => 'f',
+'朏' => 'F',
+'朐' => '郺',
+'柁' => '魶',
+'柲' => '',
+'柈' => '',
+'枺' => '',
+'柜' => '嶄',
+'枻' => '',
+'柸' => '',
+'柘' => '駋',
+'柀' => '',
+'枷' => '樞',
+'柅' => '',
+'柫' => '',
+'柤' => '',
+'柟' => '撉',
+'枵' => '髳',
+'柍' => '',
+'枳' => '髱',
+'柷' => '',
+'柶' => '',
+'柮' => '',
+'柣' => '',
+'柂' => '',
+'枹' => '',
+'柎' => '',
+'柧' => '',
+'柰' => '駖',
+'枲' => '',
+'柼' => '',
+'柆' => '',
+'柭' => '',
+'柌' => '',
+'枮' => '',
+'柦\' => '',
+'柛' => '',
+'柺' => '',
+'柉' => '',
+'柊' => '',
+'柃' => '魧',
+'柪' => '',
+'柋' => '',
+'欨' => '',
+'殂' => '殫',
+'殄' => '毇',
+'殶' => '',
+'毖' => '敗',
+'毘' => '讒',
+'毠' => '蘌',
+'氠' => '',
+'氡' => '貑',
+'洨' => '',
+'洴' => '',
+'洭' => '',
+'洟' => '',
+'洼' => '俍',
+'洿' => '',
+'洒' => '',
+'洊' => '',
+'泚' => '',
+'洳' => '銌',
+'洄' => '銣',
+'洙' => '鋮',
+'洺' => '',
+'洚' => '銈',
+'洑' => '',
+'洀' => '',
+'洝' => '',
+'浂' => '',
+'洁' => '賞',
+'洘' => '',
+'洷' => '',
+'洃' => '',
+'洏' => '',
+'浀' => '',
+'洇' => '鉿',
+'洠' => '',
+'洬' => '',
+'洈' => '',
+'洢' => '',
+'洉' => '',
+'洐' => '',
+'炷' => '嚄',
+'炟' => '',
+'炾' => '',
+'炱' => '噾',
+'炰' => '',
+'炡' => '',
+'炴' => '',
+'炵' => '',
+'炩' => '',
+'牁' => '',
+'牉' => '',
+'牊' => '',
+'牬' => '',
+'牰' => '',
+'牳' => '',
+'牮' => '膴',
+'狊' => '',
+'狤' => '枕',
+'狨' => '斠',
+'狫' => '枇',
+'狟' => '朋',
+'狪' => '杷',
+'狦' => '果',
+'狣' => '枋',
+'玅' => '鏝',
+'珌' => '',
+'珂' => '豍',
+'珈' => '賚',
+'珅' => '咽',
+'玹' => '咨',
+'玶' => '咬',
+'玵' => '叛',
+'玴' => '厚',
+'珫' => '',
+'玿' => '咦',
+'珇' => '品',
+'玾' => '咸',
+'珃' => '哇',
+'珆' => '咪',
+'玸' => '哀',
+'珋' => '',
+'瓬' => '',
+'瓮' => '怤',
+'甮' => '射',
+'畇' => '峴',
+'畈' => '豰',
+'疧' => '烙',
+'疪' => '爹',
+'癹' => '迴',
+'盄' => '娼',
+'眈' => '艚',
+'眃' => '康',
+'眄' => '臇',
+'眅' => '庸',
+'眊' => '庵',
+'盷' => '崧',
+'盻' => '巢',
+'盺' => '崗',
+'矧' => '濿',
+'矨' => '莧',
+'砆' => '被',
+'砑' => '篲',
+'砒' => '罐',
+'砅' => '袈',
+'砐' => '訪',
+'砏' => '規',
+'砎' => '覓',
+'砉' => '竁',
+'砃' => '術',
+'砓' => '訝',
+'祊' => '皖',
+'祌' => '皴',
+'祋' => '皓',
+'祅' => '偯',
+'祄' => '痠',
+'秕' => '瀦',
+'种' => '笱',
+'秏' => '鄉',
+'秖' => '硐',
+'秎' => '郵',
+'窀' => '騆',
+'穾' => '',
+'竑' => '粵',
+'笀' => '',
+'笁' => '',
+'籺' => '裨',
+'籸' => '裸',
+'籹' => '衒',
+'籿' => '裯',
+'粀' => '誦',
+'粁' => '誌',
+'紃' => '廝',
+'紈' => '膣',
+'紁' => '廚',
+'罘' => '貔',
+'羑' => '袀',
+'羍' => '縹',
+'羾' => '',
+'耇' => '嬸',
+'耎' => '擴',
+'耏' => '擲',
+'耔' => '鼨',
+'耷' => '痯',
+'胘' => '懵',
+'胇' => '鼬',
+'胠' => '攏',
+'胑' => '眱',
+'胈' => '儳',
+'胂' => '輴',
+'胐\' => '寵',
+'胅' => '鼕',
+'胣' => '曝',
+'胙' => '遹',
+'胜' => '吨',
+'胊' => '郺',
+'胕' => '懶',
+'胉' => '嚥',
+'胏' => '壢',
+'胗' => '邆',
+'胦' => '櫥',
+'胍' => '遻',
+'臿' => '鶯',
+'舡' => '繾',
+'芔' => '雒',
+'苙' => '躪',
+'苾' => '',
+'苹' => 'し',
+'茇' => '嗏',
+'苨' => '鱷',
+'茀' => '',
+'苕' => '塍',
+'茺' => '媿',
+'苫' => '伒',
+'苖' => '讜',
+'苴' => '嗢',
+'苬' => '豔',
+'苡' => '嗄',
+'苲' => '驪',
+'苵' => '鸛',
+'茌' => '嗲',
+'苻' => '嗍',
+'苶' => '鸞',
+'苰' => '爨',
+'苪' => '黷',
+'苤' => '嗒',
+'苠' => '塏',
+'苺' => '',
+'苳' => '鬱',
+'苭' => '鑿',
+'虷' => '洰',
+'虴' => '沀',
+'虼' => '繯',
+'虳' => '沝',
+'衁' => '胑',
+'衎' => '胕',
+'衧' => '苫',
+'衪' => '苖',
+'衩' => '鯇',
+'觓' => '羖',
+'訄' => '',
+'訇' => '渟',
+'赲' => '焮\',
+'迣' => '',
+'迡' => '',
+'迮' => '暩',
+'迠' => '',
+'郱' => '貅',
+'邽' => '',
+'邿' => '',
+'郕' => '詷',
+'郅' => '菑',
+'邾' => '菪',
+'郇' => '菬',
+'郋' => '詶',
+'郈' => '觜',
+'釔' => '醢',
+'釓' => '醚',
+'陔' => '絘',
+'陏' => '瞜',
+'陑' => '瞛',
+'陓' => '瞣',
+'陊' => '瞝',
+'陎' => '瞡',
+'倞' => '',
+'倅' => 'y',
+'倇' => '{',
+'倓' => '',
+'倢' => '',
+'倰' => '',
+'倛' => '',
+'俵' => 'l',
+'俴' => 'k',
+'倳' => '',
+'倷' => '',
+'倬' => '椈',
+'俶' => '棆',
+'俷' => 'n',
+'倗' => '',
+'倜' => '棆',
+'倠' => '',
+'倧' => '',
+'倵' => '',
+'倯' => '',
+'倱' => '',
+'倎' => '',
+'党' => '絨',
+'冔' => '',
+'冓' => '',
+'凊' => '',
+'凄' => 'ぼ',
+'凅' => '',
+'凈' => '噱',
+'凎' => '',
+'剡' => '崵',
+'剚' => '',
+'剒' => 'z',
+'剞' => '崿',
+'剟' => '',
+'剕' => '|',
+'剢' => '',
+'勍' => '',
+'匎' => '',
+'厞' => '',
+'唦' => '~',
+'哢' => 'U',
+'唗' => 't',
+'唒' => 'p',
+'哧' => '蛸',
+'哳' => '蛶',
+'哤' => 'W',
+'唚' => '葸',
+'哿' => '衖',
+'唄' => '葺',
+'唈' => 'j',
+'哫' => 'X',
+'唑' => '裋',
+'唅' => '漪',
+'哱' => '\\',
+'唊' => 'k',
+'哻' => 'c',
+'哷' => '`',
+'哸' => 'a',
+'哠' => 'S',
+'唎' => 'o',
+'唃' => 'g',
+'唋' => 'l',
+'圁' => '',
+'圂' => '',
+'埌' => '',
+'堲' => '',
+'埕' => '跖',
+'埒' => '跙',
+'垺' => '',
+'埆' => '',
+'垽' => '',
+'垼' => '',
+'垸' => '跈',
+'垶' => '',
+'垿' => '',
+'埇' => '',
+'埐' => '',
+'垹' => '',
+'埁' => '',
+'夎' => '',
+'奊' => 'G',
+'娙' => '',
+'娖\' => '',
+'娭' => '',
+'娮' => '',
+'娕' => '',
+'娏' => '',
+'娗' => '',
+'娊' => '',
+'娞' => '',
+'娳' => '',
+'孬' => '堳',
+'宧' => 'h',
+'宭' => 'l',
+'宬' => 'k',
+'尃' => '',
+'屖' => '',
+'屔' => '',
+'峬' => 'm',
+'峿' => '}',
+'峮' => 'n',
+'峱' => 'p',
+'峷' => 'v',
+'崀' => '~',
+'峹' => 'x',
+'帩' => '',
+'帨' => '',
+'庨' => '',
+'庮' => '',
+'庪' => '',
+'庬' => '',
+'弳' => '殣',
+'弰' => '',
+'彧' => '',
+'恝' => '瞱',
+'恚' => '瞨',
+'恧' => '矰',
+'恁' => '磳',
+'悢' => '',
+'悈' => '',
+'悀' => '~',
+'悒' => '膍',
+'悁' => '',
+'悝' => '膃',
+'悃' => '膇',
+'悕' => '',
+'悛' => '膋',
+'悗' => '',
+'悇' => '',
+'悜' => '',
+'悎' => '',
+'戙' => '',
+'扆' => '',
+'拲' => '',
+'挐' => '',
+'捖' => '',
+'挬' => '匿',
+'捄' => '寰',
+'捅' => '舠',
+'挶' => '',
+'捃' => '睖',
+'揤' => 'V',
+'挹' => '睠',
+'捋' => '睒',
+'捊' => '',
+'挼' => '鑑',
+'挩' => '',
+'捁' => '',
+'挴' => '',
+'捘' => '',
+'捔' => '',
+'捙' => '',
+'挭' => '',
+'捇' => '',
+'挳' => '',
+'捚' => '',
+'捑' => '',
+'挸' => '',
+'捗' => '',
+'捀' => '',
+'捈' => '',
+'敊' => '',
+'敆' => '',
+'旆' => '鼒',
+'旃' => '儦',
+'旄' => '鼽',
+'旂' => 'よ',
+'晊' => 'y',
+'晟' => '糗',
+'晇' => 'v',
+'晑' => '}',
+'朒' => 'H',
+'朓' => 'I',
+'栟' => '',
+'栚' => '',
+'桉' => '黓',
+'栲' => '魰',
+'栳' => '魨',
+'栻' => '',
+'桋' => '',
+'桏' => '',
+'栖' => 'へ',
+'栱' => '',
+'栜' => '',
+'栵' => '',
+'栫' => '',
+'栭' => '',
+'栯' => '',
+'桎' => '鳼',
+'桄' => '鳽',
+'栴' => '',
+'栝' => '鴇',
+'栒' => '',
+'栔' => 'ゑ',
+'栦' => '',
+'栨' => '',
+'栮' => '',
+'桍' => '',
+'栺' => '',
+'栥' => '',
+'栠' => '',
+'欬' => '褥',
+'欯' => '@',
+'欭' => '',
+'欱' => 'B',
+'欴' => 'D',
+'歭' => 'l',
+'肂' => '額',
+'殈' => '~',
+'毦' => '',
+'毤' => '',
+'毨' => '',
+'毣' => '',
+'毢' => '',
+'毧' => '',
+'氥' => '',
+'浺' => '',
+'浣' => '雿',
+'浤' => '',
+'浶' => '',
+'洍' => '',
+'浡' => '',
+'涒' => '',
+'浘' => '',
+'浢' => '',
+'浭' => '',
+'浯' => '銧',
+'涑' => '銙',
+'涍' => '',
+'淯' => 'U',
+'浿' => '',
+'涆' => '',
+'浞' => '銩',
+'浧' => '',
+'浠' => '隞',
+'涗' => '',
+'浰' => '',
+'浼' => '隡',
+'浟' => '',
+'涂\' => '芨',
+'涘' => '',
+'洯' => '',
+'浨' => '',
+'涋' => '',
+'浾' => '',
+'涀' => '',
+'涄' => '',
+'洖' => '',
+'涃' => '',
+'浻' => '',
+'浽' => '',
+'浵' => '',
+'涐' => '',
+'烜' => '@',
+'烓' => '',
+'烑' => '',
+'烝' => 'A',
+'烋' => '',
+'缹' => '',
+'烢' => 'E',
+'烗' => '',
+'烒' => '',
+'烞' => 'B',
+'烠' => 'C',
+'烔' => '',
+'烍' => '',
+'烅' => '',
+'烆' => '',
+'烇' => '',
+'烚' => '',
+'烎' => '',
+'烡' => 'D',
+'牂' => '',
+'牸' => '',
+'牷' => '',
+'牶' => '',
+'猀' => '松',
+'狺' => '槉',
+'狴' => '朅',
+'狾' => '板',
+'狶' => '林',
+'狳' => '榱',
+'狻' => '漶',
+'猁' => '朢',
+'珓' => '',
+'珙' => '賧',
+'珥' => '賝',
+'珖' => '',
+'玼' => '哎',
+'珧' => '趛',
+'珣' => '',
+'珩' => '趡',
+'珜' => '',
+'珒' => '',
+'珛' => '',
+'珔' => '',
+'珝' => '',
+'珚' => '',
+'珗' => '',
+'珘' => '',
+'珨' => '',
+'瓞' => '藇',
+'瓟' => '',
+'瓴' => '窶',
+'瓵' => '唧',
+'甡' => '害',
+'畛' => '豲',
+'畟' => '',
+'疰' => '謷',
+'痁' => '班',
+'疻' => '狸',
+'痄' => '謱',
+'痀' => '玆',
+'疿' => '貗',
+'疶' => '狼',
+'疺' => '狽',
+'皊' => '酒',
+'盉' => '婚',
+'眝' => '恿',
+'眛' => '徠',
+'眐' => '彗',
+'眓' => '彫',
+'眒' => '彩',
+'眣' => '悠',
+'眑' => '彬',
+'眕' => '徙',
+'眙' => '薀',
+'眚' => '艜',
+'眢' => '薃',
+'眧' => '悴',
+'砣' => '篸',
+'砬' => '簁',
+'砢' => '訢',
+'砵' => '赦',
+'砯' => '貨',
+'砨' => '豚',
+'砮' => '貫',
+'砫' => '責',
+'砡' => '訛',
+'砩' => '篽',
+'砳' => '赧',
+'砪' => '販',
+'砱' => '貪',
+'祔' => '稍',
+'祛' => '斁',
+'祏' => '短',
+'祜' => '斀',
+'祓' => '斶',
+'祒' => '硯',
+'祑' => '硬',
+'秫' => '瀊',
+'秬' => '',
+'秠' => '鈇',
+'秮' => '',
+'秭' => '濼',
+'秪' => '閑',
+'秜' => '鈞',
+'秞' => '鈐',
+'秝' => '鈍',
+'窆' => '髀',
+'窉' => '',
+'窅' => '',
+'窋' => '',
+'窌' => '',
+'窊' => '',
+'窇' => '',
+'竘' => '絛',
+'笐' => '',
+'笄' => '鯪',
+'笓' => '',
+'笅' => '',
+'笏' => '鯤',
+'笈' => '鬌',
+'笊' => '鯠',
+'笎' => '',
+'笉' => '',
+'笒' => '',
+'粄' => '認',
+'粑' => '譠',
+'粊' => '',
+'粌' => '',
+'粈' => '',
+'粍' => '',
+'粅' => '誡',
+'紞' => '',
+'紝' => '',
+'紑' => '',
+'紎' => '慝',
+'紘' => '',
+'紖' => '',
+'紓' => '蝤',
+'紟' => '',
+'紒' => '',
+'紏' => '慕',
+'紌' => '慧',
+'罜' => '磷',
+'罡\' => '賹',
+'罞' => '磴',
+'罠' => '磯',
+'罝' => '磺',
+'罛' => '矯',
+'羖' => '翼',
+'羒' => '縯',
+'翃' => '',
+'翂' => '',
+'翀' => '',
+'耖' => '齌',
+'耾' => '濾',
+'耹' => '殯',
+'胺' => '健',
+'胲' => '醏',
+'胹' => '瀛',
+'胵' => '櫚',
+'脁' => '瀕',
+'胻' => '瀟',
+'脀' => '瀝',
+'舁' => '籊',
+'舯' => '翿',
+'舥' => '攤',
+'茳' => '嫈',
+'茭' => '媰',
+'荄' => 'ガ',
+'茙' => '',
+'荑' => '塯',
+'茥' => '',
+'荖' => 'ザ',
+'茿' => 'ォ',
+'荁' => 'オ',
+'茦' => '',
+'茜' => '塉',
+'茢' => '',
+'荂' => 'カ',
+'荎' => 'コ',
+'茛' => '摃',
+'茪' => '',
+'茈' => '塝',
+'茼' => '塥',
+'荍' => 'ゲ',
+'茖' => '塱',
+'茤' => '',
+'茠' => '',
+'茷' => '瑗',
+'茯' => '壼',
+'茩' => '',
+'荇' => '嫄',
+'荅' => 'キ',
+'荌' => '滕',
+'荓' => 'ゴ',
+'茞' => '',
+'茬' => '脩',
+'荋' => 'グ',
+'茧' => '潺',
+'荈' => 'ギ',
+'虓' => '潺',
+'虒' => '',
+'蚢' => '狒',
+'蚨' => '繲',
+'蚖' => '鞷',
+'蚍' => '繴',
+'蚑' => '炆',
+'蚞' => '狋',
+'蚇' => '泩',
+'蚗' => '炂',
+'蚆' => '泏',
+'蚋' => '繨',
+'蚚' => '羃',
+'蚅' => '紮',
+'蚥' => '閤',
+'蚙' => '炃',
+'蚡' => '罊',
+'蚧' => '羃',
+'蚕' => '紮',
+'蚘' => '炚',
+'蚎' => '炘',
+'蚝' => '罊',
+'蚐' => '炓',
+'蚔' => '炑',
+'衃' => '胂',
+'衄' => '繻',
+'衭' => '苴',
+'衵' => '茌',
+'衶' => '苻',
+'衲' => '鯆',
+'袀' => '',
+'衱' => '苡',
+'衿' => '鮿',
+'衯' => '苬',
+'袃' => '',
+'衾' => '蘉',
+'衴' => '苵',
+'衼' => '',
+'訒' => '',
+'豇' => '鐖',
+'豗' => '傎',
+'豻' => '喤',
+'貤' => '摿',
+'貣' => '',
+'赶' => '裒',
+'赸' => '裒',
+'趵' => '儺',
+'趷' => '',
+'趶' => '',
+'軑' => '',
+'軓' => '',
+'迾' => '',
+'迵' => '',
+'适' => '巠',
+'迿' => '巠',
+'迻' => '',
+'逄' => '樗',
+'迼' => '',
+'迶' => '',
+'郖' => '誂',
+'郠' => '詺',
+'郙' => '詵',
+'郚' => '誃',
+'郣' => '谼',
+'郟' => '菇',
+'郥' => '豊',
+'郘' => '誄',
+'郛' => '萛',
+'郗' => '菢',
+'郜' => '菗',
+'郤' => '豋',
+'酐' => '鐉',
+'酎' => '鏸',
+'酏' => '鐊',
+'釕' => '醟',
+'釢' => '',
+'釚' => '',
+'陜' => '',
+'陟' => '絯',
+'隼' => '鶺',
+'飣' => '鴸',
+'髟' => '奲',
+'鬯' => '袷',
+'乿' => 'v',
+'偰' => '',
+'偪' => '排',
+'偡' => '排',
+'偞' => '',
+'偠' => '',
+'偓' => '',
+'偋' => '',
+'偝' => '',
+'偲' => '',
+'偈' => '椋',
+'偍' => '',
+'偁' => '',
+'偛' => '',
+'偊' => '',
+'偢' => '礭',
+'倕' => '',
+'偅\' => '',
+'偟' => '',
+'偩' => '',
+'偫' => '',
+'偣' => '',
+'偤' => '',
+'偆' => '',
+'偀' => '',
+'偮' => '',
+'偳' => '',
+'偗' => '',
+'偑' => '',
+'凐' => '',
+'剫' => '',
+'剭' => '',
+'剬' => '',
+'剮' => '塵',
+'勖' => '袺',
+'勓' => '',
+'匭' => '寪',
+'厜' => '',
+'啵' => '逽',
+'啶' => '鄐',
+'唼' => '觤',
+'啍' => '',
+'啐' => '觥',
+'唴' => '',
+'唪' => '裎',
+'啑' => '',
+'啢' => '鄔',
+'唶' => '',
+'唵' => '',
+'唰' => '鄑',
+'啒' => '',
+'啅' => '',
+'唌' => 'm',
+'唲' => '',
+'啥' => '伅',
+'啎' => '',
+'唹' => '睯',
+'啈' => '',
+'唭' => '',
+'唻' => '',
+'啀' => '',
+'啋' => '',
+'圊' => '僛',
+'圇' => '僦',
+'埻' => '',
+'堔' => '',
+'埢' => '',
+'埶' => '',
+'埜' => '',
+'埴' => '跗',
+'堀' => '雈',
+'埭' => '雂',
+'埽' => '隀',
+'堈' => '',
+'埸' => '軯',
+'堋' => '隉',
+'埳' => '隉',
+'埏' => '趉',
+'堇' => '暌',
+'埮' => '',
+'埣' => '',
+'埲' => '',
+'埥' => '',
+'埬' => '',
+'埡' => '貹',
+'堎' => '',
+'埼' => '',
+'堐' => '',
+'埧' => '',
+'堁' => '商',
+'堌' => '',
+'埱' => '',
+'埩' => '',
+'埰' => '',
+'堍' => '隃',
+'堄' => '',
+'奜' => 'O',
+'婠' => '',
+'婘' => '',
+'婕' => '瞍',
+'婧' => '皞',
+'婞' => '',
+'娸' => '',
+'娵' => '',
+'婭' => '瑹',
+'婐' => '',
+'婟' => '皝',
+'婥' => 'C',
+'婬' => 'H',
+'婓' => '窋',
+'婤' => 'B',
+'婗' => '',
+'婃' => '',
+'婝' => '',
+'婒' => '',
+'婄' => '',
+'婛' => '',
+'婈' => '',
+'媎' => 'd',
+'娾' => '',
+'婍' => '',
+'娹' => '',
+'婌' => '',
+'婰' => 'L',
+'婩' => 'F',
+'婇' => '',
+'婑' => '',
+'婖' => '',
+'婂' => '',
+'婜' => '',
+'孲' => 'S',
+'孮' => 'Q',
+'寁' => 'v',
+'寀' => 'u',
+'屙' => '樇',
+'崞' => '慱',
+'崋' => '',
+'崝' => '',
+'崚' => '彃',
+'崠' => '幓',
+'崌' => '',
+'崨' => '',
+'崍' => '徶',
+'崦' => '愨',
+'崥' => '',
+'崏' => '',
+'崰' => '廕',
+'崒' => '',
+'崣' => '',
+'崟' => '',
+'崮' => '慁',
+'帾' => '',
+'帴' => '',
+'庱' => '',
+'庴' => '',
+'庹' => '甀',
+'庲' => '',
+'庳' => '畽',
+'弶' => '',
+'弸' => '',
+'徛' => '',
+'徖' => '',
+'徟' => '',
+'悊' => '',
+'悐' => '殍',
+'悆' => '',
+'悾' => '',
+'悰' => '',
+'悺' => '',
+'惓' => '',
+'惔' => '',
+'惏' => '',
+'惤' => '懋',
+'惙' => '╰',
+'惝\' => '蒡',
+'惈' => '蒡',
+'悱' => '蒤',
+'惛' => '',
+'悷' => '',
+'惊' => '儐',
+'悿' => '儐',
+'惃' => '',
+'惍' => '',
+'惀' => '',
+'挲' => '蕡',
+'捥' => '',
+'掊' => '碚',
+'掂' => '菽',
+'捽' => '',
+'掽' => '',
+'掞' => '癲',
+'掭' => '睚',
+'掝' => '',
+'掗' => '',
+'掫' => '',
+'掎' => '睙',
+'捯' => '',
+'掇' => '嗇',
+'掐' => 'ェ',
+'据' => '擂',
+'掯' => '擂',
+'捵' => '',
+'掜' => '痵',
+'捭' => '矠',
+'掮' => '碏',
+'捼' => '',
+'掤' => '鑑',
+'挻' => '',
+'掟' => '',
+'捸' => '',
+'掅' => '',
+'掁' => '',
+'掑' => '',
+'掍' => '',
+'捰' => '',
+'敓' => '',
+'旍' => '嗤',
+'晥' => '儥',
+'晡' => '縗',
+'晛' => '',
+'晙' => '',
+'晜' => '',
+'晢' => '',
+'朘' => 'K',
+'桹' => 'O',
+'梇' => '曙',
+'梐' => 'a',
+'梜' => 'k',
+'桭' => 'F',
+'桮' => 'G',
+'梮' => '戚',
+'梫' => 'v',
+'楖' => '',
+'桯' => 'H',
+'梣' => 'q',
+'梬' => 'w',
+'梩' => 't',
+'桵' => 'M',
+'桴' => '儓',
+'梲' => 'z',
+'梏' => '儜',
+'桷' => '儗',
+'梒' => 'c',
+'桼' => 'R',
+'桫' => '儑',
+'桲' => 'K',
+'梪' => 'u',
+'梀' => 'V',
+'桱' => 'J',
+'桾' => 'T',
+'梛' => 'j',
+'梖' => 'f',
+'梋' => ']',
+'梠' => 'o',
+'梉' => '[',
+'梤' => 'r',
+'桸' => 'N',
+'桻' => 'Q',
+'梑' => 'b',
+'梌' => '^',
+'梊' => '\\',
+'桽' => 'S',
+'欶' => 'F',
+'欳' => 'C',
+'欷' => '鴗',
+'欸' => 'G',
+'殑' => '',
+'殏' => '',
+'殍' => '氆',
+'殎' => '',
+'殌' => '',
+'氪' => '賵',
+'淀' => '蛭',
+'涫' => '韍',
+'涴' => '',
+'涳' => '',
+'湴' => '',
+'涬' => '',
+'淩' => 'R',
+'淢' => 'M',
+'涷' => '銂',
+'淶' => '鉾',
+'淔' => 'F',
+'渀' => '`',
+'淈' => '',
+'淠' => '鞂',
+'淟' => 'L',
+'淖' => '儷',
+'涾' => '',
+'淥' => '頖',
+'淜' => 'K',
+'淝' => '鞁',
+'淛' => 'J',
+'淴' => '涳',
+'淊' => '',
+'涽' => '敊',
+'淭' => 'T',
+'淰' => 'V',
+'涺' => '',
+'淕' => 'G',
+'淂' => '',
+'淏' => '傅',
+'淉' => '',
+'淐' => 'C',
+'淲' => 'W',
+'淓' => 'E',
+'淽' => ']',
+'淗' => 'H',
+'淍' => '@',
+'淣' => 'N',
+'涻' => '',
+'烺' => 'R',
+'焍' => 'b',
+'烷' => '俛',
+'焗' => 'h',
+'烴' => '泲',
+'焌' => 'a',
+'烰' => 'J',
+'焄' => '[',
+'烳' => 'M',
+'焐' => '嚁',
+'烼' => 'T',
+'烿' => 'V',
+'焆' => ']',
+'焓' => '壖',
+'焀' => 'W',
+'烸' => 'Q',
+'烶' => 'P',
+'焋' => '`',
+'焂' => 'Y',
+'焎' => 'c',
+'牾\' => '艕',
+'牻' => '',
+'牼' => '',
+'牿' => '艖',
+'猝' => '漰',
+'猗' => '潳',
+'猇' => '杼',
+'猑' => '氛',
+'猘' => '泳',
+'猊' => '漭',
+'猈' => '杪',
+'狿' => '枉',
+'猏' => '歿\',
+'猞' => '潀',
+'玈' => '俟',
+'珶' => '',
+'珸' => '拯',
+'珵' => '',
+'琄' => '春',
+'琁' => '施',
+'珽' => '霂',
+'琇' => '昭',
+'琀' => '斫',
+'珺' => '漪',
+'珼' => '挑',
+'珿' => '故',
+'琌' => '是',
+'琋' => '昧',
+'珴' => '',
+'琈' => '映',
+'畤' => '',
+'畣' => '',
+'痎' => '湘',
+'痒' => '欭',
+'痏' => '欭',
+'痋' => '珮\',
+'痌' => '珠',
+'痑' => '雯',
+'痐' => '畔',
+'皏' => '閤',
+'皉' => '郢',
+'盓' => '孰',
+'眹' => '',
+'眯' => '譜',
+'眭' => '薏',
+'眱' => '',
+'眲' => '',
+'眴' => '',
+'眳' => '',
+'眽' => '',
+'眥' => '薧',
+'眻' => '躺',
+'眵' => '薕',
+'硈' => '連',
+'硒' => '昮',
+'硉' => '速',
+'硍' => '逕',
+'硊' => '逝',
+'硌' => '縼',
+'砦' => '簊',
+'硅' => '寡',
+'硐' => '糨',
+'祤' => '',
+'祧' => '檥',
+'祩' => '',
+'祪' => '',
+'祣' => '窘',
+'祫' => '',
+'祡' => '標',
+'离' => '燭',
+'秺' => '燭',
+'秸' => '調',
+'秶' => '',
+'秷' => '',
+'窏' => '',
+'窔' => '',
+'窐' => '',
+'笵' => '道',
+'筇' => '鯦',
+'笴' => '遊',
+'笥' => '鯙',
+'笰' => '農',
+'笢' => '',
+'笤' => '鯥',
+'笳' => '鯕',
+'笘' => '',
+'笪' => '鯰',
+'笝' => '',
+'笱' => '鯬',
+'笫' => '鯞',
+'笭' => '',
+'笯' => '辟',
+'笲' => '運',
+'笸' => '鯢',
+'笚' => '',
+'笣' => '',
+'粔' => '',
+'粘' => '梜',
+'粖' => '',
+'粣' => '',
+'紵' => '',
+'紽' => '瘤',
+'紸' => '璀',
+'紶' => '',
+'紺' => '蝷',
+'絅' => '瞇',
+'紬' => '',
+'紩' => '喙',
+'絁' => '皚',
+'絇' => '瞑',
+'紾' => '瘦',
+'紿' => '蝒',
+'絊' => '磅',
+'紻' => '瘩',
+'紨' => '',
+'罣' => '礁',
+'羕' => '境',
+'羜' => '聳',
+'羝' => '蠑',
+'羛' => '聯',
+'翊' => '騑',
+'翋' => '',
+'翍' => '',
+'翐' => '',
+'翑' => '',
+'翇' => '',
+'翏' => '',
+'翉' => '',
+'耟' => '曜',
+'耞' => '斷',
+'耛' => '擻',
+'聇' => '燻',
+'聃' => '嚬',
+'聈' => '燼',
+'脘' => '錸',
+'脥' => '錸',
+'脙' => '',
+'脛' => '鄵',
+'脭' => '',
+'脟' => '',
+'脬' => '鍺',
+'脞' => '錏',
+'脡' => '',
+'脕' => '錸',
+'脧' => '',
+'脝' => '',
+'脢' => '',
+'舑' => '齧',
+'舸' => '臙',
+'舳' => '艨',
+'舺' => '瓤',
+'舴' => '艩',
+'舲' => '玀',
+'艴' => '氁',
+'莐' => 'ビ',
+'莣' => 'ミ',
+'莨' => '搮',
+'莍\' => 'パ',
+'荺' => 'ツ\',
+'荳' => '飪',
+'莤' => '飪',
+'荴' => 'ダ',
+'莏' => 'ヒ',
+'莁' => 'ト',
+'莕' => 'ブ',
+'莙' => '嫄',
+'荵' => 'チ',
+'莔' => 'フ',
+'莩' => '摀',
+'荽' => '搥',
+'莃' => 'ナ',
+'莌' => 'バ',
+'莝' => 'ホ',
+'莛' => '塣',
+'莪' => '搨',
+'莋' => 'ハ',
+'荾' => 'ヅ',
+'莥' => 'メ',
+'莯' => '',
+'莈' => 'ネ',
+'莗' => 'ヘ',
+'莰' => '搢',
+'荿' => 'テ',
+'莦' => 'モ',
+'莇' => 'ヌ',
+'莮' => 'ユ',
+'荶' => 'ヂ',
+'莚' => 'ペ',
+'虙' => '',
+'虖' => '撋',
+'蚿' => '網',
+'蚷' => '玦',
+'蛂' => '甾',
+'蛁' => '畀',
+'蛅' => '疘',
+'蚺' => '艣',
+'蚰' => '艡',
+'蛈' => '皯',
+'蚹' => '玠',
+'蚳' => '玭',
+'蚸' => '玢',
+'蛌' => '盳',
+'蚴' => '藡',
+'蚻' => '玬',
+'蚼' => '玝',
+'蛃' => '疌',
+'蚽' => '瓝',
+'蚾' => '瓨',
+'衒' => '胦',
+'袉' => '嚃',
+'袕' => '',
+'袨' => '',
+'袢' => '鮵',
+'袪' => '',
+'袚' => '',
+'袑' => '',
+'袡' => '',
+'袟' => '棉',
+'袘' => '蹠',
+'袧' => '',
+'袙' => '',
+'袛' => '',
+'袗' => '',
+'袤' => '湁',
+'袬' => '唊',
+'袌' => '',
+'袓' => '蠱',
+'袎' => '',
+'覂' => '',
+'觖' => '蘠',
+'觙' => '耖',
+'觕' => '翃',
+'訰' => '偋',
+'訧' => '髟',
+'訬' => '偡',
+'訞' => '酎',
+'谹' => '釷',
+'谻' => '釮',
+'豜' => '傒',
+'豝' => '傂',
+'豽' => '喌',
+'貥' => '',
+'赽' => '焟',
+'赻' => '焢',
+'赹' => '焣',
+'趼' => '劘',
+'跂' => '',
+'趹' => '',
+'趿' => '儹',
+'跁' => '',
+'軘' => '',
+'軞' => '',
+'軝' => '',
+'軜' => '',
+'軗' => '',
+'軠' => '',
+'軡' => '',
+'逤' => '窢',
+'逋' => '槥',
+'逑' => '樕',
+'逜' => '稐',
+'逌' => '',
+'逡' => '樠',
+'郯' => '菾',
+'郪' => '豤',
+'郰' => '貄',
+'郴' => '頂',
+'郲' => '賌',
+'郳' => '赨\',
+'郔' => '訿',
+'郫' => '菛',
+'郬' => '豦',
+'郩' => '豥',
+'酖' => '嘧',
+'酘' => '嘕',
+'酚' => '照',
+'酓' => '勫',
+'酕' => '厬',
+'釬' => '榑',
+'釴' => '爾',
+'釱' => '榩',
+'釳' => '榯',
+'釸' => '槔',
+'釤' => '醠',
+'釹' => '鎯',
+'釪' => '榬',
+'釫' => '榼',
+'釷' => '醡',
+'釨' => '榖',
+'釮' => '榎',
+'镺' => '墼',
+'閆' => '蒩',
+'閈' => '嬞\',
+'陼' => '',
+'陭' => '',
+'陫' => '',
+'陱' => '',
+'陯' => '',
+'隿' => '螘',
+'靪' => '魾',
+'頄' => '',
+'飥' => '',
+'馗' => '婺',
+'傛' => '',
+'傕' => '',
+'傔' => '',
+'傞' => '',
+'傋' => '',
+'傣' => '湯',
+'傃' => '',
+'傌' => '',
+'傎' => '鎬',
+'傝' => '',
+'偨' => '',
+'傜\' => '',
+'傒' => '撂',
+'傂' => '',
+'傇' => '',
+'兟' => '',
+'凔' => '',
+'匒' => 'A',
+'匑' => '@',
+'厤' => '',
+'厧' => '盪',
+'喑' => '鈳',
+'喨' => '',
+'喥' => '謠',
+'喭' => '',
+'啷' => '鄍',
+'噅' => 'j',
+'喢' => '',
+'喓' => '',
+'喈' => '鉈',
+'喏' => '裛',
+'喵' => '裚',
+'喁' => '鉒',
+'喣' => '',
+'喒' => '',
+'喤' => '',
+'啽' => '',
+'喌' => '',
+'喦' => '',
+'啿' => '旂',
+'喕' => '',
+'喡' => '',
+'喎' => '',
+'圌' => '葃',
+'堩' => '',
+'堷' => '',
+'堙' => '雱',
+'堞' => '雃',
+'堧' => '',
+'堣' => '',
+'堨' => '',
+'埵' => '',
+'塈' => 'I',
+'堥' => '',
+'堜' => '',
+'堛' => '',
+'堳' => '',
+'堿' => '澗',
+'堶' => '',
+'堮' => '',
+'堹' => '',
+'堸' => '',
+'堭' => '',
+'堬' => '',
+'堻' => '',
+'奡' => 'S',
+'媯' => '璉',
+'媔' => 'i',
+'媟' => 'r',
+'婺' => '磑',
+'媢' => 'u',
+'媞' => 'q',
+'婸' => 'P',
+'媦' => 'y',
+'婼' => 'S',
+'媥' => 'x',
+'媬' => '~',
+'媕' => 'j',
+'媮' => '',
+'娷' => '芚',
+'媄' => 'Z',
+'媊' => '`',
+'媗' => 'l',
+'媃' => 'Y',
+'媋' => 'a',
+'媩' => '|',
+'婻' => 'R',
+'婽' => 'T',
+'媌' => 'b',
+'媜' => 'o',
+'媏' => 'e',
+'媓' => 'h',
+'媝' => 'p',
+'寪' => '',
+'寍' => '|',
+'寋' => '{',
+'寔' => '',
+'寑' => '妗',
+'寊' => 'х',
+'寎' => '}',
+'尌' => '',
+'尰' => '',
+'崷' => '',
+'嵃' => '',
+'嵫' => '慥',
+'嵁' => '',
+'嵋' => '愻',
+'崿' => '',
+'崵' => '',
+'嵑' => '竀',
+'嵎' => '',
+'嵕' => '趶',
+'崳' => '慔',
+'崺' => '',
+'嵒' => '',
+'崽' => '憀',
+'崱' => '',
+'嵙' => '',
+'嵂' => '',
+'崹' => '',
+'嵉' => '',
+'崸' => '',
+'崼' => '',
+'崲' => '',
+'崶' => '',
+'嵀' => '',
+'嵅' => '',
+'幄' => '屣',
+'幁' => '',
+'彘' => '樥',
+'徦' => '',
+'徥' => '',
+'徫' => '',
+'惉' => '',
+'悹' => '',
+'惌' => '',
+'惢' => '',
+'惎' => '',
+'惄' => '',
+'愔' => '',
+'惲' => '聝',
+'愊' => '',
+'愖' => '',
+'愅' => '鹿',
+'惵' => '',
+'愓' => '',
+'惸' => '',
+'惼' => '娾',
+'惾' => '',
+'惁' => '',
+'愃' => '',
+'愘' => '',
+'愝' => '',
+'愐' => '',
+'惿' => '',
+'愄' => '',
+'愋' => '',
+'扊' => '',
+'掔' => '',
+'掱' => '',
+'掰' => '蕘',
+'揎' => '碙',
+'揥' => 'W',
+'揨' => 'Z',
+'揯' => '^',
+'揃' => 'B',
+'撝' => '',
+'揳' => 'a',
+'揊\' => 'F',
+'揠' => '碆',
+'揶' => '睩',
+'揕' => 'L',
+'揲' => '碕',
+'揵' => 'b',
+'摡' => '',
+'揟' => 'T',
+'掾' => '硻',
+'揝' => 'S',
+'揜' => 'R',
+'揄' => '碃',
+'揘' => 'N',
+'揓' => 'J',
+'揂' => 'A',
+'揇' => 'D',
+'揌' => 'H',
+'揋' => 'G',
+'揈' => 'E',
+'揰' => '箔',
+'揗' => 'M',
+'揙' => 'O',
+'攲' => '',
+'敧' => '',
+'敪' => '',
+'敤' => '',
+'敜' => '',
+'敨' => '',
+'敥' => '',
+'斌' => '棄',
+'斝' => '',
+'斞' => '',
+'斮' => '',
+'旐' => '簀',
+'旒' => '儤',
+'晼' => '',
+'晬' => '',
+'晻' => '',
+'暀' => '做',
+'晱' => '',
+'晹' => '',
+'晪' => '',
+'晲' => '',
+'朁' => '',
+'椌' => '',
+'棓' => '',
+'椄' => '',
+'棜' => '',
+'椪' => '',
+'棬' => '',
+'棪' => '',
+'棱' => '濩',
+'椏' => '魤',
+'棖' => '駍',
+'棷' => '﹜',
+'棫' => '勷',
+'棤' => '',
+'棶' => '',
+'椓' => '',
+'椐' => '擏',
+'棳' => '',
+'棡' => '',
+'椇' => '',
+'棌' => '',
+'椈' => '',
+'楰' => 'K',
+'梴' => '{',
+'椑' => '',
+'棯' => '',
+'棆' => '',
+'椔' => '',
+'棸' => '',
+'棐' => '',
+'棽' => '',
+'棼' => '叡',
+'棨' => '',
+'椋' => '憌',
+'椊' => '',
+'椗' => '縪',
+'棎' => '',
+'棈' => '',
+'棝' => '',
+'棞' => '',
+'棦' => '',
+'棴' => '',
+'棑' => '',
+'椆' => '',
+'棔' => '',
+'棩' => '',
+'椕' => '',
+'椥' => '',
+'棇' => '',
+'欹' => '鴠',
+'欻' => 'H',
+'欿' => 'K',
+'欼' => 'I',
+'殔' => '',
+'殗' => '',
+'殙' => '',
+'殕' => '',
+'殽' => '秎',
+'毰' => '',
+'毲' => '',
+'毳' => '諝',
+'氰' => 'я',
+'淼' => '穔',
+'湆' => '',
+'湇' => '',
+'渟' => 's',
+'湉' => '',
+'溈' => '蝂',
+'渼' => '',
+'渽' => '',
+'湅' => '',
+'湢' => '',
+'渫' => '颮',
+'渿' => '',
+'湁' => '',
+'湝' => '',
+'湳' => '',
+'渜' => 'q',
+'渳' => '}',
+'湋' => '',
+'湀' => '',
+'湑' => '',
+'渻' => '',
+'渃' => 'c',
+'渮' => '監',
+'湞' => '銗',
+'湨' => '',
+'湜' => '',
+'湡' => '',
+'渱' => '|',
+'渨' => 'w',
+'湠' => '',
+'湱' => '',
+'湫' => '餇',
+'渹' => '',
+'渢' => 't',
+'渰' => '{',
+'湓' => '馹',
+'湥' => '',
+'渧' => 'v',
+'湸' => '',
+'湤' => '',
+'湷' => '',
+'湕' => '',
+'湹' => '',
+'湒' => '',
+'湦' => '',
+'渵' => '~',
+'渶' => '',
+'湚' => '',
+'焠' => 'n',
+'焞' => 'l',
+'焯' => '壏',
+'烻' => 'S',
+'焮\' => '{',
+'焱' => '壒',
+'焣' => '陷',
+'焥' => 's',
+'焢' => 'p',
+'焲' => '|',
+'焟' => 'm',
+'焨' => 'u',
+'焺' => '',
+'焛' => 'i',
+'牋' => '潦',
+'牚' => '',
+'犈' => '',
+'犉' => '',
+'犆' => '',
+'犅' => '',
+'犋' => '蕖',
+'猒' => '泣',
+'猋' => '鴙',
+'猰' => '沸',
+'猢' => '漵',
+'猱' => '漅',
+'猳' => '油',
+'猧' => '波',
+'猲' => '泄',
+'猭' => '法',
+'猦' => '沼',
+'猣' => '沽',
+'猵' => '況',
+'猌' => '武',
+'琮' => '踦',
+'琬' => '踧',
+'琰' => '踙',
+'琫' => '枸',
+'琖' => '桮',
+'琚' => '鋡',
+'琡' => '柄',
+'琭' => '柏',
+'琱' => '蛐',
+'琤' => '枴',
+'琣' => '柑',
+'琝' => '枯\',
+'琩' => '查',
+'琠' => '柯',
+'琲' => '枰',
+'瓻' => '圃',
+'甯' => '撣',
+'畯' => '',
+'畬' => '豱',
+'痧' => '貙',
+'痚' => '疾',
+'痡' => '疽',
+'痦' => '謺',
+'痝' => '症',
+'痟' => '疲',
+'痤' => '豂',
+'痗' => '畚',
+'皕' => '釙',
+'皒' => '釗',
+'盚' => '寄',
+'睆' => '',
+'睇' => '蕻',
+'睄' => 'Ю',
+'睍' => '',
+'睅' => '',
+'睊' => '',
+'睎' => '',
+'睋' => '',
+'睌' => '',
+'矞' => '',
+'矬' => '瀀',
+'硠' => '',
+'硤' => '篱',
+'硥' => '',
+'硜' => '',
+'硭' => '篰',
+'硱' => '',
+'硪' => '繂',
+'确' => '',
+'硰' => '',
+'硩' => '',
+'硨' => '簅',
+'硞' => '',
+'硢' => '',
+'祴' => '',
+'祳' => '',
+'祲' => '',
+'祰' => '',
+'稂' => '爃',
+'稊' => '',
+'稃' => '燹',
+'稌' => '',
+'稄' => '',
+'窙' => '',
+'竦' => '騊',
+'竤' => '群',
+'筊' => '鄗',
+'笻' => '違',
+'筄' => '逾',
+'筈' => '鄒',
+'筌' => '鶈',
+'筎' => '酪',
+'筀' => '遏',
+'筘' => '鵷',
+'筅' => '鶊',
+'粢' => '譣',
+'粞' => '譨',
+'粨' => '',
+'粡' => '',
+'絘' => '窮',
+'絯' => '緩',
+'絣' => '締',
+'絓' => '穀\',
+'絖' => '膟',
+'絧' => '緘',
+'絪' => '編',
+'絏' => '蟡',
+'絭' => '緞',
+'絜' => '賞',
+'絫' => '緣',
+'絒' => '稼',
+'絔' => '稽',
+'絩' => '緝',
+'絑' => '稿',
+'絟' => '篁',
+'絎' => '蝚',
+'缾' => 'せ',
+'缿' => '',
+'罥' => '禪',
+'罦' => '穗',
+'羢' => '',
+'羠' => '臆',
+'羡' => '畈',
+'翗' => '',
+'聑' => '璧',
+'聏' => '獷',
+'聐' => '獵',
+'胾' => '瀨',
+'胔' => '懷',
+'腃' => '饅',
+'腊' => '幫',
+'腒' => '鯛',
+'腏' => '鯖',
+'腇' => '騙',
+'脽' => '',
+'腍' => '鯨',
+'脺' => '毯',
+'臦' => '霸',
+'臮' => '顧',
+'臷' => '髏\',
+'臸' => '魔',
+'臹' => '魑',
+'舄' => '籅',
+'舼' => '疊',
+'舽' => '癮',
+'舿' => '癬',
+'艵' => '靂',
+'茻\' => '',
+'菏' => '監',
+'菹' => '椿',
+'萣' => 'b',
+'菀' => '椹',
+'菨' => '',
+'萒' => 'T',
+'菧' => '',
+'菤' => '',
+'菼' => 'I',
+'菶' => 'E',
+'萐' => 'S',
+'菆' => '',
+'菈' => '',
+'菫' => '',
+'菣' => '',
+'莿' => '',
+'萁' => '斒',
+'菝' => '暋',
+'菥' => '旓',
+'菘' => '暆',
+'菿' => 'K',
+'菡' => '楙',
+'菋' => '',
+'菎' => '',
+'菖' => '暙',
+'菵' => 'D',
+'菉' => '蟯',
+'萉' => 'Q',
+'萏' => '楎',
+'菞' => '',
+'萑' => '朠',
+'萆' => '楦',
+'菂' => '',
+'菳' => 'B',
+'菕' => '',
+'菺' => 'G',
+'菇' => '厭',
+'菑' => '婐',
+'菪' => '楅',
+'萓' => 'U',
+'菃' => '',
+'菬' => '',
+'菮' => '@',
+'菄' => '',
+'菻' => 'H',
+'菗' => '',
+'菢' => '惕',
+'萛' => '\\',
+'菛' => '',
+'菾' => 'J',
+'蛘' => '藑',
+'蛢' => '',
+'蛦' => '',
+'蛓' => '盵',
+'蛣' => '奱',
+'蛚' => '矻',
+'蛪' => '',
+'蛝' => '',
+'蛫' => '',
+'蛜' => '矺',
+'蛬' => '',
+'蛩' => '藨',
+'蛗' => '矹',
+'蛨' => '',
+'蛑' => '藰',
+'衈' => '胣',
+'衖' => '讀',
+'衕' => '肮',
+'袺' => '標',
+'裗' => '娮',
+'袹' => '埌',
+'袸' => '圂',
+'裀' => '垽',
+'袾' => '垺',
+'袶' => '圁',
+'袼' => '鮶',
+'袷' => '鯓',
+'袽' => '埒',
+'袲' => '哠',
+'褁' => '庬',
+'裉' => '鯄',
+'覕' => '╰',
+'覘' => '膱',
+'覗' => '',
+'觝' => '萋',
+'觚' => '蘞',
+'觛' => '耾',
+'詎' => '琲',
+'詍' => '勖',
+'訹' => '倕',
+'詙' => '啢',
+'詀' => '偤',
+'詗' => '唪',
+'詘' => '痚',
+'詄' => '偳',
+'詅' => '偗',
+'詒' => '痡',
+'詈' => '蹎',
+'詑' => '啵',
+'詊' => '剭',
+'詌' => '剮',
+'詏' => '匭',
+'豟' => '兟',
+'貁' => '喡',
+'貀' => '喕',
+'貺' => '縔',
+'貾' => '愋',
+'貰' => '縍',
+'貹' => '愘',
+'貵' => '惼',
+'趄' => '鐍',
+'趀' => '焛',
+'趉' => '犅',
+'跘' => '羢',
+'跓' => '',
+'跍' => '',
+'跇' => '',
+'跖' => '嚽',
+'跜' => '羡',
+'跏' => '巏',
+'跕' => '罦',
+'跙' => '羠',
+'跈' => '',
+'跗' => '嚾',
+'跅' => '',
+'軯' => '寙',
+'軷' => '嵬',
+'軺' => '澥',
+'軹' => '澽',
+'軦' => '嫀',
+'軮' => '寘',
+'軥' => '媷',
+'軵' => '嵥',
+'軧' => '嫊',
+'軨' => '媴',
+'軶' => '濎',
+'軫' => '濊',
+'軱' => '尳',
+'軬' => '媐',
+'軴' => '嵊',
+'軩' => '媶',
+'逭' => '槢',
+'逴' => '筰',
+'逯' => '樛',
+'鄆' => '菮',
+'鄬' => '',
+'鄄' => '蛢',
+'郿' => '趔',
+'郼' => '趓',
+'鄈' => '跮',
+'郹' => '趎',
+'郻' => '趍',
+'鄁' => '缿',
+'鄀' => '趐',
+'鄇' => '跱',
+'鄅' => '跠',
+'鄃\' => '跰',
+'酡' => '鶔',
+'酤' => '鏿',
+'酟' => '嘏',
+'酢' => '鶠',
+'酠' => '嘜',
+'鈁' => '鍜',
+'鈊' => '歍',
+'鈥' => '鍐',
+'鈃' => '榗',
+'鈚' => '漮',
+'鈦' => '鍖',
+'鈏' => '毃',
+'鈌' => '殞',
+'鈀' => '鍑',
+'鈒' => '滎',
+'釿' => '榪',
+'釽' => '榳',
+'鈆' => 'レ',
+'鈄' => '鍉',
+'鈧' => '鍶',
+'鈂' => '槙\',
+'鈜' => '潎',
+'鈤' => '漊',
+'鈙' => '滻',
+'鈗' => '滸',
+'鈅' => '埥',
+'鈖' => '漥',
+'镻' => '壆',
+'閍' => '嶧',
+'閌' => '蒘',
+'閐' => '嶮',
+'隇' => '',
+'陾' => '',
+'隈' => '絪',
+'隉' => '絣',
+'隃' => '',
+'隀' => '',
+'雂' => '蝹',
+'雈' => '螣',
+'雃' => '螇',
+'雱' => '',
+'雰' => '煬',
+'靬' => '鮂',
+'靰' => '魺',
+'靮' => '鮒',
+'頇' => '嬿',
+'颩' => '餬',
+'飫' => '熏',
+'鳦' => '讈',
+'黹' => '臄',
+'亃' => 'z',
+'亄' => '{',
+'亶' => '',
+'傽' => '@',
+'傿' => 'B',
+'僆' => 'I',
+'傮' => '',
+'僄' => 'G',
+'僊' => '珈',
+'傴' => '嵅',
+'僈' => 'K',
+'僂' => '棎',
+'傰' => '',
+'僁' => 'D',
+'傺' => '棦',
+'傱' => '',
+'僋' => 'N',
+'僉' => '欼',
+'傶' => '',
+'傸' => '',
+'凗' => '',
+'剺' => '',
+'剸' => '',
+'剻' => '',
+'剼' => '',
+'嗃' => '',
+'嗛' => '',
+'嗌' => '鉓',
+'嗐' => '',
+'嗋' => '',
+'嗊' => '',
+'嗝' => '鈱',
+'嗀' => '',
+'嗔' => '鉡',
+'嗄' => '鉔',
+'嗩' => '蜍',
+'喿' => '',
+'嗒' => '鄋',
+'喍' => '',
+'嗏' => '',
+'嗕' => '',
+'嗢' => '',
+'嗖' => '鉦',
+'嗈' => '',
+'嗲' => '鉲',
+'嗍' => '鉌',
+'嗙' => '',
+'嗂' => '',
+'圔' => 'B',
+'塓' => 'Q',
+'塨' => 'b',
+'塤' => '跕',
+'塏' => '趀',
+'塍' => '鋹',
+'塉' => 'J',
+'塯' => 'g',
+'塕' => 'R',
+'塎' => 'M',
+'塝' => 'Y',
+'塙' => '',
+'塥' => '靰',
+'塛' => 'W',
+'堽' => '詳',
+'塣' => '^',
+'塱' => 'i',
+'壼' => '',
+'嫇' => '',
+'嫄' => '',
+'嫋' => '蘅',
+'媺' => '',
+'媸' => '磉',
+'媱' => '',
+'媵' => '鋷',
+'媰' => '',
+'媿' => '壕',
+'嫈' => '',
+'媻' => '',
+'嫆' => '',
+'媷' => '',
+'嫀' => '',
+'嫊' => '',
+'媴' => '',
+'媶' => '',
+'嫍' => '',
+'媹' => '',
+'媐' => 'f',
+'寖' => '輞',
+'寘' => '离',
+'寙' => '',
+'尟' => '珅',
+'尳' => '',
+'嵱' => '',
+'嵣' => '',
+'嵊' => '慪',
+'嵥' => '',
+'嵲' => '',
+'嵬' => '慴',
+'嵞' => '',
+'嵨' => '昶',
+'嵧' => '',
+'嵢' => '',
+'巰' => '裉',
+'幏' => '',
+'幎' => '蹶',
+'幊' => '',
+'幍' => '',
+'幋\' => '',
+'廅' => '',
+'廌' => 'D',
+'廆' => '@',
+'廋' => 'C',
+'廇' => 'A',
+'彀' => '麆',
+'徯' => '',
+'徭' => '撂',
+'惷' => '替',
+'慉' => 'A',
+'慊' => '蒚',
+'愫' => '蒪',
+'慅' => '',
+'愶' => '',
+'愲' => '',
+'愮' => '',
+'慆' => '',
+'愯' => '',
+'慏' => 'D',
+'愩' => '',
+'慀' => '',
+'戠' => '',
+'酨' => '嘂',
+'戣' => '',
+'戥' => '磠',
+'戤' => '禤',
+'揅' => 'C',
+'揱' => '`',
+'揫' => '噢',
+'搐' => '握',
+'搒' => '埤',
+'搉' => 'n',
+'搠' => '稑',
+'搤' => '塭',
+'搳' => '',
+'摃' => '蕈',
+'搟' => '{',
+'搕' => 't',
+'搘' => 'w',
+'搹' => '塭',
+'搷' => '',
+'搢' => '|',
+'搣' => '}',
+'搌' => '稘',
+'搦' => '稙',
+'搰' => '',
+'搨' => '阹',
+'摁' => '禂',
+'搵' => '',
+'搯' => '',
+'搊' => '昅',
+'搚' => 'y',
+'摀' => '拰',
+'搥' => '晰',
+'搧' => '圮',
+'搋' => '祽',
+'揧' => 'Y',
+'搛' => '祹',
+'搮' => '',
+'搡' => '稒',
+'搎' => 'q',
+'敯' => '',
+'斒' => '唯',
+'旓' => '',
+'暆' => '',
+'暌' => '縓',
+'暕' => '',
+'暐' => '',
+'暋' => '',
+'暊' => '',
+'暙' => '',
+'暔' => '',
+'晸' => '',
+'朠' => 'P',
+'楦' => '曏',
+'楟' => '',
+'椸' => '',
+'楎' => '',
+'楢' => 'A',
+'楱' => '擉',
+'椿' => '暑',
+'楅' => '',
+'楪' => 'G',
+'椹' => '撽',
+'楂' => '擃',
+'楗' => '擖',
+'楙' => '',
+'楺' => 'Q',
+'楈' => '',
+'楉' => '',
+'椵' => '',
+'楬' => 'H',
+'椳' => '',
+'椽' => '揪',
+'楥' => '曏',
+'棰' => '憸',
+'楸' => '敼',
+'椴' => '斢',
+'楩' => 'F',
+'楀' => '',
+'楯' => 'J',
+'楄' => '',
+'楶' => 'P',
+'楘' => '',
+'楁' => '',
+'楴' => 'N',
+'楌' => '',
+'椻' => '',
+'楋' => '',
+'椷' => '澎',
+'楜' => '',
+'楏' => '',
+'楑' => '',
+'椲' => '',
+'楒' => '',
+'椯' => '',
+'楻' => 'R',
+'椼' => '',
+'歆' => '鴔',
+'歅' => 'P',
+'歃' => '鴞',
+'歂' => 'N',
+'歈' => 'Q',
+'歁' => 'M',
+'殛' => '濋',
+'嗀' => 'A',
+'毻' => '',
+'毼' => '',
+'毹' => '諟',
+'毷' => '',
+'毸' => '',
+'溛' => '',
+'滖' => '',
+'滈' => '',
+'溏' => '儃',
+'滀' => '',
+'溟' => '僸',
+'溓' => '',
+'溔' => '',
+'溠' => '',
+'溱' => '骱',
+'溹' => '咁',
+'滆' => '',
+'滒' => '',
+'溽' => '魟',
+'滁' => '壹',
+'溞' => '',
+'滉' => '',
+'溷' => '鳲',
+'溰' => '馬',
+'滍' => '',
+'溦' => '',
+'滏' => '僿',
+'溲' => '馝',
+'溾' => '',
+'滃' => '',
+'滜\' => '',
+'滘' => '',
+'溙' => '',
+'溒' => '',
+'溎' => '',
+'溍' => '',
+'溤' => '',
+'溡' => '',
+'溿' => '',
+'溳' => '',
+'滐' => '',
+'滊' => '',
+'溗' => '',
+'溮' => '',
+'溣' => '',
+'煇' => '閩',
+'煔' => '',
+'煒' => '勴',
+'煣' => '',
+'煠' => '',
+'煁' => '',
+'煝' => '',
+'煢' => '塤',
+'煲' => '嬬',
+'煸' => '嬦',
+'煪' => '',
+'煡' => '',
+'煂' => '',
+'煘' => '',
+'煃' => '',
+'煋' => '',
+'煰' => '',
+'煟' => '',
+'煐' => '',
+'煓' => '',
+'煄' => '',
+'煍' => '',
+'煚' => '',
+'牏' => '',
+'犍' => '蕅',
+'犌' => '',
+'犑' => '',
+'犐' => '',
+'犎' => '',
+'猼' => '泱',
+'獂' => '泛',
+'猻' => '暟',
+'猺' => '泗',
+'獀' => '治',
+'獊' => '',
+'獉' => '暺',
+'瑄' => '泉',
+'瑊' => '洌',
+'瑋' => '誺',
+'瑒' => '',
+'瑑' => '',
+'瑗' => '镼',
+'瑀' => '毒',
+'瑏' => '',
+'瑐' => '',
+'瑎' => '',
+'瑂' => '毗',
+'瑆' => '洲',
+'瑍' => '洗',
+'瑔' => '',
+'瓡' => '',
+'瓿' => '窸',
+'瓾' => '埔',
+'瓽' => '埂',
+'甝' => '孫',
+'畹' => '豯',
+'畷' => '',
+'榃' => 'W',
+'痯' => '皰',
+'瘏' => '',
+'瘃' => '貘',
+'痷' => '真',
+'痾' => '謼',
+'痼' => '賾',
+'痹' => '敘',
+'痸' => '眠',
+'瘐' => '贂',
+'痻' => '矩',
+'痶' => '眩',
+'痭' => '疸',
+'痵' => '盎',
+'痽' => '砰',
+'皙' => '薵',
+'皵' => '',
+'盝' => '宿',
+'睕' => '',
+'睟' => '氫',
+'睠' => '樺',
+'睒' => '',
+'睖' => '',
+'睚' => '薚',
+'睩' => '淙',
+'睧' => '淳',
+'睔' => '',
+'睙' => '',
+'睭' => '淡',
+'矠' => '',
+'碇' => '縪',
+'碚' => '縸',
+'碔' => '富',
+'碏' => '孳',
+'碄' => '婷',
+'碕' => '寓',
+'碅' => '媚',
+'碆' => '婿',
+'碡' => '繀',
+'碃' => '',
+'硹' => '',
+'碙' => '尊',
+'碀' => '',
+'碖' => '寐',
+'硻' => '',
+'祼' => '',
+'禂' => '',
+'祽' => '',
+'祹' => '',
+'稑' => '',
+'稘' => '',
+'稙' => '',
+'稒' => '',
+'稗' => '啕',
+'稕' => '',
+'稢' => '嵩',
+'稓' => '',
+'稛' => '',
+'稐' => '',
+'窣' => '睹',
+'窢' => '',
+'窞' => '袼',
+'竫' => '腱',
+'筦' => '奪',
+'筤' => '鉋',
+'筭' => '呾',
+'筴' => '翎',
+'筩' => '芠',
+'筲' => '鶌',
+'筥' => '鉤',
+'筳' => '隔',
+'筱' => '鵽',
+'筰' => '隘',
+'筡' => '鈾\',
+'筸' => '雋',
+'筶' => '雍',
+'筣' => '鉛',
+'粲' => '譥',
+'粴' => '',
+'粯' => '',
+'綈' => '蝪',
+'綆' => '蝞',
+'綀' => '',
+'綍' => '蝔',
+'絿' => '',
+'綅\' => '',
+'絺' => '',
+'綎' => '',
+'絻' => '',
+'綃' => '蝭',
+'絼' => '',
+'綌' => '',
+'綔' => '',
+'綄' => '',
+'絽' => '',
+'綒' => '',
+'罭' => '篾',
+'罫' => '簇',
+'罧' => '窿',
+'罨' => '蹍',
+'罬' => '簍',
+'羦' => '臀',
+'羥' => '蠗',
+'羧' => '蠓',
+'翛' => '',
+'翜' => '',
+'耡' => '堝',
+'腤' => '嚷',
+'腠' => '錍',
+'腷' => '懺',
+'腜' => '鵬',
+'腩' => '鋋',
+'腛' => '鵪',
+'腢' => '勸',
+'腲' => '孽',
+'朡' => 'Q',
+'腞' => '麗',
+'腶' => '懸',
+'腧' => '錓',
+'腯' => '孃',
+'腄' => '饉',
+'腡' => '錆',
+'舝' => '巒',
+'艉' => '蘁',
+'艄' => '藿',
+'艀' => '',
+'艂' => '',
+'艅' => '',
+'蓱' => 'げ',
+'萿' => 'u',
+'葖' => '',
+'葶' => '楯',
+'葹' => '',
+'蒏' => '屮',
+'蒍' => '兀',
+'葥' => '',
+'葑' => '楈',
+'葀' => 'v',
+'蒆' => '亍',
+'葧' => '',
+'萰' => 'j',
+'葍' => '',
+'葽' => '乂',
+'葚' => '楉',
+'葙' => '椵',
+'葴' => '',
+'葳' => '楬',
+'葝' => '',
+'蔇' => '犵',
+'葞' => '',
+'萷' => 'е',
+'萺' => 'r',
+'萴' => 'm',
+'葺' => '楥',
+'葃' => 'y',
+'葸' => '楸',
+'萲' => 'k',
+'葅' => '{',
+'萩' => 'c',
+'菙' => '',
+'葋' => '',
+'萯' => 'i',
+'葂' => 'x',
+'萭' => 'g',
+'葟' => '',
+'葰' => '',
+'萹' => 'q',
+'葎' => '',
+'葌' => '',
+'葒' => '搹',
+'葯' => '狻',
+'蓅' => '宄',
+'蒎' => '楶',
+'萻' => 's',
+'葇' => '|',
+'萶' => 'o',
+'萳' => 'l',
+'葨' => '',
+'葾' => '殲',
+'葄' => 'z',
+'萫' => 'e',
+'葠' => '統',
+'葔' => '',
+'葮' => '楁',
+'葐' => '',
+'蜋' => '譖',
+'蜄' => '',
+'蛷' => '',
+'蜌' => '',
+'蛺' => '藚',
+'蛖' => '矼',
+'蛵' => '',
+'蝍' => '奓',
+'蛸' => '藞',
+'蜎' => '',
+'蜉' => '蠃',
+'蜁' => '',
+'蛶' => '',
+'蜍' => '蟺',
+'蜅' => '',
+'裖' => '娭',
+'裋' => '埐',
+'裍' => '埁',
+'裎' => '鮽',
+'裞' => '娞',
+'裛' => '娏',
+'裚' => '娕',
+'裌' => '標',
+'裐' => '奊',
+'覅' => '',
+'覛' => '',
+'觟' => '胺',
+'觥' => '騿',
+'觤' => '脁',
+'觡' => '胹',
+'觠' => '胲',
+'觢' => '胵',
+'觜' => '蘥',
+'触' => '揖',
+'詶' => '',
+'誆' => '痦',
+'詿' => '痟',
+'詡' => '睄',
+'訿' => '鬗',
+'詷' => '',
+'誂' => '崒',
+'誄' => '痝',
+'詵' => '皕',
+'誃' => '崣',
+'誁' => '崰',
+'詴' => '',
+'詺' => '',
+'谼' => '镺',
+'豋' => '飥',
+'豊' => '頄',
+'豥' => '厤',
+'豤' => '匑',
+'豦' => '厧',
+'貆' => '堩',
+'貄' => '圌',
+'貅' => '蘙',
+'賌' => '揶',
+'赨\' => '渵',
+'赩' => '渶',
+'趑' => '鐀',
+'趌' => '犋',
+'趎' => '猋',
+'趏' => '猰',
+'趍' => '猒',
+'趓' => '猳',
+'趔' => '鏵',
+'趐' => '猢',
+'趒' => '猱',
+'跰' => '錧',
+'跠' => '翗',
+'跬' => '攛',
+'跱' => '腇',
+'跮' => '腒',
+'跐' => '',
+'跩' => '腃',
+'跣' => '欃',
+'跢' => '聏',
+'跧' => '胔',
+'跲' => '脽',
+'跫' => '齞',
+'跴' => '笮',
+'輆' => '廇',
+'軿' => '幊',
+'輁' => '幋\',
+'輀' => '幍',
+'輅' => '澪',
+'輇' => '澬',
+'輈' => '徯',
+'輂' => '廅',
+'輋' => '慉',
+'遒' => '樧',
+'逿' => '粲',
+'遄' => '樝',
+'遉' => '淈',
+'逽' => '筣',
+'鄐' => '跴',
+'鄍' => '跧',
+'鄏' => '跫',
+'鄑' => '輆',
+'鄖' => '堌',
+'鄔' => '絑',
+'鄋' => '跣',
+'鄎' => '跲',
+'酮' => '耵',
+'酯' => '鶗',
+'鉈' => '鍞',
+'鉒' => '窬',
+'鈰' => '鍻',
+'鈺' => '鍠',
+'鉦' => '鍭',
+'鈳' => '鍌',
+'鉥' => '粿',
+'鉞' => '鍕',
+'銃' => '鴷',
+'鈮' => '鍧',
+'鉊' => '稫',
+'鉆' => '郰',
+'鉭' => '鍏',
+'鉬' => '鍒',
+'鉏' => '堝',
+'鉠' => '劄',
+'鉧' => '粺',
+'鉯' => '緅',
+'鈶' => '',
+'鉡' => '箙',
+'鉰' => '綝',
+'鈱' => '',
+'鉔' => '箈',
+'鉣' => '箂',
+'鉐' => '窨',
+'鉲' => '鼢',
+'鉎' => '稨',
+'鉓' => '竮',
+'鉌' => '稰',
+'鉖' => '箊',
+'鈲' => '',
+'閟' => '廥',
+'閜' => '廧',
+'閞' => '廨',
+'閛' => '廩',
+'隒' => '蕁',
+'隓' => '蕢',
+'隑' => '蕤',
+'隗' => '絭',
+'雎' => '鷈',
+'雺' => '',
+'雽' => '',
+'雸' => '',
+'雵' => '',
+'靳' => '輟',
+'靷' => '',
+'靸' => '',
+'靲' => '',
+'頏' => '幰',
+'頍' => '',
+'頎' => '巃',
+'颬' => '餲',
+'飶' => '',
+'飹' => '',
+'馯' => '鎒',
+'馲' => '鎝',
+'馰' => '鎷',
+'馵' => '鎎',
+'骭' => '醭',
+'骫' => '鄿',
+'魛' => '',
+'鳪' => '轤',
+'鳭' => '鑢',
+'鳧' => '溈',
+'麀' => '~',
+'黽' => '鶻',
+'僦' => '棩',
+'僔' => 'V',
+'僗' => 'X',
+'僨' => '棽',
+'僳' => '咇',
+'僛' => '[',
+'僪' => 'h',
+'僝' => ']',
+'僤' => 'd',
+'僓' => 'U',
+'僬' => '棔',
+'僰' => 'k',
+'僯' => 'j',
+'僣' => '椆',
+'僠' => '`',
+'凘' => '@',
+'劀' => '',
+'劁' => '崺',
+'勩' => '',
+'勫' => '',
+'匰' => 'S',
+'厬' => '',
+'嘧' => '雽',
+'嘕' => 'J',
+'嘌' => '隒',
+'嘒' => 'G',
+'嗼' => '',
+'嘏' => '媗',
+'嘜' => '蝍',
+'嘁' => '隓',
+'嘓' => 'H',
+'嘂' => '',
+'嗺' => '',
+'嘝' => 'P',
+'嘄' => '',
+'嗿' => '',
+'嗹' => '',
+'墉' => '颩',
+'塼' => 't',
+'墐' => '',
+'墘' => '',
+'墆' => 'y',
+'墁' => '頇',
+'塿\' => 'v',
+'塴' => '隉',
+'墋' => '}',
+'塺' => 'r',
+'墇' => 'z',
+'墑' => '圴',
+'墎' => '',
+'塶' => 'n',
+'墂' => 'w',
+'墈' => '{',
+'塻' => 's',
+'墔' => '',
+'墏' => '',
+'壾' => '',
+'奫' => '[',
+'嫜' => '歶',
+'嫮' => '',
+'嫥' => '',
+'嫕' => '',
+'嫪' => '',
+'嫚' => '',
+'嫭' => '',
+'嫫' => '磔',
+'嫳' => '',
+'嫢' => '',
+'嫠' => '禚',
+'嫛' => '',
+'嫬' => '',
+'嫞' => '',
+'嫝' => '',
+'嫙' => '',
+'嫨' => '',
+'嫟' => '',
+'孷' => '糒',
+'寠' => '',
+'寣' => '',
+'屣' => '樖',
+'嶂' => '戩',
+'嶀' => '',
+'嵽' => '',
+'嶆' => '',
+'嵺' => '',
+'嶁' => '慛',
+'嵷' => '',
+'嶊' => '',
+'嶉' => '',
+'嶈' => '',
+'嵾' => '統',
+'嵼' => '',
+'嶍' => '',
+'嵹' => '',
+'嵿' => '',
+'幘' => '僣',
+'幙' => '躉',
+'幓' => '',
+'廘' => 'L',
+'廑' => '瘈',
+'廗' => 'K',
+'廎' => 'F',
+'廜' => 'O',
+'廕' => '秭',
+'廙' => 'M',
+'廒' => '瘖',
+'廔' => 'I',
+'彄' => '',
+'彃' => '',
+'彯' => '',
+'徶' => '',
+'愬' => '咂',
+'愨' => '磻',
+'慁' => '',
+'慞' => 'P',
+'慱' => '_',
+'慳' => '膆',
+'慒' => 'F',
+'慓' => 'G',
+'慲' => '`',
+'慬' => '[',
+'憀' => 'l',
+'慴' => '扤',
+'慔' => 'H',
+'慺' => 'f',
+'慛' => 'N',
+'慥' => 'V',
+'愻' => '',
+'慪' => '睮',
+'慡' => 'S',
+'慖' => 'I',
+'戩' => '穄',
+'戧' => '磛',
+'戫' => '',
+'搫' => '唸',
+'摍' => '',
+'摛' => '',
+'摝' => '',
+'摴' => '',
+'摶' => '痭',
+'摲' => '',
+'摳' => '諭',
+'摽' => '',
+'摵' => '',
+'摦' => '',
+'撦' => '雀',
+'摎' => '',
+'撂' => '賻',
+'摞' => '稗',
+'摜' => '碄',
+'摋' => '',
+'摓' => '',
+'摠' => '',
+'摐' => '',
+'摿' => '',
+'搿' => '諢',
+'摬' => '',
+'摫' => '',
+'摙' => '',
+'摥' => '',
+'摷' => '',
+'敳' => '',
+'斠' => '',
+'暡' => '',
+'暠' => '',
+'暟' => '',
+'朅' => 'A',
+'朄' => '@',
+'朢' => '咡',
+'榱' => '橧',
+'榶' => 'y',
+'槉' => '',
+'榠' => 'i',
+'槎' => '曊',
+'榖' => 'b',
+'榰' => 'u',
+'榬' => 'r',
+'榼' => '}',
+'榑' => '_',
+'榙' => 'd',
+'榎' => '\\',
+'榧' => '曌',
+'榍' => '橶',
+'榩' => 'p',
+'榾' => '',
+'榯' => 't',
+'榿' => '鳿',
+'槄' => '',
+'榽' => '~',
+'榤' => 'm',
+'槔' => '橉',
+'榹' => '{',
+'槊' => '橨',
+'榚' => 'e',
+'槏' => '',
+'榳' => 'w',
+'榓' => 'a',
+'榪' => '餈',
+'榡' => 'j',
+'榞' => 'g',
+'槙\' => '',
+'榗' => 'c',
+'榐' => '^',
+'槂' => '',
+'榵' => 'x',
+'榥' => 'n',
+'槆' => '╰',
+'歊' => '╰',
+'歍' => 'T',
+'歋' => 'S',
+'殞' => '氄',
+'殟' => '',
+'殠' => '',
+'毃' => '',
+'毄' => '',
+'毾' => '',
+'滎' => '嫆',
+'滵' => 'D',
+'滱' => 'A',
+'漃' => 'P',
+'漥' => 'j',
+'滸' => '銊',
+'漷' => 't',
+'滻' => 'I',
+'漮' => 'o',
+'漉' => '勯',
+'潎' => '',
+'漙' => '`',
+'漚' => '鬚',
+'漧' => '補',
+'漘' => '_',
+'漻' => 'x',
+'漒' => '\\',
+'滭' => '',
+'漊' => 'U',
+'漶' => '儊',
+'潳' => '',
+'滹' => '儌',
+'滮' => '',
+'漭' => '鬾',
+'潀' => '|',
+'漰' => 'p',
+'漼' => 'y',
+'漵' => '駃',
+'滫' => '',
+'漇' => 'S',
+'漎' => 'Y',
+'潃' => '',
+'漅' => 'R',
+'滽' => 'K',
+'滶' => 'E',
+'漹' => 'v',
+'漜' => 'c',
+'滼' => 'J',
+'漺' => 'w',
+'漟' => 'f',
+'漍' => 'X',
+'漞' => 'e',
+'漈' => 'T',
+'漡' => 'g',
+'熇' => '',
+'熐' => '',
+'熉' => '',
+'熀' => '',
+'熅' => '',
+'熂' => '',
+'熏' => '悇',
+'煻' => '',
+'熆' => '',
+'熁' => '',
+'熗' => '嚌',
+'牄' => '',
+'牓' => '埤',
+'犗' => '',
+'犕' => '',
+'犓' => '',
+'獃' => '渭',
+'獍' => '滶',
+'獑' => '',
+'獌' => '',
+'瑢' => '',
+'瑳' => '',
+'瑱' => '',
+'瑵' => '',
+'瑲' => '',
+'瑧' => '',
+'瑮' => '',
+'甀' => '埋',
+'甂' => '堉',
+'甃' => '夏',
+'畽' => '貕',
+'疐' => '涌',
+'瘖' => '鈳',
+'瘈' => '',
+'瘌' => '蹢',
+'瘕' => '蹥',
+'瘑' => '',
+'瘊' => '蹗',
+'瘔' => '',
+'皸' => '鼥',
+'瞁' => '淵',
+'睼' => '混',
+'瞅' => '圍',
+'瞂' => '淅',
+'睮' => '淌',
+'瞀' => '謢',
+'睯' => '淤',
+'睾' => '媞',
+'瞃' => '淒',
+'碲' => '縩',
+'碪' => '涷',
+'碴' => '舂',
+'碭' => '竀',
+'碨' => '巽',
+'硾' => '',
+'碫' => '幀',
+'碞' => '就',
+'碥' => '縰',
+'碠' => '嵌',
+'碬' => '幃',
+'碢' => '篸',
+'碤' => '崴',
+'禘' => '診',
+'禊' => '檛',
+'禋' => '',
+'禖' => '詆',
+'禕' => '詐',
+'禔' => '詛',
+'禓' => '詔',
+'禗' => '訴',
+'禈' => '',
+'禒' => '',
+'禐' => '',
+'稫' => '徬',
+'穊' => '搓',
+'稰' => '感',
+'稯' => '慈',
+'稨' => '廈',
+'稦' => '幹',
+'窨' => '鬵',
+'窫' => '睨',
+'窬' => '鬩',
+'竮' => '腸',
+'箈' => '頑',
+'箜' => '鵯',
+'箊' => '頊',
+'箑' => '',
+'箐' => '鵫',
+'箖' => '',
+'箍' => '嘀',
+'箌' => '頌',
+'箛' => '',
+'箎' => '齁',
+'箅' => '鵻',
+'箘' => '',
+'劄' => '崥',
+'箙' => '',
+'箤\' => '',
+'箂' => '零',
+'粻' => '',
+'粿' => '劇',
+'粼' => '譧',
+'粺' => '啕',
+'綧' => '醃',
+'綷' => '閱\',
+'緂' => '頜',
+'綣' => '蝜',
+'綪' => '銷',
+'緁' => '頫',
+'緀' => '頡',
+'緅' => '餓',
+'綝' => '遭',
+'緎' => '駒',
+'緄' => '蝯',
+'緆' => '餒',
+'緋' => '蝟',
+'緌' => '駑',
+'綯' => '鋁',
+'綹' => '蝮',
+'綖' => '',
+'綼' => '靠',
+'綟' => '鄰',
+'綦' => '鐏',
+'綮' => '鐔',
+'綩' => '銻',
+'綡' => '鄧',
+'緉' => '駐',
+'罳' => '篠',
+'翢' => '',
+'翣' => '',
+'翥' => '醷',
+'翞' => '',
+'耤' => '檬',
+'聝' => '毳',
+'聜' => '',
+'膉' => '瀰',
+'膆' => '櫬',
+'膃' => '鋺',
+'膇' => '瀾',
+'膍' => '獻',
+'膌' => '爐',
+'膋' => '瀲',
+'舕' => '儻',
+'蒗' => '歆',
+'蒤' => '尐\',
+'蒡' => '椯',
+'蒟' => '厹',
+'蒺' => '椲',
+'蓎' => '庀',
+'蓂' => '夯',
+'蒬' => '丼',
+'蒮' => '仜',
+'蒫' => '丱',
+'蒹' => '楻',
+'蒴' => '椼',
+'蓁' => '楴',
+'蓍' => '楌',
+'蒪' => '爿',
+'蒚' => '仈',
+'蒱' => '仡',
+'蓐' => '椻',
+'蒝' => '勼',
+'蒧' => '殳',
+'蒻' => '卌',
+'蒢' => '夃',
+'蒔' => '搌',
+'蓇' => '尻',
+'蓌' => '帄',
+'蒛' => '冘',
+'蒩' => '椿',
+'蒯' => '嵎',
+'蒨' => '塉',
+'蓖' => '敝',
+'蒘' => '仉',
+'蒶' => '刌',
+'蓏' => '庂',
+'蒠' => '圠',
+'蓗' => '氕',
+'蓔' => '戉',
+'蓒' => '忉',
+'蓛' => '',
+'蒰' => '仩',
+'蒑' => '丏',
+'虡' => '',
+'蜳' => '垵',
+'蜣' => '蟶',
+'蜨' => '評',
+'蝫' => '宨',
+'蝀' => '垔',
+'蜮' => '蠋',
+'蜞' => '蠉',
+'蜡' => '嶸',
+'蜙' => '哃',
+'蜛' => '茍',
+'蝃' => '垙',
+'蜬' => '咢',
+'蝁' => '垘',
+'蜾' => '蟼',
+'蝆' => '垕',
+'蜠' => '哖',
+'蜲' => '咰',
+'蜪' => '呰',
+'蜭' => '咾',
+'蜼' => '垝',
+'蜒' => '旄',
+'蜺' => '巍',
+'蜱' => '蠊',
+'蜵' => '垞',
+'蝂' => '垏',
+'蜦' => '哅',
+'蜧' => '哆',
+'蜸' => '垤',
+'蜤' => '咶',
+'蜚' => '蠆',
+'蜰' => '哞',
+'蜑' => '粥',
+'裷' => '峹',
+'裧' => '宭',
+'裱' => '鵏',
+'裲' => '峱',
+'裺' => '帩',
+'裾' => '鵙',
+'裮' => '峿',
+'裼' => '鵛',
+'裶' => '崀',
+'裻' => '帨',
+'裰' => '鵖',
+'裬' => '屔',
+'裫' => '屖',
+'覝' => '',
+'覡' => '膮',
+'覟' => '',
+'覞' => '',
+'觩' => '舁',
+'觫' => '髍',
+'觨' => '脀',
+'誫' => '捥',
+'誙' => '悰',
+'誋' => '庴',
+'誒' => '睎',
+'誏' => '弶',
+'誖' => '聜',
+'谽' => '閆',
+'豨' => '喨',
+'豩' => '喥',
+'賕' => '翯',
+'賏' => '揵',
+'賗' => '揓',
+'趖' => '猲',
+'踉' => '灄',
+'踂' => '舄',
+'跿' => '臷',
+'踍' => '菹',
+'跽' => '灊',
+'踊\' => '蚖',
+'踃' => '舼',
+'踇' => '艵',
+'踆' => '舿',
+'踅' => '齝',
+'跾' => '臮',
+'踀' => '臸',
+'踄' => '舽',
+'輐' => '愲',
+'輑' => '愮',
+'輎' => '慅',
+'輍' => '愫',
+'鄣' => '蛣',
+'鄜' => '逿',
+'鄠' => '',
+'鄢' => '蛦',
+'鄟' => '',
+'鄝' => '',
+'鄚' => '輋',
+'鄤' => '',
+'鄡' => '',
+'鄛' => '遒',
+'酺' => '嗿',
+'酲' => '鶢',
+'酹' => '鶞',
+'酳' => '嘄',
+'銥' => '瓵',
+'銤' => '',
+'鉶' => '緌',
+'銛' => '',
+'鉺' => '闀',
+'銠' => '闇',
+'銔' => '',
+'銪' => '闉',
+'銍' => '',
+'銦' => '霠',
+'銚' => '鵂',
+'銫' => '鴾',
+'鉹' => '綖',
+'銗' => '',
+'鉿' => '鞜',
+'銣' => '翵',
+'鋮' => '闃',
+'銎' => '鶳',
+'銂' => '翢',
+'銕' => '沺',
+'銢' => '',
+'鉽' => '綮',
+'銈' => '',
+'銡' => '',
+'銊' => '',
+'銆' => '',
+'銌' => '',
+'銙' => '',
+'銧' => '',
+'鉾' => '綩',
+'銇' => '',
+'銩' => '霙',
+'銝' => '',
+'銋' => '',
+'鈭' => '',
+'隞' => '蕛',
+'隡' => '蕮',
+'雿' => '',
+'靘' => '骻',
+'靽' => '堅',
+'靺' => '',
+'靾' => '',
+'鞃' => '',
+'鞀' => '婸',
+'鞂' => '',
+'靻' => '',
+'鞄' => '',
+'鞁' => '鷞',
+'靿' => '',
+'韎' => '璐',
+'韍' => '璫',
+'頖' => '裾',
+'颭' => '餯',
+'颮' => '鴝',
+'餂' => '',
+'餀' => '',
+'餇' => '',
+'馝' => '蹔',
+'馜' => '蹩',
+'駃' => '鎴',
+'馹' => '鎕',
+'馻' => '鎙',
+'馺' => '鎈',
+'駂' => '鎨',
+'馽' => '鐠',
+'駇' => '闓',
+'骱' => '鷚',
+'髣' => '溘',
+'髧' => '',
+'鬾' => '獼',
+'鬿' => '璺',
+'魠' => '',
+'魡' => '',
+'魟' => '',
+'鳱' => '鑞',
+'鳲' => '韄',
+'鳵' => '藈',
+'麧' => '',
+'僿' => 'w',
+'儃' => '{',
+'儰' => '',
+'僸' => 'q',
+'儆' => '棑',
+'儇' => '椥',
+'僶' => 'o',
+'僾' => 'v',
+'儋' => '棇',
+'儌' => '衝',
+'僽' => 'u',
+'儊' => '',
+'劋' => '誼',
+'劌' => '嵫',
+'勱' => '蛗',
+'勯' => '',
+'噈' => 'm',
+'噂' => 'g',
+'噌' => '颬',
+'嘵' => '萶',
+'噁' => '填',
+'噊' => 'o',
+'噉' => '遉',
+'噆' => 'k',
+'噘' => '雵',
+'噚' => 'x',
+'噀' => 'e',
+'嘳' => ']',
+'嘽' => 'c',
+'嘬' => '靸',
+'嘾' => 'd',
+'嘸' => '葝',
+'嘪' => 'X',
+'嘺' => 'a',
+'圚' => 'H',
+'墫' => '',
+'墝' => '',
+'墱' => '',
+'墠' => '',
+'墣' => '',
+'墯' => '',
+'墬' => '',
+'墥' => '',
+'墡' => '',
+'壿' => '',
+'嫿' => '',
+'嫴' => '',
+'嫽' => '',
+'嫷' => '',
+'嫶' => '',
+'嬃' => '',
+'嫸' => '',
+'嬂' => '',
+'嫹\' => '',
+'嬁' => '',
+'嬇' => '',
+'嬅' => '',
+'嬏' => '',
+'屧' => '',
+'嶙' => '戧',
+'嶗' => '彯',
+'嶟' => '',
+'嶒' => '',
+'嶢' => 'A',
+'嶓' => '',
+'嶕' => '',
+'嶠' => '廔',
+'嶜' => '',
+'嶡' => '@',
+'嶚' => '',
+'嶞' => '',
+'幩' => '',
+'幝' => '',
+'幠' => '',
+'幜' => '',
+'緳' => '',
+'廛' => '瘌',
+'廞' => 'Q',
+'廡' => '瑱',
+'彉' => '',
+'徲' => '',
+'憋' => '梧',
+'憃' => '樼',
+'慹' => '簐',
+'憱' => '',
+'憰' => '',
+'憢' => '',
+'憉' => 'u',
+'憛' => '',
+'憓' => '需',
+'憯' => '',
+'憭' => '樼',
+'憟' => '',
+'憒' => '蒮',
+'憪' => '',
+'憡' => '',
+'憍' => 'x',
+'慦' => 'W',
+'憳' => '',
+'戭' => '',
+'摮' => '',
+'摰' => '',
+'撖' => '稓',
+'撠' => '',
+'撅' => '橘',
+'撗' => '',
+'撜' => '',
+'撏' => '',
+'撋' => '',
+'撊' => '',
+'撌' => '',
+'撣' => '筆',
+'撟' => '睕',
+'摨' => '',
+'撱' => '',
+'撘' => '減',
+'敶' => '淝',
+'敺' => '',
+'敹' => '',
+'敻' => '',
+'斲' => '簀',
+'斳' => '',
+'暵' => '殲',
+'暰' => '',
+'暩' => '',
+'暲' => '',
+'暷' => '',
+'暪' => '',
+'暯' => '',
+'樀' => '',
+'樆' => '',
+'樗' => '橚',
+'槥' => '',
+'槸' => '',
+'樕' => '',
+'槱' => '',
+'槤' => '',
+'樠' => '',
+'槿' => '橛',
+'槬' => '',
+'槢' => '',
+'樛' => '',
+'樝' => '',
+'槾' => '頇',
+'樧' => '',
+'槲' => '橁',
+'槮' => '',
+'樔' => '',
+'槷' => '',
+'槧' => '噠',
+'橀' => '',
+'樈' => '',
+'槦' => '',
+'槻' => '',
+'樍' => '',
+'槼' => '寞',
+'槫' => '',
+'樉' => '',
+'樄' => '',
+'樘' => '樻',
+'樥' => '',
+'樏' => '',
+'槶' => '',
+'樦' => '',
+'樇' => '',
+'槴' => '',
+'樖' => '',
+'歑' => 'X',
+'殥' => '',
+'殣' => '',
+'殢' => '',
+'殦' => '',
+'氁' => '',
+'氀' => '',
+'毿' => '諤',
+'氂' => '',
+'潁' => '礗',
+'漦' => 'k',
+'潾' => '',
+'澇' => '殮',
+'濆' => '',
+'澒' => '',
+'澍' => '噌',
+'澉' => '噂',
+'澌' => '嘵',
+'潢' => '儆',
+'潏' => '',
+'澅' => '',
+'潚' => '僶',
+'澖' => '',
+'潶' => '',
+'潬' => '戽',
+'澂' => '割',
+'潕' => '',
+'潲' => '噊',
+'潒' => '',
+'潐' => '',
+'潗' => '',
+'澔' => '瘋',
+'澓' => '',
+'潝' => '',
+'漀' => 'N',
+'潡' => '',
+'潫' => '',
+'潽' => '',
+'潧' => '',
+'澐' => '',
+'潓' => '',
+'澋' => '',
+'潩' => '',
+'潿\' => '銇',
+'澕' => '',
+'潣' => '',
+'潷' => '鳵',
+'潪' => '',
+'潻' => '',
+'熲' => '',
+'熯' => '',
+'熛' => '',
+'熰' => '',
+'熠' => '嶷',
+'熚' => '',
+'熩' => '',
+'熵' => '寱',
+'熝' => '',
+'熥' => '',
+'熞' => '',
+'熤' => '',
+'熡' => '',
+'熪' => '',
+'熜' => '',
+'熧' => '',
+'熳' => '孻',
+'犘' => '',
+'犚' => '',
+'獘' => '教',
+'獒' => '殧',
+'獞' => '',
+'獟' => '',
+'獠' => '漜',
+'獝' => '',
+'獛' => '',
+'獡' => '',
+'獚' => '',
+'獙' => '',
+'獢' => '',
+'璇' => '霂',
+'璉' => '踤',
+'璊' => '胡',
+'璆' => '⑩',
+'璁' => '霈',
+'瑽' => '耑',
+'璅' => '坱',
+'璈' => '胄',
+'瑼' => '耍',
+'瑹' => '',
+'甈' => '娑',
+'甇' => '奚',
+'畾' => '',
+'瘥' => '蹖',
+'瘞' => '蹠',
+'瘙' => '蹧',
+'瘝' => '',
+'瘜' => '',
+'瘣' => '',
+'瘚' => '',
+'瘨' => '騍',
+'瘛' => '鞢',
+'皜' => '簬',
+'皝' => '',
+'皞' => '',
+'皛' => '謋',
+'瞍' => '謅',
+'瞏' => '深',
+'瞉' => '淫',
+'瞈' => '淚\',
+'磍' => '惻',
+'碻' => '',
+'磏' => '慨',
+'磌' => '惰',
+'磑' => '惱',
+'磎' => '惴',
+'磔' => '縻',
+'磈' => '愕',
+'磃' => '惠',
+'磄' => '愜',
+'磉' => '繄',
+'禚' => '檡',
+'禡' => '貽',
+'禠' => '貳',
+'禜' => '象',
+'禢' => '賁',
+'禛' => '詖',
+'歶' => 'u',
+'稹' => '臐',
+'窲' => '碗',
+'窴' => '沓',
+'窳' => '魌',
+'箷' => '',
+'篋' => '鵵',
+'箾' => '',
+'箬' => '鵩',
+'篎' => '慚',
+'箯' => '',
+'箹' => '',
+'篊' => '慢',
+'箵' => '',
+'糅' => '轖',
+'糈' => '轙',
+'糌' => '躈',
+'糋' => '嘮',
+'緷' => '',
+'緛' => '',
+'緪' => '',
+'緧' => '',
+'緗' => '蝵',
+'緡' => '褋',
+'縃' => '澦',
+'緺' => '',
+'緦' => '衚',
+'緶' => '褅',
+'緱' => '褌',
+'緰' => '',
+'緮' => '',
+'緟' => '',
+'罶' => '糜',
+'羬' => '臨',
+'羰' => '襣',
+'羭' => '舉',
+'翭' => '鴿',
+'翫' => '俙',
+'翪' => '鮪',
+'翬' => '鴻',
+'翦' => '醲',
+'翨' => '喔',
+'聤' => '',
+'聧' => '',
+'膣' => '錩',
+'膟' => '籍',
+'膞' => '籃',
+'膕' => '礬',
+'膢' => '辮',
+'膙' => '競',
+'膗' => '竇',
+'舖' => 'と',
+'艏' => '蘛',
+'艓' => '',
+'艒' => '',
+'艐' => '',
+'艎' => '',
+'艑' => '',
+'蔤' => '佖',
+'蔻' => '煍',
+'蔏' => '艼',
+'蔀' => '',
+'蔩' => '佤',
+'蔎' => '艸',
+'蔉' => '甪',
+'蔍' => '网',
+'蔟' => '毻',
+'蔊' => '癿',
+'蔧' => '佉',
+'蔜' => '邛',
+'蓻' => '眙',
+'蔫' => '殲',
+'蓺' => '',
+'蔈' => '玎',
+'蔌\' => '歂',
+'蓴' => '搎',
+'蔪' => '伾',
+'蓲' => '',
+'蔕' => '菱',
+'蓷' => '',
+'蓫' => '',
+'蓳' => '',
+'蓼' => '牏',
+'蔒' => '艽',
+'蓪' => '',
+'蓩' => '',
+'蔖' => '襾',
+'蓾' => '',
+'蔨' => '体',
+'蔝' => '邔',
+'蔮' => '佒',
+'蔂' => '',
+'蓽' => '塎',
+'蔞' => '楄',
+'蓶' => '',
+'蔱' => '佘',
+'蔦' => '嗂',
+'蓧' => '',
+'蓨' => '搵',
+'蓰' => '殛',
+'蓯' => '嗃',
+'蓹' => '',
+'蔘' => '邙',
+'蔠' => '阤',
+'蔰' => '佁',
+'蔋' => '穵',
+'蔙' => '邗',
+'蔯' => '佟',
+'虢' => '踳',
+'蝖' => '姺',
+'蝣' => '譐',
+'蝤' => '譊',
+'蝷' => '',
+'蟡' => '毠',
+'蝳' => '賟',
+'蝘' => '姽',
+'蝔' => '姱',
+'蝛' => '姶',
+'蝒' => '娀',
+'蝡' => '',
+'蝚' => '姼',
+'蝑' => '姮',
+'蝞' => '潃',
+'蝭' => '峐',
+'蝪' => '姭',
+'蝐' => '姞',
+'蝎' => '衎',
+'蝟' => '漎',
+'蝝' => '姲',
+'蝯' => '堀',
+'蝬' => '屌',
+'蝺' => '',
+'蝮' => '覷',
+'蝜' => '姤',
+'蝥' => '譓',
+'蝏' => '姡',
+'蝻' => '襘',
+'蝵' => '峛',
+'蝢' => '姳',
+'蝧' => '姠',
+'蝩' => '姴',
+'衚' => '苙',
+'褅' => '彧',
+'褌' => '',
+'褔' => '',
+'褋' => '',
+'褗' => '',
+'褘' => '',
+'褙' => '鵗',
+'褆' => '恝',
+'褖' => '',
+'褑' => '',
+'褎' => '凈',
+'褉' => '',
+'覢' => '笄',
+'覤' => '笅',
+'覣' => '笓',
+'觭' => '茳',
+'觰' => '荄',
+'觬' => '舥',
+'諏' => '睋',
+'諆' => '',
+'誸' => '掐',
+'諓' => '',
+'諑' => '睌',
+'諔' => '',
+'諕' => '',
+'誻' => '捵',
+'諗' => '硠',
+'誾' => '掮',
+'諀' => '掤',
+'諅' => '',
+'諘' => '',
+'諃' => '',
+'誺' => '掯',
+'誽' => '捭',
+'諙' => '',
+'谾' => '閈',
+'豍' => '馗',
+'貏' => '',
+'賥' => '敨',
+'賟' => '揙',
+'賙' => '揇',
+'賨' => '斝',
+'賚' => '羱',
+'賝' => '銵',
+'賧' => '耩',
+'趠' => '琫',
+'趜' => '琮',
+'趡' => '琖',
+'趛' => '猌',
+'踠' => '莿',
+'踣' => '爚',
+'踥' => '菥',
+'踤' => '菝',
+'踮' => '爝',
+'踕' => '菤',
+'踛' => '菫',
+'踖' => '菼',
+'踑' => '菨',
+'踙' => '菆',
+'踦' => '菘',
+'踧' => '菿',
+'踔' => '灈',
+'踒' => '萒',
+'踘' => '萐',
+'踓' => '菧',
+'踜' => '菣',
+'踗' => '菶',
+'踚' => '菈',
+'輬' => '',
+'輤' => '',
+'輘' => '酨',
+'輚' => '戥',
+'輠' => '廓',
+'輣' => '搤',
+'輖' => '慀',
+'輗' => '戠',
+'遳' => '腜',
+'遰' => '菰',
+'遯' => '嗜',
+'遧' => '羦',
+'遫' => '翛',
+'鄯' => '蛪',
+'鄫' => '',
+'鄩' => '',
+'鄪' => '',
+'鄲' => '策',
+'鄦' => '勍',
+'鄮' => '',
+'醅' => '鶿',
+'醆\' => '桮',
+'醊' => '墋',
+'醁' => '墐',
+'醂' => '墘',
+'醄' => '墁',
+'醀' => '塼',
+'鋐' => '輎',
+'鋃' => '龠',
+'鋄' => '跽',
+'鋀' => '踉',
+'鋙' => '鄤',
+'銶' => '誒',
+'鋏' => '闅',
+'鋱' => '麉',
+'鋟' => '儱',
+'鋘' => '鄚',
+'鋩' => '',
+'鋗' => '鄝',
+'鋝' => '鼤',
+'鋌' => '霝',
+'鋯' => '黚',
+'鋂' => '擿',
+'鋨' => '黻',
+'鋊' => '踅',
+'鋈' => '鶲',
+'鋎' => '輐',
+'鋦' => '儭',
+'鋍' => '毿',
+'鋕' => '鄢',
+'鋉' => '踆',
+'鋠' => '銥',
+'鋞' => '酹',
+'鋧' => '銪',
+'鋑' => '輍',
+'鋓' => '鄜',
+'銵' => '麍',
+'鋡' => '銤',
+'鋆' => '踃',
+'銴' => '誙',
+'镼' => '嬗',
+'閬' => '蓔',
+'閫' => '蒠',
+'閮' => '',
+'閰' => '',
+'隤' => '虰',
+'隢' => '蕵',
+'雓' => '螄',
+'霅' => '',
+'霈' => '鰬',
+'霂' => '',
+'靚' => '鬖',
+'鞊' => '',
+'鞎' => '',
+'鞈' => '',
+'韐' => '璭',
+'韏' => '璪',
+'頞' => '薂',
+'頝' => '薢',
+'頦' => '礞',
+'頩' => '螭',
+'頨' => '螪',
+'頠' => '薅',
+'頛' => '薝',
+'頧' => '螾',
+'颲' => '馣',
+'餈' => '躄',
+'飺' => '',
+'餑' => '熗',
+'餔' => '癜',
+'餖' => '癙',
+'餗' => '癐',
+'餕' => '癤',
+'駜' => '',
+'駍' => '雟',
+'駏' => '雝',
+'駓' => '鞬',
+'駔' => '糈',
+'駎' => '雘',
+'駉' => '隳',
+'駖' => '鞫',
+'駘' => '緧',
+'駋' => '雚',
+'駗' => '鞤',
+'駌' => '巂',
+'骳' => '鏂',
+'髬' => '',
+'髫' => '彏',
+'髳' => '巘',
+'髲' => '',
+'髱' => '╰',
+'魆' => '皪',
+'魃' => '鼵',
+'魧' => '',
+'魴' => '鼚',
+'魱' => '',
+'魦' => '',
+'魶' => '',
+'魵' => '',
+'魰' => '',
+'魨' => '',
+'魤' => '',
+'魬' => '',
+'鳼' => '鱐',
+'鳺' => '鱒',
+'鳽' => '鱊',
+'鳿' => '鱋\',
+'鳷' => '鬞',
+'鴇' => '藈',
+'鴀' => '鱕',
+'鳹' => '鬠',
+'鳻' => '鱘',
+'鴈' => '栜',
+'鴅' => '鷷',
+'鴄' => '鷻',
+'麃' => '栥',
+'黓' => ']',
+'鼏' => '',
+'鼐' => '媥',
+'儜' => '',
+'儓' => '',
+'儗' => '',
+'儚' => '',
+'儑' => '',
+'凞' => 'D',
+'匴' => 'W',
+'叡' => '謑',
+'噰' => '',
+'噠' => '蒎',
+'噮' => '',
+'噳' => '',
+'噦' => '葄',
+'噣' => '',
+'噭' => '',
+'噲' => '葮',
+'噞' => '{',
+'噷' => '',
+'圜' => '僝',
+'圛' => 'I',
+'壈' => '',
+'墽' => '',
+'壉' => '',
+'墿' => '',
+'墺' => '',
+'壂' => '',
+'墼' => '觚',
+'壆' => '',
+'嬗' => '窲',
+'嬙' => '禠',
+'嬛' => '',
+'嬡' => '磃',
+'嬔' => '',
+'嬓' => '',
+'嬐' => '',
+'嬖' => '窴',
+'嬨' => '',
+'嬚' => '',
+'嬠' => '',
+'嬞\' => '',
+'寯' => '',
+'嶬' => 'K',
+'嶱' => 'P',
+'嶩' => 'H',
+'嶧' => '廙',
+'嶵' => 'T',
+'嶰' => 'O',
+'嶮' => '玸',
+'嶪' => 'I',
+'嶨' => '嵼',
+'嶲' => 'Q',
+'嶭' => 'L',
+'嶯' => 'N',
+'嶴' => '嵼',
+'幧' => '',
+'幨' => '',
+'幦' => '',
+'幯' => '',
+'廩' => '瘑',
+'廧' => 'Z',
+'廦' => 'Y',
+'廨' => '瘕',
+'廥' => 'X',
+'彋' => '',
+'徼' => '摜',
+'憝' => '磾',
+'憨' => '漫',
+'憖' => '',
+'懅' => '',
+'憴' => '',
+'懆' => '',
+'懁' => '',
+'懌' => '禘',
+'憺' => '',
+'憿' => '',
+'憸' => '',
+'憌' => 'w',
+'擗' => '艅',
+'擖' => '',
+'擐' => '艂',
+'擏' => 'э',
+'擉' => '',
+'撽' => '',
+'撉' => '',
+'擃' => '',
+'擛' => '@',
+'擳' => 'T',
+'擙' => '',
+'攳' => '',
+'敿' => '',
+'敼' => '',
+'斢' => '',
+'曈' => '',
+'暾' => '縕',
+'曀' => '',
+'曊' => '',
+'曋' => '',
+'曏' => '砃',
+'暽' => '',
+'暻' => '',
+'暺' => '',
+'曌' => '',
+'朣' => 'S',
+'樴' => '',
+'橦' => 'H',
+'橉' => '',
+'橧' => 'I',
+'樲' => '',
+'橨' => 'J',
+'樾' => '橤',
+'橝' => 'A',
+'橭' => 'O',
+'橶' => 'W',
+'橛' => '橔',
+'橑' => '',
+'樨' => '橞',
+'橚' => '',
+'樻' => '',
+'樿' => '',
+'橁' => '',
+'橪' => 'L',
+'橤' => '',
+'橐' => '橏',
+'橏' => '',
+'橔' => '',
+'橯' => 'Q',
+'橩' => 'K',
+'橠' => 'D',
+'樼' => '',
+'橞' => 'B',
+'橖' => '',
+'橕' => '傅',
+'橍' => '',
+'橎' => '',
+'橆' => '拸',
+'歕' => '\\',
+'歔' => '[',
+'歖' => ']',
+'殧' => '',
+'殪' => '濇',
+'殫' => '澭',
+'毈' => '',
+'毇' => '',
+'氄' => '',
+'氃' => '',
+'氆' => '諞',
+'澭' => '',
+'濋' => '',
+'澣' => '雿',
+'濇' => '优',
+'澼' => '',
+'濎' => '',
+'濈' => '',
+'潞' => '繙',
+'濄' => '',
+'澽' => '',
+'澞' => '',
+'濊' => '魁',
+'澨' => '',
+'瀄' => '\\',
+'澥' => '',
+'澮' => '銕',
+'澺' => '',
+'澬' => '',
+'澪' => '',
+'濏' => '',
+'澿' => '',
+'澸' => '',
+'澢' => '',
+'濉' => '憛',
+'澫' => '驕',
+'濍' => '',
+'澯' => '',
+'澲' => '',
+'澰' => '',
+'燅' => '',
+'燂' => '',
+'熿' => '銓',
+'熸' => '',
+'燖' => '@',
+'燀' => '',
+'燁' => '嚂',
+'燋' => '',
+'燔' => '幪',
+'燊' => '',
+'燇' => '',
+'燏' => '',
+'熽' => '',
+'燘' => 'B',
+'熼' => '',
+'燆' => '',
+'燚' => 'D',
+'燛' => 'E',
+'犝' => '',
+'犞' => '',
+'獩' => '',
+'獦\' => '',
+'獧' => '朄',
+'獬' => '滼',
+'獥' => '',
+'獫' => '榶',
+'獪' => '暡',
+'瑿' => '耶',
+'璚' => '⑤',
+'璠' => '茉',
+'璔' => '舢',
+'璒' => '胝',
+'璕' => '苧',
+'璡' => '苒\',
+'甋' => '娟',
+'疀' => '',
+'瘯' => '',
+'瘭' => '韘',
+'瘱' => '',
+'瘽' => '',
+'瘳' => '饁',
+'瘼' => '鞥',
+'瘵' => '顑',
+'瘲' => '',
+'瘰' => '韺',
+'皻' => '觾',
+'盦' => '屝',
+'瞚' => '烹',
+'瞝' => '烽',
+'瞡' => '爽',
+'瞜' => '焊',
+'瞛' => '焉',
+'瞢' => '獂',
+'瞣' => '牽',
+'瞕' => '淄',
+'瞙' => '淦',
+'瞗' => '淬',
+'磝' => '掌',
+'磩' => '',
+'磥' => '濠',
+'磪' => '',
+'磞' => '描',
+'磣' => '繉',
+'磛' => '扉',
+'磡' => '揉',
+'磢' => '揆',
+'磭' => '',
+'磟' => '繕',
+'磠' => '揩',
+'禤' => '賀',
+'穄' => '愍',
+'穈' => '戡',
+'穇' => '愷',
+'窶' => '魊',
+'窸' => '碑',
+'窵' => '碌',
+'窱' => '碰',
+'窷' => '硼',
+'篞' => '摑',
+'篣' => '摻',
+'篧' => '斡',
+'篝' => '黀',
+'篕' => '摘',
+'篥' => '鼭',
+'篚' => '黼',
+'篨' => '旗',
+'篹' => '榷',
+'篔' => '撇',
+'篪' => '齁',
+'篢' => '摭',
+'篜' => '摺',
+'篫' => '暢',
+'篘' => '摸',
+'篟' => '摧',
+'糒' => '嘴',
+'糔' => '噓',
+'糗' => '轗',
+'糐' => '嘲',
+'糑' => '嘿',
+'縒' => '獨',
+'縡' => '瞞',
+'縗' => '璞\',
+'縌' => '熹',
+'縟' => '褙',
+'縠' => '瞠',
+'縓' => '璜',
+'縎' => '燙',
+'縜' => '瘸',
+'縕' => '璘',
+'縚' => '昐',
+'縢' => '瞟',
+'縋' => '褔',
+'縏' => '燜',
+'縖' => '璟',
+'縍' => '燎',
+'縔' => '璣',
+'縥' => '磚',
+'縤' => '磨',
+'罃' => '',
+'罻' => '糙',
+'罼' => '救',
+'罺' => '糟',
+'羱' => '薪',
+'翯' => '麋',
+'耪' => '纓',
+'耩' => '嚫',
+'聬' => '',
+'膱' => '',
+'膦' => '鮈',
+'膮' => '',
+'膹' => '',
+'膵' => '',
+'膫' => '',
+'膰' => '',
+'膬' => '毯',
+'膴' => '',
+'膲' => '',
+'膷' => '',
+'膧' => '',
+'臲' => '驃',
+'艕' => '',
+'艖' => '',
+'艗' => '',
+'蕖' => '煄',
+'蕅' => '匉',
+'蕫' => '',
+'蕍' => '吰',
+'蕓' => '傺',
+'蕡' => '',
+'蕘' => '塕',
+'蕀' => '刞',
+'蕆' => '椳',
+'蕤' => '犐',
+'蕁' => '搳',
+'蕢' => '棰',
+'蕄' => '劮',
+'蕑' => '呅',
+'蕇' => '卲',
+'蕣' => '',
+'蔾' => '冹',
+'蕛' => '',
+'蕱' => '',
+'蕎' => '塱',
+'蕮' => '',
+'蕵' => '',
+'蕕' => '搧',
+'蕧' => '',
+'蕠' => '',
+'薌' => '僂',
+'蕦' => '',
+'蕝' => '',
+'蕔' => '吥',
+'蕥' => '',
+'蕬' => '',
+'虣' => '',
+'虥' => '',
+'虤' => '',
+'螛' => '',
+'螏\' => '',
+'螗' => '韞',
+'螓' => '譖',
+'螒' => '',
+'螈' => '鞷',
+'螁' => '',
+'螖' => '',
+'螘' => '眐',
+'蝹' => '',
+'螇' => '',
+'螣' => '',
+'螅' => '鞶',
+'螐' => '',
+'螑' => '',
+'螝' => '',
+'螄' => '藲',
+'螔' => '',
+'螜' => '',
+'螚' => '',
+'螉' => '',
+'褞' => '',
+'褦' => '',
+'褰' => '敶',
+'褭' => '蘅',
+'褮' => '',
+'褧' => '',
+'褱' => '',
+'褢' => '',
+'褩' => '',
+'褣' => '',
+'褯' => '',
+'褬' => '',
+'褟' => '',
+'觱' => '茙',
+'諠' => '唈',
+'諢' => '睇',
+'諲' => '烰',
+'諴' => '烳',
+'諵' => '焐',
+'諝' => '',
+'謔' => '硱',
+'諤' => '确',
+'諟' => '',
+'諰' => '烴',
+'諈' => '',
+'諞' => '祴',
+'諡' => '',
+'諨' => '淗',
+'諿' => '焎',
+'諯' => '焗',
+'諻' => '烸',
+'貑' => '',
+'貒' => '',
+'貐' => '',
+'賵' => '',
+'賮' => '',
+'賱' => '',
+'賰' => '',
+'賳' => '',
+'赬' => '焠',
+'赮' => '焞',
+'趥' => '',
+'趧' => '',
+'踳' => '菉',
+'踾' => '菳',
+'踸' => '萑',
+'蹀' => '甗',
+'蹅' => '',
+'踶' => '萏',
+'踼' => '菂',
+'踽' => '礭',
+'蹁' => '籔',
+'踰' => '貣',
+'踿' => '',
+'躽' => '閍',
+'輶' => '',
+'輮' => '',
+'輵' => '',
+'輲' => '',
+'輹' => '',
+'輷' => '箔',
+'輴' => '',
+'遶' => '',
+'遹' => '腲',
+'遻' => '腞',
+'邆' => '',
+'郺' => '趏',
+'鄳' => '',
+'鄵' => '',
+'鄶' => '萓',
+'醓' => '墑',
+'醐' => '鶩',
+'醑' => '鶦',
+'醍' => '鶖',
+'醏' => '墇',
+'錧' => '嬁',
+'錞' => '檴',
+'錈' => '屪',
+'錟' => '巀',
+'錆' => '嚘',
+'錏' => '嘾',
+'鍺' => '淀',
+'錸' => '麊',
+'錼' => '緳',
+'錛' => '嚗',
+'錣' => '嬃',
+'錒' => '儮',
+'錁' => '嚝',
+'鍆' => '鎡',
+'錭' => '嶗',
+'錎' => '嘬',
+'錍' => '嘽',
+'鋋' => '跾',
+'錝' => '壿',
+'鋺' => '',
+'錥' => '嬂',
+'錓' => '圚',
+'鋹' => '',
+'鋷' => '',
+'錴' => '嶜',
+'錂' => '',
+'錤' => '嫸',
+'鋿' => '',
+'錩' => '嬅',
+'錹' => '幝',
+'錵' => '嶡',
+'錪' => '嬏',
+'錔' => '墫',
+'錌' => '嘳',
+'錋' => '噀',
+'鋾' => '',
+'錉' => '',
+'錀' => '',
+'鋻' => '',
+'錖' => '墱',
+'閼' => '蜳',
+'闍' => '濉',
+'閾' => '蓒',
+'閹' => '捀',
+'閺' => '',
+'閶' => '蓛',
+'閿' => '蒑',
+'閵' => '',
+'閽' => '虡',
+'隩' => '蕝',
+'雔' => '螔',
+'霋' => '',
+'霒' => '秝',
+'霐' => '',
+'鞙' => '',
+'鞗' => '',
+'鞔' => '鰲',
+'韰' => '',
+'韸' => '',
+'頵' => '螷',
+'頯' => '螼',
+'頲' => '蟃',
+'餤\' => '礉',
+'餟' => '瞺',
+'餧' => '庣',
+'餩' => '禬',
+'馞' => '轆',
+'駮' => '眶',
+'駬' => '',
+'駥' => '',
+'駤' => '',
+'駰' => '',
+'駣' => '',
+'駪' => '',
+'駩' => '',
+'駧' => '',
+'骹' => '鏹',
+'骿' => '錧',
+'骴' => '鏚',
+'骻' => '輯',
+'髶' => '',
+'髺' => '',
+'髹' => '戄',
+'髷' => '',
+'鬳' => '瀹',
+'鮀' => '鐆',
+'鮅' => '霯',
+'鮇' => '鞻',
+'魼' => '齤',
+'魾' => '鏶',
+'魻' => '',
+'鮂' => '闠',
+'鮓' => '饌',
+'鮒' => '亹',
+'鮐' => '囅',
+'魺' => '',
+'鮕' => '饓',
+'魽' => '鐌',
+'鮈' => '韽',
+'鴥' => '',
+'鴗' => '黂',
+'鴠' => '齃',
+'鴞' => '鼷',
+'鴔' => '鷳',
+'鴩' => '',
+'鴝' => '蘤',
+'鴘' => '黐',
+'鴢' => '',
+'鴐' => '鷰',
+'鴙' => '黲',
+'鴟' => '薸',
+'麈' => '爢',
+'麆' => '',
+'麇' => '灚',
+'麮' => 'C',
+'麭' => '婦',
+'黕' => '^',
+'黖' => '_',
+'黺' => 'v',
+'鼒' => '',
+'鼽' => '襴',
+'儦' => '',
+'儥' => '',
+'儢' => '',
+'儤' => '',
+'儠' => '',
+'儩' => '',
+'勴' => '',
+'嚓' => '魛',
+'嚌' => '蜋',
+'嚍' => '',
+'嚆' => '飹',
+'嚄' => '',
+'嚃' => '',
+'噾' => '',
+'嚂' => '',
+'噿' => '',
+'嚁' => '',
+'壖' => '',
+'壔' => '',
+'壏' => '',
+'壒' => '',
+'嬭' => '騷',
+'嬥' => '',
+'嬲' => '窳',
+'嬣' => '',
+'嬬' => '',
+'嬧' => '',
+'嬦' => '',
+'嬯' => '',
+'嬮' => '',
+'孻' => 'Y',
+'寱' => '蔇',
+'寲' => '',
+'嶷' => '摍',
+'幬' => '僯',
+'幪' => '',
+'徾' => '',
+'徻' => '',
+'懃' => 'с',
+'憵' => '',
+'憼' => '',
+'懧' => '',
+'懠' => '',
+'懥' => '',
+'懤' => '',
+'懨' => '禖',
+'懞' => '蟹',
+'擯' => '梔',
+'擩' => '絲',
+'擣' => 'F',
+'擫' => 'L',
+'擤' => '蓱',
+'擨' => 'I',
+'斁' => '',
+'斀' => '',
+'斶' => '',
+'旚' => '',
+'曒' => '',
+'檍' => 'j',
+'檖' => 'p',
+'檁' => '橆',
+'檥' => '纀',
+'檉' => '魵',
+'檟' => 'x',
+'檛' => 't',
+'檡' => 'y',
+'檞' => 'w',
+'檇' => 'd',
+'檓' => 'm',
+'檎' => '橩',
+'檕' => 'o',
+'檃' => 'a',
+'檨' => '',
+'檤' => '|',
+'檑' => '橍',
+'橿' => '^',
+'檦' => '~',
+'檚' => 's',
+'檅' => 'b',
+'檌' => 'i',
+'檒' => 'l',
+'歛' => '螻',
+'殭' => '蔗',
+'氉' => '',
+'濌' => '',
+'澩' => '穘',
+'濴' => 'L',
+'濔' => '譆',
+'濣' => '',
+'濜' => '',
+'濭' => 'G',
+'濧' => 'A',
+'濦' => '@',
+'濞' => '憡',
+'濲' => 'J',
+'濝' => '',
+'濢' => '',
+'濨' => 'B',
+'燡\' => 'J',
+'燱' => 'W',
+'燨' => 'O',
+'燲' => 'X',
+'燤' => 'M',
+'燰' => 'V',
+'燢' => 'K',
+'獳' => '隹',
+'獮' => '',
+'獯' => '漺',
+'璗' => '茅',
+'璲' => '虻',
+'璫' => '苞',
+'璐' => '韐',
+'璪' => '苑',
+'璭' => '苟',
+'璱' => '虹',
+'璥' => '苜',
+'璯' => '茆',
+'甐' => '姬',
+'甑' => '窱',
+'甒' => '娠',
+'甏' => '窵',
+'疄' => '',
+'癃' => '顒',
+'癈' => '煙',
+'癉' => '蹜',
+'癇' => '豵',
+'皤' => '薽',
+'盩' => '崎',
+'瞵' => '謈',
+'瞫' => '猖',
+'瞲' => '琊',
+'瞷' => '現',
+'瞶' => '理',
+'瞴' => '球',
+'瞱' => '琅',
+'瞨' => '猛',
+'矰' => '蛇',
+'磳' => '',
+'磽' => '簐',
+'礂' => '',
+'磻' => '',
+'磼' => '',
+'磲' => '罅',
+'礅' => '罿',
+'磹' => '',
+'磾' => '',
+'礄' => '',
+'禫' => '越',
+'禨' => '貶',
+'穜' => '斟',
+'穛' => '敬',
+'穖' => '搶',
+'穘' => '搖',
+'穔' => '搔',
+'穚' => '搆',
+'窾' => '萬',
+'竀' => '禽',
+'竁' => '稜',
+'簅' => '榣',
+'簏' => '齘',
+'篲' => '榕',
+'簀' => '鵴',
+'篿' => '槐',
+'篻' => '榫',
+'簎' => '滾',
+'篴' => '榮',
+'簋' => '嚲',
+'篳' => '鶁',
+'簂' => '槌',
+'簉' => '氳',
+'簃' => '榦',
+'簁' => '榭',
+'篸' => '榛',
+'篽' => '榴',
+'簆' => '歉',
+'篰' => '榨',
+'篱' => '燦',
+'簐' => '漓',
+'簊' => '漳',
+'糨' => '轕',
+'縭' => '褖',
+'縼' => '',
+'繂' => '',
+'縳' => '篛',
+'顈' => '覭',
+'縸' => '糖',
+'縪' => '穎',
+'繉' => '',
+'繀' => '',
+'繇' => '鏾',
+'縩' => '積',
+'繌' => '',
+'縰' => '簑',
+'縻' => '毊',
+'縶' => '鐠',
+'繄' => '',
+'縺' => '',
+'罅' => '髂',
+'罿' => '績',
+'罾' => '轃',
+'罽' => '縮',
+'翴' => '點',
+'翲' => '黏',
+'耬' => '厴',
+'膻' => '錌',
+'臄' => '',
+'臌' => '錵',
+'臊' => '錔',
+'臅' => '',
+'臇' => '',
+'膼' => '',
+'臩' => '露',
+'艛' => '',
+'艚' => '蘀',
+'艜' => '',
+'薃' => '杕',
+'薀' => '堄',
+'薏' => '瑊',
+'薧' => '灴',
+'薕' => '沋',
+'薠' => '沜',
+'薋' => '杚',
+'薣' => '汥',
+'蕻' => '獀',
+'薤' => '獊',
+'薚' => '沚',
+'薞' => '沇',
+'蕷' => '歃',
+'蕼' => '',
+'薉' => '韶',
+'薡' => '汦',
+'蕺' => '猼',
+'蕸' => '',
+'蕗' => '',
+'薎' => '氙',
+'薖' => '沏',
+'薆' => '杌',
+'薍' => '毐',
+'薙' => '殀',
+'薝' => '汭',
+'薁' => '',
+'薢' => '汳',
+'薂' => '杙',
+'薈' => '媺',
+'薅' => '瑗',
+'蕹' => '瑋',
+'蕶' => '',
+'薘' => '汯',
+'薐' => '氚',
+'薟' => '搚',
+'虨' => '',
+'螾' => '翽',
+'螪' => '柀',
+'螭' => '韝',
+'蟅' => '枲',
+'螰\' => '柅',
+'螬' => '顝',
+'螹' => '柷',
+'螵' => '顗',
+'螼' => '柮',
+'螮' => '枷',
+'蟉' => '柭',
+'蟃' => '柧',
+'蟂' => '柎',
+'蟌' => '柌',
+'螷' => '柍',
+'螯' => '譔',
+'蟄' => '殎',
+'蟊' => '饃',
+'螴' => '柟',
+'螶' => '枵',
+'螿' => '柂',
+'螸' => '枳',
+'螽' => '颾',
+'蟞' => '毖',
+'螲' => '柤',
+'褵' => '褖',
+'褳' => '鮹',
+'褼' => '氥',
+'褾' => '浣',
+'襁' => '麌',
+'襒' => '涗',
+'褷' => '',
+'襂' => '洍',
+'覭' => '粊',
+'覯' => '膫',
+'覮' => '粌',
+'觲' => '荑',
+'觳' => '麮',
+'謞' => '琈',
+'謘' => '珺',
+'謖' => '祰',
+'謑' => '珸',
+'謅' => '粘',
+'謋' => '猈',
+'謢' => '痒',
+'謏' => '玈',
+'謒' => '珵',
+'謕' => '珽',
+'謇' => '敻',
+'謍' => '猏',
+'謈' => '猑',
+'謆' => '猇',
+'謜' => '琋',
+'謓' => '琄',
+'謚' => '稂',
+'豏' => '傕',
+'豰' => '喓',
+'豲' => '喏',
+'豱' => '喈',
+'豯' => '喢',
+'貕' => '',
+'貔' => '蘮',
+'賹' => '',
+'赯' => '焯',
+'蹎' => '',
+'蹍' => '',
+'蹓' => '槧',
+'蹐' => '',
+'蹌' => '囃',
+'蹇' => '敹',
+'轃' => '',
+'轀' => '',
+'邅' => '',
+'遾' => '腧',
+'鄸' => '',
+'醚' => '識',
+'醢' => '鶧',
+'醛' => '',
+'醙' => '墔',
+'醟' => '嫜',
+'醡' => '嫥',
+'醝' => '壾',
+'醠' => '嫮',
+'鎡' => '犚',
+'鎃' => '潧',
+'鎯' => '',
+'鍤' => '懮',
+'鍖' => '',
+'鍇' => '懘',
+'鍼' => '渀',
+'鍘' => '捸',
+'鍜' => '',
+'鍶' => '懟',
+'鍉' => '憉',
+'鍐' => '',
+'鍑' => '',
+'鍠' => '',
+'鍭' => '澉',
+'鎏' => '黮',
+'鍌' => '',
+'鍪' => '麜',
+'鍹' => '潒',
+'鍗' => '',
+'鍕' => '',
+'鍒' => '',
+'鍏' => '',
+'鍱' => '澅',
+'鍷' => '潕',
+'鍻' => '潗',
+'鍡' => '',
+'鍞' => '',
+'鍣' => '',
+'鍧' => '',
+'鎀' => '潡',
+'鍎' => '',
+'鍙' => '',
+'闇' => '做',
+'闀' => '箏',
+'闉' => '',
+'闃' => '蜣',
+'闅' => '',
+'閷' => '',
+'隮' => '欀',
+'隰' => '絜',
+'隬' => '蕬',
+'霠' => '',
+'霟' => '',
+'霘' => '',
+'霝' => '',
+'霙' => '',
+'鞚' => '',
+'鞡' => '亃',
+'鞜' => '蝝',
+'鞞' => '檕',
+'鞝' => '',
+'韕' => '甑',
+'韔' => '甐',
+'韱' => '',
+'顁' => '褳',
+'顄' => '襁',
+'顊' => '覮',
+'顉' => '覯',
+'顅' => '襒',
+'顃' => '褾',
+'餥' => '礐',
+'餫' => '簜',
+'餬' => '緇',
+'餪' => '穟',
+'餳' => '熉',
+'餲' => '簝',
+'餯' => '簠',
+'餭' => '簙',
+'餱' => '躆',
+'餰' => '簟',
+'馘' => '毳',
+'馣' => '鄺',
+'馡' => '轋',
+'騂' => '斄',
+'駺' => '徿',
+'駴' => '',
+'駷' => '',
+'駹\' => '',
+'駸' => '',
+'駶' => '',
+'駻' => '懻',
+'駽' => '攐',
+'駾' => '攍',
+'駼' => '攇',
+'騃' => '渭',
+'骾' => '攠',
+'髾' => '',
+'髽' => '',
+'鬁' => '',
+'髼' => '',
+'魈' => '齂',
+'鮚' => '奱',
+'鮨' => '鯷',
+'鮞' => '孌',
+'鮛' => '騶',
+'鮦' => '鰋',
+'鮡' => '髊',
+'鮥' => '鬑',
+'鮤' => '鬒',
+'鮆' => '巕',
+'鮢' => '髆',
+'鮠' => '髇',
+'鮯' => '鰆',
+'鴳' => '',
+'鵁' => '',
+'鵧' => '黵',
+'鴶' => '',
+'鴮' => '',
+'鴯' => '薾',
+'鴱' => '',
+'鴸' => '',
+'鴰' => '蟧',
+'鵅' => '纙',
+'鵂' => '蟦',
+'鵃' => 'b',
+'鴾' => '',
+'鴷' => '',
+'鵀' => '',
+'鴽' => '',
+'翵' => '黜',
+'鴭' => '',
+'麊' => '',
+'麉' => '',
+'麍' => '',
+'麰' => 'E',
+'黈' => 'W',
+'黚' => 'b',
+'黻' => '臌',
+'黿' => '鶵',
+'鼤' => '',
+'鼣' => '',
+'鼢' => '蠰',
+'齔' => '鶶',
+'龠' => '殗',
+'儱' => '',
+'儭' => '',
+'儮' => '',
+'嚘' => '',
+'嚜' => '',
+'嚗' => '',
+'嚚' => '',
+'嚝' => '',
+'嚙' => '蘚',
+'奰' => '`',
+'嬼' => '',
+'屩' => '',
+'屪' => '',
+'巀' => '^',
+'幭' => '',
+'幮' => '',
+'懘' => '',
+'懟' => '瞴',
+'懭' => '',
+'懮' => '',
+'懱' => '',
+'懪' => '',
+'懰' => '',
+'懫' => '',
+'懖' => '',
+'懩' => '欭',
+'擿' => '祣',
+'攄' => '祼',
+'擽' => '^',
+'擸' => 'Y',
+'攁' => 'a',
+'攃' => 'c',
+'擼' => '舝',
+'斔' => '',
+'旛' => '',
+'曚' => '',
+'曛' => '縚',
+'曘' => '',
+'櫅' => '',
+'檹' => '',
+'檽' => '',
+'櫡' => '',
+'櫆' => '',
+'檺' => '',
+'檶' => '',
+'檷' => '',
+'櫇' => '',
+'檴' => '鳹',
+'檭' => '',
+'歞' => 'd',
+'毉' => '瓟',
+'氋' => '',
+'瀇' => '_',
+'瀌' => 'd',
+'瀍' => 'e',
+'瀁' => 'Y',
+'瀅' => '鬿',
+'瀔' => 'k',
+'瀎' => 'f',
+'濿' => 'W',
+'瀀' => 'X',
+'濻' => 'S',
+'瀦' => '劌',
+'濼' => '裲',
+'濷' => 'O',
+'瀊' => 'b',
+'爁' => 'f',
+'燿' => '珓',
+'燹' => '徻',
+'爃' => 'h',
+'燽' => 'b',
+'獶' => '非',
+'璸' => '計',
+'瓀' => '趴',
+'璵' => '衫',
+'瓁' => '軍',
+'璾' => '赴',
+'璶' => '要',
+'璻' => '訃',
+'瓂' => '軌',
+'甔' => '娣',
+'甓' => '窷',
+'癜' => '騋',
+'癤' => '謣',
+'癙' => '訐',
+'癐' => '衰',
+'癓' => '袂',
+'癗' => '衹',
+'癚' => '討',
+'皦' => '',
+'皽' => '',
+'盬' => '崢',
+'矂' => '',
+'瞺' => '瓶',
+'磿' => '',
+'礌' => '濠',
+'礓' => '罽',
+'礔' => '羈',
+'礉' => '',
+'礐' => '湣',
+'礒\' => '湲',
+'礑' => '湄',
+'禭' => '趁',
+'禬' => '超',
+'穟' => '暉',
+'簜' => '',
+'簩' => '',
+'簙' => '漢',
+'簠' => '',
+'簟' => '禲',
+'簭' => '',
+'簝' => '',
+'簦' => '穬',
+'簨' => '',
+'簢' => '',
+'簥' => '',
+'簰' => '鵻',
+'繜' => '',
+'繐' => '',
+'繖' => '氶',
+'繣' => '錙',
+'繘' => '',
+'繢' => '蝩',
+'繟' => '錦',
+'繑' => '',
+'繠' => '錡',
+'繗' => '',
+'繓' => '',
+'羵' => '',
+'羳' => '',
+'翷' => '黛',
+'翸' => '鼾',
+'聵' => '夒',
+'臑' => '',
+'臒' => '',
+'臐' => '',
+'艟' => '藶',
+'艞' => '',
+'薴' => '犺',
+'藆' => '肙',
+'藀' => '疕',
+'藃' => '礽',
+'藂' => '椒',
+'薳' => '狁',
+'薵' => '狅',
+'薽' => '町',
+'藇' => '歃',
+'藄' => '耴',
+'薿' => '疔',
+'藋' => '芐',
+'藎' => '搟',
+'藈' => '肒',
+'藅' => '肕',
+'薱' => '狃',
+'薶' => '鎚',
+'藒' => '芓',
+'蘤' => '岪',
+'薸' => '玗',
+'薷' => '瑏',
+'薾' => '甹',
+'虩' => '',
+'蟧' => '洭',
+'蟦' => '洴',
+'蟢' => '氠',
+'蟛' => '馦',
+'蟫' => '洿',
+'蟪' => '馧',
+'蟥' => '顙',
+'蟟' => '毘',
+'蟳' => '洺',
+'蟤' => '洨',
+'蟔' => '柉',
+'蟜' => '殄',
+'蟓' => '颻',
+'蟭' => '洊',
+'蟘' => '柋',
+'蟣' => '繸',
+'螤' => '',
+'蟗' => '柪',
+'蟙' => '欨',
+'蠁' => '',
+'蟴' => '洚',
+'蟨' => '洟',
+'蟝' => '殶',
+'襓' => '浰',
+'襋' => '涍',
+'襏' => '浞',
+'襌' => '淯',
+'襆' => '嵽',
+'襐' => '浧',
+'襑' => '浠',
+'襉' => '鵓',
+'謪' => '',
+'謧' => '',
+'謣' => '痏',
+'謳' => '琠',
+'謰' => '',
+'謵' => '',
+'譇' => '耛',
+'謯' => '逡',
+'謼' => '網',
+'謾' => '獺',
+'謱' => '',
+'謥' => '',
+'謷' => '',
+'謦' => '鬘',
+'謶' => '',
+'謮' => '裞',
+'謤' => '',
+'謻' => '',
+'謽' => '',
+'謺' => '',
+'豂' => '陫',
+'豵' => '喁',
+'貙' => '',
+'貘' => '蘧',
+'貗' => '',
+'賾' => '寔',
+'贄' => '縤',
+'贂' => '',
+'贀' => '',
+'蹜' => '',
+'蹢' => '爙',
+'蹠' => '嚽',
+'蹗' => '',
+'蹖' => '',
+'蹞' => '爙',
+'蹥' => '',
+'蹧' => '媎',
+'蹛' => '',
+'蹚' => '昋',
+'蹡' => '',
+'蹝' => '樖',
+'蹩' => '龑',
+'蹔' => '',
+'轆' => '磥',
+'轇' => '毸',
+'轈' => '溛',
+'轋' => '溏',
+'鄨' => '',
+'鄺' => '絔',
+'鄻' => '',
+'鄾' => '',
+'醨' => '嫫',
+'醥' => '嫪',
+'醧' => '嫭',
+'醯' => '黤',
+'醪' => '麛',
+'鎵' => '斔',
+'鎌' => '蟑',
+'鎒' => '嚭',
+'鎷' => '',
+'鎛' => '熡',
+'鎝' => '嚚',
+'鎉' => '澕',
+'鎧' => '霟',
+'鎎' => '熲',
+'鎪\' => '懱',
+'鎞' => '熧',
+'鎦' => '攃',
+'鎕' => '熩',
+'鎈' => '潿\',
+'鎙' => '熞',
+'鎟' => '熳',
+'鎍' => '潻',
+'鎱' => '',
+'鎑' => '熛',
+'鎲' => '',
+'鎤' => '獞',
+'鎨' => '獛',
+'鎴' => '',
+'鎣' => '獒',
+'鎥' => '獟',
+'闒' => '澰',
+'闓' => '燅',
+'闑' => '澲',
+'隳' => '蓌',
+'雗' => '螚',
+'雚' => '褦',
+'巂' => '`',
+'雟' => '褱',
+'雘' => '螉',
+'雝' => '褮',
+'霣' => '錉',
+'霢' => '鋾',
+'霥' => '鋻',
+'鞬' => '歛',
+'鞮' => '殭',
+'鞨' => '檅',
+'鞫' => '鰶',
+'鞤' => '檑',
+'鞪' => '檒',
+'鞢' => '檤',
+'鞥' => '橿',
+'韗' => '甏',
+'韙' => '頦',
+'韖' => '甒',
+'韘' => '疄',
+'韺' => '',
+'顐' => '睇',
+'顑' => '',
+'顒' => '',
+'颸' => '駹\',
+'饁' => '繗',
+'餼' => '熅',
+'餺' => '繖',
+'騏' => '緦',
+'騋' => '櫋',
+'騉' => '櫑',
+'騍' => '緶',
+'騄' => '旝',
+'騑' => '櫍',
+'騊' => '櫙',
+'騅' => '緱',
+'騇' => '櫠',
+'騆' => '櫧',
+'髀' => '鷘',
+'髜' => '霦',
+'鬈' => '攩',
+'鬄' => '',
+'鬅' => '',
+'鬩' => '蒰',
+'鬵' => '瀻',
+'魊' => '蠋',
+'魌' => '矌',
+'魋' => '盭',
+'鯇' => '灖',
+'鯆' => '',
+'鯃' => '',
+'鮿' => '',
+'鯁' => '攠',
+'鮵' => '鶤',
+'鮸' => '鶘',
+'鯓' => '',
+'鮶' => '鶝',
+'鯄' => '',
+'鮹' => '鶐',
+'鮽' => '',
+'鵜' => '蟳',
+'鵓' => '蟛',
+'鵏' => '觿',
+'鵊' => '虈',
+'鵛' => '顲',
+'鵋' => '襹',
+'鵙' => '鑳',
+'鵖' => '鑭',
+'鵌' => '襺',
+'鵗' => '鑯',
+'鵒' => '蟥',
+'鵔' => '躣',
+'鵟' => '鱭\',
+'鵘' => '鑱',
+'鵚' => '靉',
+'麎' => '',
+'麌' => '',
+'黟' => '蘺',
+'鼁' => 'z',
+'鼀' => 'y',
+'鼖' => '',
+'鼥' => '',
+'鼫' => '',
+'鼪' => '',
+'鼩' => '',
+'鼨' => '',
+'齌' => 'T',
+'齕' => '[',
+'儴' => '',
+'儵' => '',
+'劖' => '',
+'勷' => '',
+'厴' => '婻',
+'嚫' => '',
+'嚭' => '',
+'嚦' => '萷',
+'嚧' => '',
+'嚪' => '遉',
+'嚬' => 'け',
+'壚' => '詏',
+'壝' => '',
+'壛' => '',
+'夒' => '',
+'嬽' => '擱',
+'嬾' => '',
+'嬿' => '',
+'巃' => 'a',
+'幰' => '',
+'徿' => '',
+'懻' => '',
+'攇' => 'g',
+'攐' => 'o',
+'攍' => 'l',
+'攉' => '葖',
+'攌' => 'k',
+'攎' => 'm',
+'斄' => '',
+'旞' => '',
+'旝' => '',
+'曞' => '',
+'櫧' => '橭',
+'櫠' => '',
+'櫌' => '',
+'櫑' => '',
+'櫙' => '',
+'櫋' => '',
+'櫟' => '魦',
+'櫜' => '',
+'櫐' => '',
+'櫫' => '樿',
+'櫏' => '',
+'櫍' => '',
+'櫞' => '橕',
+'歠' => 'f',
+'殰' => '',
+'氌' => '諈',
+'瀙\' => 'p',
+'瀧' => '蜤',
+'瀠' => '儇',
+'瀖' => 'm',
+'瀫' => '',
+'瀡' => 'v',
+'瀢' => 'w',
+'瀣' => '戭',
+'瀩' => '}',
+'瀗' => 'n',
+'瀤' => 'x',
+'瀜' => 'q',
+'瀪' => '~',
+'爌' => 'p',
+'爊' => 'n',
+'爇' => 'k',
+'爂' => 'g',
+'爅' => 'j',
+'犥' => '偏',
+'犦' => '',
+'犤' => '',
+'犣' => '',
+'犡' => '',
+'瓋' => '',
+'瓅' => '迢',
+'璷' => '觔',
+'瓃' => '述',
+'甖' => '騜',
+'癠' => '託',
+'矉' => 'け',
+'矊' => '',
+'矄' => '',
+'矱' => '蛀',
+'礝' => '焜',
+'礛' => '然',
+'礡' => '耬',
+'礜' => '煮',
+'礗' => '焚',
+'礞' => '翲',
+'禰' => '曒',
+'穧' => '榔',
+'穨' => '虰',
+'簳' => '',
+'簼' => '',
+'簹' => '',
+'簬' => '',
+'簻' => '',
+'糬' => '嬉',
+'糪' => '墦\',
+'繶' => '頷',
+'繵' => '頻',
+'繸' => '頹',
+'繰' => '諑',
+'繷' => '頭',
+'繯' => '諔',
+'繺' => '餐\',
+'繲' => '鞘',
+'繴' => '頸',
+'繨' => '雕',
+'罋' => '怤',
+'罊' => '',
+'羃' => '蹶',
+'羆' => '蹓',
+'羷' => '',
+'翽' => '嚕',
+'翾' => '嚮',
+'聸' => '',
+'臗' => '躊',
+'臕' => '桿',
+'艤' => '纀',
+'艡' => '',
+'艣' => '',
+'藫' => '',
+'藱' => '',
+'藭' => '',
+'藙' => '',
+'藡' => '',
+'藨' => '',
+'藚' => '',
+'藗' => '',
+'藬' => '',
+'藲' => '',
+'藸' => '',
+'藘' => '',
+'藟' => '',
+'藣' => '',
+'藜' => '瑆',
+'藑' => '芑',
+'藰' => '',
+'藦' => '',
+'藯' => '',
+'藞' => '',
+'藢' => '',
+'蠀' => '',
+'蟺' => '騕',
+'蠃' => '湀',
+'蟶' => '藙',
+'蟷' => '颿',
+'蠉' => '',
+'蠌' => '',
+'蠋' => '',
+'蠆' => '繰',
+'蟼' => '',
+'蠈' => '',
+'蟿' => '騢',
+'蠊' => '騛',
+'蠂' => '',
+'襢' => '抳',
+'襚' => '涋',
+'襛' => '浾',
+'襗' => '涘',
+'襡' => '涃',
+'襜' => '涀',
+'襘' => '洯',
+'襝' => '鵜',
+'襙' => '浨',
+'覈' => '瞄',
+'覷' => '膬',
+'覶' => '紘',
+'觶' => '鬕',
+'譐' => '脟',
+'譈' => '磾',
+'譊' => '聈',
+'譀' => '',
+'譓' => '脡',
+'譖' => '稄',
+'譔' => '蚴',
+'譋' => '擰',
+'譕' => '脧',
+'譑' => '脬',
+'譂' => '',
+'譒' => '脞',
+'譗' => '脢',
+'豃' => '陱',
+'豷' => '喒',
+'豶' => '喣',
+'貚' => '',
+'贆' => '',
+'贇' => '',
+'贉' => '',
+'趬' => '',
+'趪' => '',
+'趭' => '',
+'趫' => '',
+'蹭' => '脖',
+'蹸' => '耰',
+'蹳' => '軹',
+'蹪' => '',
+'蹯' => '纍',
+'蹻' => '軨',
+'軂' => '隈',
+'轒' => '溹',
+'轑' => '溱',
+'轏' => '溔',
+'轐' => '溠',
+'轓' => '滆',
+'辴' => '煸',
+'酀\' => '',
+'鄿' => '',
+'醰' => '嫛',
+'醭' => '麚',
+'鏞' => '檹',
+'鏇' => '櫡',
+'鏏' => '膕',
+'鏂' => '',
+'鏚' => '艑',
+'鏐' => '膢',
+'鏹' => '氋',
+'鏬' => '髂',
+'鏌' => '攄',
+'鏙' => '艎',
+'鎩' => '鵅',
+'鏦' => '蔜',
+'鏊' => '黫',
+'鏔' => '艏',
+'鏮' => '蓲',
+'鏣' => '蔟',
+'鏕' => '艓',
+'鏄' => '',
+'鏎' => '膞',
+'鏀' => '',
+'鏒' => '膗',
+'鏧' => '蓻',
+'镽' => '嬙',
+'闚' => '燋',
+'闛' => '燔',
+'雡' => '褢',
+'霩' => '閾',
+'霫' => '閹',
+'霬' => '閺',
+'霨' => '闍',
+'霦' => '錖',
+'鞳' => '澩',
+'鞷' => '濣',
+'鞶' => '濔',
+'韝' => '鷒',
+'韞' => '頩',
+'韟' => '瞵',
+'顜' => '',
+'顙' => '簹',
+'顝' => '',
+'顗' => '',
+'颿' => '楞',
+'颽' => '駾',
+'颻' => '駻',
+'颾' => '駼',
+'饈' => '獃',
+'饇' => '熏',
+'饃' => '犓',
+'馦' => '鄾',
+'馧' => '醨',
+'騚' => '瀫',
+'騕' => '氌',
+'騥' => '爇',
+'騝' => '瀣',
+'騤' => '爊',
+'騛' => '瀡',
+'騢' => '瀪',
+'騠' => '瀤',
+'騧' => '爅',
+'騣' => '跂',
+'騞' => '瀩',
+'騜' => '瀢',
+'騔' => '殰',
+'髂' => '鷵',
+'鬋' => '孅',
+'鬊' => '壣',
+'鬎' => '廮',
+'鬌' => '巆',
+'鬷' => '灁',
+'鯪' => '爞',
+'鯫' => '爟',
+'鯠' => '蠤',
+'鯞' => '蠛',
+'鯤' => '獿',
+'鯦' => '襮',
+'鯢' => '瓙',
+'鯰' => '瓗',
+'鯔' => '礵',
+'鯗' => '廲',
+'鯬' => '譺',
+'鯜' => '蠩',
+'鯙' => '',
+'鯥' => '襩',
+'鯕' => '',
+'鯡' => '犩',
+'鯚' => '',
+'鵷' => '鑶',
+'鶁' => '鼊',
+'鶊' => '',
+'鶄' => '',
+'鶈' => '',
+'鵱' => '蠼',
+'鶀' => '黶',
+'鵸' => '鑵',
+'鶆' => '',
+'鶋' => '',
+'鶌' => '',
+'鵽' => '鱵',
+'鵫' => '齻',
+'鵴' => '釃',
+'鵵' => '鑴',
+'鵰' => '蛐',
+'鵩' => '齇',
+'鶅' => '',
+'鵳' => '躦',
+'鵻' => '鱳',
+'鶂' => '',
+'鵯' => '蟓',
+'鵹' => '驠',
+'鵿' => '鸓',
+'鶇' => '薶',
+'鵨' => '鼉',
+'麔' => '',
+'麑' => '簷',
+'黀' => 'P',
+'黼' => '臊',
+'鼭' => '',
+'齀' => 'I',
+'齁' => 'J',
+'齍' => 'U',
+'齖' => '\\',
+'齗' => ']',
+'齘' => '^',
+'匷' => 'Z',
+'嚲' => '',
+'嚵' => '',
+'嚳' => '鈮',
+'壣' => '',
+'孅' => '',
+'巆' => 'c',
+'巇' => 'd',
+'廮' => '_',
+'廯' => '`',
+'忀' => '',
+'忁' => '',
+'懹' => '',
+'攗' => '睖',
+'攖' => '稕',
+'攕' => 's',
+'攓' => '摨',
+'旟' => '',
+'曨' => '',
+'曣' => '',
+'曤' => '',
+'櫳' => '駗',
+'櫰' => '跼',
+'櫪' => '飺',
+'櫨' => '髬',
+'櫹' => '',
+'櫱' => '',
+'櫮' => '',
+'櫯' => '',
+'瀼' => '',
+'瀵\' => '撖',
+'瀯' => '',
+'瀷' => '',
+'瀴' => '',
+'瀱' => '',
+'灂' => '',
+'瀸' => '',
+'瀿' => '',
+'瀺' => '',
+'瀹' => '摰',
+'灀' => '',
+'瀻' => '',
+'瀳' => '',
+'灁' => '',
+'爓' => '栭',
+'爔' => 'x',
+'犨' => '',
+'獽' => '便',
+'獼' => '漼',
+'璺' => '頝',
+'皫' => '',
+'皪' => '',
+'皾' => '',
+'盭' => '崑',
+'矌' => '',
+'矎' => '',
+'矏' => '',
+'矍' => '裀',
+'矲' => '蚶',
+'礥' => '猴',
+'礣' => '猥',
+'礧' => '濠',
+'礨' => '琪',
+'礤' => '翴',
+'礩' => '琳',
+'禲' => '跑',
+'穮' => '',
+'穬' => '',
+'穭' => '爁',
+'竷' => '',
+'籉' => '鯚',
+'籈' => '聚',
+'籊' => '腐',
+'籇' => '聞',
+'籅' => '翡',
+'糮' => '嬋',
+'繻' => '館',
+'繾' => '諓',
+'纁' => '駢',
+'纀' => '駭',
+'羺' => '',
+'翿' => '壙',
+'聹' => '壝',
+'臛' => '辯',
+'臙' => '醐',
+'舋' => '鼙',
+'艨' => '蘄',
+'艩' => '',
+'蘢' => '喍',
+'藿' => '瑍',
+'蘁' => '姏',
+'藾' => '妵',
+'蘛' => '岠',
+'蘀' => '妺',
+'藶' => '僉',
+'蘄' => '猺',
+'蘉' => '蚸',
+'蘅' => '瓡',
+'蘌' => '妽',
+'藽' => '奅',
+'蠙' => '',
+'蠐' => '藣',
+'蠑' => '襜',
+'蠗' => '',
+'蠓' => '騝',
+'蠖' => '騥',
+'襣' => '浽',
+'襦' => '黟',
+'覹' => '紟',
+'觷' => '荁',
+'譠' => '莣',
+'譪' => '莕',
+'譝' => '舲',
+'譨' => '莏',
+'譣' => '桄',
+'譥' => '莤',
+'譧' => '荴',
+'譭' => '障',
+'趮' => '婇',
+'躆' => '逭',
+'躈' => '逴',
+'躄' => '軩',
+'轙' => '溷',
+'轖' => '滁',
+'轗' => '溞',
+'轕' => '溽',
+'轘' => '滉',
+'轚' => '溰',
+'邍' => '',
+'酃' => '蛫',
+'酁' => '',
+'醷' => '嫨',
+'醵' => '黧',
+'醲' => '嫞',
+'醳' => '嫝',
+'鐋' => '鵀',
+'鐓' => '檴',
+'鏻' => '蔮',
+'鐠' => '歞',
+'鐏' => '',
+'鐔' => '檺',
+'鏾' => '蔞',
+'鐕' => '',
+'鐐' => '趨',
+'鐨' => '懩',
+'鐙' => '瀇',
+'鐍' => '虢',
+'鏵' => '鞚',
+'鐀' => '嶄',
+'鏷' => '檷',
+'鐇' => '蔘',
+'鐎' => '',
+'鐖' => '',
+'鐒' => '鴭',
+'鏺' => '蔝',
+'鐉' => '蔰',
+'鏸' => '蓾',
+'鐊' => '蔋',
+'鏿' => '蓶',
+'鏼' => '蔂',
+'鐌' => '蔯',
+'鏶' => '蓩',
+'鐑' => '幮',
+'鐆' => '蓹',
+'闞' => '蜞',
+'闠' => '燘',
+'闟' => '熽',
+'霮' => '閶',
+'霯' => '閿',
+'鞹' => '濭',
+'鞻' => '濦',
+'韽' => '',
+'韾' => '',
+'顠' => '',
+'顢' => '簼',
+'顣' => '',
+'顟' => '',
+'飁' => '髾',
+'飂' => '髽',
+'饐' => '',
+'饎' => '',
+'饙' => '',
+'饌' => '獌',
+'饋' => '嚏',
+'饓' => '',
+'騲' => '翌',
+'騴' => '矊',
+'騱\' => '甖',
+'騬' => '犡',
+'騪' => '犤',
+'騶' => '緷',
+'騩' => '犦',
+'騮' => '羬',
+'騸' => '羰',
+'騭' => '緮',
+'髇' => '鏮',
+'髊' => '鏄',
+'髆' => '眷',
+'鬐' => '廯',
+'鬒' => '褘',
+'鬑' => '忀',
+'鰋' => '霺',
+'鰈' => '穰',
+'鯷' => '酄',
+'鰅' => '鑀',
+'鰒' => '籜',
+'鯸' => '酅\',
+'鱀' => '羇',
+'鰇' => '闥',
+'鰎' => '顤',
+'鰆' => '鐱',
+'鰗' => '驄',
+'鰔' => '騹',
+'鰉' => '籙',
+'鶟' => '',
+'鶙' => '',
+'鶤' => 'A',
+'鶝' => '',
+'鶒' => '',
+'鶘' => '蟘',
+'鶐' => '',
+'鶛' => '',
+'鶠' => '',
+'鶔' => '',
+'鶜' => '',
+'鶪' => 'G',
+'鶗' => '',
+'鶡' => '',
+'鶚' => '蟣',
+'鶢' => '',
+'鶨' => 'E',
+'鶞' => '',
+'鶣' => '@',
+'鶿' => '螤',
+'鶩' => '蟙',
+'鶖' => '',
+'鶦' => 'C',
+'鶧' => 'D',
+'麙' => '',
+'麛' => '',
+'麚' => '',
+'黥' => '蘱',
+'黤' => 'f',
+'黧' => '蘼',
+'黦' => 'g',
+'鼰' => '',
+'鼮' => '',
+'齛' => 'a',
+'齠' => '鷇',
+'齞' => 'd',
+'齝' => 'c',
+'齙' => '鷁',
+'龑' => '',
+'儺' => '棞',
+'儹' => '婗',
+'劘' => '',
+'劗' => '',
+'囃' => '',
+'嚽' => '',
+'嚾' => '辣',
+'孈' => '@',
+'孇' => '',
+'巋' => '錯',
+'巏' => 'k',
+'廱' => 'b',
+'懽' => '辣',
+'攛' => '艄',
+'欂' => '',
+'櫼' => '',
+'欃' => '',
+'櫸' => '曋',
+'欀' => '',
+'灃' => '蝆',
+'灄' => '髧',
+'灊' => '',
+'灈' => '',
+'灉' => '',
+'灅' => '',
+'灆' => '',
+'爝' => '懃',
+'爚' => '~',
+'爙' => '}',
+'獾' => '漟',
+'甗' => '娌',
+'癪' => '貢',
+'矐' => '',
+'礭' => '琶',
+'礱' => '篳',
+'礯' => '琯',
+'籔' => '與',
+'籓' => '臺',
+'糲' => '譪',
+'纊' => '膟',
+'纇' => '髭',
+'纈' => '觬',
+'纋' => '鴣',
+'纆' => '髻',
+'纍' => '濛',
+'罍' => '',
+'羻' => '',
+'耰' => '檯',
+'臝' => '邃',
+'蘘' => '屇',
+'蘪' => '瓽',
+'蘦' => '岝',
+'蘟' => '岬',
+'蘣' => '岢',
+'蘜' => '擅',
+'蘙' => '岮',
+'蘧' => '瑔',
+'蘮' => '帔',
+'蘡' => '岣',
+'蘠' => 'Ц',
+'蘩' => '瓿',
+'蘞' => '嗀',
+'蘥' => '岧',
+'蠩' => '籸',
+'蠝' => '',
+'蠛' => '騢',
+'蠠' => '',
+'蠤' => '穾',
+'蠜' => '',
+'蠫' => '籿',
+'衊' => '鏖',
+'襭' => '觬',
+'襩' => '烑',
+'襮' => '烗',
+'襫' => '烋',
+'觺' => '茢',
+'譹' => '莯',
+'譸' => '莥',
+'譅' => '',
+'譺' => '莈',
+'譻' => '莗',
+'贐' => '罼',
+'贔' => '湱',
+'趯' => '',
+'躎' => '郼',
+'躌' => '鄄',
+'轞' => '熨',
+'轛' => '滍',
+'轝' => '豗',
+'酆' => '蛜',
+'酄' => '',
+'酅\' => '凘',
+'醹' => '孷',
+'鐿' => '瀁',
+'鐻' => '輣',
+'鐶' => '鍤',
+'鐩' => '',
+'鐽' => '輗',
+'鐼' => '輖',
+'鐰' => '踒',
+'鐹' => '輚',
+'鐪' => '',
+'鐷' => '輤',
+'鐬' => '',
+'鑀' => '懰',
+'鐱' => '膛',
+'闥' => '蒶',
+'闤' => '燛',
+'闣' => '燚',
+'霵' => '雔',
+'霺' => '霐',
+'鞿' => '濢',
+'韡' => '瞲',
+'顤' => '',
+'飉' => '鮛',
+'飆' => '鴙',
+'飀' => '骾',
+'饘' => '',
+'饖' => '',
+'騹' => '',
+'騽' => '',
+'驆' => '',
+'驄' => '翭',
+'驂' => '緰',
+'驁' => '罶',
+'騺' => '',
+'騿' => '',
+'髍' => '鏎',
+'鬕' => '攗',
+'鬗' => '攕',
+'鬘' => '攓',
+'鬖' => '攖',
+'鬺' => '犨',
+'魒' => '矍',
+'鰫' => '',
+'鰝' => '',
+'鰜' => '',
+'鰬' => '',
+'鰣' => '欈',
+'鰨' => '驐',
+'鰩' => '鬙',
+'鰤' => '',
+'鰡' => '',
+'鶷' => 'T',
+'鶶' => 'S',
+'鶼' => '蟴',
+'鷁' => '^',
+'鷇' => 'd',
+'鷊' => 'g',
+'鷏' => 'l',
+'鶾' => '[',
+'鷅' => 'b',
+'鷃' => '`',
+'鶻' => '鷜',
+'鶵' => 'R',
+'鷎' => 'k',
+'鶹' => 'V',
+'鶺' => 'W',
+'鶬' => 'I',
+'鷈' => 'e',
+'鶱' => 'N',
+'鶭' => 'J',
+'鷌' => 'i',
+'鶳' => 'P',
+'鷍' => 'j',
+'鶲' => 'O',
+'鹺' => '齛',
+'麜' => '',
+'黫' => 'i',
+'黮' => 'l',
+'黭' => 'k',
+'鼛' => '',
+'鼘' => '',
+'鼚' => '',
+'鼱' => '',
+'齎' => '耪',
+'齥' => 'k',
+'齤' => 'j',
+'龒' => '',
+'亹' => '皜',
+'囆' => '',
+'囅' => '氰',
+'囋' => '',
+'奱' => 'a',
+'孋' => 'C',
+'孌' => '畾',
+'巕' => 'q',
+'巑' => 'm',
+'廲' => 'c',
+'攡' => '~',
+'攠' => '}',
+'攦' => '',
+'攢' => '婗',
+'欋' => '噮',
+'欈' => '',
+'欉' => '',
+'氍' => '諡',
+'灕' => '燬',
+'灖' => '',
+'灗' => '',
+'灒' => '',
+'爞' => '',
+'爟' => '辣',
+'犩' => '',
+'獿' => '俠',
+'瓘' => '',
+'瓕' => '譆',
+'瓙' => '',
+'瓗' => '',
+'癭' => '顐',
+'皭' => '',
+'礵' => '甦',
+'禴' => '跌',
+'穰' => '藀',
+'穱' => '',
+'籗' => '艋',
+'籜' => '鵳',
+'籙' => '蒿',
+'籛' => '蓄',
+'籚' => '蓆',
+'糴' => '殕',
+'糱' => '嬌',
+'纑' => '黔',
+'罏' => '詏',
+'羇' => '縱',
+'臞' => '鐳',
+'艫' => '舋',
+'蘴' => '彔',
+'蘵' => '徂',
+'蘳' => '弤',
+'蘬' => '岦',
+'蘲' => '弣',
+'蘶' => '彾',
+'蠬' => '粀',
+'蠨' => '籺',
+'蠦' => '笀',
+'蠪' => '籹',
+'蠥' => '竑',
+'襱' => '烠',
+'覿' => '膹',
+'覾' => '罡\',
+'觻' => '',
+'譾' => '稌',
+'讄' => '虖',
+'讂' => '莚',
+'讆' => '蚷',
+'讅' => '机',
+'譿' => '莇',
+'贕\' => '湫',
+'躕' => '纈',
+'躔' => '臝',
+'躚' => '櫸',
+'躒' => '孇',
+'躐' => '蘘',
+'躖' => '鄀',
+'躗' => '鄇',
+'轠' => '滃',
+'轢' => '瀄',
+'酇' => '劀',
+'鑌' => '旛',
+'鑐' => '醄',
+'鑊' => '瀌',
+'鑋' => '醅',
+'鑏' => '醂',
+'鑇' => '鄪',
+'鑅' => '鄫',
+'鑈' => '鄲',
+'鑉' => '鄦',
+'鑆' => '鄩',
+'霿' => '韰',
+'韣' => '瞶',
+'顪' => '',
+'顩' => '',
+'飋' => '鮡',
+'饔' => '壧',
+'饛' => '',
+'驎' => '',
+'驓' => '',
+'驔' => '',
+'驌' => '',
+'驏' => '翫',
+'驈' => '',
+'驊' => '緡',
+'驉' => '',
+'驒' => '',
+'驐' => '',
+'髐' => '鏧',
+'鬙' => '旟',
+'鬫' => '瀴',
+'鬻' => '毿',
+'魖' => '礣',
+'魕' => '礥',
+'鱆' => '蘬',
+'鱈' => '魖',
+'鰿' => '罏',
+'鱄' => '蘵',
+'鰹' => '欋',
+'鰳' => '鬫',
+'鱁' => '臞',
+'鰼' => '',
+'鰷' => '欉',
+'鰴' => '',
+'鰲' => '驉',
+'鰽' => '糱',
+'鰶' => '',
+'鷛' => 'x',
+'鷒' => 'o',
+'鷞' => '{',
+'鷚' => '襓',
+'鷋' => 'h',
+'鷐' => 'm',
+'鷜' => 'y',
+'鷑' => 'n',
+'鷟' => '|',
+'鷩' => '',
+'鷙' => '虩',
+'鷘' => 'u',
+'鷖' => 's',
+'鷵' => '',
+'鷕' => 'r',
+'鷝' => 'z',
+'麶' => 'J',
+'黰' => '褘',
+'鼵' => 'C',
+'鼳' => 'A',
+'鼲' => '@',
+'齂' => 'K',
+'齫' => 'q',
+'龕' => '膻',
+'龢' => '睿',
+'儽' => '',
+'劙' => '',
+'壨' => '',
+'壧' => '',
+'奲' => 'b',
+'孍' => 'E',
+'巘' => 't',
+'蠯' => '紈',
+'彏' => '',
+'戁' => '',
+'戃' => '',
+'戄' => '',
+'攩' => '結',
+'攥' => '葶',
+'斖' => '',
+'曫' => '',
+'欑' => '婗',
+'欒' => '鴄',
+'欏' => '憿',
+'毊' => '',
+'灛' => '',
+'灚' => '',
+'爢' => '',
+'玂' => '保',
+'玁' => '榶',
+'玃' => '騣',
+'癰' => '虒',
+'矔' => '',
+'籧' => '蓊',
+'籦' => '蓑',
+'纕' => '償',
+'艬' => '贛',
+'蘺' => '楒',
+'虀' => '怋',
+'蘹' => '忞',
+'蘼' => '瓽',
+'蘱' => '弢',
+'蘻' => '怭',
+'蘾' => '怙',
+'蠰' => '紁',
+'蠲' => '遾',
+'蠮' => '紃',
+'蠳' => '羑',
+'襶' => '烇',
+'襴' => '烅',
+'襳' => '烍',
+'觾' => '',
+'讌' => '栯',
+'讎' => '鷌',
+'讋' => '',
+'讈' => '',
+'豅' => '隿',
+'贙' => '湓',
+'躘' => '鄅',
+'轤' => '濄',
+'轣' => '溙',
+'醼' => '',
+'鑢' => '鋨',
+'鑕' => '鋀',
+'鑝' => '鋗',
+'鑗' => '銶',
+'鑞' => '鋝',
+'韄' => '燲',
+'韅' => '燤',
+'頀' => '',
+'驖' => '',
+'驙' => '',
+'鬞' => '櫰',
+'鬟' => '曫',
+'鬠' => '櫪',
+'鱒' => '鰹',
+'鱘' => '攡',
+'鱐' => '覾',
+'鱊' => '蠨',
+'鱍' => '鼱',
+'鱋\' => '蠦',
+'鱕' => '讆',
+'鱙' => '躕',
+'鱌' => '蠪',
+'鱎' => '襱',
+'鷻' => '',
+'鷷' => '',
+'鷯' => '襋',
+'鷣' => '',
+'鷫' => '',
+'鷸' => '襆',
+'鷤' => '',
+'鷶' => '',
+'鷡' => '~',
+'鷮' => '',
+'鷦' => '襏',
+'鷲' => '襌',
+'鷰' => '桏',
+'鷢' => '',
+'鷬' => '',
+'鷴' => '',
+'鷳' => '蟟',
+'鷨' => '',
+'鷭' => '',
+'黂' => 'R',
+'黐' => '[',
+'黲' => '蘻',
+'黳' => 'p',
+'鼆' => '',
+'鼜' => '',
+'鼸' => 'E',
+'鼷' => '襶',
+'鼶' => 'D',
+'齃' => 'L',
+'齏' => '黕',
+'齱' => 'w',
+'齰' => '捰',
+'齮' => 't',
+'齯' => 'u',
+'囓' => '蘚',
+'囍' => '',
+'孎' => 'F',
+'屭' => '',
+'攭' => '',
+'曭' => '',
+'曮' => '',
+'欓' => '',
+'灟' => '',
+'灡' => '',
+'灝' => '撠',
+'灠' => '僾',
+'爣' => '',
+'瓛' => '',
+'瓥' => '',
+'矕' => '',
+'礸' => '痢',
+'禷' => '軻',
+'禶' => '跆',
+'籪' => '匷',
+'纗' => '儲',
+'羉' => '繁',
+'艭' => '釀',
+'虃' => '',
+'蠸' => '耏',
+'蠷' => '耎',
+'蠵' => '羾',
+'衋' => '胊',
+'讔' => '',
+'讕' => '擰',
+'躞' => '蘦',
+'躟' => '酢',
+'躠' => '酠',
+'躝' => '酟',
+'醾' => '',
+'醽' => '',
+'釂' => '',
+'鑫' => '鼛',
+'鑨' => '鋕',
+'鑩' => '鋉',
+'雥' => '褬',
+'靆' => '餧',
+'靃' => '齊',
+'靇' => '餩',
+'韇' => '燢',
+'韥' => '',
+'驞' => '豃',
+'髕' => '鷝',
+'魙' => '礤',
+'鱣' => '鱄',
+'鱧' => '鰳',
+'鱦' => '鑋',
+'鱢' => '酇',
+'鱞' => '躖',
+'鱠' => '醑',
+'鸂' => '',
+'鷾' => '',
+'鸇' => 'D',
+'鸃' => '@',
+'鸆' => 'C',
+'鸅' => 'B',
+'鸀' => '',
+'鸁' => '',
+'鸉' => 'F',
+'鷿' => '',
+'鷽' => '',
+'鸄' => 'A',
+'麠' => '',
+'鼞' => '隄',
+'齆' => 'N',
+'齴' => 'z',
+'齵' => '{',
+'齶' => '錥',
+'囔' => '鳭',
+'攮' => '葹',
+'斸' => '',
+'欘' => '',
+'欙' => '',
+'欗' => '',
+'欚' => '',
+'灢' => '',
+'爦' => '',
+'犪' => '',
+'矘' => '',
+'矙' => '謍',
+'礹' => '痛',
+'籩' => '鯡',
+'籫' => '蜢',
+'糶' => '譝',
+'纚' => '嚀',
+'纘' => '諕',
+'纛' => '鐕',
+'纙' => '嚎',
+'臠' => '湳',
+'臡' => '鐸',
+'虆' => '',
+'虇' => '',
+'虈' => '',
+'襹' => '烡',
+'襺' => '牂',
+'襼' => '牸',
+'襻' => '鼁',
+'觿' => '',
+'讘' => '',
+'讙' => '辣',
+'躥' => '款',
+'躤' => '鈃',
+'躣' => '鈥',
+'鑮' => '鋑',
+'鑭' => '檭',
+'鑯' => '鋓',
+'鑱' => '',
+'鑳' => '瑩',
+'靉' => '駮',
+'顲' => '韔',
+'饟' => '熁',
+'鱨' => '鑇',
+'鱮' => '韣',
+'鱭\' => '巕',
+'鸋' => 'H',
+'鸍' => 'J',
+'鸐' => 'M',
+'鸏' => 'L',
+'鸒' => 'O',
+'鸑' => 'N',
+'麡' => '',
+'黵' => 'r',
+'鼉' => '鷎',
+'齇' => 'O',
+'齸' => '~',
+'齻' => '',
+'齺' => '',
+'齹' => '',
+'圞' => '鴄',
+'灦' => '',
+'籯' => '蝕',
+'蠼' => '騣',
+'趲' => '鏷',
+'躦' => '蘪',
+'釃' => '鶚',
+'鑴' => '',
+'鑸' => '',
+'鑶' => '',
+'鑵' => '嫡',
+'驠' => '豶',
+'鱴' => '驎',
+'鱳' => '饛',
+'鱱' => '飋',
+'鱵' => '驓',
+'鸔' => 'Q',
+'鸓' => 'P',
+'黶' => 's',
+'鼊' => '',
+'龤' => '迣',
+'灨' => '該',
+'灥' => '',
+'糷' => '層',
+'虪' => '',
+'蠾' => '胇',
+'蠽' => '胘',
+'蠿' => '胠',
+'讞' => '竤',
+'貜' => '',
+'躩' => '鈀',
+'軉' => '',
+'靋' => '駥',
+'顳' => '簳',
+'顴' => '',
+'飌' => '鮥',
+'饡' => '',
+'馫' => '醯',
+'驤' => '翬',
+'驦' => '轓',
+'驧' => '趭',
+'鬤' => '櫱',
+'鸕' => '藒',
+'鸗' => 'T',
+'齈' => 'P',
+'戇' => '禨',
+'欞' => '凞',
+'爧' => '',
+'虌' => '',
+'躨' => '鈌',
+'钂' => '噞',
+'钀' => '',
+'钁' => '檶',
+'驩' => '蹭',
+'驨' => '趫',
+'鬮' => '蓗',
+'鸙' => 'V',
+'爩' => '',
+'虋' => '',
+'讟' => '',
+'钃' => '',
+'鱹' => '驈',
+'麷' => 'K',
+'癵' => '迷',
+'驫' => '蹻',
+'鱺' => '攦',
+'鸝' => '蟫',
+'灩' => '駇',
+'灪' => '',
+'麤' => '棉',
+'齾' => '',
+'齉' => 'Q',
+'龘' => '',
+'' => '',
+'' => '凄',
+'' => '爵',
+'' => 'Х',
+'' => '箝',
+'' => '衒',
+'' => '瘚',
+'' => '汴',
+'' => '甫',
+'' => '沍',
+'' => '牡',
+'' => '私',
+'' => '狂',
+'' => '沂',
+'' => '皂',
+'' => '災',
+'' => '汲',
+'' => '玖',
+'' => '沆',
+'' => '灸',
+'' => '盯',
+'' => '牠',
+'' => '沔\',
+'' => '男',
+'' => '灶',
+'' => '汾',
+'' => '甬',
+'' => '汶',
+'' => '牢',
+'' => '矣',
+'' => '狄',
+'' => '沘',
+'' => '甸',
+'' => '灼',
+'' => '沃',
+'' => '汽',
+'' => '秀',
+'' => '禿',
+'' => '系',
+'' => '究',
+'' => '',
+);
+
+our(@ISA, @EXPORT);
+@ISA = qw(Exporter);
+@EXPORT = qw(%b2g);
+
+sub big5togb
+{
+ $_[0] =~ s/([\xA1-\xF9].)/$b2g{$1}/eg;
+}
+
+1;
diff --git a/pttbbs/staticweb/banner.html b/pttbbs/staticweb/banner.html
new file mode 100644
index 00000000..2c59b6bb
--- /dev/null
+++ b/pttbbs/staticweb/banner.html
@@ -0,0 +1,10 @@
+<div id="banner">
+ <h1><a href="http://www.ptt.cc"
+ accesskey="1">批踢踢實業坊</a></h1>
+
+<div style="position:absolute; right: 15%;">
+<a href="./">繁體中文(big5)</a>
+<a href="./?gb=1">簡體中文(gb2312)</a>
+</div>
+
+</div>
diff --git a/pttbbs/staticweb/dir.html b/pttbbs/staticweb/dir.html
new file mode 100644
index 00000000..071cac22
--- /dev/null
+++ b/pttbbs/staticweb/dir.html
@@ -0,0 +1,43 @@
+[% INCLUDE header.html %]
+<table width="75%" align="center">
+<tr><td>
+[% INCLUDE banner.html %]
+</td></tr>
+
+<tr><td>
+<a href="http://[% IF gb %]gb[% END %]www.ptt.cc">批踢踢實業坊</a> |
+<a href="http://man.ptt.cc/[% IF gb %]?gb=1[% END %]">網頁版精華區首頁</a> |
+<a href="http://webbbs.ptt.cc/[% brdname %]/DIR.html">[% brdname %]看板首頁</a> |
+<a href="http://man.ptt.cc/man.pl/[% brdname %]/[% IF gb %]?gb=1[% END %]">[% brdname %]精華區首頁</a>
+<br />
+<font size=+1>看板名稱: <a href="/man.pl/[% brdname %]/">[% brdname %]</a></font>
+<hr />
+</td></tr>
+
+<tr><td>
+[% IF !isroot %]
+<a href="../[% IF gb %]?gb=1[% END %]"><img src="http://images.ptt.cc/folder.gif" style="border:0;" />返回上一層</a><br />
+[% END %]
+
+[% FOREACH x=dat %]
+<img src="http://images.ptt.cc/[% IF x.isdir %]folder.gif[% ELSE %]f.gif[% END %]" style="border:0;" />
+<a href="/[% x.fn %][% IF gb %]?gb=1[% END %]">
+[% x.title %]</a><br />
+[% END %]
+</td></tr>
+<tr><td>
+[% IF !gb %]
+<hr />
+<form method="POST" action="/man.pl/[% brdname %]/">
+在這個精華區內翻弄
+<input type="text" name="key">
+<input type="submit" value="撈">
+</form>
+[% END %]
+<hr />
+<font size="-1">製作時間: [% buildtime %]</font><br>
+<a href="http://ptt.cc">批踢踢實業坊</a>
+</td></tr>
+</table>
+</body>
+</html>
diff --git a/pttbbs/staticweb/header.html b/pttbbs/staticweb/header.html
new file mode 100644
index 00000000..112efd70
--- /dev/null
+++ b/pttbbs/staticweb/header.html
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="[% encoding %]"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xml:lang="[% lang %]" lang="[% lang %]">
+<head>
+ <title>批踢踢實業坊[% IF exttitle %] - [% exttitle %][% END %]</title>
+ <meta http-equiv="Content-Type"
+ content="text/html; charset=[% charset %]">
+ <meta name="generator" content="pttMan" />
+ <meta name="robots" content="all" />
+ <link rel="stylesheet" href="http://pttx.ptt.cc:4099/staticpage/ptt.man.styles.css" type="text/css" />
+</head>
+<body>
diff --git a/pttbbs/staticweb/index.html b/pttbbs/staticweb/index.html
new file mode 100644
index 00000000..c0c87174
--- /dev/null
+++ b/pttbbs/staticweb/index.html
@@ -0,0 +1,47 @@
+[% INCLUDE header.html %]
+<table width="75%" align="center">
+<tr><td>
+[% INCLUDE banner.html %]
+</td></tr>
+
+<tr><td>
+<a href="http://www.ptt.cc">批踢踢實業坊</a> &raquo; <a href="http://man.ptt.cc/index.pl/[% IF gb %]?gb=1[% END %]">批踢踢實業坊之精華區</a>
+[% FOREACH x=class %]
+&raquo;<a href="/index.pl[% x.path %]">[% x.title %]</a>
+[% END %]
+<br />
+<br />
+<table>
+ [% IF !isroot %]
+ <tr>
+ <td>
+ <img src="http://images.ptt.cc/folder.gif" style="border: 0;" />
+ </td>
+ <td>
+ </td>
+ <td>
+ <a href="../">返回上一層</a>
+ </td>
+ </tr>
+ [% END %]
+ [% FOREACH x=dat %]
+ <tr>
+ <td>
+ <img src="http://images.ptt.cc/[% IF x.0 == -1 %]f.gif[% ELSE %]folder.gif[% END %]" style="border: 0;" />
+ </td>
+ <td>[% x.1 %]</td>
+ <td>
+ [% IF x.0 == -1 %]<a href="/man.pl/[% x.1 %]/[% IF gb %]?gb=1[% END %]">[% ELSE %]<a href="[% x.0 %]/[% IF gb %]?gb=1[% END %]">[% END %]
+ [% x.2 %]</a>
+ </td>
+ </tr>
+ [% END %]
+</table>
+</td></tr>
+<tr><td>
+<hr />
+<a href="http://ptt.cc">批踢踢實業坊</a>
+</td></tr>
+</table>
+</body>
+</html>
diff --git a/pttbbs/staticweb/index.pl b/pttbbs/staticweb/index.pl
new file mode 100755
index 00000000..b135655d
--- /dev/null
+++ b/pttbbs/staticweb/index.pl
@@ -0,0 +1,99 @@
+#!/usr/bin/env perl
+# $Id$
+use lib qw/./;
+use LocalVars;
+use CGI qw/:cgi :html2/;
+use strict;
+use Template;
+use b2g;
+use DB_File;
+use Data::Serializer;
+use vars qw/$serializer $tmpl %brdlist/;
+
+sub deserialize
+{
+ my($what) = @_;
+ $serializer = Data::Serializer->new(serializer => 'Storable',
+ digester => 'MD5',
+ compress => 0,
+ )
+ if( !$serializer );
+ return $serializer->deserialize($what);
+}
+
+sub main
+{
+ my(%rh, $bid) = ();
+
+ if( param('gb') ){
+ $rh{gb} = 1;
+ $rh{encoding} = 'gb2312';
+ $rh{lang} = 'zh-CN';
+ $rh{charset} = 'gb2312'; }
+ else{
+ print redirect("/man.pl/$1/")
+ if( $ENV{REDIRECT_REQUEST_URI} =~ m|/\?(.*)| );
+ $rh{encoding} = 'Big5';
+ $rh{lang} = 'zh-TW';
+ $rh{charset} = 'big5';
+ }
+
+ return redirect('/index.pl/'.($rh{gb}?'?gb=1':''))
+ if( $ENV{REQUEST_URI} eq '/' );
+
+ charset('');
+ print header();
+ tie %brdlist, 'DB_File', 'boardlist.db', O_RDONLY, 0666, $DB_HASH
+ if( !%brdlist );
+
+ ($bid) = $ENV{PATH_INFO} =~ m|.*/(\d+)/$|;
+ $bid ||= 1;
+ $rh{isroot} = ($bid == 1);
+
+ if( !exists $brdlist{"class.$bid"} ){
+ print "sorry, this bid $bid not found :(";
+ return ;
+ }
+
+ foreach( @{deserialize($brdlist{"class.$bid"})} ){
+ next if( $brdlist{"$_.isboard"} &&
+ !-e "$MANDATA/".$brdlist{"tobrdname.$_"}.'.db' );
+
+ push @{$rh{dat}}, [$brdlist{"$_.isboard"} ? -1 : $_,
+ $brdlist{"$_.brdname"},
+ $brdlist{"$_.title"},
+ ];
+ }
+
+ my $path = '';
+ foreach( $ENV{PATH_INFO} =~ m|(\w+)|g ){
+ push @{$rh{class}}, {path => "$path/$_/",
+ title => $brdlist{"$_.title"}};
+ $path .= "/$_";
+ }
+ $rh{exttitle} = ($rh{class} ?
+ $rh{class}[ $#{@{$rh{class}}} ]{title} : '首頁');
+
+ $tmpl = Template->new({INCLUDE_PATH => '.',
+ ABSOLUTE => 0,
+ RELATIVE => 0,
+ RECURSION => 0,
+ EVAL_PERL => 0,
+ COMPILE_EXT => '.tmpl',
+ COMPILE_DIR => $MANCACHE,
+ })
+ if( !$tmpl );
+
+ if( !$rh{gb} ){
+ $tmpl->process('index.html', \%rh);
+ }
+ else{
+ my $output;
+ $tmpl->process('index.html', \%rh, \$output);
+ b2g::big5togb($output);
+ print $output;
+ }
+}
+
+main();
+1;
diff --git a/pttbbs/staticweb/man.pl b/pttbbs/staticweb/man.pl
new file mode 100755
index 00000000..f7f13804
--- /dev/null
+++ b/pttbbs/staticweb/man.pl
@@ -0,0 +1,145 @@
+#!/usr/bin/env perl
+# $Id$
+use CGI qw/:cgi :html2/;
+use lib qw/./;
+use LocalVars;
+use DB_File;
+use strict;
+use Data::Dumper;
+use Template;
+use OurNet::FuzzyIndex;
+use Data::Serializer;
+use Time::HiRes qw/gettimeofday tv_interval/;
+use b2g;
+use POSIX;
+use Compress::Zlib;
+
+use vars qw/%db $brdname $fpath $isgb $tmpl/;
+
+sub main
+{
+ my($rh, $key) = ();
+
+ if( !(($brdname, $fpath) = $ENV{PATH_INFO} =~ m|^/([\w\-]+?)(/.*)|) ||
+ (!exists $db{$brdname} &&
+ !tie %{$db{$brdname}}, 'DB_File',
+ "$MANDATA/$brdname.db", O_RDONLY, 0666, $DB_HASH) ){
+ return redirect("/man.pl/$1/")
+ if( $ENV{PATH_INFO} =~ m|^/([\w\-]+?)$| );
+ print header(-status => 404);
+ return;
+ }
+
+ charset('');
+ print header();
+
+ $isgb = (param('gb') ? 1 : 0);
+
+ if( ($key = param('key')) ){
+ $rh = search($key);
+ }
+ else{
+ $rh = (($fpath =~ m|/$|) ? dirmode($fpath) : articlemode($fpath));
+ }
+ $rh->{brdname} = $brdname;
+ $tmpl = Template->new({INCLUDE_PATH => '.',
+ ABSOLUTE => 0,
+ RELATIVE => 0,
+ RECURSION => 0,
+ EVAL_PERL => 0,
+ COMPILE_EXT => '.tmpl',
+ COMPILE_DIR => $MANCACHE,
+ })
+ if( !$tmpl );
+
+ if( $rh->{gb} = $isgb ){
+ $rh->{encoding} = 'gb2312';
+ $rh->{lang} = 'zh-CN';
+ $rh->{charset} = 'gb2312';
+ }
+ else{
+ $rh->{encoding} = 'Big5';
+ $rh->{lang} = 'zh-TW';
+ $rh->{charset} = 'big5';
+ }
+
+ if( !$rh->{gb} ){
+ $tmpl->process($rh->{tmpl}, $rh);
+ }
+ else{
+ my $output;
+ $tmpl->process($rh->{tmpl}, $rh, \$output);
+ b2g::big5togb($output);
+ print $output;
+ }
+}
+
+sub dirmode
+{
+ my(%th, $isdir);
+ my $serial = Data::Serializer->new(serializer => 'Storable',
+ digester => 'MD5',
+ compress => 0,
+ );
+ foreach( @{$serial->deserialize($db{$brdname}{$fpath}) || []} ){
+ $isdir = (($_->[0] =~ m|/$|) ? 1 : 0);
+ push @{$th{dat}}, {isdir => $isdir,
+ fn => "man.pl/$brdname$_->[0]",
+ title => $_->[1]};
+ }
+
+ $th{tmpl} = 'dir.html';
+ $th{isroot} = ($fpath eq '/') ? 1 : 0;
+ $th{buildtime} = POSIX::ctime($db{$brdname}{_buildtime} || 0);
+ return \%th;
+}
+
+sub articlemode
+{
+ my(%th);
+ $th{tmpl} = 'article.html';
+
+ # 先拿出來才 unzip, 要不然會爛掉 :p
+ $th{content} = $db{$brdname}{$fpath};
+ $th{content} = Compress::Zlib::memGunzip($th{content})
+ if( $db{$brdname}{_gzip} );
+
+ $th{content} =~ s/\033\[.*?m//g;
+
+ $th{content} =~ s|(http://[\w\-\.\:\/\,@\?=~]+)|<a href="$1">$1</a>|gs;
+ $th{content} =~ s|(ftp://[\w\-\.\:\/\,@~]+)|<a href="$1">$1</a>|gs;
+ $th{content} =~
+ s|ptt\.cc|<a href="telnet://ptt.cc">ptt.cc</a>|gs;
+ $th{content} =~
+ s|ptt\.twbbs\.org|<a href="telnet://ptt.cc">ptt.twbbs.org</a>|gs;
+ $th{content} =~
+ s|批踢踢兔|<a href="http://blog.ptt2.cc">批踢踢兔</a>|gs;
+ $th{content} =~
+ s|發信站: 批踢踢實業坊|發信站: <a href="http://ptt.cc">批踢踢實業坊</a>|gs;
+
+ return \%th;
+}
+
+sub search($)
+{
+ my($key) = @_;
+ my(%th, $idx, $k, $t0);
+ $t0 = [gettimeofday()];
+ $idx = OurNet::FuzzyIndex->new("$MANIDX/$brdname.idx");
+ my %result = $idx->query($th{key} = $key, MATCH_FUZZY);
+ foreach my $t (sort { $result{$b} <=> $result{$a} } keys(%result)) {
+ $k = $idx->getkey($t);
+ push @{$th{search}}, {title => $db{$brdname}{"title-$k"},
+ fn => $k,
+ score => $result{$t} / 10};
+ }
+
+ $th{elapsed} = tv_interval($t0);
+ $th{key} = $key;
+ $th{tmpl} = 'search.html';
+ undef $idx;
+ return \%th;
+}
+
+main();
+1;
diff --git a/pttbbs/staticweb/manbuilder.pl b/pttbbs/staticweb/manbuilder.pl
new file mode 100755
index 00000000..806d9a1c
--- /dev/null
+++ b/pttbbs/staticweb/manbuilder.pl
@@ -0,0 +1,105 @@
+#!/usr/bin/env perl
+# or local/bin/speedy
+# $Id$
+use lib '/home/bbs/bin/';
+use strict;
+use OurNet::FuzzyIndex;
+use Getopt::Std;
+use DB_File;
+use BBSFileHeader;
+use Data::Serializer;
+use Compress::Zlib;
+
+my(%db, $idx, $serial, $idxname);
+
+sub main
+{
+ die usage() unless( getopts('nz') || !@ARGV );
+
+ $serial = Data::Serializer->new(serializer => 'Storable',
+ digester => 'MD5',
+ compress => 0,
+ );
+
+ foreach( @ARGV ){
+ undef $idx;
+ if( /\.db$/ ){
+ next if( $Getopt::Std::opt_n );
+
+ $idxname = substr($_, 0, -3). '.idx';
+ print "building idx for $_\n";
+ tie %db, 'DB_File', $_, O_RDONLY, 0664, $DB_HASH;
+ $idx = OurNet::FuzzyIndex->new($idxname);
+ buildidx();
+ }
+ else{
+ tie %db, 'DB_File', "$_.db", O_CREAT | O_RDWR, 0664, $DB_HASH;
+ $idxname = "$_.idx";
+ $idx = OurNet::FuzzyIndex->new($idxname)
+ if( !$Getopt::Std::opt_n );
+ build("/home/bbs/man/boards/".substr($_, 0, 1)."/$_", '');
+ $db{_buildtime} = time();
+ $db{_gzip} = 1 if( $Getopt::Std::opt_z );
+ untie %db;
+ }
+
+ if( $idx ){
+ undef $idx;
+ chmod 0664, $idxname;
+ }
+ }
+}
+
+sub buildidx
+{
+ my($gzipped, $content);
+ $gzipped = $db{_gzip};
+ foreach( keys %db ){
+ next if( /^title/ || /\/$/ ); # 是 title 或目錄的都跳過
+ $content = $db{$_};
+ $content = Compress::Zlib::memGunzip($content)
+ if( $gzipped );
+
+ $idx->insert($_,
+ ($db{"title-$_"}. "\n$content"));
+ }
+}
+
+sub build($$)
+{
+ my($basedir, $doffset) = @_;
+ my(%bfh, $fn, @tdir);
+
+ print "building $basedir\n";
+ tie %bfh, 'BBSFileHeader', $basedir;
+ foreach( 0..($bfh{num} - 1) ){
+ next if( $bfh{"$_.filemode"} & 32 ); # skip HIDDEN
+ next if( !($fn = $bfh{"$_.filename"}) ); # skip empty filename
+
+ if( $bfh{"$_.isdir"} ){
+ push @tdir, ["$doffset/$fn/", # 目錄結尾要加 /
+ $db{"title-$doffset/$fn/"} = $bfh{"$_.title"}];
+ build("$basedir/$fn", "$doffset/$fn");
+ }
+ else{
+ push @tdir, ["$doffset/$fn",
+ $db{"title-$doffset/$fn"} = $bfh{"$_.title"}];
+ my $c = $bfh{"$_.content"};
+ $idx->insert("$doffset/$fn", $bfh{"$_.title"}. "\n$c")
+ if( !$Getopt::Std::opt_n );
+ $db{"$doffset/$fn"} = ($Getopt::Std::opt_z ?
+ Compress::Zlib::memGzip($c) : $c);
+ }
+ }
+ $db{"$doffset/"} = $serial->serialize(\@tdir);
+}
+
+sub usage
+{
+ print ("$0 [-n] boardname ...\n".
+ "\t -n for .db only (no .idx)\n");
+ exit(0);
+}
+
+main();
+1;
diff --git a/pttbbs/staticweb/search.html b/pttbbs/staticweb/search.html
new file mode 100644
index 00000000..d19fdc1d
--- /dev/null
+++ b/pttbbs/staticweb/search.html
@@ -0,0 +1,36 @@
+[% INCLUDE header.html %]
+<table width="75%" align="center">
+<tr><td>
+[% INCLUDE banner.html %]
+</td></tr>
+
+<tr><td>
+<a href="http://man.ptt.cc">網頁版精華區首頁</a>
+<a href="http://man.ptt.cc/man.pl/[% brdname %]/">[% brdname %]精華區首頁</a>
+<a href="http://blog.ptt.cc">批踢踢部落格</a>
+<br />
+<font size=+1>在看板 [% brdname %] 內搜尋 [% key %] (共費時 [% elapsed %] 秒)</font>
+<hr />
+</td></tr>
+<tr><td>
+
+<ul>
+[% FOREACH x=search %]
+<li><a href="/man.pl/[% brdname %][% x.fn %]">[% x.title %]</a> (score: [% x.score %])</li>
+[% END %]
+</ul>
+
+</td></tr>
+<tr><td>
+<hr />
+<form method="POST" action="/man.pl/[% brdname %]/">
+在這個精華區內翻弄
+<input type="text" name="key">
+<input type="submit" value="撈">
+</form>
+<hr />
+<a href="telnet://ptt.cc">批踢踢實業坊</a> (<a href="http://ptt.cc">PttWeb</a>)
+</td></tr>
+</table>
+</body>
+</html>
diff --git a/pttbbs/staticweb/styles.css b/pttbbs/staticweb/styles.css
new file mode 100644
index 00000000..87382c7b
--- /dev/null
+++ b/pttbbs/staticweb/styles.css
@@ -0,0 +1,20 @@
+#banner {
+ font-family: georgia, verdana, arial, sans-serif;
+ color: #FFFFFF;
+ font-size: 20px;
+ font-weight: bold;
+
+ padding: 8px 8px 8px 8px;
+ border: none;
+}
+
+A:link {color: #FFFFFF; text-decoration:none;}
+A:active {color: #CCFFCC; text-decoration:none;}
+A:visited {color: #FFFFCC; text-decoration:none;}
+A:hover {background: #555555;}
+
+body {
+ background: #000000;
+ color: #FFFFFF;
+ font-family: 細明體;
+} \ No newline at end of file
diff --git a/pttbbs/util/BBSFileHeader.pm b/pttbbs/util/BBSFileHeader.pm
new file mode 100644
index 00000000..f203f57c
--- /dev/null
+++ b/pttbbs/util/BBSFileHeader.pm
@@ -0,0 +1,61 @@
+#!/usr/bin/perl
+package BBSFileHeader;
+use strict;
+use IO::Handle;
+use Data::Dumper;
+
+use fields qw/dir fh cache/;
+
+sub TIEHASH
+{
+ my($class, $dir) = @_;
+ my $self = fields::new($class);
+
+ open $self->{fh}, "<$dir/.DIR";
+ return undef if( !$self->{fh} );
+
+ $self->{dir} = $dir;
+ return $self;
+}
+
+sub FETCH
+{
+ my($self, $k) = @_;
+
+ return $self->{dir} if( $k eq 'dir' );
+ return ((-s "$self->{dir}/.DIR") / 128) if( $k eq 'num' );
+
+ my($num, $key) = $k =~ /(.*)\.(.*)/;
+ my($t, %h);
+
+ $num += $self->FETCH('num') if( $num < 0 );
+
+ return $self->{cache}{$num}{$key} if( $self->{cache}{$num}{$key} );
+
+ seek($self->{fh}, $num * 128, 0);
+ $self->{fh}->read($t, 128);
+
+ if( $key eq 'isdir' ){
+ my $fn = "$self->{dir}/" . $self->FETCH("$num.filename");
+ return (-d $fn);
+ }
+ elsif( $key eq 'content' ){
+ my $fn = "$self->{dir}/" . $self->FETCH("$num.filename");
+ my($c, $fh);
+ open $fh, "<$fn" || return '';
+ $fh->read($c, (-s $fn));
+ return $c;
+ }
+ else{
+ ($h{filename}, $h{recommend}, $h{owner}, $h{date}, $h{title},
+ $h{money}, undef, $h{filemode}) =
+ unpack('Z33cZ14Z6Z65iCC', $t);
+ $h{title} = substr($h{title}, 3);
+ $self->{cache}{$num}{$_} = $h{$_}
+ foreach( 'filename', 'owner', 'date',
+ 'title', 'money', 'filemode' );
+ return $h{$key};
+ }
+}
+
+1;
diff --git a/pttbbs/util/BM_money.c b/pttbbs/util/BM_money.c
new file mode 100644
index 00000000..1f5cc10f
--- /dev/null
+++ b/pttbbs/util/BM_money.c
@@ -0,0 +1,110 @@
+/* $Id$ */
+
+/* 給板主錢的程式 */
+
+#define _UTIL_C_
+#include "bbs.h"
+
+#define FUNCTION (2100 - c*5)
+
+extern int numboards;
+extern boardheader_t *bcache;
+extern struct UCACHE *uidshm;
+
+int c, n;
+
+
+
+int Link(const char *src, const char *dst) {
+ char cmd[200];
+
+ if (link(src, dst) == 0)
+ return 0;
+
+ sprintf(cmd, "/bin/cp -R %s %s", src, dst);
+ return system(cmd);
+}
+
+
+int main(int argc, char **argv)
+{
+ FILE *fp = fopen(BBSHOME "/etc/topboardman", "r");
+ char buf[201], bname[20], BM[90], *ch;
+ boardheader_t *bptr = NULL;
+ int nBM;
+
+ attach_SHM();
+ resolve_boards();
+ if(passwd_init())
+ exit(1);
+ if (!fp)
+ return 0;
+
+ c = 0;
+ fgets(buf, 200, fp); /* 第一行拿掉 */
+
+ printf(
+ " \033[1;44m 獎勵優良板主 每週花薪 依精華區排名分配 \033[m\n\n"
+ "\033[33m (排名太後面或幾乎沒有精華區者不列入)\033[m\n"
+ " ─────────────────────────────────────\n"
+ "\n\n");
+
+ while (fgets(buf, 200, fp) != NULL)
+ {
+ buf[24] = 0;
+ sscanf(&buf[9], "%s", bname);
+ for (n = 0; n < numboards; n++)
+ {
+ bptr = &bcache[n];
+ if (!strcmp(bptr->brdname, bname))
+ break;
+ }
+ if (n == numboards)
+ continue;
+ strcpy(BM, bptr->BM);
+ printf(" (%d) %-15.15s %s \n", c + 1, bptr->brdname, bptr->title);
+
+ if (BM[0] == 0 || BM[0] == ' ')
+ continue;
+
+ ch = BM;
+ for (nBM = 1; (ch = strchr(ch, '/')) != NULL; nBM++)
+ {
+ ch++;
+ };
+ ch = BM;
+
+ if (FUNCTION <= 0)
+ break;
+
+ printf(" 獎金 \033[32m%6d \033[m 分給 \033[33m%s\033[m \n",
+ FUNCTION, bptr->BM);
+
+ for (n = 0; n < nBM; n++)
+ {
+ fileheader_t mymail;
+ char *ch1,uid ;
+ if((ch1 = strchr(ch, '/')))
+ *ch1 = 0;
+ if ((uid=searchuser(ch, ch))!=0)
+ {
+
+ char genbuf[200];
+ deumoney(uid,FUNCTION / nBM);
+ sprintf(genbuf, BBSHOME "/home/%c/%s", ch[0], ch);
+ stampfile(genbuf, &mymail);
+
+ strcpy(mymail.owner, "[薪水袋]");
+ sprintf(mymail.title,
+ "\033[32m %s \033[m板的薪水 $\033[33m%d\033![m", bptr->brdname, FUNCTION / nBM);
+ unlink(genbuf);
+ Link(BBSHOME "/etc/BM_money", genbuf);
+ sprintf(genbuf, BBSHOME "/home/%c/%s/.DIR", ch[0], ch);
+ append_record(genbuf, &mymail, sizeof(mymail));
+ }
+ ch = ch1 + 1;
+ }
+ c++;
+ }
+ return 0;
+}
diff --git a/pttbbs/util/BM_money.sh b/pttbbs/util/BM_money.sh
new file mode 100644
index 00000000..8bef4fc9
--- /dev/null
+++ b/pttbbs/util/BM_money.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+# $Id: BM_money.sh,v 1.1 2002/03/07 15:13:45 in2 Exp $
+
+bin/BM_money > etc/BM_money
+bin/post Record 星期五' '版主發薪日 [財金消息] etc/BM_money
diff --git a/pttbbs/util/Makefile b/pttbbs/util/Makefile
new file mode 100644
index 00000000..6cc09e87
--- /dev/null
+++ b/pttbbs/util/Makefile
@@ -0,0 +1,116 @@
+# $Id$
+
+.include "../pttbbs.mk"
+
+CFLAGS+= -DPTTBBS_UTIL
+
+BBSBASE= ../include/var.h
+
+UTIL_OBJS= \
+ util_cache.o util_record.o util_passwd.o util_var.o \
+ util_stuff.o util_osdep.o util_args.o util_file.o \
+ util_crypt.o util_calendar.o
+
+MBBSD_OBJS= \
+ cache record passwd var \
+ stuff osdep args file \
+ crypt calendar
+
+# 下面這些程式, 會被 compile 並且和 $(UTIL_OBJS) 聯結
+CPROG_WITH_UTIL= \
+ boardlist BM_money post poststat \
+ jungo account birth deluserfile \
+ expire mandex horoscope broadcast \
+ openvice openticket topusr \
+ yearsold toplazyBM toplazyBBM writemoney \
+ reaper buildAnnounce inndBM mailangel \
+ outmail chkhbf merge_dir \
+ transman angel gamblegive wretch_man \
+ chesscountry tunepasswd buildir xchatd
+
+# 下面這些程式, 會直接被 compile
+CPROG_WITHOUT_UTIL= \
+ uhash_loader showboard countalldice bbsrf \
+ initbbs userlist merge_board bbsmail
+
+# 下面這些程式會被 install
+PROGS= ${CPROG_WITH_UTIL} ${CPROG_WITHOUT_UTIL} \
+ shmctl \
+ BM_money.sh backpasswd.sh mailog.sh opendice.sh \
+ openticket.sh stock.sh topsong.sh weather.sh \
+ stock.perl weather.perl toplazyBM.sh toplazyBBM.sh \
+ dailybackup.pl tarqueue.pl waterball.pl filtermail.pl \
+ getbackup.pl udnnews.pl rebuildaloha.pl
+
+all: ${CPROG_WITH_UTIL} ${CPROG_WITHOUT_UTIL} ${PROGS}
+
+../include/var.h: ../mbbsd/var.c
+ cd ../mbbsd; $(MAKE) ../include/var.h
+
+.for fn in ${CPROG_WITH_UTIL}
+${fn}: ${BBSBASE} ${fn}.c ${UTIL_OBJS}
+ $(CCACHE) ${CC} ${CFLAGS} ${LDFLAGS} -o ${fn} ${UTIL_OBJS} ${fn}.c
+.endfor
+
+.for fn in ${MBBSD_OBJS}
+util_${fn}.o: ${BBSBASE} ../mbbsd/${fn}.c
+ $(CCACHE) ${CC} ${CFLAGS} -D_BBS_UTIL_C_ -c -o $@ ../mbbsd/${fn}.c
+.endfor
+
+shmctl: ${BBSBASE} shmctl.c ${UTIL_OBJS}
+ $(CCACHE) ${CC} ${CFLAGS} ${LDFLAGS} -o shmctl ${UTIL_OBJS} shmctl.c
+#shmctl: ${BBSBASE} shmctl.c ${UTIL_OBJS}
+# $(CCACHE) gcc -g -DBBSHOME='"/home/bbs"' -I../include -D__OS_MAJOR_VERSION__="2" -D__OS_MINOR_VERSION__="6" -DPTTBBS_UTIL -O1 -o shmctl ${UTIL_OBJS} shmctl.c
+#shmctl: ${BBSBASE} shmctl.cc ${UTIL_OBJS}
+# $(CCACHE) g++ -g -DBBSHOME='"/home/bbs"' -I../include -D__OS_MAJOR_VERSION__="2" -D__OS_MINOR_VERSION__="6" -DPTTBBS_UTIL -O1 -o shmctl ${UTIL_OBJS} shmctl.cc
+
+bbsmail: ${BBSBASE} bbsmail.c ../innbbsd/str_decode.c $(UTIL_OBJS)
+ $(CCACHE) $(CC) $(CFLAGS) $(LDFLAGS) -o bbsmail -DUSE_ICONV \
+ bbsmail.c ../innbbsd/str_decode.c $(UTIL_OBJS)
+
+install: $(PROGS)
+ install -d $(BBSHOME)/bin/
+ install -c -m 755 $(PROGS) $(BBSHOME)/bin/
+ chmod 4755 $(BBSHOME)/bin/post
+.if defined(WITHFILTERMAIL)
+ $(MAKE) installfiltermail
+.endif
+
+clean:
+ rm -f *.o $(CPROGS) $(CPROG_WITH_UTIL) $(CPROG_WITHOUT_UTIL)
+
+
+installfiltermail:
+ mv $(BBSHOME)/bin/bbsmail $(BBSHOME)/bin/realbbsmail
+ ln -s $(BBSHOME)/bin/filtermail.pl $(BBSHOME)/bin/bbsmail
+
+# for diskstat(FreeBSD 4.x only) .
+# diskstat should be compiled with bbs and installed with root
+diskstat: diskstat.c
+ $(CCACHE) $(CC) $(CFLAGS) -o diskstat diskstat.c -ldevstat -lkvm
+
+installdiskstat: diskstat
+ cp -f diskstat /usr/local/bin/
+ chgrp kmem /usr/local/bin/diskstat
+ chmod 2755 /usr/local/bin/diskstat
+
+# for bbsctl. bbsctl should be compiled with bbs and installed with root
+bbsctl: bbsctl.c
+ $(CCACHE) $(CC) $(CFLAGS) -o $@ $@.c
+
+installbbsctl: bbsctl
+ rm -f /home/bbs/bin/bbsctl
+ cp /home/bbs/pttbbs/util/bbsctl /home/bbs/bin/bbsctl
+ chown root /home/bbs/bin/bbsctl
+ chmod 4755 /home/bbs/bin/bbsctl
+
+cleanpasswd: cleanpasswd.c ${UTIL_OBJS}
+ $(CCACHE) ${CC} ${CFLAGS} ${LDFLAGS} -o cleanpasswd ${UTIL_OBJS} cleanpasswd.c
+
+r2014transfer: r2014convert
+ $(CCACHE) ${CC} ${CFLAGS} ${LDFLAGS} -o r2014convert r2014convert.c
+ ./r2014convert
+ rm r2014convert
+
+passwdconverter: passwdconverter.c
+ $(CCACHE) $(CC) $(CFLAGS) $(LDFLAGS) $(UTIL_OBJS) -o passwdconverter passwdconverter.c
diff --git a/pttbbs/util/account.c b/pttbbs/util/account.c
new file mode 100644
index 00000000..35437755
--- /dev/null
+++ b/pttbbs/util/account.c
@@ -0,0 +1,394 @@
+/* $Id$ */
+#include "bbs.h"
+
+// test
+#define ACCOUNT_MAX_LINE 16
+#define ADJUST_M 6 /* adjust back 5 minutes */
+
+void
+reset_garbage(void)
+{
+ if (SHM == NULL) {
+ attach_SHM();
+ if (SHM->Ptouchtime == 0)
+ SHM->Ptouchtime = 1;
+ }
+ /*
+ * 不整個reload? for(n=0;n<=SHM->last_film;n++) printf("\n**%d**\n %s
+ * \n",n,SHM->notes[n]);
+ */
+ SHM->Puptime = 0;
+ reload_pttcache();
+
+ printf("\n動態看板數[%d]\n", SHM->last_film);
+ /*
+ * for(n=0; n<MAX_MOVIE_SECTION; n++) printf("sec%d=> 起點:%d
+ * 下次要換的:%d\n ",n,SHM->n_notes[n], SHM->next_refresh[n]);
+ * printf("\n");
+ */
+}
+
+void
+keeplog(char *fpath, char *board, char *title, char *sym)
+{
+ fileheader_t fhdr;
+ int bid;
+ char genbuf[256], buf[256];
+
+ if (!board)
+ board = "Record";
+
+ sprintf(genbuf, BBSHOME "/boards/%c/%s", board[0], board);
+ stampfile(genbuf, &fhdr);
+ sprintf(buf, "mv %s %s", fpath, genbuf);
+ system(buf);
+
+ if( sym ){
+ sprintf(buf, "log/%s", sym);
+ unlink(buf);
+ symlink(genbuf, buf);
+ }
+ /*
+ * printf("keep record:[%s][%s][%s][%s]\n",fpath, board, title,genbuf);
+ */
+ strcpy(fhdr.title, title);
+ strcpy(fhdr.owner, "[歷史老師]");
+ sprintf(genbuf, "boards/%c/%s/.DIR", board[0], board);
+ append_record(genbuf, &fhdr, sizeof(fhdr));
+ /* XXX: bid of cache.c's getbnum starts from 1 */
+ if ((bid = getbnum(board)) > 0)
+ touchbtotal(bid);
+}
+
+
+static void
+my_outs(fp, buf, mode)
+ FILE *fp;
+ char buf[], mode;
+{
+ static char state = '0';
+
+ if (state != mode)
+ fprintf(fp, "[3%cm", state = mode);
+ if (buf[0]) {
+ fprintf(fp, buf);
+ buf[0] = 0;
+ }
+}
+
+/* XXX: 怪怪的, 看不懂在 gzip() 什麼, 而且其中的 stamp 好像都亂傳進來 */
+void
+gzip(source, target, stamp)
+ char *source, *target, *stamp;
+{
+ char buf[128];
+ sprintf(buf, "gzip -f9n adm/%s%s", target, stamp);
+ rename(source, &buf[14]);
+ system(buf);
+}
+
+int
+main(int argc, char **argv)
+{
+ int hour, max, item, total, i, j, mo, da, max_user = 0,
+ max_login = 0, max_reg = 0, mahour = 0, k, wday;
+ char *act_file = ".act";
+ char *log_file = "usies";
+ char *wday_str = "UMTWRFS";
+ char buf[256], buf1[256], *p;
+ FILE *fp, *fp1;
+ int act[27]; /* 次數/累計時間/pointer */
+ time4_t now;
+ struct tm *ptime;
+ int per_hour_unit = 10;
+
+ attach_SHM();
+ nice(10);
+ chdir(BBSHOME);
+ now = time(NULL) - ADJUST_M * 60; /* back to ancent */
+ ptime = localtime4(&now);
+
+ memset(act, 0, sizeof(act));
+ printf("次數/累計時間\n");
+ if ((ptime->tm_hour != 0) && (fp = fopen(act_file, "r"))) {
+ fread(act, sizeof(act), 1, fp);
+ fclose(fp);
+ }
+ if ((fp = fopen(log_file, "r")) == NULL) {
+ printf("cann't open usies\n");
+ return 1;
+ }
+ if (act[26])
+ fseek(fp, act[26], 0);
+ while (fgets(buf, 256, fp)) {
+ buf[11 + 2] = 0;
+ hour = atoi(buf + 11);
+ if (hour < 0 || hour > 23) {
+ continue;
+ }
+ //"09/06/1999 17:44:58 Mon "
+ // 012345678901234567890123
+ if (strstr(buf + 20, "ENTER")) {
+ act[hour]++;
+ continue;
+ }
+ if ((p = (char *)strstr(buf + 40, "Stay:"))) {
+ if ((hour = atoi(p + 5))) {
+ act[24] += hour;
+ act[25]++;
+ }
+ continue;
+ }
+ }
+ act[26] = ftell(fp);
+ fclose(fp);
+ for (i = max = total = 0; i < 24; i++) {
+ total += act[i];
+ if (act[i] > max) {
+ max_user = max = act[i];
+ mahour = i;
+ }
+ }
+ item = max / ACCOUNT_MAX_LINE + 1;
+
+ if (!ptime->tm_hour) {
+ keeplog("etc/today", "Record", "上站人次統計", NULL);
+ keeplog(FN_MONEY, "Security", "本日金錢往來記錄", NULL);
+ keeplog("etc/illegal_money", "Security", "本日違法賺錢記錄", NULL);
+ keeplog("etc/osong.log", "Security", "本日點歌記錄", NULL);
+ keeplog("etc/chicken", "Record", "雞場報告", NULL);
+ }
+ printf("上站人次統計\n");
+ if ((fp = fopen("etc/today", "w")) == NULL) {
+ printf("cann't open etc/today\n");
+ return 1;
+ }
+ fprintf(fp, "\t\t\t 每小時上站人次統計 [%02d/%02d/%02d] \n\n", ptime->tm_year % 100, ptime->tm_mon + 1, ptime->tm_mday);
+ for (i = ACCOUNT_MAX_LINE + 1; i > 0; i--) {
+ strcpy(buf, " ");
+ for (j = 0; j < 24; j++) {
+ max = item * i;
+ hour = act[j];
+ if (hour && (max > hour) && (max - item <= hour)) {
+ my_outs(fp, buf, '3');
+ fprintf(fp, "%-3d", hour / per_hour_unit);
+ } else if (max <= hour) {
+ my_outs(fp, buf, '4');
+ fprintf(fp, "█ ");
+ } else
+ strcat(buf, " ");
+ }
+ fprintf(fp, "\n");
+ }
+ fprintf(fp, " "
+ "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23\n\n"
+ "\t 單位: %d 人", per_hour_unit);
+ fprintf(fp, " 總共上站人次:%-7d平均使用人數:%d\n", total, total / 24);
+ fclose(fp);
+
+ if ((fp = fopen(act_file, "w"))) {
+ fwrite(act, sizeof(act), 1, fp);
+ fclose(fp);
+ }
+ /* -------------------------------------------------------------- */
+
+ sprintf(buf, "-%02d%02d%02d",
+ ptime->tm_year % 100, ptime->tm_mon + 1, ptime->tm_mday);
+
+ now += ADJUST_M * 60; /* back to future */
+
+
+ printf("歷史事件處理\n");
+ /* Ptt 歷史事件處理 */
+ if ((fp = fopen("etc/history.data", "r"))) { /* 最多同時上線 */
+ if (fscanf(fp, "%d %d %d %d", &max_login, &max, &max_reg, &k)) {
+ int a;
+ resolve_fcache();
+ printf("此時段最多同時上線:%d 過去:%d\n", a = SHM->max_user, k);
+ fclose(fp);
+ if (a > k) {
+ ptime = localtime4(&SHM->max_time);
+ if ((fp1 = fopen("etc/history", "a"))) {
+ fprintf(fp1,
+ "◎ 【%02d/%02d/%02d %02d:%02d】"
+ "同時在坊內人數首次達到 %d 人次\n",
+ ptime->tm_mon + 1, ptime->tm_mday, ptime->tm_year % 100,
+ ptime->tm_hour, ptime->tm_min, a);
+ fclose(fp1);
+ }
+ if ((fp = fopen("etc/history.data", "w"))) {
+ fprintf(fp, "%d %d %d %d", max_login, max, max_reg, a);
+ fclose(fp);
+ }
+ }
+ } else
+ fclose(fp);
+ }
+ ptime = localtime4(&now);
+
+ if (ptime->tm_hour) {
+ /* rotate one line in today_is */
+ puts("多個節日處理");
+ if ((fp1 = fopen("etc/today_is", "r"))) {
+ char tod[100][20];
+
+ i = 0;
+ while (i < 100 && fgets(tod[i], sizeof(tod[0]), fp1))
+ i++;
+ fclose(fp1);
+
+ fp1 = fopen("etc/today_is", "w");
+ for (j = 0; j < i; j++)
+ fputs(tod[j + 1 < i ? j + 1 : 0], fp1);
+ fclose(fp1);
+ }
+ }
+ if (!ptime->tm_hour) {
+ keeplog(".note", "Record", "心情留言版", NULL);
+ system("/bin/cp etc/today etc/yesterday");
+ /* system("rm -f note.dat"); */
+ /* Ptt */
+ sprintf(buf1, "[公安報告] 使用者上線監控 [%02d/%02d:%02d]",
+ ptime->tm_mon + 1, ptime->tm_mday, ptime->tm_hour);
+ keeplog("usies", "Security", buf1, "usies");
+ printf("[公安報告] 使用者上線監控\n");
+ gzip(log_file, "usies", buf);
+ printf("壓縮使用者上線監控\n");
+ /* Ptt 歷史事件處理 */
+ now = time(NULL) - ADJUST_M * 60; /* back to ancent */
+ ptime = localtime4(&now);
+
+ attach_SHM();
+ if ((fp = fopen("etc/history.data", "r"))) { /* 單日最多次人次,同時上線
+ * ,註冊 */
+ if (fscanf(fp, "%d %d %d %d", &max_login, &max, &max_reg, &k)) {
+ fp1 = fopen("etc/history", "r+");
+ fseek(fp1, 0, 2);
+ if (max_user > max) {
+ fprintf(fp1, "◇ 【%02d/%02d/%02d %02d】 "
+ "單一小時上線人次首次達到 %d 人次 \n"
+ ,ptime->tm_mon + 1, ptime->tm_mday, ptime->tm_year % 100, mahour, max_user);
+ max = max_user;
+ }
+ if (total > max_login) {
+ fprintf(fp1, "◆ 【%02d/%02d/%02d】 "
+ "單日上線人次首次達到 %d 人次 \n"
+ ,ptime->tm_mon + 1, ptime->tm_mday, ptime->tm_year % 100, total);
+ max_login = total;
+ }
+ if (SHM->number > max_reg + max_reg / 10) {
+ fprintf(fp1, "★ 【%02d/%02d/%02d】 "
+ "總註冊人數提升到 %d 人 \n"
+ ,ptime->tm_mon + 1, ptime->tm_mday, ptime->tm_year % 100, SHM->number);
+ max_reg = SHM->number;
+ }
+ fclose(fp1);
+ }
+ fclose(fp);
+ fp = fopen("etc/history.data", "w");
+ fprintf(fp, "%d %d %d %d", max_login, max, max_reg, k);
+ fclose(fp);
+ }
+ now += ADJUST_M * 60; /* back to future */
+ ptime = localtime4(&now);
+
+ /* Ptt 節日處理 */
+ printf("節日處理\n");
+ if ((fp1 = fopen("etc/today_is", "w"))) {
+ i = 0;
+ if ((fp = fopen("etc/feast", "r"))) {
+ while (fgets(buf1, sizeof(buf1), fp)) {
+ if (buf[0] != '#' &&
+ sscanf(buf1, "%d %c%c", &mo, buf, buf + 1) == 3) {
+ if (isdigit(buf[0])) {
+ if (isdigit(buf[1])) {
+ da = 10 * (buf[0] - '0') + (buf[1] - '0');
+ if (ptime->tm_mday == da && ptime->tm_mon + 1 == mo) {
+ i = 1;
+ fprintf(fp1, "%-14.14s", &buf1[6]);
+ }
+ } else {
+ if (buf[0] - '0' <= 4) {
+ wday = 0;
+ buf[1] = toupper(buf[1]);
+ while (wday < 7 && buf[1] != *(wday_str + wday))
+ wday++;
+ if (ptime->tm_mon + 1 == mo && ptime->tm_wday == wday &&
+ (ptime->tm_mday - 1) / 7 + 1 == (buf[0] - '0')) {
+ i = 1;
+ fprintf(fp1, "%-14.14s", &buf1[6]);
+ }
+ }
+ }
+ }
+ }
+ }
+ fclose(fp);
+ }
+ printf("節日處理1\n");
+ if (i == 0) {
+ if ((fp = fopen("etc/today_boring", "r"))) {
+ while (fgets(buf1, sizeof(buf1), fp))
+ if (strlen(buf1) > 3)
+ fprintf(fp1, "%s", buf1);
+ fclose(fp);
+ } else
+ fprintf(fp1, "本日節日徵求中");
+ }
+ fclose(fp1);
+ }
+ /* Ptt 歡迎畫面處理 */
+ printf("歡迎畫面處理\n");
+
+ if ((fp = fopen("etc/Welcome.date", "r"))) {
+ char temp[50];
+ while (fscanf(fp, "%d %d %s\n", &mo, &da, buf1) != EOF) {
+ if (ptime->tm_mday == da && ptime->tm_mon + 1 == mo) {
+ strcpy(temp, buf1);
+ sprintf(buf1, "cp -f etc/Welcomes/%s etc/Welcome", temp);
+ system(buf1);
+ break;
+ }
+ }
+ fclose(fp);
+ }
+ printf("歡迎畫面處理\n");
+ if (ptime->tm_wday == 0) {
+ keeplog("etc/week", "Record", "本週熱門話題", NULL);
+
+ gzip("bbslog", "bntplink", buf);
+ gzip("innd/bbslog", "innbbsd", buf);
+ gzip("etc/mailog", "mailog", buf);
+ }
+ if (ptime->tm_mday == 1)
+ keeplog("etc/month", "Record", "本月熱門話題", NULL);
+
+ if (ptime->tm_yday == 1)
+ keeplog("etc/year", "Record", "年度熱門話題", NULL);
+ } else if (ptime->tm_hour == 3 && ptime->tm_wday == 6) {
+ char *fn1 = "tmp";
+ char *fn2 = "suicide";
+ rename(fn1, fn2);
+ mkdir(fn1, 0755);
+ sprintf(buf, "tar cfz adm/%s-%02d%02d%02d.tgz %s",
+ fn2, ptime->tm_year % 100, ptime->tm_mon + 1, ptime->tm_mday, fn2);
+ system(buf);
+ sprintf(buf, "/bin/rm -fr %s", fn2);
+ system(buf);
+ }
+ /* Ptt reset Ptt's share memory */
+ printf("重設Pttcache 與fcache\n");
+
+ SHM->Puptime = 0;
+ resolve_fcache();
+ reset_garbage();
+
+ printf("計算進站畫面數: ");
+ for (i = 0; i < 5; ++i) {
+ sprintf(buf, "etc/Welcome_login.%d", i);
+ if (access(buf, 0) < 0)
+ break;
+ }
+ printf("%d\n", SHM->GV2.e.nWelcomes = i);
+ return 0;
+}
diff --git a/pttbbs/util/angel.c b/pttbbs/util/angel.c
new file mode 100644
index 00000000..c12cb555
--- /dev/null
+++ b/pttbbs/util/angel.c
@@ -0,0 +1,207 @@
+/* $Id$ */
+#include "bbs.h"
+
+#ifndef PLAY_ANGEL
+int main(){ return 0; }
+#else
+
+int total[MAX_USERS + 1];
+unsigned char reject[MAX_USERS + 1];
+int nReject[4];
+int rej_question;
+int double_rej;
+int (*list)[2];
+int *rej_list;
+int nReport = 50;
+int count;
+char* mailto = "SYSOP";
+
+int ListCmp(const void * a, const void * b){
+ return *(int*)b - *(int*)a;
+}
+
+int RejCmp(const void * a, const void * b){
+ return strcasecmp(SHM->userid[*(int*)a - 1], SHM->userid[*(int*)b - 1]);
+}
+
+void readData();
+void sendResult();
+void slurp(FILE* to, FILE* from);
+
+int main(int argc, char* argv[]){
+ if (argc > 1)
+ mailto = argv[1];
+ if (argc > 2)
+ nReport = atoi(argv[2]);
+
+ readData();
+ sendResult();
+ return 0;
+}
+
+void readData(){
+ int i, j;
+ int k;
+ userec_t user;
+ FILE* fp;
+
+ attach_SHM();
+
+ fp = fopen(BBSHOME "/.Angel", "rb");
+ if (fp != 0) {
+ fread(reject, 1, sizeof(reject), fp);
+ fclose(fp);
+ }
+
+ fp = fopen(BBSHOME "/.PASSWDS", "rb");
+ j = count = 0;
+ while (fread(&user, sizeof(userec_t), 1, fp) == 1) {
+ ++j; /* j == uid */
+ if (user.myangel[0]) {
+ i = searchuser(user.myangel, NULL);
+ if (i)
+ ++total[i];
+ }
+ if (user.userlevel & PERM_ANGEL) {
+ ++count;
+ ++nReject[((user.uflag2 & ANGEL_MASK) >> 12)];
+ ++total[j]; /* make all angel have total > 0 */
+ if (user.uflag2 & REJ_QUESTION) {
+ ++rej_question;
+ if (++reject[j] >= 2)
+ ++double_rej;
+ } else
+ reject[j] = 0;
+ } else { /* don't have PERM_ANGEL */
+ total[j] = INT_MIN;
+ reject[j] = 0;
+ }
+ }
+ fclose(fp);
+
+ fp = fopen(BBSHOME "/.Angel", "wb");
+ if (fp != NULL) {
+ fwrite(reject, sizeof(reject), 1, fp);
+ fclose(fp);
+ }
+
+ if(nReport > count)
+ nReport = count;
+
+ list = (int(*)[2]) malloc(count * sizeof(int[2]));
+ rej_list = (int*) malloc(double_rej * sizeof(int));
+ k = j = 0;
+ for (i = 1; i <= MAX_USERS; ++i)
+ if (total[i] > 0) {
+ list[j][0] = total[i] - 1;
+ list[j][1] = i;
+ ++j;
+ if (reject[i] >= 2)
+ rej_list[k++] = i;
+ }
+
+ qsort(list, count, sizeof(int[2]), ListCmp);
+ qsort(rej_list, double_rej, sizeof(int), RejCmp);
+}
+
+int mailalertuser(char* userid)
+{
+ userinfo_t *uentp=NULL;
+ if (userid[0] && (uentp = search_ulist_userid(userid)))
+ uentp->alerts|=ALERT_NEW_MAIL;
+ return 0;
+}
+
+void sendResult(){
+ int i;
+ FILE* fp;
+ time4_t t;
+ fileheader_t header;
+ struct stat st;
+ char filename[512];
+
+ sprintf(filename, BBSHOME "/home/%c/%s", mailto[0], mailto);
+ if (stat(filename, &st) == -1) {
+ if (mkdir(filename, 0755) == -1) {
+ fprintf(stderr, "mail box create error %s \n", filename);
+ return;
+ }
+ }
+ else if (!(st.st_mode & S_IFDIR)) {
+ fprintf(stderr, "mail box error\n");
+ return;
+ }
+
+ stampfile(filename, &header);
+ fp = fopen(filename, "w");
+ if (fp == NULL) {
+ fprintf(stderr, "Cannot open file %s\n", filename);
+ return;
+ }
+
+ t = time(NULL);
+ fprintf(fp, "作者: Ptt 站方統計\n"
+ "標題: 小天使統計資料\n"
+ "時間: %s\n"
+ "\n現在全站小天使有 %d 位\n"
+ "\n小主人人數最多的 %d 位小天使:\n",
+ ctime4(&t), count, nReport);
+ for (i = 0; i < nReport; ++i)
+ fprintf(fp, "%15s %5d 人\n", SHM->userid[list[i][1] - 1], list[i][0]);
+ fprintf(fp, "\n現在男女皆收的小天使有 %d 位\n"
+ "只收女生的有 %d 位 (共 %d 位收女生)\n"
+ "只收男生的有 %d 位 (共 %d 位收男生)\n"
+ "不接受新小天使的有 %d 位\n",
+ nReject[0], nReject[1], nReject[1] + nReject[0],
+ nReject[2], nReject[2] + nReject[0], nReject[3]);
+ fprintf(fp, "\n現在開放小主人問問題的小天使有 %d 位\n"
+ "不開放的有 %d 位\n"
+ "連續兩次統計都不開放的有 %d 位:\n",
+ count - rej_question, rej_question, double_rej);
+ for (i = 0; i < double_rej; ++i) {
+ fprintf(fp, "%13s %3d 次", SHM->userid[rej_list[i] - 1],
+ reject[rej_list[i]]);
+ if (i % 4 == 3) fputc('\n', fp);
+ }
+ if (i % 4 != 0) fputc('\n', fp);
+
+ {
+ FILE* changefp = fopen(BBSHOME "/log/changeangel.log", "r");
+ if (changefp) {
+ remove(BBSHOME "/log/changeangel.log");
+
+ fputs("\n== 本周更換小天使紀錄 ==\n", fp);
+ slurp(fp, changefp);
+ fclose(changefp);
+ }
+ }
+
+ fputs("\n--\n\n 本資料由 angel 程式產生\n\n", fp);
+ fclose(fp);
+
+ strcpy(header.title, "小天使統計資料");
+ strcpy(header.owner, "站方統計");
+ sprintf(filename, BBSHOME "/home/%c/%s/.DIR", mailto[0], mailto);
+ append_record(filename, &header, sizeof(header));
+ mailalertuser(mailto);
+}
+
+void slurp(FILE* to, FILE* from)
+{
+ char buf[4096]; // 4K block
+ int count;
+
+ while ((count = fread(buf, 1, sizeof(buf), from)) > 0) {
+ char * p = buf;
+ while (count > 0) {
+ int i = fwrite(p, 1, count, to);
+
+ if (i <= 0) return;
+
+ p += i;
+ count -= i;
+ }
+ }
+}
+
+#endif /* defined PLAY_ANGEL */
diff --git a/pttbbs/util/backpasswd.sh b/pttbbs/util/backpasswd.sh
new file mode 100644
index 00000000..ed354d5f
--- /dev/null
+++ b/pttbbs/util/backpasswd.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# $Id: backpasswd.sh,v 1.3 2002/04/06 08:32:34 in2 Exp $
+
+mv PASSWDS.NEW5 PASSWDS.NEW6
+mv PASSWDS.NEW4 PASSWDS.NEW5
+mv PASSWDS.NEW3 PASSWDS.NEW4
+mv PASSWDS.NEW2 PASSWDS.NEW3
+mv PASSWDS.NEW1 PASSWDS.NEW2
+mv PASSWDS.NEW PASSWDS.NEW1
+cp .PASSWDS PASSWDS.NEW
+
+mv backup/PASSWDS.NEW5 backup/PASSWDS.NEW6
+mv backup/PASSWDS.NEW4 backup/PASSWDS.NEW5
+mv backup/PASSWDS.NEW3 backup/PASSWDS.NEW4
+mv backup/PASSWDS.NEW2 backup/PASSWDS.NEW3
+mv backup/PASSWDS.NEW1 backup/PASSWDS.NEW2
+mv backup/PASSWDS.NEW backup/PASSWDS.NEW1
+cp .PASSWDS backup/PASSWDS.NEW
+
+mv backup/BRD.NEW5 backup/BRD.NEW6
+mv backup/BRD.NEW4 backup/BRD.NEW5
+mv backup/BRD.NEW3 backup/BRD.NEW4
+mv backup/BRD.NEW2 backup/BRD.NEW3
+mv backup/BRD.NEW1 backup/BRD.NEW2
+mv backup/BRD.NEW backup/BRD.NEW1
+cp .BRD backup/BRD.NEW
+
diff --git a/pttbbs/util/banip.pl b/pttbbs/util/banip.pl
new file mode 100644
index 00000000..da36c619
--- /dev/null
+++ b/pttbbs/util/banip.pl
@@ -0,0 +1,151 @@
+#!/usr/bin/perl
+# $Id$
+
+@list = (
+#bbsnet
+'140.116.49.3', '163.23.212.5', '140.134.107.16', '134.208.3.64', '137.189.178.189',
+'140.134.107.16', '134.208.10.250', '211.151.95.188', '140.112.18.71', '140.123.107.90',
+'166.111.37.13', '206.222.17.254', '61.135.159.154', '210.51.188.45', '202.114.68.70',
+'140.116.49.3', '163.23.212.5', '140.116.246.180', '140.116.25.10', '202.207.208.100',
+'163.23.212.5', '211.151.89.114', '211.96.233.1', '202.205.80.137', '202.113.16.117',
+# tor
+'86.59.21.38', '149.9.137.153', '88.134.102.161', '24.170.55.120', '64.90.179.108',
+'88.112.243.180', '209.51.169.86', '213.114.29.49', '82.56.16.111', '141.76.46.90',
+'72.226.235.186', '64.5.53.220', '82.67.37.163', '83.243.88.133', '81.11.163.149',
+'85.178.254.218', '84.48.64.142', '69.136.226.204', '196.203.247.39', '66.91.243.239',
+'68.148.199.84', '84.172.127.88', '60.36.181.86', '71.32.251.76', '209.9.232.195',
+'83.233.97.253', '62.75.185.15', '80.202.104.135', '68.171.51.161', '67.68.0.144',
+'80.186.67.109', '217.119.47.6', '83.250.216.202', '207.210.101.242', '80.145.188.199',
+'68.202.36.79', '84.61.32.213', '81.57.158.21', '70.246.51.198', '208.40.218.136',
+'149.9.92.194', '219.95.23.216', '80.126.234.27', '221.118.188.140', '82.248.226.122',
+'213.84.94.72', '80.126.37.100', '24.36.129.106', '64.136.200.11', '213.239.217.146',
+'84.119.119.199', '66.90.118.68', '84.60.97.100', '213.132.141.91', '149.9.0.25',
+'208.40.218.131', '213.228.241.143', '128.2.141.33', '18.187.1.68', '72.232.45.106',
+'209.103.127.1', '213.112.22.232', '70.110.70.238', '140.78.95.135', '71.246.25.54',
+'212.202.78.170', '24.111.174.178', '209.242.5.54', '81.169.176.135', '64.74.207.50',
+'217.85.115.240', '62.75.218.208', '64.5.53.254', '87.105.54.221', '81.169.180.180',
+'131.179.224.133', '82.66.155.32', '80.221.50.147', '194.29.137.67', '24.42.92.42',
+'128.30.28.19', '217.160.109.40', '64.132.239.129', '212.114.250.252', '68.94.207.198',
+'62.75.184.254', '207.44.180.3', '68.40.192.5', '217.160.167.28', '87.123.149.66',
+'24.9.185.227', '68.174.141.133', '149.9.205.73', '217.160.203.26', '64.81.100.208',
+'72.177.87.57', '81.169.158.102', '196.11.98.20', '69.41.174.196', '66.197.193.212',
+'66.93.45.117', '70.156.79.236', '84.73.10.171', '59.134.15.153', '212.112.235.78',
+'195.245.255.11', '213.133.99.185', '84.152.111.155', '81.216.157.211', '195.85.225.145',
+'154.35.72.223', '85.16.11.120', '88.84.130.170', '88.113.36.247', '69.194.143.65',
+'24.255.8.175', '150.140.191.108', '217.155.206.93', '128.2.132.175', '213.239.194.175',
+'154.35.36.18', '84.34.133.217', '61.242.102.18', '217.84.135.45', '72.232.45.107',
+'85.214.49.175', '84.9.35.164', '68.62.178.1', '70.92.178.34', '208.14.31.5',
+'137.226.181.149', '69.249.105.136', '154.35.85.17', '72.20.1.166', '68.215.81.135',
+'66.159.225.72', '66.236.18.180', '85.31.186.26', '69.31.42.139', '83.219.212.101',
+'24.60.138.241', '213.113.166.243', '154.35.47.59', '80.222.227.120', '69.214.190.114',
+'64.246.50.101', '217.172.49.89', '86.195.237.246', '24.27.135.128', '208.40.218.144',
+'208.40.218.151', '83.64.135.124', '194.95.224.201', '216.231.168.178', '84.238.15.86',
+'81.169.136.161', '68.166.71.225', '24.238.36.100', '84.57.65.203', '24.22.8.74',
+'166.70.207.2', '82.46.18.28', '217.79.181.118', '24.155.82.33', '217.84.23.50',
+'213.84.94.72', '149.9.25.222', '71.192.202.141', '89.110.144.180', '74.67.47.248',
+'142.59.222.152', '213.84.192.84', '62.75.151.195', '213.239.206.174', '213.84.43.3',
+'69.56.216.138', '70.187.87.248', '64.81.246.230', '80.252.209.6', '83.171.167.167',
+'62.178.28.11', '83.171.154.26', '65.196.226.32', '85.31.187.84', '68.57.216.138',
+'84.221.205.52', '65.174.217.58', '203.59.176.119', '217.20.117.240', '219.78.58.197',
+'80.139.157.249', '87.14.241.155', '69.123.5.158', '83.194.181.199', '85.176.9.178',
+'82.238.188.44', '84.16.234.153', '203.218.52.16', '81.169.130.130', '81.231.82.192',
+'138.202.192.210', '217.147.80.73', '219.121.56.122', '81.5.172.97', '67.166.235.186',
+'64.146.134.221', '72.0.207.216', '82.238.225.133', '64.90.164.74', '66.98.136.49',
+'216.55.149.21', '198.161.91.196', '68.167.210.203', '192.42.113.248', '69.60.120.99',
+'82.76.242.25', '212.112.242.21', '80.86.191.107', '129.21.228.86', '85.10.194.117',
+'85.214.29.9', '24.250.192.233', '84.9.189.25', '72.165.204.124', '66.92.220.226',
+'66.70.10.53', '82.165.180.112', '217.79.72.59', '129.187.150.131', '66.93.134.53',
+'85.25.20.114', '62.194.151.17', '84.9.61.90', '85.10.226.188', '216.239.83.190',
+'64.95.64.86', '195.72.0.6', '66.228.127.27', '68.167.210.88', '72.36.146.118',
+'85.226.99.98', '80.203.211.14', '85.10.240.250', '72.3.249.87', '24.183.45.221',
+'62.75.129.201', '70.28.148.123', '134.169.164.172', '84.221.71.186', '207.210.106.25',
+'24.218.212.125', '213.203.214.130', '124.120.233.24', '62.163.136.55', '203.59.191.2',
+'18.244.0.188', '69.110.16.109', '166.111.249.39', '85.214.26.137', '81.169.176.178',
+'218.189.210.17', '216.237.143.47', '83.85.107.219', '216.9.82.85', '38.99.66.86',
+'85.25.136.135', '129.21.144.211', '81.169.174.124', '212.239.118.83', '141.149.128.197',
+'87.122.12.22', '82.231.59.44', '67.173.143.46', '80.242.195.68', '81.169.177.134',
+'68.227.90.101', '216.39.146.25', '69.162.42.169', '82.82.71.189', '68.116.85.27',
+'193.184.9.66', '84.56.188.25', '217.14.64.70', '217.160.170.132', '83.155.131.118',
+'213.253.212.106', '80.137.90.161', '217.172.182.26', '68.113.150.180', '193.110.91.7',
+'68.167.210.195', '20 6.174.19.25', '68.167.210.150', '66.52.66.26', '217.160.108.109',
+'83.65.91.110', '85.25.132.119', '84.16.235.143', '24.6.177.106', '148.88.224.185',
+'80.190.242.130', '84.179.37.99', '212.112.241.159', '207.210.85.116', '85.182.21.158',
+'85.178.67.22', '81.227.234.84', '85.214.59.14', '80.237.146.62', '194.109.109.109',
+'84.16.233.47', '64.142.31.83', '75.7.35.204', '64.126.160.82', '68.40.171.66',
+'80.190.241.118', '142.59.97.101', '194.109.161.161', '84.55.87.137', '83.227.72.7',
+'128.174.124.128', '216.32.80.75', '200.121.55.151', '195.158.167.131', '67.159.27.27',
+'68.4.226.53', '217.153.252.4', '66.11.179.38', '70.128.40.144', '66.219.161.166',
+'80.222.75.74', '83.171.161.111', '66.93.170.242', '66.210.104.251', '62.216.16.183',
+'81.0.225.179', '64.34.180.99', '66.98.208.43', '84.73.181.254', '18.78.1.92',
+'74.129.166.67', '213.220.233.15', '211.94.188.225', '24.91.169.157', '84.221.93.72',
+'8.7.49.235', '84.157.194.20', '24.80.166.229', '82.156.33.125', '134.169.156.140',
+'85.14.220.126', '71.245.115.23', '66.130.125.131', '82.165.253.73', '82.243.190.56',
+'84.151.137.89', '88.191.22.62', '216.75.15.60', '80.74.148.176', '24.21.201.102',
+'84.19.182.23', '69.148.137.24', '211.80.41.38', '70.235.64.97', '68.89.251.8',
+'84.60.110.244', '81.190.201.225', '212.242.188.20', '201.50.46.196', '82.6.104.255',
+'66.96.192.44', '70.226.164.81', '82.109.81.98', '65.101.98.113', '207.210.85.112',
+'218.22.2.22', '62.197.40.155', '70.16.142.12', '64.122.12.107', '74.0.33.114',
+'213.239.212.133', '84.154.134.88', '209.8.40.177', '212.112.241.137', '63.85.194.6',
+'128.83.114.63', '24.178.205.107', '81.169.178.215', '72.225.234.118', '24.59.103.203',
+'80.34.138.229', '140.247.60.64', '140.247.62.119', '18.152.2.242', '69.62.156.11',
+'134.130.57.18', '80.190.251.24', '71.242.124.82', '80.190.242.122', '85.214.18.185',
+'194.21.56.6', '85.178.118.33', '24.136.24.119', '85.227.10.132', '64.142.98.231',
+'151.8.40.35', '88.73.64.70', '69.12.171.250', '71.133.227.217', '213.239.202.232',
+'24.10.127.243', '217.28.206.143', '84.56.134.49', '216.137.65.86', '80.87.131.159',
+'195.230.168.95', '88.84.139.250', '128.138.207.49', '88.191.12.200', '84.19.181.66',
+'64.26.169.184', '71.120.32.3', '217.160.219.14', '84.220.99.237', '63.149.255.18',
+'64.142.22.13', '80.127.66.162', '84.41.143.100', '82.165.144.169', '202.29.6.56',
+'85.214.44.126', '72.21.33.202', '68.20.180.186', '85.10.195.106', '82.225.251.113',
+'82.128.208.175', '204.253.162.11', '62.241.240.86', '70.127.121.248', '18.246.1.160',
+'207.150.167.67', '155.207.113.227', '213.239.211.172', '81.56.27.175', '217.160.220.28',
+'213.39.162.76', '80.244.242.127', '66.63.162.152', '70.84.114.153', '193.202.88.3',
+'67.171.52.98', '87.89.99.153', '217.160.95.117', '69.113.27.92', '85.212.155.168',
+'212.60.156.94', '217.160.177.118', '84.72.104.77', '85.31.186.86', '66.199.240.50',
+'85.234.130.43', '71.56.235.157', '128.61.106.164', '62.75.171.154', '195.169.149.213',
+'212.112.231.99', '212.112.235.83', '217.172.183.219', '69.163.32.140', '202.173.141.155',
+'66.219.59.183', '82.94.251.206', '137.120.180.50', '70.81.159.32', '213.113.151.30',
+'82.235.7.49', '213.114.177.134', '193.16.154.187', '64.230.63.177', '218.58.83.156',
+'209.172.52.78', '69.12.145.165', '216.27.178.157', '64.62.190.126', '216.218.240.205',
+'221.137.6.171', '209.221.193.39', '82.92.225.162', '82.149.72.85', '83.223.108.108',
+'195.71.99.214', '208.99.207.139', '81.57.111.73', '195.158.168.91', '85.225.168.164',
+'64.142.22.12', '213.100.254.195', '82.24.161.140', '66.199.252.58', '193.219.28.245',
+'207.210.74.82', '68.146.1.177', '83.245.82.184', '204.15.133.171', '199.77.130.14',
+'212.24.170.230', '212.202.233.2', '62.121.31.116', '147.251.52.140', '195.177.251.100',
+'209.221.138.34', '209.114.200.129', '82.227.67.108', '88.198.253.18', '83.160.255.58',
+'193.88.185.116', '24.11.233.143', '194.109.206.212', '85.214.29.61', '213.239.212.45',
+'62.75.222.205', '212.112.242.159', '159.149.57.14', '66.92.188.226', '71.229.90.176',
+'64.34.217.23', '18.244.0.114', '193.198.70.44', '18.244.0.188', '137.120.180.65',
+'85.76.189.225', '88.112.63.167', '192.67.63.148', '140.247.62.64', '212.168.190.8',
+'82.182.109.115', '213.146.114.96', '66.90.89.162', '80.223.105.208', '84.249.213.94',
+'72.130.85.166', '85.8.136.101', '84.141.157.11', '217.160.132.150', '217.196.64.205',
+'70.20.27.229', '217.160.176.49', '70.176.93.179', '208.66.194.6', '65.254.45.211',
+'24.238.57.137', '80.237.206.93', '62.212.121.77', '68.229.206.234', '24.199.198.152',
+'85.31.187.90', '64.142.78.232', '64.81.240.144', '213.113.27.69', '63.211.169.142',
+'66.111.43.137', '222.212.62.102', '68.80.155.150', '130.161.165.129', '65.38.64.204',
+'24.21.11.160', '67.177.13.99', '84.179.226.226', '24.202.5.70', '130.89.160.179',
+'82.173.155.16', '86.56.26.161', '85.214.58.238', '166.70.232.84', '217.160.251.19',
+'63.228.65.81', '68.127.89.54', '217.80.73.55', '69.203.21.27', '207.210.106.25',
+'217.224.170.147', '216.254.98.195', '82.238.188.44', '168.150.251.36', '75.17.59.97',
+'68.192.208.164', '207.55.224.210', '212.187.48.185', '24.185.154.23', '204.15.208.64',
+'68.41.182.107', '24.127.236.10', '68.167.71.188', '64.4.231.146', '209.221.206.114',
+'65.95.244.151', '212.7.12.43', '71.126.191.234', '217.85.76.76', '83.214.50.131',
+'128.16.66.81', '87.123.108.76', '218.9.14.122', '212.202.43.223', '87.217.59.75',
+'84.180.181.170', '83.233.34.183', '213.218.161.250', '84.160.97.247', '213.100.5.62',
+'63.228.146.210', '85.210.1.124', '84.62.43.153', '69.148.137.24', '80.190.248.207',
+'212.202.43.223', '84.148.43.128', '202.69.182.69');
+
+print "const unsigned int banip[] = {";
+
+foreach( @list ){
+ $hash{(($1 * 256 + $2) * 256 + $3) * 256 + $4} = 1
+ if( /^(\d+)\.(\d+)\.(\d+)\.(\d+)/ );
+
+}
+print scalar keys %hash, ",\n";
+
+foreach (sort { $a <=> $b } keys %hash) {
+ print "${_}U,\t";
+ print "\n" if ++$i % 4 == 0;
+}
+
+print "0};\n";
diff --git a/pttbbs/util/bbsctl.c b/pttbbs/util/bbsctl.c
new file mode 100644
index 00000000..320fa5f0
--- /dev/null
+++ b/pttbbs/util/bbsctl.c
@@ -0,0 +1,291 @@
+/* $Id$ */
+#include "bbs.h"
+
+#ifdef __FreeBSD__
+ #include <sys/syslimits.h>
+ #include <sys/types.h>
+ #include <grp.h>
+ #define SU "/usr/bin/su"
+ #define CP "/bin/cp"
+ #define KILLALL "/usr/bin/killall"
+ #define IPCS "/usr/bin/ipcs"
+ #define IPCRM "/usr/bin/ipcrm"
+ #define AWK "/usr/bin/awk"
+#endif
+#ifdef __linux__
+ #include <linux/limits.h>
+ #include <sys/types.h>
+ #include <grp.h>
+ #define SU "/bin/su"
+ #define CP "/bin/cp"
+ #define KILLALL "/usr/bin/killall"
+ #define IPCS "/usr/bin/ipcs"
+ #define IPCRM "/usr/bin/ipcrm"
+ #define AWK "/usr/bin/awk"
+#endif
+
+int HaveBBSADM(void)
+{
+ gid_t gids[NGROUPS_MAX];
+ int i, ngids;
+ struct group *gr;
+
+ if( getuid() == 0 || getuid() == BBSUID )
+ return 1;
+
+ ngids = getgroups(NGROUPS_MAX, gids);
+ if( (gr = getgrnam("bbsadm")) == NULL ){
+ puts("group bbsadm not found");
+ return 0;
+ }
+
+ for( i = 0 ; i < ngids ; ++i )
+ if( gr->gr_gid == (int)gids[i] )
+ return 1;
+
+ return 0;
+}
+
+int startbbs(int argc, char **argv)
+{
+ if( setuid(0) < 0 ){
+ perror("setuid(0)");
+ exit(1);
+ }
+ printf("starting mbbsd at 23, 443, 3000-3010\n");
+ execl(BBSHOME "/bin/mbbsd", "mbbsd", "23", "443",
+ "3000", "3001", "3002", "3003", "3004", "3005",
+ "3006", "3007", "3008", "3009", "3010", NULL);
+ printf("starting mbbsd failed\n");
+ return 1;
+}
+
+int stopbbs(int argc, char **argv)
+{
+ DIR *dirp;
+ struct dirent *de;
+ FILE *fp;
+ char buf[512], fn[512];
+ if( !(dirp = opendir("run")) ){
+ perror("open " BBSHOME "/run");
+ exit(0);
+ }
+
+ while( (de = readdir(dirp)) ){
+ if( strstr(de->d_name, "mbbsd") && strstr(de->d_name, "pid")){
+ sprintf(fn, BBSHOME "/run/%s", de->d_name);
+ if( (fp = fopen(fn, "r")) != NULL ){
+ if( fgets(buf, sizeof(buf), fp) != NULL ){
+ printf("stopping listening-mbbsd at pid %5d\n", atoi(buf));
+ kill(atoi(buf), 9);
+ }
+ fclose(fp);
+ unlink(fn);
+ }
+ }
+ }
+
+ closedir(dirp);
+ return 0;
+}
+
+int STOP(int argc, char **argv)
+{
+ DIR *dirp;
+ struct dirent *de;
+ FILE *fp;
+ char buf[512];
+ if( !(dirp = opendir("/proc")) ){
+ perror("open /proc");
+ exit(0);
+ }
+
+ while( (de = readdir(dirp)) ){
+ if( de->d_type & DT_DIR ){
+ sprintf(buf, "/proc/%s/cmdline", de->d_name);
+ if( (fp = fopen(buf, "r")) ){
+ if( fgets(buf, sizeof(buf), fp) != NULL ){
+ if( strstr(buf, "mbbsd") ){
+ kill(atoi(de->d_name), 1);
+ printf("stopping mbbsd at pid %5d\n",
+ atoi(de->d_name));
+ }
+ }
+ fclose(fp);
+ }
+ }
+ }
+
+ closedir(dirp);
+ return 0;
+}
+
+int restartbbs(int argc, char **argv)
+{
+ stopbbs(argc, argv);
+ sleep(1);
+ startbbs(argc, argv);
+ return 0;
+}
+
+int bbsadm(int argc, char **argv)
+{
+ if( setuid(0) < 0 ){
+ perror("setuid(0)");
+ return 1;
+ }
+ puts("permission granted");
+ execl(SU, "su", "bbsadm", NULL);
+ return 0;
+}
+
+int bbstest(int argc, char **argv)
+{
+ if( access("mbbsd", 0) < 0 ){
+ perror("./mbbsd");
+ return 1;
+ }
+ system(CP " -f mbbsd testmbbsd");
+ if( setuid(0) < 0 ){
+ perror("setuid(0)");
+ return 1;
+ }
+ if( setuid(BBSUID) < 0 ){
+ perror("setuid(BBSUID)");
+ return 1;
+ }
+ system(KILLALL " testmbbsd");
+ execl("./testmbbsd", "testmbbsd", "9000", NULL);
+ perror("execl()");
+ return 0;
+}
+
+int Xipcrm(int argc, char **argv)
+{
+#ifdef __FreeBSD__
+ char buf[256], cmd[256];
+ FILE *fp;
+ setuid(BBSUID); /* drop privileges so we don't remove other users' IPC */
+ sprintf(buf, IPCS " | " AWK " '{print $1 $2}'");
+ if( !(fp = popen(buf, "r")) ){
+ perror(buf);
+ return 1;
+ }
+ while( fgets(buf, sizeof(buf), fp) != NULL ){
+ if( buf[0] == 't' || buf[0] == 'm' || buf[0] == 's' ){
+ buf[strlen(buf) - 1] = 0;
+ sprintf(cmd, IPCRM " -%c %s\n", buf[0], &buf[1]);
+ system(cmd);
+ }
+ }
+ pclose(fp);
+ system(IPCS);
+ return 0;
+#elif defined(__linux__)
+ char buf[256], cmd[256], *type = "ms";
+ FILE *fp;
+ setuid(BBSUID); /* drop privileges so we don't remove other users' IPC */
+ for( ;*type != '\0';type++ ){
+ sprintf(buf, IPCS " -%c | " AWK " '{print $2}'", *type);
+ if( !(fp = popen(buf, "r")) ){
+ perror(buf);
+ return 1;
+ }
+ while( fgets(buf, sizeof(buf), fp) != NULL ){
+ if( isdigit(buf[0]) ){
+ buf[strlen(buf) - 1] = 0;
+ sprintf(cmd, IPCRM " -%c %s\n", *type, buf);
+ system(cmd);
+ }
+ }
+ pclose(fp);
+ }
+ system(IPCS);
+ return 0;
+#else
+ puts("not implement!");
+ return 1;
+#endif
+}
+
+int permreport(int argc, char **argv)
+{
+ int fd, i, count;
+ userec_t usr;
+ struct {
+ int perm;
+ char *desc;
+ } check[] = {{PERM_BBSADM, "PERM_BBSADM"},
+ {PERM_SYSOP, "PERM_SYSOP"},
+ {PERM_ACCOUNTS, "PERM_ACCOUNTS"},
+ {PERM_SYSSUBOP, "PERM_SYSSUBOP"},
+ {PERM_ACCTREG, "PERM_ACCTREG"},
+#if 0
+ {PERM_RELATION, "PERM_RELATION"},
+ {PERM_PRG, "PERM_PRG"},
+ {PERM_ACTION, "PERM_ACTION"},
+ {PERM_PAINT, "PERM_PAINT"},
+ {PERM_POLICE_MAN, "PERM_POLICE_MAN"},
+ {PERM_MSYSOP, "PERM_MSYSOP"},
+ {PERM_PTT, "PERM_PTT"},
+#endif
+ {0, NULL}};
+
+ if( (fd = open(".PASSWDS", O_RDONLY)) < 0 ){
+ perror(".PASSWDS");
+ return 1;
+ }
+ for( count = i = 0 ; check[i].perm != 0 ; ++i ){
+ count = 0;
+ lseek(fd, 0, SEEK_SET);
+ printf("%s\n", check[i].desc);
+ while( read(fd, &usr, sizeof(usr)) > 0 ){
+ if( usr.userid[0] != 0 && isalpha(usr.userid[0]) &&
+ usr.userlevel & check[i].perm ){
+ ++count;
+ printf("%-20s%-10s\n", usr.userid, usr.realname);
+ }
+ }
+ printf("total %d users\n\n", count);
+ }
+ return 0;
+}
+
+struct {
+ int (*func)(int, char **);
+ char *cmd, *descript;
+} cmds[] =
+ { {startbbs, "start", "start mbbsd at port 23, 3000~3010"},
+ {stopbbs, "stop", "killall listening mbbsd"},
+ {restartbbs, "restart", "stop and then start"},
+ {bbsadm, "bbsadm", "switch to user: bbsadm"},
+ {bbstest, "test", "run ./mbbsd as bbsadm"},
+ {Xipcrm, "ipcrm", "ipcrm all msg, shm, sem"},
+ {STOP, "STOP", "killall ALL mbbsd"},
+ {permreport, "permreport", "permission report"},
+ {NULL, NULL, NULL} };
+
+int main(int argc, char **argv)
+{
+ int i = 0;
+ if( !HaveBBSADM() )
+ return 1;
+ if( argc >= 2 ){
+ chdir(BBSHOME);
+ for( i = 0 ; cmds[i].func != NULL ; ++i )
+ if( strcmp(cmds[i].cmd, argv[1]) == 0 ){
+ cmds[i].func(argc - 2, &argv[2]);
+ break;
+ }
+ }
+ if( argc == 1 || cmds[i].func == NULL ){
+ /* usage */
+ printf("usage: bbsctl [command] [options]\n");
+ printf("commands:\n");
+ for( i = 0 ; cmds[i].func != NULL ; ++i )
+ printf("\t%-15s%s\n", cmds[i].cmd, cmds[i].descript);
+ if ( geteuid() != 0 )
+ printf("Warning: bbsctl should be SUID\n");
+ }
+ return 0;
+}
diff --git a/pttbbs/util/bbsenv.pl b/pttbbs/util/bbsenv.pl
new file mode 100644
index 00000000..4bbdb8ba
--- /dev/null
+++ b/pttbbs/util/bbsenv.pl
@@ -0,0 +1,28 @@
+#!/usr/bin/perl -w
+# for saving memory, filter out unnecessary environment variables
+use strict;
+
+########################################
+# mbbsd 會用到的 external program:
+# tar rm cat mv cp stty
+# bin/railway_wrapper.pl -> lynx -> LANG
+# bin/buildir bin/builddb.pl bin/xchatd
+# mutt -> TMPDIR
+# /usr/bin/uuencode /usr/sbin/sendmail
+##########################################
+# 若無 getpwuid(2), mutt 會需要 HOME,USER
+
+$ENV{PATH}="/bin:/usr/bin:/usr/local/bin";
+#$ENV{SHELL}="/bin/sh";
+my @acceptenv=qw(
+ ^PATH$
+ ^USER$ ^HOME$
+ ^TZ$ ^TZDIR$ ^TMPDIR$
+ ^MALLOC_
+ );
+# TERM SHELL PWD LANG LOGNAME
+for my $env(keys %ENV) {
+ delete $ENV{$env} if !grep { $env =~ $_ } @acceptenv;
+}
+
+exec { $ARGV[0] } @ARGV;
diff --git a/pttbbs/util/bbsmail.c b/pttbbs/util/bbsmail.c
new file mode 100644
index 00000000..4cc441f2
--- /dev/null
+++ b/pttbbs/util/bbsmail.c
@@ -0,0 +1,258 @@
+/* $Id$ */
+#define _UTIL_C_
+#include "bbs.h"
+
+#define LOG_FILE (BBSHOME "/etc/mailog")
+
+#ifdef HMM_USE_ANTI_SPAM
+extern char *notitle[], *nofrom[], *nocont[];
+#endif
+
+
+int
+strip_ansi(char *buf, const char *str, int mode)
+{
+ register int ansi, count = 0;
+
+ for (ansi = 0; *str /* && *str != '\n' */ ; str++) {
+ if (*str == 27) {
+ if (mode) {
+ if (buf)
+ *buf++ = *str;
+ count++;
+ }
+ ansi = 1;
+ } else if (ansi && strchr("[;1234567890mfHABCDnsuJKc=n", *str)) {
+ if ((mode == NO_RELOAD && !strchr("c=n", *str)) ||
+ (mode == ONLY_COLOR && strchr("[;1234567890m", *str))) {
+ if (buf)
+ *buf++ = *str;
+ count++;
+ }
+ if (strchr("mHn ", *str))
+ ansi = 0;
+ } else {
+ ansi = 0;
+ if (buf)
+ *buf++ = *str;
+ count++;
+ }
+ }
+ if (buf)
+ *buf = '\0';
+ return count;
+}
+
+int mailalertuid(int tuid)
+{
+ userinfo_t *uentp=NULL;
+ if(tuid>0 && (uentp = (userinfo_t *)search_ulist(tuid)) )
+ uentp->alerts|=ALERT_NEW_MAIL;
+ return 0;
+}
+
+void
+mailog(msg)
+ char *msg;
+{
+ FILE *fp;
+
+ if ((fp = fopen(LOG_FILE, "a")))
+ {
+ time_t now;
+ struct tm *p;
+
+ time(&now);
+ p = localtime(&now);
+ fprintf(fp, "%02d/%02d/%02d %02d:%02d:%02d <bbsmail> %s\n",
+ p->tm_year % 100, p->tm_mon + 1, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec,
+ msg);
+ fclose(fp);
+ }
+}
+
+#ifdef USE_ICONV
+void str_decode_M3(unsigned char *str);
+#endif
+
+int mail2bbs(char *userid)
+{
+ int uid;
+ fileheader_t mymail;
+ char genbuf[512], title[512], sender[512], filename[512], *ip, *ptr;
+ time_t tmp_time;
+ struct stat st;
+ FILE *fout;
+ userec_t xuser;
+
+ /* check if the userid is in our bbs now */
+ if( !(uid = getuser(userid, &xuser)) ){
+ sprintf(genbuf, "BBS user <%s> not existed", userid);
+ puts(genbuf);
+ mailog(genbuf);
+ return -1;//EX_NOUSER;
+ }
+
+ if( xuser.uflag2 & REJ_OUTTAMAIL )
+ return -1; //不接受站外信
+
+ sprintf(filename, BBSHOME "/home/%c/%s", xuser.userid[0], xuser.userid);
+
+ if( stat(filename, &st) == -1 ){
+ if( mkdir(filename, 0755) == -1 ){
+ printf("mail box create error %s \n", filename);
+ return -1;
+ }
+ }
+ else if( !(st.st_mode & S_IFDIR) ){
+ printf("mail box error\n");
+ return -1;
+ }
+
+/* parse header */
+ while( fgets(genbuf, sizeof(genbuf), stdin) ){
+ if( genbuf[0] == '\n' )
+ break;
+ if( strncmp(genbuf, "Subject: ", 9) == 0 ){
+ strlcpy(title, genbuf + 9, sizeof(title));
+#ifdef USE_ICONV
+ str_decode_M3(title);
+#endif
+ continue;
+ }
+ if( strncmp(genbuf, "Content-Type:", 13) == 0 ){
+ if( strstr(genbuf, "multipart") && !strstr(genbuf, "report") )
+ return -1;
+ }
+ if( strncmp(genbuf, "From", 4) == 0 ){
+ if( (ip = strchr(genbuf, '<')) && (ptr = strrchr(ip, '>')) ){
+ *ptr = '\0';
+ if (ip[-1] == ' ')
+ ip[-1] = '\0';
+ ptr = (char *) strchr(genbuf, ' ');
+ if( ptr )
+ while (*ptr == ' ')
+ ptr++;
+ else
+ ptr = "unknown";
+ sprintf(sender, "%s (%s)", ip + 1, ptr);
+ }
+ else{
+ strtok(genbuf, " \t\n\r");
+ ptr = strtok(NULL, " \t\n\r");
+ if(ptr)
+ strlcpy(sender, ptr, sizeof(sender));
+ }
+ continue;
+ }
+ }
+
+ if( (ptr = strchr(sender, '\n')) )
+ *ptr = '\0';
+
+ if( (ptr = strchr(title, '\n')) )
+ *ptr = '\0';
+
+ if( strchr(sender, '@') == NULL ) /* 由 local host 寄信 */
+ strcat(sender, "@" MYHOSTNAME);
+
+/* allocate a file for the new mail */
+ stampfile(filename, &mymail);
+
+#ifdef HMM_USE_ANTI_SPAM
+ for (n = 0; notitle[n]; n++)
+ if (strstr(title, notitle[n]))
+ {
+ sprintf(genbuf, "Title <%s> not accepted", title);
+ puts(genbuf);
+ mailog(genbuf);
+ return -1;
+ }
+ for (n = 0; nofrom[n]; n++)
+ if (strstr(sender, nofrom[n]))
+ {
+ sprintf(genbuf, "From <%s> not accepted", sender);
+ puts(genbuf);
+ mailog(genbuf);
+ return -1;
+ }
+#endif
+
+ if ((fout = fopen(filename, "w")) == NULL)
+ {
+ printf("Cannot open %s\n", filename);
+ return -1;
+ }
+
+ if (!title[0])
+ sprintf(title, "來自 %.64s", sender);
+ title[TTLEN] = 0;
+ time(&tmp_time);
+ fprintf(fout, "作者: %s\n標題: %s\n時間: %s\n",
+ sender, title, ctime(&tmp_time));
+
+/* copy the stdin to the specified file */
+ while (fgets(genbuf, 255, stdin))
+ {
+#ifdef HMM_USE_ANTI_SPAM
+ for (n = 0; nocont[n]; n++)
+ if (strstr(genbuf, nocont[n]))
+ {
+ fclose(fout);
+ unlink(filename);
+ sprintf(genbuf, "Content <%s> not accepted", nocont[n]);
+ puts(genbuf);
+ mailog(genbuf);
+ return -1;
+ }
+#endif
+ fputs(genbuf, fout);
+ }
+ fclose(fout);
+
+ sprintf(genbuf, "%s => %s", sender, xuser.userid);
+ mailog(genbuf);
+
+/* append the record to the MAIL control file */
+ strip_ansi(title, title, 0);
+ strlcpy(mymail.title, title, sizeof(mymail.title));
+
+ if (strtok(sender, " .@\t\n\r"))
+ strcat(sender, ".");
+ sender[IDLEN + 1] = '\0';
+ strlcpy(mymail.owner, sender, sizeof(mymail.owner));
+
+ sprintf(genbuf, BBSHOME "/home/%c/%s/.DIR", xuser.userid[0], xuser.userid);
+ mailalertuid(uid);
+ return append_record(genbuf, &mymail, sizeof(mymail));
+}
+
+
+int
+main(int argc, char* argv[])
+{
+ char receiver[512];
+
+/* argv[1] is userid in bbs */
+
+ if (argc < 2){
+ printf("Usage:\t%s <bbs_uid>\n", argv[0]);
+ exit(-1);
+ }
+ (void) setgid(BBSGID);
+ (void) setuid(BBSUID);
+ attach_SHM();
+ if( passwd_init() )
+ return 0;
+ chdir(BBSHOME);
+
+ strlcpy(receiver, argv[1], sizeof(receiver));
+
+ strtok(receiver,".");
+ if (mail2bbs(receiver)){
+ /* eat mail queue */
+ while (fgets(receiver, sizeof(receiver), stdin))
+ ;
+ }
+ return 0;
+}
diff --git a/pttbbs/util/bbsrf.c b/pttbbs/util/bbsrf.c
new file mode 100644
index 00000000..a2ca235c
--- /dev/null
+++ b/pttbbs/util/bbsrf.c
@@ -0,0 +1,131 @@
+/* $Id$ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include "config.h"
+
+#ifdef Solaris
+ #include <utmpx.h>
+ #define U_FILE UTMPX_FILE
+#else
+ #include <utmp.h>
+ #define U_FILE UTMP_FILE
+#endif
+
+#ifdef __FreeBSD__
+ #define UTMP_FILE _PATH_UTMP
+#endif
+
+#ifndef Solaris
+ #if MAXHOSTNAMELEN < UT_HOSTSIZE
+ #define MAX_HOMENAME_LEN MAXHOSTNAMELEN
+ #else
+ #define MAX_HOMENAME_LEN UT_HOSTSIZE
+ #endif
+#else
+ /* according to /usr/include/utmpx.h ... */
+ #define MAX_HOMENAME_LEN 256
+#endif
+
+/* fill the hid with from hostname */
+void gethid(char *hid, char *tty)
+{
+ char frombuf[100];
+
+ if (getenv("SSH_CLIENT"))
+ sscanf(getenv("SSH_CLIENT"), "%s", frombuf);
+ else
+ strcpy(frombuf, "127.0.0.1");
+
+ if (strrchr(frombuf, ':'))
+ strncpy(hid, strrchr(frombuf, ':') + 1, MAX_HOMENAME_LEN);
+ else
+ strncpy(hid, frombuf, MAX_HOMENAME_LEN);
+}
+
+/*
+ show ban file
+ if filename exist, print it out, sleep 1 second, and return 0;
+ otherwise, return -1.
+ */
+int showbanfile(char *filename)
+{
+ FILE *fp;
+ char buf[256];
+
+ fp = fopen(filename, "r");
+ if (fp)
+ {
+ while (fgets(buf, sizeof(buf), fp))
+ fputs(buf, stdout);
+ printf("\n============================="
+ "=============================\n");
+ fclose(fp);
+ sleep(10);
+ }
+ return fp ? 0 : -1;
+}
+
+int main(void)
+{
+ int uid, rtv = 0;
+ char *tty, ttybuf[32], hid[MAX_HOMENAME_LEN + 1];
+
+#ifndef Solaris
+ openlog("bbsrf", LOG_PID | LOG_PERROR, LOG_USER);
+#else
+ openlog("bbsrf", LOG_PID, LOG_USER);
+#endif
+ chdir(BBSHOME);
+ uid = getuid();
+
+ while (1)
+ {
+ if (!showbanfile(BAN_FILE))
+ {
+ rtv = 1;
+ break;
+ }
+ else if (uid != BBSUID)
+ {
+ syslog(LOG_ERR, "UID DOES NOT MATCH");
+ rtv = -1;
+ break;
+ }
+ else if (!getpwuid(uid))
+ {
+ syslog(LOG_ERR, "YOU DONT EXIST");
+ rtv = -1;
+ break;
+ }
+ else
+ {
+ tty = ttyname(0);
+ if (tty)
+ {
+ strcpy(ttybuf, tty);
+ gethid(hid, ttybuf);
+ }
+ else
+ {
+ strcpy(ttybuf, "notty");
+ strcpy(hid, "unknown");
+ }
+ execl(BBSPROG, "mbbsd", hid, ttybuf, NULL);
+ syslog(LOG_ERR, "execl(): %m");
+ sleep(3); // prevent flooding
+ rtv = -1;
+ }
+ break;
+ }
+ return rtv;
+}
diff --git a/pttbbs/util/birth.c b/pttbbs/util/birth.c
new file mode 100644
index 00000000..a3503390
--- /dev/null
+++ b/pttbbs/util/birth.c
@@ -0,0 +1,90 @@
+/* 壽星程式 96 10/11 */
+
+#define _UTIL_C_
+#include "bbs.h"
+
+#define OUTFILE BBSHOME "/etc/birth.today"
+
+struct userec_t user;
+
+int bad_user_id(const char *userid) {
+ register char ch;
+ int j;
+ if (strlen(user.userid) < 2 || !isalpha(user.userid[0]))
+ return 1;
+ if (user.numlogins == 0 || user.numlogins > 15000)
+ return 1;
+ if (user.numposts > 15000)
+ return 1;
+ for (j = 1; (ch = user.userid[j]); j++)
+ {
+ if (!isalnum(ch))
+ return 1;
+ }
+ return 0;
+}
+
+int Link(const char *src, const char *dst) {
+ char cmd[200];
+
+ if (link(src, dst) == 0)
+ return 0;
+
+ sprintf(cmd, "/bin/cp -R %s %s", src, dst);
+ return system(cmd);
+}
+
+int main(argc, argv)
+ int argc;
+ char **argv;
+{
+ FILE *fp1;
+ fileheader_t mymail;
+ //int i, day = 0;
+ time_t now;
+ struct tm *ptime;
+ int j;
+
+ attach_SHM();
+ now = time(NULL); /* back to ancent */
+ ptime = localtime(&now);
+
+ if(passwd_init())
+ exit(1);
+
+ printf("*製表\n");
+ fp1 = fopen(OUTFILE, "w");
+
+ fprintf(fp1, "\n "
+ "★★★★★★ 壽星大觀 "
+ "★★★★★★ \n\n");
+ fprintf(fp1, "【本日壽星】 \n");
+ int horoscope = getHoroscope(ptime->tm_mon + 1, ptime->tm_mday);
+ char path[PATHLEN];
+ snprintf(path, sizeof(path), BBSHOME "/etc/Welcome_birth.%d", horoscope);
+ for(j = 1; j <= MAX_USERS; j++) {
+ passwd_query(j, &user);
+ if (bad_user_id(NULL))
+ continue;
+ if (user.month == ptime->tm_mon + 1 && user.day == ptime->tm_mday) {
+ char genbuf[200];
+ sprintf(genbuf, BBSHOME "/home/%c/%s", user.userid[0], user.userid);
+ stampfile(genbuf, &mymail);
+ strcpy(mymail.owner, BBSNAME);
+ strcpy(mymail.title, "!! 生日快樂 !!");
+ unlink(genbuf);
+ Link(path, genbuf);
+ sprintf(genbuf, BBSHOME "/home/%c/%s/.DIR", user.userid[0], user.userid);
+ append_record(genbuf, &mymail, sizeof(mymail));
+ if ((user.numlogins + user.numposts) < 20)
+ continue;
+
+ fprintf(fp1,
+ " [%2d/%-2d] %-14s %-24s login:%-5d post:%-5d\n",
+ ptime->tm_mon + 1, ptime->tm_mday, user.userid,
+ user.nickname, user.numlogins, user.numposts);
+ }
+ }
+ fclose(fp1);
+ return 0;
+}
diff --git a/pttbbs/util/boardlist.c b/pttbbs/util/boardlist.c
new file mode 100644
index 00000000..35956a8e
--- /dev/null
+++ b/pttbbs/util/boardlist.c
@@ -0,0 +1,181 @@
+/* $Id$ */
+/* 這是用來將樹狀分類輸出成 perl module (可以給像是 man/ 使用) */
+#include "bbs.h"
+/* 產生 hash 的內容如下:
+
+ $db{'tobid.BRDNAME'} 把 BRDNAME(大小寫需正確) 查 bid
+ $db{'parent.BID'} 從 BID 查 parent 的 bid
+ $db{'tobrdname.BID'} 從 BID 查英文看板名稱
+ $db{'look.SBRDNAME'} 從全部都是小寫的 SBRDNAME 查正確的看板大小寫
+ $db{'BID.isboard'} 看 BID 是看板(1)或群組(0)
+ $db{'BID.brdname'} 從 BID 查 brdname
+ $db{'BID.title'} 查 BID 的中文板名
+ $db{'BID.over18'} 是否為 18 禁
+ $db{'BID.BM.0'} .. $db{'BID.BM.4'}
+ 該板板主 ID
+ */
+int parent[MAX_BOARD];
+
+static void
+load_uidofgid(const int gid, const int type)
+{
+ boardheader_t *bptr, *currbptr, *parent;
+ int bid, n, childcount = 0;
+ currbptr = parent = &bcache[gid - 1];
+ for (n = 0; n < numboards; ++n) {
+ bid = SHM->bsorted[type][n]+1;
+ if( bid<=0 || !(bptr = &bcache[bid-1])
+ || bptr->brdname[0] == '\0' )
+ continue;
+ if (bptr->gid == gid) {
+ if (currbptr == parent)
+ currbptr->firstchild[type] = bid;
+ else {
+ currbptr->next[type] = bid;
+ currbptr->parent = gid;
+ }
+ childcount++;
+ currbptr = bptr;
+ }
+ }
+ parent->childcount = childcount;
+ if (currbptr == parent) // no child
+ currbptr->firstchild[type] = -1;
+ else // the last child
+ currbptr->next[type] = -1;
+}
+
+char *skipEscape(char *s)
+{
+ static char buf[TTLEN * 2 + 1];
+ int r, w;
+ for( w = r = 0 ; s[r] != 0 ; ++r ){
+ if( s[r] == '\'' || s[r] == '\\' )
+ buf[w++] = '\\';
+ buf[w++] = s[r];
+ }
+ buf[w++] = 0;
+ return buf;
+}
+
+void dumpdetail(void)
+{
+ int i, k, bid;
+ boardheader_t *bptr;
+ char BM[IDLEN * 3 + 3], *p;
+ char smallbrdname[IDLEN + 1];
+ for( i = 0 ; i < MAX_BOARD ; ++i ){
+ bptr = &bcache[i];
+
+ if( !bptr->brdname[0] ||
+ (bptr->brdattr & (BRD_HIDE | BRD_TOP)) ||
+ (bptr->level && !(bptr->brdattr & BRD_POSTMASK) &&
+ (bptr->level &
+ ~(PERM_BASIC|PERM_CHAT|PERM_PAGE|PERM_POST|PERM_LOGINOK))) )
+ continue;
+
+ for( k = 0 ; bptr->brdname[k] ; ++k )
+ smallbrdname[k] = (isupper(bptr->brdname[k]) ?
+ tolower(bptr->brdname[k]) :
+ bptr->brdname[k]);
+ smallbrdname[k] = 0;
+
+ bid = bptr - bcache + 1;
+ printf("$db{'tobid.%s'} = %d;\n", bptr->brdname, bid);
+ printf("$db{'parent.%d'} = %d;\n", bid, parent[bid]);
+ printf("$db{'tobrdname.%d'} = '%s';\n", bid, bptr->brdname);
+ printf("$db{'look.%s'} = '%s';\n", smallbrdname, bptr->brdname);
+ printf("$db{'%d.isboard'} = %d;\n", bid,
+ (bptr->brdattr & BRD_GROUPBOARD) ? 0 : 1);
+ printf("$db{'%d.brdname'} = '%s';\n", bid, bptr->brdname);
+ printf("$db{'%d.title'} = '%s';\n", bid, skipEscape(&bptr->title[7]));
+ printf("$db{'%d.over18'} = '%d';\n",
+ bid, (bptr->brdattr & BRD_OVER18) ? 1 : 0);
+ strlcpy(BM, bptr->BM, sizeof(BM));
+ for( p = BM ; *p != 0 ; ++p )
+ if( !isalpha(*p) && !isdigit(*p) )
+ *p = ' ';
+ for( k = 0, p = strtok(BM, " ") ; p != NULL ; ++k, p = strtok(NULL, " ") )
+ printf("$db{'%d.BM.%d'} = '%s';\n", bid, k, p);
+ }
+}
+
+void dumpclass(int gid)
+{
+ boardheader_t *bptr;
+ int bid;
+ bptr = getbcache(gid);
+ if (bptr->firstchild[0] == 0 || bptr->childcount <= 0)
+ load_uidofgid(gid, 0);
+ printf("$db{'class.%d'} = $serializer->serialize([", gid);
+ for( bid = bptr->firstchild[0] ; bid > 0 ; bid = bptr->next[0] ) {
+ bptr = getbcache(bid);
+ if( (bptr->brdattr & (BRD_HIDE | BRD_TOP)) ||
+ (bptr->level && !(bptr->brdattr & BRD_POSTMASK) &&
+ (bptr->level &
+ ~(PERM_BASIC|PERM_CHAT|PERM_PAGE|PERM_POST|PERM_LOGINOK))) )
+ continue;
+
+ printf("%5d,\t", bid);
+ parent[bid] = gid;
+ }
+ printf("]);\n");
+
+ bptr = getbcache(gid);
+ for( bid = bptr->firstchild[0] ; bid > 0 ; bid = bptr->next[0] ) {
+ bptr = getbcache(bid);
+ if( (bptr->brdattr & (BRD_HIDE | BRD_TOP)) ||
+ (bptr->level && !(bptr->brdattr & BRD_POSTMASK) &&
+ (bptr->level &
+ ~(PERM_BASIC|PERM_CHAT|PERM_PAGE|PERM_POST|PERM_LOGINOK))) )
+ continue;
+ if( bptr->brdattr & BRD_GROUPBOARD )
+ dumpclass(bid);
+ }
+}
+
+void dumpallbrdname(void)
+{
+ int i;
+ boardheader_t *bptr;
+ FILE *fp;
+
+ if( !(fp = fopen("boardlist.all", "wt")) )
+ return;
+
+ for( i = 0 ; i < MAX_BOARD ; ++i ){
+ bptr = &bcache[i];
+
+ if( !bptr->brdname[0] ||
+ (bptr->brdattr & (BRD_HIDE | BRD_TOP | BRD_GROUPBOARD)) ||
+ (bptr->level && !(bptr->brdattr & BRD_POSTMASK) &&
+ (bptr->level &
+ ~(PERM_BASIC|PERM_CHAT|PERM_PAGE|PERM_POST|PERM_LOGINOK))) )
+ continue;
+ fprintf(fp, "%s\n", bptr->brdname);
+ }
+ fclose(fp);
+}
+
+int main(int argc, char **argv)
+{
+ attach_SHM();
+
+ printf("#!/usr/bin/perl\n"
+ "# this is auto-generated perl module from boardlist.c\n"
+ "# please do NOT modify this directly!\n"
+ "# usage: make boardlist; ./boardlist | perl\n"
+ "# Id of boardlist.c: $Id$\n"
+ "use DB_File;\n"
+ "use Data::Serializer;\n"
+ "\n"
+ "unlink 'boardlist.db', 'boardlist.list';\n"
+ "$serializer = Data::Serializer->new(serializer => 'Storable', digester => 'MD5',compress => 0,);\n"
+ "tie %%db, 'DB_File', 'boardlist.db', (O_RDWR | O_CREAT), 0666, $DB_HASH;\n"
+ );
+ dumpclass(1);
+ dumpdetail();
+ dumpallbrdname();
+ printf("untie %%db;\n");
+ return 0;
+}
diff --git a/pttbbs/util/broadcast.c b/pttbbs/util/broadcast.c
new file mode 100644
index 00000000..92b2df9f
--- /dev/null
+++ b/pttbbs/util/broadcast.c
@@ -0,0 +1,86 @@
+/* $Id$ */
+#include "bbs.h"
+#include <getopt.h>
+
+extern SHM_t *SHM;
+
+void print_help(int argc, char *argv[])
+{
+ fprintf(stderr, "Usage: %s [-t sleep_time] [-n users_per_round] [-o broadcast_name] broadcast content\n\n", argv[0]);
+}
+
+int main(int argc, char *argv[])
+{
+ int sleep_time = 5;
+ int num_per_loop = 500;
+ char * owner = "系統廣播";
+
+ int i, j;
+ userinfo_t *uentp;
+ msgque_t msg;
+ time_t now;
+ int *sorted, UTMPnumber; // SHM snapshot
+
+ while ((i = getopt(argc, argv, "t:n:o:h")) != -1)
+ switch (i) {
+ case 'h':
+ print_help(argc, argv);
+ return 0;
+ break;
+ case 't':
+ sleep_time = atoi(optarg);
+ break;
+ case 'n':
+ num_per_loop = atoi(optarg);
+ break;
+ case 'o':
+ owner = optarg;
+ break;
+ }
+
+ if (optind == argc || strlen(argv[optind]) == 0) {
+ fprintf(stderr, "no message to broadcast\n\n");
+ return 1;
+ }
+
+ printf("broadcast \"%s\" ? [y/N]\n", argv[optind]);
+ if (tolower(getchar()) != 'y')
+ return 0;
+
+ attach_SHM();
+ sorted = (int *)malloc(sizeof(int) * USHM_SIZE);
+ memcpy(sorted, SHM->sorted[SHM->currsorted][0], sizeof(int) * USHM_SIZE);
+ UTMPnumber = SHM->UTMPnumber;
+
+ msg.pid = getpid();
+ strlcpy(msg.userid, owner, sizeof(msg.userid));
+ snprintf(msg.last_call_in, sizeof(msg.last_call_in), "[廣播]%s", argv[optind]);
+
+ now = time(NULL);
+
+ for (i = 0, j = 0; i < UTMPnumber; ++i, ++j) {
+ // XXX why use sorted list?
+ // can we just scan uinfo with proper checking?
+ uentp = &SHM->uinfo[sorted[i]];
+ if (uentp->pid && kill(uentp->pid, 0) != -1){
+ int write_pos = uentp->msgcount;
+ if (write_pos < (MAX_MSGS - 1)){
+ uentp->msgcount = write_pos + 1;
+ memcpy(&uentp->msgs[write_pos], &msg, sizeof(msg));
+#ifdef NOKILLWATERBALL
+ uentp->wbtime = (time4_t)now;
+#else
+ kill(uentp->pid, SIGUSR2);
+#endif
+ }
+ }
+
+ if (j == num_per_loop) {
+ fprintf(stderr, "%5d/%5d\n", i + 1, UTMPnumber);
+ j = 0;
+ now = time(NULL);
+ sleep(sleep_time);
+ }
+ }
+ return 0;
+}
diff --git a/pttbbs/util/buildAnnounce.c b/pttbbs/util/buildAnnounce.c
new file mode 100644
index 00000000..0754206a
--- /dev/null
+++ b/pttbbs/util/buildAnnounce.c
@@ -0,0 +1,89 @@
+/* 建立所有看板精華區的連結 */
+
+#include "bbs.h"
+#define GROUPROOT BBSHOME"/man/group"
+
+extern boardheader_t *bcache;
+extern int numboards;
+
+int cmpboardclass(const void *a, const void *b)
+{
+ boardheader_t **brd = (boardheader_t **)a;
+ boardheader_t **tmp = (boardheader_t **)b;
+ return (strncmp((*brd)->title, (*tmp)->title, 4)<<8)+
+ strncasecmp((*brd)->brdname, (*tmp)->brdname, IDLEN);
+}
+void buildchilds(int level,char *path,int gid)
+{
+ char newpath[512];
+ int i,count=0;
+ boardheader_t *ptr, **selected=NULL;
+ fileheader_t item;
+ char *p;
+ int preserved=32;
+ selected=malloc(preserved * sizeof(boardheader_t*));
+
+ /* XXX It will cost O(ngroup * numboards) totally. */
+ for(i=0; i<numboards; i++) {
+ ptr =&SHM->bcache[i];
+ if(ptr->gid != gid)
+ continue;
+ if((ptr->brdattr&(BRD_BAD | BRD_HIDE))!=0)
+ continue;
+ if((ptr->level && !(ptr->brdattr & BRD_POSTMASK)))
+ continue;
+ if(count==preserved) {
+ preserved*=2;
+ selected=(boardheader_t**)realloc(selected, preserved*sizeof(boardheader_t*));
+ }
+ assert(selected);
+ selected[count++]=ptr;
+ }
+ qsort(selected, count, sizeof(boardheader_t *),
+ cmpboardclass);
+ for(i=0;i<count;i++)
+ {
+ ptr=selected[i];
+ printf("%*.*s+-%-14s %-s \n",level*2,level*2,"| | | | | | | | |",
+ ptr->brdname, ptr->title);
+
+ if(ptr->brdattr & BRD_GROUPBOARD){
+ snprintf(newpath,sizeof(newpath),"%s/%s",path,ptr->brdname);
+ mkdir(newpath,0766);
+ buildchilds(level+1,newpath,ptr-bcache+1);
+ }
+ else{
+ if(!invalid_pname(path) && !invalid_pname(ptr->brdname) &&
+ isalpha(ptr->brdname[0])) {
+ sprintf(newpath,"/bin/ln -s "BBSHOME"/man/boards/%c/%s %s/%s",
+ ptr->brdname[0], ptr->brdname,path,ptr->brdname);
+ system(newpath);
+ } else {
+ printf("something wrong: %s, %s\n",path, ptr->brdname);
+ }
+ }
+ strlcpy(item.owner,ptr->BM, sizeof(item.owner));
+ if((p=strchr(item.owner,'/'))!=NULL)
+ *p='\0';
+ snprintf(item.title,sizeof(item.title),"%-13.13s %-32.32s", level?ptr->brdname:"", ptr->title+7);
+ item.filemode = 0 ;
+ strlcpy(item.filename,ptr->brdname,sizeof(item.filename));
+ snprintf(newpath,sizeof(newpath),"%s/.DIR",path);
+ append_record(newpath, &item, sizeof(item));
+ }
+ free(selected);
+}
+
+
+int main(int argc, char **argv)
+{
+ char path[512];
+ setsid();
+ strcpy(path,GROUPROOT);
+ system("rm -rf "GROUPROOT);
+ mkdir(GROUPROOT,0766);
+ attach_SHM();
+ resolve_boards();
+ buildchilds(0,path,1);
+ return 0;
+}
diff --git a/pttbbs/util/buildAnnounce.sh b/pttbbs/util/buildAnnounce.sh
new file mode 100644
index 00000000..d43f420b
--- /dev/null
+++ b/pttbbs/util/buildAnnounce.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+# $Id: buildAnnounce.sh,v 1.1 2002/03/07 15:13:45 in2 Exp $
+#
+bin/buildAnnounce > etc/ALLBRDLIST
+bin/post Record 全站看板列表 [自動站長] etc/ALLBRDLIST
diff --git a/pttbbs/util/buildir.c b/pttbbs/util/buildir.c
new file mode 100644
index 00000000..d8b7e339
--- /dev/null
+++ b/pttbbs/util/buildir.c
@@ -0,0 +1,126 @@
+/* $Id$ */
+#include "bbs.h"
+
+#ifdef __linux__
+int dirselect(const struct dirent *dir)
+#else
+int dirselect(struct dirent *dir)
+#endif
+{
+ return strchr("MDSGH", dir->d_name[0]) && dir->d_name[1] == '.';
+}
+
+int mysort(const void *a, const void *b)
+{
+ return atoi(((*((struct dirent **)a))->d_name+2))-atoi(((*((struct dirent **)b))->d_name+2));
+}
+
+int main(int argc, char **argv)
+{
+ int k;
+
+ if(argc < 2) {
+ fprintf(stderr, "Usage: %s <path1> [<path2> ...]\n", argv[0]);
+ return 1;
+ }
+
+ for(k = 1; k < argc; k++) {
+ int fdir, count, total;
+ char *ptr, path[MAXPATHLEN];
+ struct dirent **dirlist;
+
+ sprintf(path, "%s/.DIR", argv[k]);
+ if((fdir = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1) {
+ perror(path);
+ continue;
+ }
+
+ if((total = scandir(argv[k], &dirlist, dirselect, mysort)) == -1) {
+ fprintf(stderr, "scandir failed!\n");
+ close(fdir);
+ continue;
+ }
+
+ ptr = strrchr(path, '.');
+ for(count = 0; count < total; count++) {
+ FILE *fp;
+ struct stat st;
+
+ strcpy(ptr, dirlist[count]->d_name);
+ if(stat(path, &st) == 0 && st.st_size > 0 &&
+ (fp = fopen(path, "r")) != NULL) {
+ char buf[512];
+ time4_t filetime;
+ fileheader_t fhdr;
+
+ memset(&fhdr, 0, sizeof(fhdr));
+ /* set file name */
+ strcpy(fhdr.filename, dirlist[count]->d_name);
+
+ /* set file time */
+ filetime = atoi(dirlist[count]->d_name + 2);
+ if(filetime > 740000000) {
+ struct tm *ptime = localtime4(&filetime);
+ sprintf(fhdr.date, "%2d/%02d", ptime->tm_mon + 1,
+ ptime->tm_mday);
+ } else
+ strcpy(fhdr.date, " ");
+
+ /* set file mode */
+ fhdr.filemode = FILE_READ;
+
+ /* set article owner */
+ fgets(buf, sizeof(buf), fp);
+ if(strncmp(buf, "作者: ", 6) == 0 ||
+ strncmp(buf, "發信人: ", 8) == 0) {
+ int i, j;
+
+ for(i = 5; buf[i] != ' '; i++);
+ for(; buf[i] == ' '; i++);
+ for(j = i + 1; buf[j] != ' '; j++);
+ j -= i;
+ if(j > IDLEN + 1)
+ j = IDLEN + 1;
+ strncpy(fhdr.owner, buf + i, j);
+ fhdr.owner[IDLEN + 1] = '\0';
+ strtok(fhdr.owner, " .@\t\n\r");
+ if(strtok(NULL, " .@\t\n\r"))
+ strcat(fhdr.owner, ".");
+
+ /* set article title */
+ while(fgets(buf, sizeof(buf), fp))
+ if(strncmp(buf, "標題: ", 6) == 0 ||
+ strncmp(buf, "標 題: ", 8) == 0) {
+ for( i = 5 ; buf[i] != ' ' ; ++i )
+ ;
+ for( ; buf[i] == ' ' ; ++i )
+ ;
+ strtok(buf + i - 1, "\n");
+ strncpy(fhdr.title, buf + i, TTLEN);
+ fhdr.title[TTLEN] = '\0';
+ for( i = 0 ; fhdr.title[i] != 0 ; ++i )
+ if( fhdr.title[i] == '\e' ||
+ fhdr.title[i] == '\b' )
+ fhdr.title[i] = ' ';
+ printf("%s, owner: %s, title: %s\n", path, fhdr.owner, fhdr.title);
+ break;
+ }
+ } else if(strncmp(buf, "☉ 歡迎光臨", 11) == 0) {
+ strcpy(fhdr.title, "會議記錄");
+ } else if(strncmp(buf, "\33[1;33;46m★", 12) == 0||
+ strncmp(buf, "To", 2) == 0) {
+ strcpy(fhdr.title, "熱線記錄");
+ }
+// if(!fhdr.title[0])
+// strcpy(fhdr.title, dirlist[count]->d_name);
+ fclose(fp);
+ write(fdir, &fhdr, sizeof(fhdr));
+ }
+ }
+ close(fdir);
+ for(total--; total >= 0; total--)
+ free(dirlist[total]);
+ free(dirlist);
+ }
+ return 0;
+}
diff --git a/pttbbs/util/chesscountry.c b/pttbbs/util/chesscountry.c
new file mode 100644
index 00000000..51f0baa3
--- /dev/null
+++ b/pttbbs/util/chesscountry.c
@@ -0,0 +1,207 @@
+/* $Id$ */
+/*-------------------------------------------------------*/
+/* copied from util/cchess.c ( NTU FPG BBS Ver 1.00 ) */
+/*-------------------------------------------------------*/
+/* target : 棋國設定檔自動更新程式 */
+/*-------------------------------------------------------*/
+
+#define CHESSCOUNTRY /* This tool is built with CHESSCOUNTRY defined */
+#include "bbs.h"
+
+/* Number of minutes of files' mtime before now will be rescanned. */
+// #define UPDATE_FREQUENCY (30)
+
+void
+f_suck6(FILE* fp, char* fname)
+{
+ FILE *sfp;
+ int count = 0;
+
+ if ((sfp = fopen(fname, "r")))
+ {
+ char inbuf[256];
+
+ while (fgets(inbuf, sizeof(inbuf), sfp) && count < 6)
+ {
+ fputs(inbuf, fp);
+ count++;
+ }
+ fclose(sfp);
+ }
+ while (count++ < 6)
+ fputc('\n', fp);
+}
+
+int
+main(void)
+{
+ FILE *fp, *ftmp;
+ int i = 0, num;
+ // char *currboard[3] = {"CCK-CHUHEN", "CCK-GENERAL", "CCK-FREE"};
+ // char *kingdom[3] = {"楚漢皇朝", "將帥帝聯", "逍遙王朝"};
+ char file1[80], file2[80], line[256], str[256];
+ time_t dtime;
+ boardheader_t brd;
+ int brdfd;
+
+ setgid(BBSGID);
+ setuid(BBSUID);
+ chdir(BBSHOME);
+
+ attach_SHM();
+
+ time(&dtime);
+
+ if ((brdfd = open(BBSHOME "/" FN_BOARD, O_RDONLY)) == -1){
+ perror("open " BBSHOME "/" FN_BOARD);
+ return 0;
+ }
+
+ while(read(brdfd, &brd, sizeof(brd)) == sizeof(brd))
+ {
+ const char* photo_fname = 0;
+ const char* chess_name = 0;
+ char kingdom_name[256];
+ int bid;
+ // struct stat st;
+
+ switch(brd.chesscountry){
+ case CHESSCODE_FIVE:
+ photo_fname = "photo_fivechess";
+ chess_name = "五子棋";
+ break;
+ case CHESSCODE_CCHESS:
+ photo_fname = "photo_cchess";
+ chess_name = "象棋";
+ break;
+ case CHESSCODE_GO:
+ photo_fname = "photo_go";
+ chess_name = "圍棋";
+ break;
+ default:
+ continue;
+ }
+ bid = getbnum(brd.brdname);
+
+ setapath(str, brd.brdname);
+ sprintf(file1, "%s/chess_list", str);
+
+ printf("apath = %s\n", str);
+
+ /*
+ if (stat(file1, &st) == 0 && st.st_mtime > (dtime - UPDATE_FREQUENCY * 60))
+ continue;
+ */
+
+ sprintf(file2, "%s/chess_list.tmp", str);
+ if ((ftmp = fopen(file2, "w")) == NULL)
+ continue;
+
+ if ((fp = fopen(file1, "r")))
+ {
+ char *p;
+ char userid[IDLEN + 1], buf[256], name[11];
+ char date[11], other[IDLEN + 1];
+ int namelen;
+
+ fgets(kingdom_name, 256, fp);
+ fputs(kingdom_name, ftmp);
+ chomp(kingdom_name);
+ while (fgets(buf, sizeof(buf), fp))
+ {
+ i = 0;
+ strcpy(line, buf);
+ p = strtok(buf, " ");
+ name[0] = '\0';
+ if (p && *p != '#' && searchuser(p, userid))
+ {
+ i = 1;
+
+ if ((p = strtok(NULL, " ")))
+ strlcpy(name, p, sizeof(name));
+ else
+ i = 0;
+
+ if ((p = strtok(NULL, " ")))
+ strlcpy(date, p, sizeof(date));
+ else
+ i = 0;
+
+ if ((p = strtok(NULL, " ")))
+ strlcpy(other, p, sizeof(other));
+ else
+ i = 0;
+ }
+ if (!strcmp("除名", name))
+ {
+ sethomefile(buf, userid, photo_fname);
+ unlink(buf);
+ continue;
+ }
+ if (i == 0)
+ {
+ fprintf(ftmp, "%s", line);
+ continue;
+ }
+ namelen = strlen(name);
+
+ setapath(str, brd.brdname);
+ sprintf(buf, "%s/chess_photo/.DIR", str);
+ num = get_num_records(buf, sizeof(fileheader_t));
+ for (i = 1; i <= num; i++)
+ {
+ fileheader_t item;
+ if (get_record(buf, &item, sizeof item, i) != -1)
+ {
+ FILE *fp1;
+ if (!strncmp(item.title + 3, name, namelen) &&
+ (item.title[namelen + 3] == '\0' ||
+ item.title[namelen + 3] == ' ')
+ )
+ {
+ sethomefile(buf, userid, photo_fname);
+ if ((fp1 = fopen(buf, "w")))
+ {
+ sprintf(buf, "%s/chess_photo/%s", str, item.filename);
+ f_suck6(fp1, buf);
+ fprintf(fp1, "%d\n", bid);
+ if (strcmp("俘虜", name))
+ fprintf(fp1, "<所屬王國> %s (%s)\n", kingdom_name, chess_name);
+ else
+ fprintf(fp1, "<俘虜王國> %s\n", kingdom_name);
+ fprintf(fp1, "<現在階級> %s\n", name);
+ fprintf(fp1, "<加入日期> %s\n", date);
+ if (strcmp("俘虜", name))
+ {
+ int level;
+ fprintf(fp1, "<王國等級> ");
+ level = atoi(other);
+ for (i = 0; i < level; i++)
+ fprintf(fp1, "%s", "★");
+ }
+ else
+ {
+ chomp(other);
+ fprintf(fp1, "<被誰俘虜> %s", other);
+ }
+ fprintf(fp1, "\n<自我說明> \n");
+ fclose(fp1);
+ }
+ break;
+ }
+ }
+ }
+ if (i > num) // level photo not found
+ fprintf(ftmp, "%s", line);
+ else
+ fprintf(ftmp, "#%s", line);
+ }
+ fclose(fp);
+ fclose(ftmp);
+ rename(file2, file1);
+ } else {
+ fclose(ftmp);
+ }
+ }
+ return 0;
+}
diff --git a/pttbbs/util/chkhbf.c b/pttbbs/util/chkhbf.c
new file mode 100644
index 00000000..90c28d56
--- /dev/null
+++ b/pttbbs/util/chkhbf.c
@@ -0,0 +1,163 @@
+/* $Id$ */
+#define _UTIL_C_
+#include "bbs.h"
+
+struct {
+ char userid[IDLEN + 1];
+ time_t lastlogin, expire;
+} explist[MAX_FRIEND];
+
+void usage(void)
+{
+ fprintf(stderr, "通知隱板板主是否有板友過期/已經過期\n"
+ "usage: chkhbf [-a] [board name [board name]]\n");
+}
+
+int mailalertuid(int tuid)
+{
+ userinfo_t *uentp=NULL;
+ if(tuid>0 && (uentp = (userinfo_t *)search_ulist(tuid)) )
+ uentp->alerts |=ALERT_NEW_MAIL;
+ return 0;
+}
+
+char *CTIMEx(char *buf, time_t t)
+{
+ strcpy(buf, ctime(&t));
+ buf[strlen(buf) - 1] = 0;
+ return buf;
+}
+
+void informBM(char *userid, boardheader_t *bptr, int nEXP)
+{
+ int uid, i;
+ char filename[256], buf[64];
+ fileheader_t mymail;
+ FILE *fp;
+ if( !(uid = searchuser(userid, userid)) )
+ return;
+ sprintf(filename, BBSHOME "/home/%c/%s", userid[0], userid);
+ stampfile(filename, &mymail);
+ if( (fp = fopen(filename, "w")) == NULL )
+ return;
+
+ printf("brdname: %s, BM: %s\n", bptr->brdname, userid);
+ fprintf(fp,
+ "作者: 系統通知.\n"
+ "標題: 警告: 貴板板友即將過期/已經過期\n"
+ "時間: %s\n"
+ " %s 的板主您好: \n"
+ " 下列貴板板友即將過期或已經過期:\n"
+ "------------------------------------------------------------\n",
+ CTIMEx(buf, now), bptr->brdname);
+ for( i = 0 ; i < nEXP ; ++i )
+ if( explist[i].expire == -1 )
+ fprintf(fp, "%-15s 已經過期\n", explist[i].userid);
+ else
+ fprintf(fp, "%-15s 即將在 %s 過期\n",
+ explist[i].userid, CTIMEx(buf, explist[i].expire));
+ fprintf(fp,
+ "------------------------------------------------------------\n"
+ "說明:\n"
+ " 為了節省系統資源, 系統將自動清除掉超過四個月未上站\n"
+ "的使用者. 此時若有某位您不認識的使用者恰好註冊了該帳號,\n"
+ "將會視為貴板板友而放行進入.\n"
+ " 建議您暫時將這些使用者自貴板的板友名單中移除.\n"
+ "\n"
+ " 這封信件是由程式自動發出, 請不要直接回覆這封信. 若\n"
+ "您有相關問題請麻煩至看板 SYSOP, 或是直接與看板總管聯繫. :)\n"
+ "\n"
+ BBSNAME "站長群敬上"
+ );
+ fclose(fp);
+
+ strcpy(mymail.title, "警告: 貴板板友即將過期/已經過期");
+ strcpy(mymail.owner, "系統通知.");
+ sprintf(filename, BBSHOME "/home/%c/%s/.DIR", userid[0], userid);
+ mailalertuid(uid);
+ append_record(filename, &mymail, sizeof(mymail));
+}
+
+void chkhbf(boardheader_t *bptr)
+{
+ char fn[256], chkuser[256];
+ int i, nEXP = 0;
+ FILE *fp;
+ userec_t xuser;
+
+ sprintf(fn, "boards/%c/%s/visable", bptr->brdname[0], bptr->brdname);
+ if( (fp = fopen(fn, "rt")) == NULL )
+ return;
+ while( fgets(chkuser, sizeof(chkuser), fp) != NULL ){
+ for( i = 0 ; chkuser[i] != 0 && i < sizeof(chkuser) ; ++i )
+ if( !isalnum(chkuser[i]) ){
+ chkuser[i] = 0;
+ break;
+ }
+ if( !getuser(chkuser, &xuser) || strcmp(chkuser, "guest") == 0 ){
+ strcpy(explist[nEXP].userid, chkuser);
+ explist[nEXP].expire = -1;
+ ++nEXP;
+ }
+ else if( (xuser.lastlogin < now - 86400 * 90) &&
+ !(xuser.userlevel & PERM_XEMPT) ){
+ strcpy(explist[nEXP].userid, chkuser);
+ explist[nEXP].lastlogin = xuser.lastlogin;
+ explist[nEXP].expire = xuser.lastlogin + 86400 * 120;
+ ++nEXP;
+ }
+ }
+ fclose(fp);
+ if( nEXP ){
+ char BM[IDLEN * 3 + 3], *p;
+ strlcpy(BM, bptr->BM, sizeof(BM));
+ for( p = BM ; *p != 0 ; ++p )
+ if( !isalpha(*p) && !isdigit(*p) )
+ *p = ' ';
+ for( i = 0, p = strtok(BM, " ") ; p != NULL ;
+ ++i, p = strtok(NULL, " ") )
+ informBM(p, bptr, nEXP);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int ch, allboards = 0, i;
+ boardheader_t *bptr;
+ while( (ch = getopt(argc, argv, "ah")) != -1 )
+ switch( ch ){
+ case 'a':
+ allboards = 1;
+ break;
+ case 'h':
+ usage();
+ return 0;
+ }
+
+ chdir(BBSHOME);
+ attach_SHM();
+ argc -= optind;
+ argv += optind;
+ now = time(NULL);
+ if( allboards ){
+ for( i = 0 ; i < MAX_BOARD ; ++i ){
+ bptr = &bcache[i];
+ if( bptr->brdname[0] &&
+ !(bptr->brdattr & (BRD_TOP | BRD_GROUPBOARD)) &&
+ bptr->brdattr & BRD_HIDE )
+ chkhbf(bptr);
+ }
+ }
+ else if( argc > 0 ){
+ int bid;
+ for( i = 0 ; i < argc ; ++i )
+ if( (bid = getbnum(argv[i])) != 0 ) // XXX: bid start 1
+ chkhbf(getbcache(bid));
+ else
+ fprintf(stderr, "%s not found\n", argv[i]);
+ }
+ else
+ usage();
+
+ return 0;
+}
diff --git a/pttbbs/util/cleandir.pl b/pttbbs/util/cleandir.pl
new file mode 100644
index 00000000..606e3b4b
--- /dev/null
+++ b/pttbbs/util/cleandir.pl
@@ -0,0 +1,55 @@
+#!/usr/bin/perl
+# $Id$
+use strict;
+use lib '/home/bbs/bin/';
+use BBSFileHeader;
+
+my($nDels, $old, $prefix) = ();
+$old = $nDels = 0;
+foreach( @ARGV ){
+ print "cleaning: $_\n";
+ cleandir($_);
+ print ("\t".($nDels - $old). " files deleted\n");
+ $old = $nDels;
+}
+
+print "$nDels files deleted\n";
+
+sub toclean
+{
+ unlink("$prefix/$_[0]");
+ ++$nDels;
+}
+
+sub cleandir($)
+{
+ my($dir) = @_;
+ my(%files, %dotDIR, $now, $counter) = ();
+ $now = time();
+ $prefix = $dir;
+
+ opendir DIR, $dir;
+ foreach( readdir(DIR) ){
+ if( /^\./ ){
+ next;
+ } elsif( -d $_ ){
+ print "dir? $_";
+ `/bin/rm -rf $prefix/$_`;
+ } elsif( /^M\.\d+\.A/ ){
+ $files{$_} = 1;
+ } elsif( (/^SR\./) && (stat($_))[2] < ($now - 86400) ){
+ toclean($_);
+ }
+ }
+ close DIR;
+
+ tie %dotDIR, 'BBSFileHeader', $dir;
+ foreach( 0..($dotDIR{num} - 1) ){
+ my $fn = $dotDIR{"$_.filename"};
+ delete $files{$fn};
+ }
+ untie %dotDIR;
+
+ toclean($_)
+ foreach( keys %files );
+}
diff --git a/pttbbs/util/cleanident.c b/pttbbs/util/cleanident.c
new file mode 100644
index 00000000..0d32374c
--- /dev/null
+++ b/pttbbs/util/cleanident.c
@@ -0,0 +1,19 @@
+/* $Id$ */
+#include "bbs.h"
+
+int main(int argc, char **argv)
+{
+ userec_t usr;
+ int fd, i;
+ if( argc == 1 || (fd = open(argv[1], O_RDWR)) < 0 ){
+ fprintf(stderr, "usage: cleanident path_to_passwd\n");
+ return 1;
+ }
+ for( i = 0 ; read(fd, &usr, sizeof(usr)) == sizeof(usr) ; ++ i ){
+ memset(usr.pad0, 0, sizeof(usr.pad0));
+ if( lseek(fd, i * sizeof(usr), SEEK_SET) != -1 )
+ write(fd, &usr, sizeof(usr));
+ }
+ printf("%d users cleaned\n", i);
+ return 0;
+}
diff --git a/pttbbs/util/cleanpasswd.c b/pttbbs/util/cleanpasswd.c
new file mode 100644
index 00000000..ffb13393
--- /dev/null
+++ b/pttbbs/util/cleanpasswd.c
@@ -0,0 +1,52 @@
+/* $Id$ */
+#define _UTIL_C_
+#include "bbs.h"
+
+/* 當資料欄位有異動 例如用了舊的欄位 可用這個程式清除舊值 */
+int clean_unused_var(userec_t *rec)
+{
+ rec->goodpost = 0;
+ rec->badpost = 0;
+ rec->goodsale = 0;
+ rec->badsale = 0;
+ memset(rec->pad, 0, sizeof(rec->pad));
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int i, fd, fdw;
+ userec_t user;
+
+ setgid(BBSGID);
+ setuid(BBSUID);
+ chdir(BBSHOME);
+
+ if ((fd = open(BBSHOME"/.PASSWDS", O_RDONLY)) < 0){
+ perror("open .PASSWDS error");
+ exit(-1);
+ }
+
+ if ((fdw = open(BBSHOME"/.PASSWDS.new", O_WRONLY | O_TRUNC | O_CREAT, 0600)) < 0){
+ perror("open .PASSWDS.new error");
+ exit(-1);
+ }
+
+ for(i = 0; i < MAX_USERS; i++){
+ if (read(fd, &user, sizeof(user)) != sizeof(user))
+ break;
+ clean_unused_var(&user);
+ write(fdw, &user, sizeof(user));
+ }
+ close(fd);
+ close(fdw);
+
+ if (i != MAX_USERS)
+ fprintf(stderr, "ERROR\n");
+ else{
+ fprintf(stderr, "DONE\n");
+ system("/bin/mv " BBSHOME "/.PASSWDS " BBSHOME "/.PASSWDS.bak");
+ system("/bin/mv " BBSHOME "/.PASSWDS.new " BBSHOME "/.PASSWDS");
+ }
+ return 0;
+}
diff --git a/pttbbs/util/countalldice.c b/pttbbs/util/countalldice.c
new file mode 100644
index 00000000..45944362
--- /dev/null
+++ b/pttbbs/util/countalldice.c
@@ -0,0 +1,91 @@
+/* $Id$ */
+
+/**********************************************/
+/*這個程式是用來計算賭骰子賺得錢跟賠的錢的程式 */
+/*用法就是直接打 countalldice 就可以針對所有人 */
+/*來計算他總共賺了多少 賠了多少............... */
+/*作者:Heat 於1997/10/2 */
+/**********************************************/
+
+#include "bbs.h"
+
+#define DICE_WIN BBSHOME "/etc/windice.log"
+#define DICE_LOST BBSHOME "/etc/lostdice.log"
+
+int total = 0;
+
+typedef struct dice
+{
+ char id[14];
+ int win;
+ int lost;
+}
+dice;
+
+dice table[1024];
+
+int find(char *name)
+{
+ int i = 0;
+ if (total == 0)
+ {
+ total++;
+ return 0;
+ }
+ for (i = 0; i < total; i++)
+ if (!strcmp(name, table[i].id))
+ return i;
+ memset(&table[total++], 0, sizeof(dice));
+ return total - 1;
+}
+
+int main() {
+ int index, win = 0, lost = 0;
+ FILE *fpwin, *fplost;
+ char buf[256], *ptr, buf0[256], *name = (char *) malloc(15), *mon = (char *) malloc(5);
+
+ fpwin = fopen(DICE_WIN, "r");
+ fplost = fopen(DICE_LOST, "r");
+
+ if (!fpwin || !fplost)
+ perror("error open file");
+
+ while (fgets(buf, 255, fpwin))
+ {
+ strcpy(buf0, buf);
+ name = strtok(buf, " ");
+ mon = strstr(buf0, "淨賺:");
+ if ((ptr = strchr(mon, '\n')))
+ *ptr = 0;
+ index = find(name);
+ strcpy(table[index].id, name);
+ table[index].win += atoi(mon + 5);
+ }
+ fclose(fpwin);
+
+ while (fgets(buf, 255, fplost))
+ {
+ strcpy(buf0, buf);
+ name = strtok(buf, " ");
+ mon = strstr(buf0, "輸了 ");
+ if ((ptr = strchr(mon, '\n')))
+ *ptr = 0;
+ if ((index = find(name)) == total - 1)
+ strcpy(table[index].id, name);
+ table[index].lost += atoi(mon + 5);
+ }
+
+ for (index = 0; index < total; index++)
+ {
+ printf("%-15s 贏了 %-8d 塊錢, 輸掉 %-8d 塊錢\n", table[index].id
+ ,table[index].win, table[index].lost);
+ win += table[index].win;
+ lost += table[index].lost;
+ }
+ index = win + lost;
+ printf("\n人數: %d\n總贏錢=%d 總輸錢=%d 總金額:%d\n", total, win, lost, index);
+ printf("贏的比例:%f 輸的比例:%f\n", (float) win / index, (float) lost / index);
+ printf("\n備註:輸贏是以使用者的觀點來看\n");
+ fclose(fplost);
+ return 0;
+}
diff --git a/pttbbs/util/dailybackup.pl b/pttbbs/util/dailybackup.pl
new file mode 100644
index 00000000..fdcbee93
--- /dev/null
+++ b/pttbbs/util/dailybackup.pl
@@ -0,0 +1,48 @@
+#!/usr/bin/perl
+use lib '/home/bbs/bin/';
+use LocalVars;
+use strict;
+use vars qw/$BACKHOME $MANROOT $HOMEROOT $BOARDROOT/;
+
+$BACKHOME = "$BBSHOME/backup";
+$MANROOT = "man/boards";
+$HOMEROOT = "home";
+$BOARDROOT= "boards";
+
+chdir $BBSHOME;
+my @baktable = (['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'],
+ ['I', 'J', 'K', 'L', 'M', 'N', 'O', 'P'],
+ ['Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X'],
+ ['Y', 'Z', 'a', 'b', 'c', 'd', 'e'],
+ ['f', 'g', 'h', 'i', 'j', 'k', 'l'],
+ ['m', 'n', 'o', 'p', 'q', 'r', 's'],
+ ['t', 'u', 'v', 'w', 'x', 'y', 'z']);
+my (undef,undef,undef,undef,undef,undef,$wday) = localtime(time);
+my $week = defined($ARGV[0]) ? $ARGV[0] : $wday;
+
+no strict 'subs';
+setpriority(PRIO_PROCESS, $$, 20);
+use strict subs;
+
+my($orig, $to);
+foreach $orig ( <$BACKHOME/*new*> ){
+ $to = $orig;
+ $to =~ s/\.new//g;
+ docmd("mv $orig $to");
+}
+
+foreach( @{$baktable[$week]} ){
+ docmd("$TAR zcf $BACKHOME/man.$_.new.tgz $MANROOT/$_/*");
+ docmd("$TAR zcfh $BACKHOME/home.$_.new.tgz $HOMEROOT/$_");
+ docmd("$TAR zcf $BACKHOME/board.$_.new.tgz $BOARDROOT/$_/*");
+}
+
+if( $week == 0 ){
+ docmd("$TAR zcf $BACKHOME/general.new.tgz .BRD .PASSWDS .act .note .polling .post .post.old adm bin cron etc innd log note.ans note.dat pttbbs register.log ussong");
+}
+
+sub docmd
+{
+ print "@_\n";
+ `@_`;
+}
diff --git a/pttbbs/util/deluserfile.c b/pttbbs/util/deluserfile.c
new file mode 100644
index 00000000..aa5aaa74
--- /dev/null
+++ b/pttbbs/util/deluserfile.c
@@ -0,0 +1,139 @@
+/* $Id$ */
+/* 自動砍user目錄檔案程式 */
+
+#include "bbs.h"
+
+#define HOLDWRITELOG
+#define DELZEROFILE
+#define USERHOME BBSHOME "/home"
+
+int bad_user_id(const char *userid)
+{
+ register char ch;
+
+ if (strlen(userid) < 2)
+ return 1;
+
+ if (!isalpha(*userid))
+ return 1;
+
+ if (!strcasecmp(userid, "new"))
+ return 1;
+
+ while ((ch = *(++userid)))
+ if (!isalnum(ch))
+ return 1;
+ return 0;
+}
+
+void del_file(char *userid)
+{
+ char buf[200], buf1[200];
+ struct dirent *de;
+ DIR *dirp;
+ char *ptr;
+
+ sprintf(buf, BBSHOME "/home/%c/%s", userid[0], userid);
+
+ if (chdir(buf) == -1)
+ return;
+
+ if (!(dirp = opendir(buf)))
+ return;
+
+ while ((de = readdir(dirp)))
+ {
+ ptr = de->d_name;
+ if (ptr[0] > ' ' && ptr[0] != '.')
+ {
+ if (strstr(ptr, "writelog"))
+#ifdef HOLDWRITELOG
+ {
+ fileheader_t mymail;
+
+ stampfile(buf, &mymail);
+ mymail.filemode = FILE_READ;
+ strcpy(mymail.owner, "[備.忘.錄]");
+ strcpy(mymail.title, "熱線記錄");
+ sprintf(buf1, BBSHOME "/home/%c/%s/writelog",
+ userid[0], userid);
+ rename(buf1, buf);
+ sprintf(buf1, BBSHOME "/home/%c/%s/.DIR", userid[0], userid);
+ append_record(buf1, &mymail, sizeof(mymail));
+ }
+#else
+ unlink(ptr);
+#endif
+ else if (strstr(ptr, "chat_"))
+ unlink(ptr);
+ else if (strstr(ptr, "ve_"))
+ unlink(ptr);
+ else if (strstr(ptr, "SR."))
+ unlink(ptr);
+ else if (strstr(ptr, ".old"))
+ unlink(ptr);
+ else if (strstr(ptr, "talk_"))
+ unlink(ptr);
+ }
+ }
+ closedir(dirp);
+}
+
+void mv_user_home(char *ptr)
+{
+ char buf[200];
+
+ printf("move user %s to tmp\n", ptr);
+ sprintf(buf, "cp -R " BBSHOME "/home/%c/%s " BBSHOME "/tmp", ptr[0], ptr);
+// sprintf(buf,"rm -rf " BBSHOME "/home/%c/%s",ptr[0],ptr);
+ if (!system(buf))
+ { //Copy success
+
+ sprintf(buf, "rm -rf " BBSHOME "/home/%c/%s", ptr[0], ptr);
+ system(buf);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct dirent *de;
+ DIR *dirp;
+ char *ptr, buf[200], ch;
+ int count = 0;
+/* visit all users */
+
+ printf("new version, deleting\n");
+
+ attach_SHM();
+
+ for (ch = 'A'; ch <= 'z'; ch++)
+ {
+ if(ch > 'Z' && ch < 'a')
+ continue;
+ printf("Cleaning %c\n", ch);
+ sprintf(buf, USERHOME "/%c", ch);
+ if (!(dirp = opendir(buf)))
+ {
+ printf("unable to open %s\n", buf);
+ continue;
+ }
+
+ while ((de = readdir(dirp)))
+ {
+ ptr = de->d_name;
+
+ /* 預防錯誤 */
+ if (!bad_user_id(ptr))
+ {
+ if (!(count++ % 300))
+ printf(".\n");
+ if (!searchuser(ptr, ptr))
+ mv_user_home(ptr);
+ else
+ del_file(ptr);
+ }
+ }
+ closedir(dirp);
+ }
+ return 0;
+}
diff --git a/pttbbs/util/diskstat.c b/pttbbs/util/diskstat.c
new file mode 100644
index 00000000..bc80ece1
--- /dev/null
+++ b/pttbbs/util/diskstat.c
@@ -0,0 +1,788 @@
+#ifndef __FreeBSD__
+ #include <stdio.h>
+ int main(int argc, char **argv)
+ {
+ puts("sorry, this program is designed for FreeBSD only");
+ return 0;
+ }
+#else
+/*
+ * Copyright (c) 1997, 1998 Kenneth D. Merry.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/iostat/iostat.c,v 1.17.2.2 2001/07/19 04:15:42 kris Exp $
+ */
+/*
+ * Parts of this program are derived from the original FreeBSD iostat
+ * program:
+ */
+/*-
+ * Copyright (c) 1986, 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Ideas for the new iostat statistics output modes taken from the NetBSD
+ * version of iostat:
+ */
+/*
+ * Copyright (c) 1996 John M. Vinopal
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed for the NetBSD Project
+ * by John M. Vinopal.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <sys/dkstat.h>
+
+#include <err.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <devstat.h>
+
+struct nlist namelist[] = {
+#define X_TK_NIN 0
+ { "_tk_nin" },
+#define X_TK_NOUT 1
+ { "_tk_nout" },
+#define X_CP_TIME 2
+ { "_cp_time" },
+#define X_HZ 3
+ { "_hz" },
+#define X_STATHZ 4
+ { "_stathz" },
+#define X_END 4
+ { NULL },
+};
+
+struct statinfo cur, last;
+int num_devices;
+struct device_selection *dev_select;
+int maxshowdevs;
+int dflag = 1, Iflag = 0, Cflag = 0, Tflag = 0, oflag = 0, Kflag = 0, Bflag = 1, qflag = 0, sflag = 0, aflag = 0, fflag = 1;
+
+#define nlread(x, v) \
+ kvm_read(kd, namelist[x].n_value, &(v), sizeof(v))
+
+/* local function declarations */
+static void usage(void);
+static void phdr(int signo);
+static void devstats(int perf_select);
+static void cpustats(void);
+static void printresult(int p);
+
+static void
+usage(void)
+{
+ /*
+ * We also support the following 'traditional' syntax:
+ * iostat [drives] [wait [count]]
+ * This isn't mentioned in the man page, or the usage statement,
+ * but it is supported.
+ */
+ fprintf(stderr, "usage: iostat [-CdhIKoT?] [-c count] [-M core]"
+ " [-n devs] [-N system]\n"
+ "\t [-t type,if,pass] [-w wait] [drives]\n");
+}
+
+char firsttime = 1;
+int
+main(int argc, char **argv)
+{
+ int c;
+ register int i;
+ int tflag = 0, hflag = 0, cflag = 0, wflag = 1, nflag = 0;
+ int count = 0, waittime = 2;
+ char *memf = NULL, *nlistf = NULL;
+ struct devstat_match *matches;
+ int num_matches = 0;
+ char errbuf[_POSIX2_LINE_MAX];
+ kvm_t *kd;
+ int hz, stathz;
+ int headercount;
+ long generation;
+ int num_devices_specified;
+ int num_selected, num_selections;
+ long select_generation;
+ char **specified_devices;
+ devstat_select_mode select_mode;
+
+ matches = NULL;
+ maxshowdevs = 10;
+
+ while ((c = getopt(argc, argv, "c:CdhIKM:n:N:ot:Tw:?qsaf:")) != -1) {
+ switch(c) {
+ case 'c':
+ cflag++;
+ count = atoi(optarg);
+ if (count < 1)
+ errx(1, "count %d is < 1", count);
+ break;
+ case 'C':
+ Cflag++;
+ break;
+ case 'd':
+ dflag++;
+ break;
+ case 'h':
+ hflag++;
+ break;
+ case 'I':
+ Iflag++;
+ break;
+ case 'K':
+ Kflag++;
+ break;
+ case 'M':
+ memf = optarg;
+ break;
+ case 'n':
+ nflag++;
+ maxshowdevs = atoi(optarg);
+ if (maxshowdevs < 0)
+ errx(1, "number of devices %d is < 0",
+ maxshowdevs);
+ break;
+ case 'N':
+ nlistf = optarg;
+ break;
+ case 'o':
+ oflag++;
+ break;
+ case 't':
+ tflag++;
+ if (buildmatch(optarg, &matches,
+ &num_matches) != 0)
+ errx(1, "%s", devstat_errbuf);
+ break;
+ case 'T':
+ Tflag++;
+ break;
+ case 'w':
+ wflag++;
+ waittime = atoi(optarg);
+ if (waittime < 1)
+ errx(1, "wait time is < 1");
+ break;
+ case 'q': /* statistics only */
+ qflag++;
+ break;
+ case 's': /* scsi only */
+ sflag++;
+ break;
+ case 'a': /* average only */
+ aflag++;
+ break;
+ case 'f': /* factor output */
+ fflag = atoi(optarg);
+ break;
+ default:
+ usage();
+ exit(1);
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /*
+ * Discard setgid privileges if not the running kernel so that bad
+ * guys can't print interesting stuff from kernel memory.
+ */
+ if (nlistf != NULL || memf != NULL)
+ setgid(getgid());
+
+ /*
+ * Make sure that the userland devstat version matches the kernel
+ * devstat version. If not, exit and print a message informing
+ * the user of his mistake.
+ */
+ if (checkversion() < 0)
+ errx(1, "%s", devstat_errbuf);
+
+#if 0
+ /*
+ * Figure out how many devices we should display.
+ */
+ if (nflag == 0) {
+ if (oflag > 0) {
+ if ((dflag > 0) && (Cflag == 0) && (Tflag == 0))
+ maxshowdevs = 5;
+ else if ((dflag > 0) && (Tflag > 0) && (Cflag == 0))
+ maxshowdevs = 5;
+ else
+ maxshowdevs = 4;
+ } else {
+ if ((dflag > 0) && (Cflag == 0))
+ maxshowdevs = 4;
+ else
+ maxshowdevs = 3;
+ }
+ }
+#endif
+
+ Signal(SIGINT, printresult);
+ /* find out how many devices we have */
+ if ((num_devices = getnumdevs()) < 0)
+ err(1, "can't get number of devices");
+
+ if ((cur.dinfo = (struct devinfo *)malloc(sizeof(struct devinfo))) ==
+ NULL)
+ err(1, "devinfo malloc failed");
+ if ((last.dinfo = (struct devinfo *)malloc(sizeof(struct devinfo))) ==
+ NULL)
+ err(1, "devinfo malloc failed");
+ bzero(cur.dinfo, sizeof(struct devinfo));
+ bzero(last.dinfo, sizeof(struct devinfo));
+
+ /*
+ * Grab all the devices. We don't look to see if the list has
+ * changed here, since it almost certainly has. We only look for
+ * errors.
+ */
+ if (getdevs(&cur) == -1)
+ errx(1, "%s", devstat_errbuf);
+
+ num_devices = cur.dinfo->numdevs;
+ generation = cur.dinfo->generation;
+
+ /*
+ * If the user specified any devices on the command line, see if
+ * they are in the list of devices we have now.
+ */
+ if ((specified_devices = (char **)malloc(sizeof(char *))) == NULL)
+ err(1, "specified_devices malloc failed");
+ for (num_devices_specified = 0; *argv; ++argv) {
+ if (isdigit(**argv))
+ break;
+ num_devices_specified++;
+ specified_devices = (char **)realloc(specified_devices,
+ sizeof(char *) *
+ num_devices_specified);
+ specified_devices[num_devices_specified - 1] = *argv;
+
+ }
+ if (nflag == 0 && maxshowdevs < num_devices_specified)
+ maxshowdevs = num_devices_specified;
+
+ dev_select = NULL;
+
+ if ((num_devices_specified == 0) && (num_matches == 0))
+ select_mode = DS_SELECT_ADD;
+ else
+ select_mode = DS_SELECT_ONLY;
+
+ /*
+ * At this point, selectdevs will almost surely indicate that the
+ * device list has changed, so we don't look for return values of 0
+ * or 1. If we get back -1, though, there is an error.
+ */
+ if (selectdevs(&dev_select, &num_selected,
+ &num_selections, &select_generation,
+ generation, cur.dinfo->devices, num_devices,
+ matches, num_matches,
+ specified_devices, num_devices_specified,
+ select_mode, maxshowdevs, hflag) == -1)
+ errx(1, "%s", devstat_errbuf);
+
+ /*
+ * Look for the traditional wait time and count arguments.
+ */
+ if (*argv) {
+ waittime = atoi(*argv);
+
+ /* Let the user know he goofed, but keep going anyway */
+ if (wflag != 0)
+ warnx("discarding previous wait interval, using"
+ " %d instead", waittime);
+ wflag++;
+
+ if (*++argv) {
+ count = atoi(*argv);
+ if (cflag != 0)
+ warnx("discarding previous count, using %d"
+ " instead", count);
+ cflag++;
+ } else
+ count = -1;
+ }
+
+ /*
+ * If the user specified a count, but not an interval, we default
+ * to an interval of 1 second.
+ */
+ if ((wflag == 0) && (cflag > 0))
+ waittime = 1;
+
+ /*
+ * If the user specified a wait time, but not a count, we want to
+ * go on ad infinitum. This can be redundant if the user uses the
+ * traditional method of specifying the wait, since in that case we
+ * already set count = -1 above. Oh well.
+ */
+ if ((wflag > 0) && (cflag == 0))
+ count = -1;
+
+ kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY, errbuf);
+
+ if (kd == 0)
+ errx(1, "kvm_openfiles: %s", errbuf);
+
+ if (kvm_nlist(kd, namelist) == -1)
+ errx(1, "kvm_nlist: %s", kvm_geterr(kd));
+
+ (void)nlread(X_HZ, hz);
+ (void)nlread(X_STATHZ, stathz);
+ if (stathz)
+ hz = stathz;
+
+ /*
+ * If the user stops the program (control-Z) and then resumes it,
+ * print out the header again.
+ */
+ (void)Signal(SIGCONT, phdr);
+
+ for (headercount = 1;;) {
+ struct devinfo *tmp_dinfo;
+ long tmp;
+ double etime;
+
+ if (!--headercount) {
+ phdr(0);
+ headercount = 20;
+ }
+ (void)kvm_read(kd, namelist[X_TK_NIN].n_value,
+ &cur.tk_nin, sizeof(cur.tk_nin));
+ (void)kvm_read(kd, namelist[X_TK_NOUT].n_value,
+ &cur.tk_nout, sizeof(cur.tk_nout));
+ (void)kvm_read(kd, namelist[X_CP_TIME].n_value,
+ cur.cp_time, sizeof(cur.cp_time));
+
+ tmp_dinfo = last.dinfo;
+ last.dinfo = cur.dinfo;
+ cur.dinfo = tmp_dinfo;
+
+ last.busy_time = cur.busy_time;
+
+ /*
+ * Here what we want to do is refresh our device stats.
+ * getdevs() returns 1 when the device list has changed.
+ * If the device list has changed, we want to go through
+ * the selection process again, in case a device that we
+ * were previously displaying has gone away.
+ */
+ switch (getdevs(&cur)) {
+ case -1:
+ errx(1, "%s", devstat_errbuf);
+ break;
+ case 1: {
+ int retval;
+
+ num_devices = cur.dinfo->numdevs;
+ generation = cur.dinfo->generation;
+ retval = selectdevs(&dev_select, &num_selected,
+ &num_selections, &select_generation,
+ generation, cur.dinfo->devices,
+ num_devices, matches, num_matches,
+ specified_devices,
+ num_devices_specified,
+ select_mode, maxshowdevs, hflag);
+ switch(retval) {
+ case -1:
+ errx(1, "%s", devstat_errbuf);
+ break;
+ case 1:
+ phdr(0);
+ headercount = 20;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ /*
+ * We only want to re-select devices if we're in 'top'
+ * mode. This is the only mode where the devices selected
+ * could actually change.
+ */
+ if (hflag > 0) {
+ int retval;
+ retval = selectdevs(&dev_select, &num_selected,
+ &num_selections, &select_generation,
+ generation, cur.dinfo->devices,
+ num_devices, matches, num_matches,
+ specified_devices,
+ num_devices_specified,
+ select_mode, maxshowdevs, hflag);
+ switch(retval) {
+ case -1:
+ errx(1,"%s", devstat_errbuf);
+ break;
+ case 1:
+ phdr(0);
+ headercount = 20;
+ break;
+ default:
+ break;
+ }
+ }
+
+ tmp = cur.tk_nin;
+ cur.tk_nin -= last.tk_nin;
+ last.tk_nin = tmp;
+ tmp = cur.tk_nout;
+ cur.tk_nout -= last.tk_nout;
+ last.tk_nout = tmp;
+
+ etime = 0.0;
+
+#define X(fld) tmp = cur.fld[i]; cur.fld[i] -= last.fld[i]; last.fld[i] = tmp
+
+ for (i = 0; i < CPUSTATES; i++) {
+ X(cp_time);
+ etime += cur.cp_time[i];
+ }
+ if (etime == 0.0)
+ etime = 1.0;
+ etime /= (float)hz;
+ if ((dflag == 0) || (Tflag > 0))
+ printf("%4.0f%5.0f", cur.tk_nin / etime,
+ cur.tk_nout/etime);
+ devstats(hflag);
+ if ((dflag == 0) || (Cflag > 0))
+ cpustats();
+ if( !qflag ) printf("\n");
+ fflush(stdout);
+
+ if (count >= 0 && --count <= 0)
+ break;
+
+ sleep(waittime);
+ firsttime = 0;
+ }
+
+ printresult(0);
+ exit(0);
+}
+
+static void
+phdr(int signo)
+{
+ register int i;
+ int printed;
+ if( firsttime ){
+ for( i = 0 ; i < num_devices ; ++i ){
+ int di = dev_select[i].position;
+ if( strcmp(cur.dinfo->devices[di].device_name, "ad") != 0 &&
+ strcmp(cur.dinfo->devices[di].device_name, "da") != 0 ){
+ maxshowdevs = i;
+ break;
+ }
+ }
+ }
+
+ if( qflag )
+ return;
+ if ((dflag == 0) || (Tflag > 0))
+ (void)printf(" tty");
+ for (i = 0, printed=0;(i < num_devices) && (printed < maxshowdevs);i++){
+ int di;
+ if ((dev_select[i].selected != 0)
+ && (dev_select[i].selected <= maxshowdevs)) {
+ di = dev_select[i].position;
+ if (oflag > 0)
+ (void)printf("%12.6s%d ",
+ cur.dinfo->devices[di].device_name,
+ cur.dinfo->devices[di].unit_number);
+ else
+ printf(cur.dinfo->devices[di].unit_number < 10 ? "%6s%d" : "%5s%2d",
+ cur.dinfo->devices[di].device_name,
+ cur.dinfo->devices[di].unit_number);
+ printed++;
+ }
+ }
+ if ((dflag == 0) || (Cflag > 0))
+ (void)printf(" cpu\n");
+ else
+ (void)printf("\n");
+
+ if ((dflag == 0) || (Tflag > 0))
+ (void)printf(" tin tout");
+
+ for (i=0, printed = 0;(i < num_devices) && (printed < maxshowdevs);i++){
+ if ((dev_select[i].selected != 0)
+ && (dev_select[i].selected <= maxshowdevs)) {
+ if (oflag > 0) {
+ if (Iflag == 0)
+ (void)printf(" sps tps msps ");
+ else
+ (void)printf(" blk xfr msps ");
+ } else {
+ if(Bflag)
+ printf(" busy");
+ else if (Iflag == 0)
+ printf(" KB/t tps MB/s ");
+ else
+ printf(" KB/t xfrs MB ");
+ }
+ printed++;
+ }
+ }
+ if ((dflag == 0) || (Cflag > 0))
+ (void)printf(" us ni sy in id\n");
+ else
+ printf("\n");
+
+}
+
+int counttimes = -1;
+float busydata[1024];
+int sf(const void *a, const void *b)
+{
+ if( busydata[*(int *)b] > busydata[*(int *)a] )
+ return 1;
+ return -1;
+}
+
+static void printresult(int p)
+{
+ int dn, id[1024], i;
+ if( counttimes > 0 ){
+ if( aflag ){
+ float dat = 0;
+ int nDrivers = 0;
+ for( i = 0 ; i < maxshowdevs ; ++i )
+ if( !sflag ||
+ strcmp(cur.dinfo->devices[i].device_name, "da") == 0 ){
+ dat += busydata[i];
+ ++nDrivers;
+ }
+ printf("%f\n", dat * fflag / nDrivers / counttimes);
+ }
+ else{
+ printf("\n");
+ for( i = 0 ; i < maxshowdevs ; ++i )
+ id[i] = i;
+ qsort(id, maxshowdevs, sizeof(int), sf);
+
+ printf("--- diskstat statistics (%d samples)---\n", counttimes);
+ for (dn = 0; dn < maxshowdevs; dn++)
+ printf(" %s%d: %10f%%\n",
+ cur.dinfo->devices[ id[dn] ].device_name,
+ cur.dinfo->devices[ id[dn] ].unit_number,
+ (float)busydata[ id[dn] ] / counttimes);
+ printf("\n");
+ }
+ }
+ exit(0);
+}
+
+static void
+devstats(int perf_select)
+{
+ register int dn;
+ long double transfers_per_second;
+ long double kb_per_transfer, mb_per_second;
+ u_int64_t total_bytes, total_transfers, total_blocks;
+ long double busy_seconds;
+ long double total_mb;
+ long double blocks_per_second, ms_per_transaction;
+ long double device_busy;
+
+ /*
+ * Calculate elapsed time up front, since it's the same for all
+ * devices.
+ */
+ busy_seconds = compute_etime(cur.busy_time, last.busy_time);
+
+ for (dn = 0; dn < num_devices; dn++) {
+ int di;
+ float thisbusy;
+
+ if (((perf_select == 0) && (dev_select[dn].selected == 0))
+ || (dev_select[dn].selected > maxshowdevs))
+ continue;
+
+ di = dev_select[dn].position;
+
+ device_busy = compute_etime(cur.dinfo->devices[di].busy_time,
+ last.dinfo->devices[di].busy_time);
+
+ if (compute_stats(&cur.dinfo->devices[di],
+ &last.dinfo->devices[di], busy_seconds,
+ &total_bytes, &total_transfers,
+ &total_blocks, &kb_per_transfer,
+ &transfers_per_second, &mb_per_second,
+ &blocks_per_second, &ms_per_transaction)!= 0)
+ errx(1, "%s", devstat_errbuf);
+
+ if( (device_busy == 0) && (transfers_per_second > 5) )
+ /* the device has been 100% busy, fake it because
+ * as long as the device is 100% busy the busy_time
+ * field in the devstat struct is not updated */
+ device_busy = busy_seconds;
+ if (device_busy > busy_seconds)
+ /* this normally happens after one or more periods
+ * where the device has been 100% busy, correct it */
+ device_busy = busy_seconds;
+
+ thisbusy = (firsttime) ? 0 : device_busy * 100 / busy_seconds;
+ busydata[dn] += thisbusy;
+
+
+ if (perf_select != 0) {
+ dev_select[dn].bytes = total_bytes;
+ if ((dev_select[dn].selected == 0)
+ || (dev_select[dn].selected > maxshowdevs))
+ continue;
+ }
+
+ if (Kflag) {
+ int block_size = cur.dinfo->devices[di].block_size;
+ total_blocks = total_blocks * (block_size ?
+ block_size : 512) / 1024;
+ }
+
+ if (oflag > 0) {
+ int msdig = (ms_per_transaction < 100.0) ? 1 : 0;
+
+ if (Iflag == 0)
+ printf("%4.0Lf%4.0Lf%5.*Lf ",
+ blocks_per_second,
+ transfers_per_second,
+ msdig,
+ ms_per_transaction);
+ else
+ printf("%4.1qu%4.1qu%5.*Lf ",
+ total_blocks,
+ total_transfers,
+ msdig,
+ ms_per_transaction);
+ } else if( !qflag ) {
+
+ if (Bflag)
+ printf(" %5.1f%%", thisbusy);
+ else if (Iflag == 0){
+ printf(" %5.2Lf %3.0Lf %5.2Lf ",
+ kb_per_transfer,
+ transfers_per_second,
+ mb_per_second);
+
+ }
+ else {
+ total_mb = total_bytes;
+ total_mb /= 1024 * 1024;
+
+ printf(" %5.2Lf %3.1qu %5.2Lf ",
+ kb_per_transfer,
+ total_transfers,
+ total_mb);
+ }
+ }
+ }
+ ++counttimes;
+}
+
+static void
+cpustats(void)
+{
+ register int state;
+ double time;
+
+ time = 0.0;
+
+ for (state = 0; state < CPUSTATES; ++state)
+ time += cur.cp_time[state];
+ for (state = 0; state < CPUSTATES; ++state)
+ printf("%3.0f",
+ 100. * cur.cp_time[state] / (time ? time : 1));
+}
+#endif // __FreeBSD__
diff --git a/pttbbs/util/expire.c b/pttbbs/util/expire.c
new file mode 100644
index 00000000..d24c29d6
--- /dev/null
+++ b/pttbbs/util/expire.c
@@ -0,0 +1,320 @@
+/* $Id$ */
+/* 自動砍信工具程式 */
+
+#include "bbs.h"
+
+#define QCAST int (*)(const void *, const void *)
+
+#define DEF_DAYS 60
+#define DEF_MAXP 10000
+#define DEF_MINP 9000
+
+#define EXPIRE_CONF BBSHOME "/etc/expire.conf"
+#ifdef SAFE_ARTICLE_DELETE
+char safe_delete_only = 0;
+#endif
+extern boardheader_t *bcache;
+int checkmode = 0;
+
+typedef struct {
+ char bname[IDLEN + 1]; /* board ID */
+ int days; /* expired days */
+ int maxp; /* max post */
+ int minp; /* min post */
+} life_t;
+
+void callsystem(char *s)
+{
+ if( checkmode )
+ printf("in checkmode, skip `%s`\n", s);
+ else
+ system(s);
+}
+
+void callrm(char *s)
+{
+ if( checkmode )
+ printf("\tin checkmode, skip rm %s\n", s);
+ else
+ unlink(s);
+}
+
+void cleanSR(char *brdname)
+{
+ DIR *dirp;
+ char dirf[PATHLEN], fpath[PATHLEN];
+ struct dirent *ent;
+ int nDelete = 0;
+ setbpath(dirf, brdname);
+ if( (dirp = opendir(dirf)) == NULL )
+ return;
+
+ while( (ent = readdir(dirp)) != NULL )
+ if( strncmp(ent->d_name, "SR.", 3) == 0 ){
+ setbfile(fpath, brdname, ent->d_name);
+ callrm(fpath);
+ ++nDelete;
+ }
+
+ closedir(dirp);
+ printf("board %s: %d SRs are deleted.\n", brdname, nDelete);
+}
+
+void expire(life_t *brd)
+{
+ fileheader_t head;
+ struct stat state;
+ char lockfile[128], tmpfile[128], bakfile[128], cmd[256];
+ char fpath[128], index[128], *fname;
+ int total, bid;
+ int fdlock, fdr, fdw = 0, done, keep;
+ int duetime, ftime, nKeep = 0, nDelete = 0;
+
+ printf("%s\n", brd->bname);
+ /* XXX: bid of cache.c's getbnum starts from 1 */
+ if( (bid = getbnum(brd->bname)) == 0 ||
+ strcmp(brd->bname, bcache[bid - 1].brdname) ){
+ printf("no such board?: %s\n", brd->bname);
+ sprintf(cmd, "mv "BBSHOME"/boards/%c/%s "BBSHOME"/boards.error/%s",
+ brd->bname[0], brd->bname, brd->bname);
+ callsystem(cmd);
+ return;
+ }
+#ifdef VERBOSE
+ if( brd->days < 1 ){
+ printf(":Err: expire time must more than 1 day.\n");
+ return;
+ }
+ else if( brd->maxp < 100 ){
+ printf(":Err: maxmum posts number must more than 100.\n");
+ return;
+ }
+#endif
+ cleanSR(brd->bname);
+
+ setbfile(index, brd->bname, ".DIR");
+ sprintf(lockfile, "%s.lock", index);
+ if ((fdlock = open(lockfile, O_RDWR | O_CREAT | O_APPEND, 0644)) == -1){
+ perror("open lock file error");
+ return;
+ }
+ flock(fdlock, LOCK_EX);
+
+ strcpy(fpath, index);
+ fname = (char *) strrchr(fpath, '.');
+
+ duetime = (int)time(NULL) - brd->days * 24 * 60 * 60;
+ done = 0;
+ if( (fdr = open(index, O_RDONLY, 0)) > 0 ){
+ fstat(fdr, &state);
+ total = state.st_size / sizeof(head);
+ if( !checkmode ){
+ sprintf(tmpfile, "%s.new", index);
+ unlink(tmpfile);
+ }
+ // TODO use fread/fwrite to reduce system calls
+ if( checkmode ||
+ (fdw = open(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0644)) > 0 ){
+ while( read(fdr, &head, sizeof(head)) == sizeof(head) ){
+ done = 1;
+ ftime = atoi(head.filename + 2);
+ if (head.owner[0] == '-'
+#ifdef SAFE_ARTICLE_DELETE
+ || strncmp(head.filename, ".delete", 7) == 0
+#endif
+ )
+ keep = 0;
+#ifdef SAFE_ARTICLE_DELETE
+ else if( safe_delete_only )
+ keep = 1;
+#endif
+ else if( head.filemode & FILE_MARKED || total <= brd->minp )
+ keep = 1;
+ else if( ftime < duetime || total > brd->maxp )
+ keep = 0;
+ else
+ keep = 1;
+
+ if( keep ){
+ ++nKeep;
+ if( !checkmode &&
+ write(fdw, (char *)&head, sizeof(head)) == -1 ){
+ done = 0;
+ break;
+ }
+ }
+ else {
+ ++nDelete;
+ setbfile(fname, brd->bname, head.filename);
+ callrm(fname);
+ total--;
+ }
+ }
+ if( !checkmode )
+ close(fdw);
+ }
+ close(fdr);
+ }
+
+ if( !checkmode && done ){
+ sprintf(bakfile, "%s.old", index);
+ if( rename(index, bakfile) != -1 ){
+ rename(tmpfile, index);
+ touchbtotal(bid);
+ }
+ }
+
+ printf("board %s: %d articles are kept, %d articles are deleted.\n",
+ brd->bname, nKeep, nDelete);
+ flock(fdlock, LOCK_UN);
+ close(fdlock);
+}
+
+int count;
+life_t db, table[MAX_BOARD], *key;
+
+void toexpire(char *brdname)
+{
+ if( brdname[0] > ' ' && brdname[0] != '.' ){
+ key = NULL;
+
+ if( count )
+ key = (life_t *)bsearch(brdname, table, count,
+ sizeof(life_t), (QCAST)strcasecmp);
+ if( key == NULL )
+ key = &db;
+
+ strcpy(key->bname, brdname);
+ expire(key);
+ }
+}
+
+void visitdir(char c)
+{
+ DIR *dirp;
+ struct dirent *de;
+ char bpath[PATHLEN];
+
+ sprintf(bpath, BBSHOME "/boards/%c", c);
+ if (!(dirp = opendir(bpath))){
+ printf(":Err: unable to open %s\n", bpath);
+ return;
+ }
+
+ while( (de = readdir(dirp)) != NULL )
+ if( de->d_name[0] != '.' )
+ toexpire(de->d_name);
+
+ closedir(dirp);
+}
+
+int main(int argc, char **argv)
+{
+ FILE *fin;
+ int number, i, ch;
+ char *ptr, *bname, buf[256];
+ char dirs[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',
+ 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',
+ 'z', 'x', 'c', 'v', 'b', 'n', 'm',
+ 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P',
+ 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L',
+ 'Z', 'X', 'C', 'V', 'B', 'N', 'M', 0};
+
+ chdir(BBSHOME);
+ /* default value */
+ db.days = DEF_DAYS;
+ db.maxp = DEF_MAXP;
+ db.minp = DEF_MINP;
+
+ while( (ch = getopt(argc, argv, "d:M:m:hn"
+#ifdef SAFE_ARTICLE_DELETE
+"D"
+#endif
+ )) != -1 )
+ switch( ch ){
+#ifdef SAFE_ARTICLE_DELETE
+ case 'D':
+ safe_delete_only = 1;
+ break;
+#endif
+ case 'd':
+ db.days = atoi(optarg);
+ break;
+ case 'M':
+ db.maxp = atoi(optarg);
+ break;
+ case 'm':
+ db.minp = atoi(optarg);
+ break;
+ case 'n':
+ checkmode = 1;
+ break;
+ case 'h':
+ default:
+ fprintf(stderr,
+ "usage: expire [-m minp] [-M MAXP] [-d days] [board name...] [-n]\n"
+ "deletion policy:\n"
+ " do nothing if #articles < minp (default:%d)\n"
+ " delete NOT MARKED articles which were post before days \n"
+ " (default:%d) or #articles > MAXP (default:%d)\n",
+ DEF_MINP, DEF_DAYS, DEF_MAXP);
+ return 0;
+ }
+ argc -= optind;
+ argv += optind;
+
+/* --------------- */
+/* load expire.ctl */
+/* --------------- */
+
+ count = 0;
+ if( (fin = fopen(EXPIRE_CONF, "r")) ){
+ while( fgets(buf, 256, fin) != NULL ){
+ if (buf[0] == '#')
+ continue;
+
+ bname = (char *) strtok(buf, " \t\r\n");
+ if( bname && *bname ){
+ ptr = (char *) strtok(NULL, " \t\r\n");
+ if( ptr && (number = atoi(ptr)) > 0 ){
+ key = &(table[count++]);
+ strcpy(key->bname, bname);
+ key->days = number;
+ key->maxp = db.maxp;
+ key->minp = db.minp;
+
+ ptr = (char *) strtok(NULL, " \t\r\n");
+ if( ptr && (number = atoi(ptr)) > 0 ){
+ key->maxp = number;
+
+ ptr = (char *) strtok(NULL, " \t\r\n");
+ if( ptr && (number = atoi(ptr)) > 0 ){
+ key->minp = number;
+ }
+ }
+ }
+ }
+ }
+ fclose(fin);
+ }
+
+ if( count > 1)
+ qsort(table, count, sizeof(life_t), (QCAST)strcasecmp);
+
+ attach_SHM();
+ if( argc > 0 ){
+ for( i = 0 ; i < argc ; ++i )
+ if( argv[i][1] == '*' )
+ visitdir(argv[i][0]);
+ else{
+ toexpire(argv[i]);
+ }
+ }
+ else{ // visit all boards
+ for( i = 0 ; dirs[i] != 0 ; ++i )
+ visitdir(dirs[i]);
+ }
+
+ return 0;
+}
diff --git a/pttbbs/util/filtermail.pl b/pttbbs/util/filtermail.pl
new file mode 100644
index 00000000..08d94309
--- /dev/null
+++ b/pttbbs/util/filtermail.pl
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+# $Id$
+use lib qw(/home/bbs/bin/);
+use FILTERMAIL;
+$bbsuid = $ARGV[0];
+
+undef @ARGV;
+undef $header;
+undef $body;
+
+while( <> ){
+ $header .= $_;
+ last if( $_ =~ /^\n/ );
+}
+while( <> ){
+ $body .= $_;
+}
+
+if( FILTERMAIL::checkheader($header) && FILTERMAIL::checkbody($body) ){
+ open FH, "|/home/bbs/bin/realbbsmail $bbsuid";
+ print FH $header;
+ print FH $body;
+ close FH;
+}
+=xxx
+else {
+ $fn = `/usr/bin/mktemp -q /tmp/norelay.XXXXXXXX`;
+ open FH, ">$fn";
+ print FH $msg;
+ close FH;
+}
+=cut
diff --git a/pttbbs/util/gamblegive.c b/pttbbs/util/gamblegive.c
new file mode 100644
index 00000000..9ce66e5c
--- /dev/null
+++ b/pttbbs/util/gamblegive.c
@@ -0,0 +1,29 @@
+/* $Id$ */
+/* 爭議賭盤 產生紅包機格式 */
+#define _UTIL_C_
+#include "bbs.h"
+
+int main(int argc, char **argv)
+{
+ int money = 12, num;
+ char buf[512], *userid,*p;
+ FILE *fp;
+
+ if(argc<3 ||
+ !(num=atoi(argv[1])) ||
+ !(fp = fopen (argv[2], "r")) )
+ {
+ printf("%s <tickey-price> <result-file>", argv[0]);
+ return 0;
+ }
+
+ while (fgets(buf,512,fp) )
+ {
+ if(strncmp(buf, "恭喜", 4)) continue;
+ userid = strtok(buf+5," ");
+ p = strtok(NULL, " ");
+ num = atoi(p+4);
+ printf("%s:%d\n", userid, num*money);
+ }
+ return 0;
+}
diff --git a/pttbbs/util/getbackup.pl b/pttbbs/util/getbackup.pl
new file mode 100644
index 00000000..5714c87f
--- /dev/null
+++ b/pttbbs/util/getbackup.pl
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+if( !@ARGV ){
+ print "usage:\tgetbackup.pl\tbrd BRDNAME\n";
+ print "\t\t\tman BRDNAME\n";
+ print "\t\t\tusr USERID [all|fav|frlist|bbsrc]\n";
+}
+
+if( !-e '/home/bbs/backup/home.a.tgz' ){
+ print "there's no /home/bbs/backup/home.a.tgz?\n";
+ print "or no mount on /home/bbs/backup?\n";
+ exit;
+}
+
+chdir "/home/bbs/backup/";
+
+$prefix = substr($ARGV[1], 0, 1);
+if( $ARGV[0] eq 'usr' ){
+ $userid = $ARGV[1];
+ `rm -Rf home`;
+ `tar zxvf home.$prefix.tgz home/$prefix/$userid`;
+ shift @ARGV; shift @ARGV;
+ foreach( @ARGV ){
+ if( $_ ne 'all' && $_ ne 'fav' && $_ ne 'frlist' &&
+ $_ ne 'bbsrc' && $_ ){
+ print "usr command '$_' unknown\n";
+ exit;
+ }
+ if( $_ eq 'all' ){
+ `rm -Rf /home/bbs/home/$prefix/$userid`;
+ `mv home/$prefix/$userid /home/bbs/home/$prefix/$userid`;
+ }
+ elsif( $_ eq 'fav' ){
+ `mv home/$prefix/$userid/.fav /home/bbs/home/$prefix/$userid/.fav`;
+ }
+ elsif( $_ eq 'frlist' ){
+ `mv home/$prefix/$userid/overrides /home/bbs/home/$prefix/$userid/overrides`;
+ }
+ elsif( $_ eq 'bbsrc' ){
+ `mv home/$prefix/$userid/.bbsrc /home/bbs/home/$prefix/$userid/.bbsrc`;
+ }
+ }
+}
+elsif( $ARGV[0] eq 'brd' ){
+ chdir '/home/bbs';
+ `mv boards/$prefix/$ARGV[1] boards.error/$ARGV[1]`;
+ `tar zxvf backup/board.$prefix.tgz boards/$prefix/$ARGV[1]`;
+}
+elsif( $ARGV[0] eq 'man' ){
+ chdir '/home/bbs';
+ `mv man/boards/$prefix/$ARGV[1] boards.error/man.$ARGV[1]`;
+ `tar zxvf backup/man.$prefix.tgz man/boards/$prefix/$ARGV[1]`;
+}
diff --git a/pttbbs/util/horoscope.c b/pttbbs/util/horoscope.c
new file mode 100644
index 00000000..936c2727
--- /dev/null
+++ b/pttbbs/util/horoscope.c
@@ -0,0 +1,154 @@
+/* $Id$ */
+#define _UTIL_C_
+#include "bbs.h"
+
+struct userec_t user;
+
+int main() {
+ int i, j, k;
+ FILE *fp;
+ int max, item, maxhoroscope;
+
+ int act[12];
+
+ char *name[13] =
+ {"牡羊",
+ "金牛",
+ "雙子",
+ "巨蟹",
+ "獅子",
+ "處女",
+ "天秤",
+ "天蠍",
+ "射手",
+ "摩羯",
+ "水瓶",
+ "雙魚",
+ ""
+ };
+ char *blk[10] =
+ {
+ " ", "▏", "▎", "▍", "▌",
+ "▋", "▊", "▉", "█", "█",
+ };
+
+ attach_SHM();
+ memset(act, 0, sizeof(act));
+ if(passwd_init())
+ exit(1);
+ for(k = 1; k <= MAX_USERS; k++) {
+ passwd_query(k, &user);
+ if(!user.userid[0])
+ continue;
+ switch (user.month)
+ {
+ case 1:
+ if (user.day <= 19)
+ act[9]++;
+ else
+ act[10]++;
+ break;
+ case 2:
+ if (user.day <= 18)
+ act[10]++;
+ else
+ act[11]++;
+ break;
+ case 3:
+ if (user.day <= 20)
+ act[11]++;
+ else
+ act[0]++;
+ break;
+ case 4:
+ if (user.day <= 19)
+ act[0]++;
+ else
+ act[1]++;
+ break;
+ case 5:
+ if (user.day <= 20)
+ act[1]++;
+ else
+ act[2]++;
+ break;
+ case 6:
+ if (user.day <= 21)
+ act[2]++;
+ else
+ act[3]++;
+ break;
+ case 7:
+ if (user.day <= 22)
+ act[3]++;
+ else
+ act[4]++;
+ break;
+ case 8:
+ if (user.day <= 22)
+ act[4]++;
+ else
+ act[5]++;
+ break;
+ case 9:
+ if (user.day <= 22)
+ act[5]++;
+ else
+ act[6]++;
+ break;
+ case 10:
+ if (user.day <= 23)
+ act[6]++;
+ else
+ act[7]++;
+ break;
+ case 11:
+ if (user.day <= 22)
+ act[7]++;
+ else
+ act[8]++;
+ break;
+ case 12:
+ if (user.day <= 21)
+ act[8]++;
+ else
+ act[9]++;
+ break;
+ }
+ }
+
+ for (i = max = maxhoroscope = 0; i < 12; i++)
+ {
+ if (act[i] > max)
+ {
+ max = act[i];
+ maxhoroscope = i;
+ }
+ }
+
+ item = max / 30 + 1;
+
+ if ((fp = fopen(BBSHOME"/etc/horoscope", "w")) == NULL)
+ {
+ printf("cann't open etc/horoscope\n");
+ return 1;
+ }
+
+ for (i = 0; i < 12; i++)
+ {
+ fprintf(fp, " %s座 ", name[i]);
+ for (j = 0; j < act[i] / item; j++)
+ {
+ fprintf(fp, "%2s", blk[9]);
+ }
+ /* 為了剛好一頁 */
+ if (i != 11)
+ fprintf(fp, "%2s %d\n\n", blk[(act[i] % item) * 10 / item],
+ act[i]);
+ else
+ fprintf(fp, "%2s %d\n", blk[(act[i] % item) * 10 / item],
+ act[i]);
+ }
+ fclose(fp);
+ return 0;
+}
diff --git a/pttbbs/util/initbbs.c b/pttbbs/util/initbbs.c
new file mode 100644
index 00000000..9073d247
--- /dev/null
+++ b/pttbbs/util/initbbs.c
@@ -0,0 +1,300 @@
+/* $Id$ */
+#include "bbs.h"
+
+static void initDir() {
+ mkdir("adm", 0755);
+ mkdir("boards", 0755);
+ mkdir("etc", 0755);
+ mkdir("man", 0755);
+ mkdir("man/boards", 0755);
+ mkdir("out", 0755);
+ mkdir("tmp", 0755);
+ mkdir("run", 0755);
+ mkdir("jobspool", 0755);
+}
+
+static void initHome() {
+ int i;
+ char buf[256];
+
+ mkdir("home", 0755);
+ strcpy(buf, "home/?");
+ for(i = 0; i < 26; i++) {
+ buf[5] = 'A' + i;
+ mkdir(buf, 0755);
+ buf[5] = 'a' + i;
+ mkdir(buf, 0755);
+#if 0
+ /* in current implementation we don't allow
+ * id as digits so we don't create now. */
+ if(i >= 10)
+ continue;
+ /* 0~9 */
+ buf[5] = '0' + i;
+ mkdir(buf, 0755);
+#endif
+ }
+}
+
+static void initBoardsDIR() {
+ int i;
+ char buf[256];
+
+ mkdir("boards", 0755);
+ strcpy(buf, "boards/?");
+ for(i = 0; i < 26; i++) {
+ buf[7] = 'A' + i;
+ mkdir(buf, 0755);
+ buf[7] = 'a' + i;
+ mkdir(buf, 0755);
+ if(i >= 10)
+ continue;
+ /* 0~9 */
+ buf[7] = '0' + i;
+ mkdir(buf, 0755);
+ }
+}
+
+static void initManDIR() {
+ int i;
+ char buf[256];
+
+ mkdir("man", 0755);
+ mkdir("man/boards", 0755);
+ strcpy(buf, "man/boards/?");
+ for(i = 0; i < 26; i++) {
+ buf[11] = 'A' + i;
+ mkdir(buf, 0755);
+ buf[11] = 'a' + i;
+ mkdir(buf, 0755);
+ if(i >= 10)
+ continue;
+ /* 0~9 */
+ buf[11] = '0' + i;
+ mkdir(buf, 0755);
+ }
+}
+
+static void initPasswds() {
+ int i;
+ userec_t u;
+ FILE *fp = fopen(".PASSWDS", "w");
+
+ memset(&u, 0, sizeof(u));
+ if(fp) {
+ for(i = 0; i < MAX_USERS; i++)
+ fwrite(&u, sizeof(u), 1, fp);
+ fclose(fp);
+ }
+}
+
+static void newboard(FILE *fp, boardheader_t *b) {
+ char buf[256];
+
+ fwrite(b, sizeof(boardheader_t), 1, fp);
+ sprintf(buf, "boards/%c/%s", b->brdname[0], b->brdname);
+ mkdir(buf, 0755);
+ sprintf(buf, "man/boards/%c/%s", b->brdname[0], b->brdname);
+ mkdir(buf, 0755);
+}
+
+static void initBoards() {
+ FILE *fp = fopen(".BRD", "w");
+ boardheader_t b;
+
+ if(fp) {
+ memset(&b, 0, sizeof(b));
+
+ strcpy(b.brdname, "SYSOP");
+ strcpy(b.title, "嘰哩 ◎站長好!");
+ b.brdattr = BRD_POSTMASK | BRD_NOTRAN | BRD_NOZAP;
+ b.level = 0;
+ b.gid = 2;
+ newboard(fp, &b);
+
+ strcpy(b.brdname, "1...........");
+ strcpy(b.title, ".... Σ中央政府 《高壓危險,非人可敵》");
+ b.brdattr = BRD_GROUPBOARD;
+ b.level = PERM_SYSOP;
+ b.gid = 1;
+ newboard(fp, &b);
+
+ strcpy(b.brdname, "junk");
+ strcpy(b.title, "發電 ◎雜七雜八的垃圾");
+ b.brdattr = BRD_NOTRAN;
+ b.level = PERM_SYSOP;
+ b.gid = 2;
+ newboard(fp, &b);
+
+ strcpy(b.brdname, "Security");
+ strcpy(b.title, "發電 ◎站內系統安全");
+ b.brdattr = BRD_NOTRAN;
+ b.level = PERM_SYSOP;
+ b.gid = 2;
+ newboard(fp, &b);
+
+ strcpy(b.brdname, "2...........");
+ strcpy(b.title, ".... Σ市民廣場 報告 站長 ㄜ!");
+ b.brdattr = BRD_GROUPBOARD;
+ b.level = 0;
+ b.gid = 1;
+ newboard(fp, &b);
+
+ strcpy(b.brdname, "ALLPOST");
+ strcpy(b.title, "嘰哩 ◎跨板式LOCAL新文章");
+ b.brdattr = BRD_POSTMASK | BRD_NOTRAN;
+ b.level = PERM_SYSOP;
+ b.gid = 5;
+ newboard(fp, &b);
+
+ strcpy(b.brdname, "deleted");
+ strcpy(b.title, "嘰哩 ◎資源回收筒");
+ b.brdattr = BRD_NOTRAN;
+ b.level = PERM_BM;
+ b.gid = 5;
+ newboard(fp, &b);
+
+ strcpy(b.brdname, "Note");
+ strcpy(b.title, "嘰哩 ◎動態看板及歌曲投稿");
+ b.brdattr = BRD_NOTRAN;
+ b.level = 0;
+ b.gid = 5;
+ newboard(fp, &b);
+
+ strcpy(b.brdname, "Record");
+ strcpy(b.title, "嘰哩 ◎我們的成果");
+ b.brdattr = BRD_NOTRAN | BRD_POSTMASK;
+ b.level = 0;
+ b.gid = 5;
+ newboard(fp, &b);
+
+
+ strcpy(b.brdname, "WhoAmI");
+ strcpy(b.title, "嘰哩 ◎呵呵,猜猜我是誰!");
+ b.brdattr = BRD_NOTRAN;
+ b.level = 0;
+ b.gid = 5;
+ newboard(fp, &b);
+
+ strcpy(b.brdname, "EditExp");
+ strcpy(b.title, "嘰哩 ◎範本精靈投稿區");
+ b.brdattr = BRD_NOTRAN;
+ b.level = 0;
+ b.gid = 5;
+ newboard(fp, &b);
+
+ strcpy(b.brdname, "ALLHIDPOST");
+ strcpy(b.title, "嘰哩 ◎跨板式LOCAL新文章(隱板)");
+ b.brdattr = BRD_POSTMASK | BRD_HIDE;
+ b.level = PERM_SYSOP;
+ b.gid = 5;
+ newboard(fp, &b);
+
+#ifdef GLOBAL_DIGEST
+ strcpy(b.brdname, GLOBAL_DIGEST);
+ strcpy(b.title, "文摘 ◎" BBSNAME "文摘 好文的收集地");
+ b.brdattr = BRD_NOTRAN | BRD_POSTMASK;
+ b.level = PERM_SYSOP;
+ b.gid = 5;
+ newboard(fp, &b);
+#endif
+
+#ifdef GLOBAL_FIVECHESS_LOG
+ strcpy(b.brdname, GLOBAL_FIVECHESS_LOG);
+ strcpy(b.title, "棋藝 ◎" BBSNAME "五子棋譜 站上對局全紀錄");
+ b.brdattr = BRD_NOTRAN | BRD_POSTMASK;
+ b.level = PERM_SYSOP;
+ b.gid = 5;
+ newboard(fp, &b);
+#endif
+
+ fclose(fp);
+ }
+}
+
+static void initMan() {
+ FILE *fp;
+ fileheader_t f;
+ time_t t = time(NULL);
+ struct tm *tm = localtime(&t);
+
+ memset(&f, 0, sizeof(f));
+ strcpy(f.owner, "SYSOP");
+ sprintf(f.date, "%2d/%02d", tm->tm_mon + 1, tm->tm_mday);
+ f.multi.money = 0;
+ f.filemode = 0;
+
+ if((fp = fopen("man/boards/N/Note/.DIR", "w"))) {
+ strcpy(f.filename, "SONGBOOK");
+ strcpy(f.title, "◆ 【點 歌 歌 本】");
+ fwrite(&f, sizeof(f), 1, fp);
+ mkdir("man/boards/N/Note/SONGBOOK", 0755);
+
+ strcpy(f.filename, "SONGO");
+ strcpy(f.title, "◆ <點歌> 動態看板");
+ fwrite(&f, sizeof(f), 1, fp);
+ mkdir("man/boards/N/Note/SONGO", 0755);
+
+ strcpy(f.filename, "SYS");
+ strcpy(f.title, "◆ <系統> 動態看板");
+ fwrite(&f, sizeof(f), 1, fp);
+ mkdir("man/boards/N/Note/SYS", 0755);
+
+ strcpy(f.filename, "AD");
+ strcpy(f.title, "◆ <廣告> 動態看板");
+ fwrite(&f, sizeof(f), 1, fp);
+ mkdir("man/boards/N/Note/AD", 0755);
+
+ strcpy(f.filename, "NEWS");
+ strcpy(f.title, "◆ <新聞> 動態看板");
+ fwrite(&f, sizeof(f), 1, fp);
+ mkdir("man/boards/N/Note/NEWS", 0755);
+
+ fclose(fp);
+ }
+
+}
+
+static void initSymLink() {
+ symlink(BBSHOME "/man/boards/N/Note/SONGBOOK", BBSHOME "/etc/SONGBOOK");
+ symlink(BBSHOME "/man/boards/N/Note/SONGO", BBSHOME "/etc/SONGO");
+ symlink(BBSHOME "/man/boards/E/EditExp", BBSHOME "/etc/editexp");
+}
+
+static void initHistory() {
+ FILE *fp = fopen("etc/history.data", "w");
+
+ if(fp) {
+ fprintf(fp, "0 0 0 0");
+ fclose(fp);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ if( argc != 2 || strcmp(argv[1], "-DoIt") != 0 ){
+ fprintf(stderr,
+ "警告! initbbs只用在「第一次安裝」的時候.\n"
+ "若您的站台已經上線, initbbs將會破壞掉原有資料!\n\n"
+ "將把 BBS 安裝在 " BBSHOME "\n\n"
+ "確定要執行, 請使用 initbbs -DoIt\n");
+ return 1;
+ }
+
+ if(chdir(BBSHOME)) {
+ perror(BBSHOME);
+ exit(1);
+ }
+
+ initDir();
+ initHome();
+ initBoardsDIR();
+ initManDIR();
+ initPasswds();
+ initBoards();
+ initMan();
+ initSymLink();
+ initHistory();
+
+ return 0;
+}
diff --git a/pttbbs/util/inndBM.c b/pttbbs/util/inndBM.c
new file mode 100644
index 00000000..363220f5
--- /dev/null
+++ b/pttbbs/util/inndBM.c
@@ -0,0 +1,200 @@
+/* 依據 .BOARD檔 & newsfeeds.bbs 列出參與轉信的所有板資料 */
+
+#include "bbs.h"
+
+#define INNDHOME BBSHOME"/innd"
+
+#define INND_NEWSFEED INNDHOME "/newsfeeds.bbs"
+#define INND_NODELIST INNDHOME "/nodelist.bbs"
+#define INND_BADFEED INNDHOME "/badfeeds.bbs"
+#define INND_SCRIPT INNDHOME "/bbsnnrpall.auto.sh"
+
+int istran[MAX_BOARD];
+
+typedef
+struct newssvr_t
+{
+ char name[30];
+ char address[256];
+ char type[10];
+}newssvr_t;
+
+typedef
+struct newsfeed_t
+{
+ char group[128];
+ char board[15];
+ char server[30];
+}newsfeed_t;
+
+newssvr_t server[128];
+newsfeed_t feedline[MAX_BOARD];
+int servercount;
+int feedcount;
+
+int newsfeed_cmp(const void *a, const void *b)
+{
+ int i;
+ i=strcasecmp(((newsfeed_t*)a)->server,((newsfeed_t*)b)->server);
+ if(i) return i;
+ return strcasecmp(((newsfeed_t*)a)->board,((newsfeed_t*)b)->board);
+}
+
+int get_server(char *name)
+{
+ int i;
+ for(i=0;i<servercount;i++)
+ if(!strcasecmp(server[i].name,name))
+ {return i;}
+ return -1;
+}
+
+int load_server()
+{
+ FILE *fp;
+ char str[128];
+
+ if (!(fp = fopen(INND_NODELIST, "r")))
+ {
+ return 0;
+ }
+
+ for(servercount=0; fgets(str, 128, fp); servercount++)
+ {
+ if(str[0]=='#') continue;
+ sscanf(str,"%s %s %s",server[servercount].name,
+ server[servercount].address,
+ server[servercount].type);
+ }
+ fclose(fp);
+ return servercount;
+}
+
+int load_newsfeeds()
+{
+ int bid;
+ FILE *fp, *fo;
+ char str[128];
+ if (!(fp = fopen(INND_NEWSFEED, "r")))
+ {
+ return 0;
+ }
+ if (!(fo = fopen(INND_BADFEED, "w")))
+ {
+ return 0;
+ }
+
+ for(feedcount=0; fgets(str, 128, fp); feedcount++)
+ {
+ if(str[0]=='#') continue;
+ sscanf(str,"%s %s %s",
+ feedline[feedcount].group,feedline[feedcount].board,
+ feedline[feedcount].server);
+ /* XXX: bid of cache.c's getbnum starts from 1 */
+ bid=getbnum(feedline[feedcount].board);
+ if(!bid) {
+ fprintf(fo,"%s %s\n", feedline[feedcount].board, feedline[feedcount].group );
+ feedcount--;
+ continue; /*移除沒有的看板i*/}
+
+ strcpy(feedline[feedcount].board,bcache[bid-1].brdname);
+ /*校正大小寫 */
+
+ istran[bid-1]=1;
+
+ }
+ fclose(fp);
+ fclose(fo);
+ qsort(feedline, feedcount, sizeof(newsfeed_t), newsfeed_cmp);
+ return feedcount;
+}
+
+int dobbsnnrp(char *serverstr, int serverid,FILE *fpscript)
+{
+ char buf[256];
+ printf("set %s\r\n",serverstr);
+ strtok(serverstr,";\r\n");
+ strtok(server[serverid].address,";\r\n"); //防hack
+ sprintf(buf, INNDHOME"/bbsnnrp -c %s "
+ INNDHOME "/active/%s.auto.active "
+ " >> " INNDHOME"/log/inndBM.log &\r\n",
+ server[serverid].address,
+ serverstr);
+ system(buf);
+ sprintf(buf, INNDHOME"/bbsnnrp %s "
+ INNDHOME "/active/%s.auto.active "
+ " >> " INNDHOME"/log/inndBM.log &\r\n",
+ server[serverid].address,
+ serverstr);
+ if(fpscript)
+ fprintf(fpscript, buf);
+ return 0;
+}
+int main(int argc, char **argv)
+{
+ int i,serverid=0;
+ FILE *fp=NULL,*fpscript=fopen(INND_SCRIPT,"w");
+ char buf[256],serverstr[30]="";
+ chdir(BBSHOME "/innd");
+ attach_SHM();
+ resolve_boards();
+ memset(istran,0,sizeof(int)*MAX_BOARD);
+ load_server();
+ load_newsfeeds();
+
+ for(i=0;i<feedcount;i++)
+ {
+ if(strcasecmp(serverstr,feedline[i].server))
+ {
+ if(get_server(feedline[i].server)==-1) continue;
+ if(fp) {
+ fclose(fp);
+ dobbsnnrp(serverstr,serverid,fpscript);
+ }
+ strcpy(serverstr,feedline[i].server);
+ serverid=get_server(feedline[i].server);
+ sprintf(buf,INNDHOME"/active/%s.auto.active",serverstr);
+ fp=fopen(buf,"w");
+ }
+ if(fp)
+ fprintf(fp,"%-35s 0000000000 0000000000 y\r\n",feedline[i].group);
+ }
+ if(fp)
+ {
+ dobbsnnrp(serverstr,serverid,fpscript);
+ fclose(fp);
+ }
+ if(fpscript)
+ {
+ fclose(fpscript);
+ chmod(INND_SCRIPT,0700);
+ }
+
+ // 重設轉信與不轉信板標記
+ for(i=0;i<numboards;i++)
+ {
+ if(bcache[i].brdname[0]=='\0' ||
+ (bcache[i].brdattr & BRD_GROUPBOARD) ) continue;
+ if((bcache[i].brdattr & BRD_NOTRAN )&& istran[i])
+ {
+ while(SHM->Bbusystate) {safe_sleep(1);}
+ SHM->Bbusystate = 1;
+ bcache[i].brdattr = bcache[i].brdattr & ~BRD_NOTRAN;
+ strncpy(bcache[i].title + 5, "●", 2);
+ SHM->Bbusystate = 0;
+
+ substitute_record(BBSHOME"/.BRD", &bcache[i],sizeof(boardheader_t),i+1);
+ }
+ else if(!(bcache[i].brdattr & BRD_NOTRAN) && !istran[i])
+ {
+ while(SHM->Bbusystate) {safe_sleep(1);}
+ SHM->Bbusystate = 1;
+ bcache[i].brdattr = bcache[i].brdattr | BRD_NOTRAN;
+ strncpy(bcache[i].title + 5, "◎", 2);
+ SHM->Bbusystate = 0;
+ substitute_record(BBSHOME"/.BRD", &bcache[i],sizeof(boardheader_t),i+1);
+ }
+
+ }
+ return 0;
+}
diff --git a/pttbbs/util/jungo.c b/pttbbs/util/jungo.c
new file mode 100644
index 00000000..fdecf252
--- /dev/null
+++ b/pttbbs/util/jungo.c
@@ -0,0 +1,194 @@
+/* $Id$ */
+#define _UTIL_C_
+#include "bbs.h"
+
+#define OUTFILE BBSHOME "/etc/toplazyBBM"
+#define FIREFILE BBSHOME "/etc/topfireBBM"
+
+extern boardheader_t *bcache;
+extern int numboards;
+
+boardheader_t allbrd[MAX_BOARD];
+typedef struct lostbm {
+ char *bmname;
+ char *title;
+ char *ctitle;
+ int lostdays;
+} lostbm;
+lostbm lostbms[MAX_BOARD];
+
+typedef struct BMarray{
+ char *bmname;
+ int flag;
+} BMArray;
+BMArray bms[3];
+
+int bmlostdays_cmp(const void *va, const void *vb)
+{
+ lostbm *a=(lostbm *)va, *b=(lostbm *)vb;
+ if (a->lostdays > b->lostdays) return -1;
+ else if (a->lostdays == b->lostdays) return 0;
+ else return 1;
+}
+
+int LINK(char* src, char* dst){
+ char cmd[200];
+ if(symlink(src,dst) == -1)
+ {
+ sprintf(cmd, "/bin/cp -R %s %s", src, dst);
+ return system(cmd);
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int bmid, i, j=0;
+ FILE *inf, *firef;
+
+ attach_SHM();
+ resolve_boards();
+
+ if(passwd_init())
+ exit(1);
+
+ memcpy(allbrd,bcache,numboards*sizeof(boardheader_t));
+
+ /* write out the target file */
+
+ inf = fopen(OUTFILE, "w+");
+ if(inf == NULL){
+ printf("open file error : %s\n", OUTFILE);
+ exit(1);
+ }
+ firef = fopen(FIREFILE, "w+");
+ if(firef == NULL){
+ printf("open file error : %s\n", FIREFILE);
+ fclose(inf);
+ exit(1);
+ }
+
+ fprintf(inf, "警告: 小組長若於一個月未上站,將予於免職\n");
+ fprintf(inf,
+ "看板名稱 "
+ " 小組長 幾天沒來啦\n"
+ "---------------------------------------------------"
+ "-------------------\n");
+
+ fprintf(firef, "免職小組長\n");
+ fprintf(firef,
+ "看板名稱 "
+ " 小組長 幾天沒來啦\n"
+ "---------------------------------------------------"
+ "-------------------\n");
+
+
+ j = 0 ;
+ for (i = 0; i < numboards; i++) {
+ char *p, bmbuf[IDLEN * 3 + 3];
+ int index = 0, flag = 0, k, n;
+ userec_t xuser;
+
+ p=strtok(allbrd[i].BM,"/ ");
+ if(p)
+ do
+ {
+ if(allbrd[i].brdname[0] == '\0' || (allbrd[i].brdattr & BRD_GROUPBOARD) ==0 ) continue;
+ if (*p == '[' ){p[strlen(p)-1]='\0'; p++;}
+ bmid=getuser(p, &xuser);
+ bms[index].bmname = p;
+ bms[index].flag = 0;
+ if (((((int)time(NULL)-(int)xuser.lastlogin)/(60*60*24))>=7)
+ //&& isalpha(allbrd[i].brdname[0])
+ && isalpha(allbrd[i].BM[0])
+ && !(xuser.userlevel & PERM_SYSOP))
+ {
+ lostbms[j].bmname = p;
+ lostbms[j].title = allbrd[i].brdname;
+ lostbms[j].ctitle = allbrd[i].title;
+ lostbms[j].lostdays =
+ ((int)time(NULL)-(int)xuser.lastlogin)/(60*60*24);
+
+ printf("%s\n", lostbms[j].title);
+ //超過六十天 免職
+ if(lostbms[j].lostdays > 30){
+ xuser.userlevel &= ~PERM_BM;
+ bms[index].flag = 1;
+ flag = 1;
+ }
+ j++;
+ }
+ index++;
+ } while((p=strtok(NULL,"/ "))!=NULL);
+
+ //從板主名單拿掉名字
+
+ if(flag == 1){
+ bmbuf[0] = '\0';
+ for(k = 0 , n = 0; k < index; k++){
+ if(!bms[k].flag){
+ if( n++ != 0) strcat(bmbuf, "/");
+ strcat(bmbuf, bms[k].bmname);
+ }
+ }
+ strcpy(bcache[i].BM, bmbuf);
+ }
+
+ }
+ qsort(lostbms, j, sizeof(lostbm), bmlostdays_cmp);
+
+ //write to the etc/toplazyBBM
+ for ( i=0; i<j; i++)
+ {
+ if( lostbms[i].lostdays > 7){
+ fprintf(firef, "%-*.*s%-*.*s%-*.*s%3d天沒上站\n", IDLEN, IDLEN, lostbms[i].title,
+ BTLEN-10, BTLEN-10, lostbms[i].ctitle, IDLEN,IDLEN,
+ lostbms[i].bmname,lostbms[i].lostdays);
+ }else{
+ fprintf(inf, "%-*.*s%-*.*s%-*.*s%3d天沒上站\n", IDLEN, IDLEN, lostbms[i].title,
+ BTLEN-10, BTLEN-10, lostbms[i].ctitle, IDLEN,IDLEN,
+ lostbms[i].bmname,lostbms[i].lostdays);
+ }
+ }
+ fclose(inf);
+ fclose(firef);
+
+ //printf("Total %d boards.\n", count);
+
+ //mail to the users
+ for( i=0; i<j; i++)
+ {
+ fileheader_t mymail;
+ char genbuf[200];
+ int lostdays;
+
+ lostdays = lostbms[i].lostdays;
+
+ if( (lostdays != 14) || (lostdays != 21) ) // 14 21 天不發信
+ continue;
+
+ sprintf(genbuf, BBSHOME "/home/%c/%s", lostbms[i].bmname[0], lostbms[i].bmname);
+ stampfile(genbuf, &mymail);
+
+ strcpy(mymail.owner, "[PTT警察局]");
+
+ if(lostdays <= 60){
+ sprintf(mymail.title,
+ "\033[32m [小組長免職警告通知] \033[m %s BM %s", lostbms[i].title, lostbms[i].bmname);
+ }else{
+ sprintf(mymail.title,
+ "\033[32m [小組長免職通知] \033[m %s BM %s", lostbms[i].title, lostbms[i].bmname);
+ }
+ unlink(genbuf);
+ if(lostdays <= 60){
+ LINK(OUTFILE, genbuf);
+ }else{
+ LINK(FIREFILE, genbuf);
+ }
+
+ sprintf(genbuf, BBSHOME "/home/%c/%s/.DIR", lostbms[i].bmname[0], lostbms[i].bmname);
+ append_record(genbuf, &mymail, sizeof(mymail));
+ }
+
+ return 0;
+}
diff --git a/pttbbs/util/mailangel.c b/pttbbs/util/mailangel.c
new file mode 100644
index 00000000..93d9524b
--- /dev/null
+++ b/pttbbs/util/mailangel.c
@@ -0,0 +1,143 @@
+/* $Id$ */
+#include "bbs.h"
+
+#ifndef PLAY_ANGEL
+int main(){ return 0; }
+#else
+
+int total[MAX_USERS + 1];
+int *list;
+int count;
+char *mailto = NULL;
+char *mailfile = NULL;
+
+int ListCmp(const void * a, const void * b){
+ return *(int*)b - *(int*)a;
+}
+
+int RejCmp(const void * a, const void * b){
+ return strcasecmp(SHM->userid[*(int*)a - 1], SHM->userid[*(int*)b - 1]);
+}
+
+void readData();
+void sendResult();
+void mailUser(char *userid);
+
+int main(int argc, char* argv[]){
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s file [userid]\n", argv[0]);
+ exit(1);
+ }
+ mailfile = argv[1];
+
+ if (argc > 2)
+ mailto = argv[2];
+
+ readData();
+ if (mailto)
+ mailUser(mailto);
+ else
+ sendResult();
+
+ return 0;
+}
+
+int mailalertuser(char* userid)
+{
+ userinfo_t *uentp=NULL;
+ if (userid[0] && (uentp = search_ulist_userid(userid)))
+ uentp->alerts|=ALERT_NEW_MAIL;
+ return 0;
+}
+
+void readData(){
+ int i, j;
+ userec_t user;
+ FILE* fp;
+
+ attach_SHM();
+
+ fp = fopen(BBSHOME "/.PASSWDS", "rb");
+ j = count = 0;
+ while (fread(&user, sizeof(userec_t), 1, fp) == 1) {
+ ++j; /* j == uid */
+ if (user.userlevel & PERM_ANGEL) {
+ ++count;
+ ++total[j]; /* make all angel have total > 0 */
+ } else { /* don't have PERM_ANGEL */
+ total[j] = INT_MIN;
+ }
+ }
+ fclose(fp);
+
+ list = (int *) malloc(count * sizeof(int));
+ j = 0;
+ for (i = 1; i <= MAX_USERS; ++i)
+ if (total[i] > 0) {
+ list[j] = i;
+ ++j;
+ }
+}
+
+void sendResult(){
+ int i;
+ for (i = 0; i < count; ++i) {
+ mailUser(SHM->userid[list[i] - 1]);
+// printf("%s\n", SHM->userid[list[i] - 1]);
+ }
+}
+
+void mailUser(char *userid)
+{
+ int count;
+ FILE *fp, *fp2;
+ time4_t t;
+ fileheader_t header;
+ struct stat st;
+ char filename[512];
+
+ fp2 = fopen(mailfile, "r");
+ if (fp2 == NULL) {
+ fprintf(stderr, "Cannot open file %s\n", mailfile);
+ return;
+ }
+
+ sprintf(filename, BBSHOME "/home/%c/%s", userid[0], userid);
+ if (stat(filename, &st) == -1) {
+ if (mkdir(filename, 0755) == -1) {
+ fprintf(stderr, "mail box create error %s \n", filename);
+ return;
+ }
+ }
+ else if (!(st.st_mode & S_IFDIR)) {
+ fprintf(stderr, "mail box error\n");
+ return;
+ }
+
+ stampfile(filename, &header);
+ fp = fopen(filename, "w");
+ if (fp == NULL) {
+ fprintf(stderr, "Cannot open file %s\n", filename);
+ return;
+ }
+
+ t = time(NULL);
+ fprintf(fp, "作者: 小天使系統\n"
+ "標題: 給小天使的一封信\n"
+ "時間: %s\n",
+ ctime4(&t));
+
+ while ((count = fread(filename, 1, sizeof(filename), fp2))) {
+ fwrite(filename, 1, count, fp);
+ }
+ fclose(fp);
+ fclose(fp2);
+
+ strcpy(header.title, "給小天使的一封信");
+ strcpy(header.owner, "小天使系統");
+ sprintf(filename, BBSHOME "/home/%c/%s/.DIR", userid[0], userid);
+ append_record(filename, &header, sizeof(header));
+ mailalertuser(userid);
+ printf("%s\n", userid);
+}
+#endif /* defined PLAY_ANGEL */
diff --git a/pttbbs/util/mailog.sh b/pttbbs/util/mailog.sh
new file mode 100644
index 00000000..da89ae3d
--- /dev/null
+++ b/pttbbs/util/mailog.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+# $Id: mailog.sh,v 1.1 2002/03/07 15:13:46 in2 Exp $
+#
+# 整理出廣告信名單
+
+bin/antispam
+bin/post Record 今日違法廣告信名單 [Ptt警察局] etc/spam
+bin/post Security 站外來信紀錄mailog [系統安全局] etc/mailog
+rm etc/mailog
diff --git a/pttbbs/util/mandex.c b/pttbbs/util/mandex.c
new file mode 100644
index 00000000..2ca7edee
--- /dev/null
+++ b/pttbbs/util/mandex.c
@@ -0,0 +1,295 @@
+/* $Id$ */
+
+/* 'mandex -h' to help */
+
+#include "bbs.h"
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 1024
+#endif
+
+typedef struct
+{
+ char bname[40];
+ int ndir, nfile, k;
+} boardinfo;
+
+extern int numboards;
+extern boardheader_t *bcache;
+boardinfo board[MAX_BOARD], *biptr;
+char color[4][10] = {"", "", "", ""};
+char fn_index[] = ".index";
+char fn_new[] = ".index.new";
+char index_title[] = "◎ 精華區目錄索引";
+char topdir[128], pgem[512], pndx[512];
+FILE *fndx;
+int ndir, nfile, index_pos, k;
+int nSorted, nb;
+
+int sortbyname(const void *a, const void *b)
+{
+ return strcmp(((boardinfo*)b)->bname, ((boardinfo*)a)->bname);
+}
+
+int k_cmp(b, a)
+ boardinfo *b, *a;
+{
+ return ((a->k / 100 + a->ndir + a->nfile) - (b->k / 100 + b->ndir + b->nfile));
+}
+
+/* visit the hierarchy recursively */
+
+void
+mandex(level, num_header, fpath)
+ int level;
+ char *fpath, *num_header;
+{
+ FILE *fgem;
+ char *fname, buf[256];
+ struct stat st;
+ int count;
+ fileheader_t fhdr;
+
+ fgem = fopen(fpath, "r+");
+ if (fgem == NULL)
+ return;
+
+ fname = strrchr(fpath, '.');
+ if (!level){
+ printf("%s\r\n",fpath);
+ strcpy(pgem, fpath);
+
+ strcpy(fname, fn_new);
+ fndx = fopen(fpath, "w");
+ if (fndx == NULL){
+ fclose(fgem);
+ return;
+ }
+ fprintf(fndx, "序號\t\t\t精華區主題\n"
+ "───────────────────"
+ "───────────────────\n");
+ strcpy(pndx, fpath);
+ ndir = nfile = 0;
+ index_pos = -1;
+ }
+
+ count = 0;
+ while (fread(&fhdr, sizeof(fhdr), 1, fgem) == 1){
+ strcpy(fname, fhdr.filename);
+ if (!fname[0]) continue;
+ if (!level && !strncmp(fhdr.title, index_title, strlen(index_title))
+ && index_pos < 0){
+ index_pos = count;
+ unlink(fpath);
+ }
+ st.st_size = 0;
+ stat(fpath, &st);
+
+ sprintf(buf, "%.*s%s%3d. %s \n",
+ 11 * level, num_header, color[level % 4], ++count, fhdr.title);
+ /* Ptt */
+ fputs(buf, fndx);
+ if (dashd(fpath)){
+ ++ndir;
+ /* I can't find the code to change title? */
+ if (*fhdr.title != '#' && level < 10 &&
+ (fhdr.filemode&(FILE_BM|FILE_HIDE))==0){
+ strcat(fpath, "/.DIR");
+ mandex(level + 1, buf, fpath);
+ }
+ }
+ else
+ ++nfile;
+ }
+
+ if (!level){
+ char lpath[MAXPATHLEN];
+
+ fclose(fndx);
+ strcpy(fname, fn_index);
+ rename(pndx, fpath);
+ strcpy(pndx, fpath);
+
+ sprintf(buf, "%s.new", pgem);
+ if (index_pos >= 0 || (fndx = fopen(buf, "w"))){
+ fname[-1] = 0;
+ stamplink(fpath, &fhdr);
+ unlink(fpath);
+ strcpy(fhdr.owner, "每天自動更新");
+ sprintf(lpath, "%s/%s", topdir, pndx);
+ st.st_size = 0;
+ stat(lpath, &st);
+ sprintf(fhdr.title, "%s (%.1fk)", index_title, st.st_size / 1024.);
+ k = st.st_size; /* Ptt */
+ printf("(%s)[%dK]", fpath, k);
+ symlink(lpath, fpath);
+ if (index_pos < 0){
+ fwrite(&fhdr, sizeof(fhdr), 1, fndx);
+ rewind(fgem);
+ while (fread(&fhdr, sizeof(fhdr), 1, fgem) == 1)
+ fwrite(&fhdr, sizeof(fhdr), 1, fndx);
+ fclose(fndx);
+ fclose(fgem);
+ rename(buf, pgem);
+ }
+ else{
+ fseek(fgem, index_pos * sizeof(fhdr), 0);
+ fwrite(&fhdr, sizeof(fhdr), 1, fgem);
+ fclose(fgem);
+ }
+ return;
+ }
+ }
+ fclose(fgem);
+}
+
+
+int main(int argc, char* argv[])
+{
+ boardheader_t *bptr = NULL;
+ DIR *dirp;
+ struct dirent *de;
+ int ch, n, place = 0, fd, i;
+ char checkrebuild = 0, *fname, fpath[MAXPATHLEN];
+ char dirs[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',
+ 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',
+ 'z', 'x', 'c', 'v', 'b', 'n', 'm',
+ 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P',
+ 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L',
+ 'Z', 'X', 'C', 'V', 'B', 'N', 'M', 0};
+
+ nice(10);
+ while( (ch = getopt(argc, argv, "xh")) != -1 ){
+ switch( ch ){
+ case 'x':
+ checkrebuild = 1;
+ break;
+ case 'h':
+ printf("NAME \n"
+ " mandex - 精華區索引程式 (man index)\n\n"
+ "SYNOPSIS \n"
+ " mandex [-x] [board] \n\n"
+ "DESCRIPTION \n"
+ "精華區索引 (man index) \n\n"
+ "-x 只有含有 .rebuild的目錄才重製 \n"
+ "board 全部的板 (default to all) \n\n");
+ return 0;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ attach_SHM();
+ resolve_boards();
+/*
+ if( argc == 0 ){
+ puts("Creating the whole index...");
+ chdir(strcpy(topdir, BBSHOME));
+ strcpy(fpath, "man/.DIR");
+ mandex(0, "", fpath);
+ }
+*/
+ chdir(strcpy(topdir, BBSHOME "/man/boards"));
+
+ if( argc == 1 ){
+ sprintf(fpath, "%c/%s/.DIR", argv[0][0], argv[0]);
+ mandex(0, "", fpath);
+ return 0;
+ }
+
+ /* process all boards */
+ if( checkrebuild &&
+ (fd = open(BBSHOME "/man/.rank.cache", O_RDONLY)) >= 0 ){
+ read(fd, board, sizeof(board));
+ close(fd);
+ qsort(board, MAX_BOARD, sizeof(boardinfo), sortbyname);
+ for( nb = 0 ; board[nb].bname[0] != 0 ; ++nb )
+ ;
+ nSorted = nb;
+ }
+ else{
+ memset(board, 0, sizeof(board));
+ checkrebuild = 0;
+ nSorted = nb = 0;
+ }
+
+ for( i = 0 ; dirs[i] != 0 ; ++i ){
+ sprintf(topdir, BBSHOME "/man/boards/%c", dirs[i]);
+ chdir(topdir);
+ if(!(dirp = opendir(topdir))) {
+ printf("## unable to enter [man/boards]\n");
+ exit(-1);
+ }
+
+ while((de = readdir(dirp))){
+ fname = de->d_name;
+ ch = fname[0];
+ if (ch != '.'){
+ k = 0;
+ if( checkrebuild ){
+ sprintf(fpath, "%s/.rebuild", fname);
+ if( access(fpath, 0) < 0 ){
+ printf("skip no modify board %s\n", fname);
+ continue;
+ }
+ unlink(fpath);
+ }
+ sprintf(fpath, "%s/.DIR", fname);
+ mandex(0, "", fpath);
+ printf("%-14sd: %d\tf: %d\n", fname, ndir, nfile); /* report */
+ if( k ){
+ if( !(biptr = bsearch(fname, board, nSorted,
+ sizeof(boardinfo), sortbyname))){
+ biptr = &board[nb];
+ ++nb;
+ }
+ strcpy(biptr->bname, fname);
+ biptr->ndir = ndir;
+ biptr->nfile = nfile;
+ biptr->k = k;
+ }
+ }
+ }
+ closedir(dirp);
+ }
+
+ qsort(board, nb, sizeof(boardinfo), k_cmp);
+ unlink(BBSHOME "/man/.rank.cache");
+ if( (fd = open(BBSHOME "/man/.rank.cache",
+ O_WRONLY | O_CREAT | O_TRUNC | O_APPEND, 0660)) >= 0 ){
+ write(fd, board, sizeof(board));
+ close(fd);
+ }
+ if (!(fndx = fopen(BBSHOME "/etc/topboardman", "w")))
+ exit(0);
+
+ fprintf(fndx, "排名 看 板 目錄數 檔案數"
+ " byte數  總 分 板 主 \n");
+
+ for (ch = 0; ch < nb; ch++){
+ for (n = 0; n < numboards; n++){
+ bptr = &bcache[n];
+ if (!strcmp(bptr->brdname, board[ch].bname))
+ break;
+ }
+ if (n >= numboards ||
+ (bptr->brdattr & (BRD_BAD | BRD_NOCOUNT)))
+ continue;
+
+ /* 板主設定不列入記錄 */
+ if (bptr->brdattr & BRD_HIDE && !(bptr->brdattr & BRD_BMCOUNT))
+ continue;
+
+ if (board[ch].ndir + board[ch].nfile < 5)
+ break;
+ fprintf(fndx, "%3d.%15s %5d %7d %10d %6d %-24.24s\n",
+ ++place,
+ board[ch].bname,
+ board[ch].ndir, board[ch].nfile, board[ch].k
+ ,board[ch].k / 100 + board[ch].nfile + board[ch].ndir
+ ,bptr->BM);
+ }
+ fclose(fndx);
+ return 0;
+}
diff --git a/pttbbs/util/merge_board.c b/pttbbs/util/merge_board.c
new file mode 100644
index 00000000..4fc643c1
--- /dev/null
+++ b/pttbbs/util/merge_board.c
@@ -0,0 +1,100 @@
+/* $Id$ */
+#include "bbs.h"
+
+typedef struct hash_t {
+ char *brdname;
+ struct hash_t *next;
+} hash_t;
+
+FILE *fout;
+hash_t *hash_tbl[65536];
+int counter;
+
+void usage() {
+ fprintf(stderr, "Usage:\n\n"
+ "merge_board <output file> [input file1] [input file2] ...\n");
+}
+
+unsigned int string_hash(unsigned char *s) {
+ unsigned int v=0;
+
+ while(*s) {
+ v = (v << 8) | (v >> 24);
+ v ^= toupper(*s++); /* note this is case insensitive */
+ }
+ return (v * 2654435769U) >> (32 - 16);
+}
+
+int is_exist(char *brdname) {
+ int i;
+ hash_t *n;
+
+ i = string_hash(brdname);
+ for(n = hash_tbl[i]; n != NULL; n = n->next)
+ if(strcasecmp(brdname, n->brdname) == 0)
+ return 1;
+ return 0;
+}
+
+void add_hash(char *brdname) {
+ int i;
+ hash_t *n;
+
+ i = string_hash(brdname);
+
+ n = malloc(sizeof(*n));
+ n->brdname = strdup(brdname);
+ n->next = hash_tbl[i];
+ hash_tbl[i] = n;
+}
+
+void merge_board(boardheader_t *b) {
+ if(!is_exist(b->brdname)) {
+ fwrite(b, sizeof(*b), 1, fout);
+ add_hash(b->brdname);
+ ++counter;
+ }
+}
+
+void merge_file(char *fname) {
+ FILE *fin;
+ boardheader_t b;
+
+ if((fin = fopen(fname, "r")) == NULL) {
+ perror(fname);
+ return;
+ }
+
+ counter = 0;
+ while(fread(&b, sizeof(b), 1, fin) == 1)
+ if(b.brdname[0])
+ merge_board(&b);
+
+ printf("merge from %s: %d boards\n", fname, counter);
+
+ fclose(fin);
+}
+
+int main(int argc, char **argv) {
+ int i;
+
+ if(argc < 2) {
+ usage();
+ return 1;
+ }
+
+ bzero(hash_tbl, sizeof(hash_tbl));
+
+ if((fout = fopen(argv[1], "w")) == NULL) {
+ perror(argv[1]);
+ return 2;
+ }
+
+ for(i = 2; i < argc; ++i)
+ merge_file(argv[i]);
+
+ fclose(fout);
+ printf("Done\n");
+
+ return 0;
+}
diff --git a/pttbbs/util/merge_dir.c b/pttbbs/util/merge_dir.c
new file mode 100644
index 00000000..1fe74368
--- /dev/null
+++ b/pttbbs/util/merge_dir.c
@@ -0,0 +1,48 @@
+/* $Id$ */
+#define _UTIL_C_
+#include "bbs.h"
+
+void usage() {
+ fprintf(stderr, "Usage:\n\n"
+ "merge_dir <dir1> <dir2> \n");
+}
+
+
+fileheader_t *fh;
+int dir_cmp(const void *a, const void *b)
+{
+ return (atoi( &((fileheader_t *)a)->filename[2] ) -
+ atoi( &((fileheader_t *)b)->filename[2] ));
+}
+
+int main(int argc, char **argv) {
+ int pn, sn, i;
+
+ if(argc < 2) {
+ usage();
+ return 1;
+ }
+
+ pn=get_num_records(argv[1], sizeof(fileheader_t));
+ sn=get_num_records(argv[2], sizeof(fileheader_t));
+ if(sn <=0 || pn<=0)
+ {
+ usage();
+ return 1;
+ }
+ fh = (fileheader_t *)malloc( (pn+sn)*sizeof(fileheader_t));
+ get_records(argv[1], fh, sizeof(fileheader_t), 1, pn);
+ get_records(argv[2], fh+pn, sizeof(fileheader_t), 1, sn);
+ qsort(fh, pn+sn, sizeof(fileheader_t), dir_cmp);
+ for(i=1; i<pn+sn; i++ )
+ {
+ if(!fh[i].title[0] || !fh[i].filename[0]) continue;
+ if( strcmp(fh[i-1].filename, fh[i].filename))
+ append_record("DIR.merge", &fh[i-1], sizeof(fileheader_t));
+ else
+ fh[i].filemode |= fh[i-1].filemode;
+ }
+ append_record("DIR.merge", &fh[i-1], sizeof(fileheader_t));
+
+ return 0;
+}
diff --git a/pttbbs/util/mvdir.pl b/pttbbs/util/mvdir.pl
new file mode 100644
index 00000000..e853df05
--- /dev/null
+++ b/pttbbs/util/mvdir.pl
@@ -0,0 +1,36 @@
+#!/usr/bin/perl
+if( !@ARGV ){
+ print "usage: mvdir.pl which prefix from_id to_id\n";
+ exit;
+}
+
+($which, $prefix, $from_id, $to_id) = @ARGV;
+
+$fromdir = "/scsi$from_id/bbs/$which/$prefix";
+$todir = "/scsi$to_id/bbs/$which/$prefix";
+$sym = ($which eq 'man' ? "/home/bbs/man/boards/$prefix" : "/home/bbs/$which/$prefix");
+
+if( !-e $fromdir ){
+ print "from dir ($fromdir) not found\n";
+ exit;
+}
+
+mkdir($todir, 0755);
+
+chdir $fromdir;
+foreach( <*> ){
+ next if( /^\./ );
+ symlink("$fromdir/$_", "$todir/$_");
+ push @dirs, $_;
+}
+
+unlink $sym;
+symlink($todir, $sym);
+
+foreach( @dirs ){
+ printf("processing %-20s (%04d/%04d)\n", $_, $index++, $#dirs);
+ unlink "$todir/$_";
+ `mv $fromdir/$_ $todir/$_`;
+}
+
+rmdir "$fromdir";
diff --git a/pttbbs/util/newvers.sh b/pttbbs/util/newvers.sh
new file mode 100644
index 00000000..f0f73538
--- /dev/null
+++ b/pttbbs/util/newvers.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+# $Id: newvers.sh,v 1.2 2003/06/22 14:45:17 in2 Exp $
+
+# prevent localized logs
+LC_ALL=C
+export LC_ALL
+
+t=`date`
+
+# are we working in CVS?
+if [ -d ".svn" ] ; then
+
+ #determine branch
+ branch=`svn info | grep '^URL: ' | sed 's/.*\/pttbbs\/\([a-zA-Z0-9_\-\.]*\)\/pttbbs\/.*/\1/'`
+ branch="$branch "
+
+ #determine rev
+ rev=`svn info | grep Revision | sed 's/Revision: /r/'`
+
+ if [ "$rev" != "" ]
+ then
+ t="$t, $branch$rev"
+ fi
+
+fi
+
+cat << EOF > vers.c
+char * const compile_time = "${t}";
+EOF
diff --git a/pttbbs/util/opendice.sh b/pttbbs/util/opendice.sh
new file mode 100644
index 00000000..767e213a
--- /dev/null
+++ b/pttbbs/util/opendice.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+# $Id: opendice.sh,v 1.1 2002/03/07 15:13:46 in2 Exp $
+
+bin/countalldice > etc/dice.dis
+bin/post Record "骰子中獎名單" "[骰子報告]" etc/windice.log
+bin/post Security "骰子失敗名單" "[骰子報告]" etc/lostdice.log
+bin/post Security "骰子期望值" "[骰子報告]" etc/dice.dis
+rm -f etc/windice.log
+rm -f etc/lostdice.log
+rm -f etc/dice.dis
diff --git a/pttbbs/util/openticket.c b/pttbbs/util/openticket.c
new file mode 100644
index 00000000..c200510d
--- /dev/null
+++ b/pttbbs/util/openticket.c
@@ -0,0 +1,125 @@
+/* $Id$ */
+/* 開獎的 utility */
+#define _UTIL_C_
+#include "bbs.h"
+
+static char *betname[8] = {"Ptt", "Jaky", "Action", "Heat",
+ "DUNK", "Jungo", "waiting", "wofe"};
+
+#define MAX_DES 7 /* 最大保留獎數 */
+
+int Link(const char *src, const char *dst)
+{
+ char cmd[200];
+
+ if (link(src, dst) == 0)
+ return 0;
+
+ sprintf(cmd, "/bin/cp -R %s %s", src, dst);
+ return system(cmd);
+}
+
+int main(int argc, char **argv)
+{
+ int money, bet, n, total = 0, ticket[8] =
+ {0, 0, 0, 0, 0, 0, 0, 0};
+ FILE *fp;
+ time4_t now = (time4_t)time(NULL);
+ char des[MAX_DES][200] =
+ {"", "", "", ""};
+
+ nice(10);
+ attach_SHM();
+ if(passwd_init())
+ exit(1);
+
+ rename(BBSHOME "/etc/" FN_TICKET_RECORD,
+ BBSHOME "/etc/" FN_TICKET_RECORD ".tmp");
+ rename(BBSHOME "/etc/" FN_TICKET_USER,
+ BBSHOME "/etc/" FN_TICKET_USER ".tmp");
+
+ if (!(fp = fopen(BBSHOME "/etc/"FN_TICKET_RECORD ".tmp", "r")))
+ return 0;
+ fscanf(fp, "%9d %9d %9d %9d %9d %9d %9d %9d\n",
+ &ticket[0], &ticket[1], &ticket[2], &ticket[3],
+ &ticket[4], &ticket[5], &ticket[6], &ticket[7]);
+ for (n = 0; n < 8; n++)
+ total += ticket[n];
+ fclose(fp);
+
+ if (!total)
+ return 0;
+
+ if((fp = fopen(BBSHOME "/etc/" FN_TICKET , "r")))
+ {
+ for (n = 0; n < MAX_DES && fgets(des[n], 200, fp); n++) ;
+ fclose(fp);
+ }
+
+ /* 現在開獎號碼並沒用到 random function.
+ * 小站的 UTMPnumber 可視為定值, 且 UTMPnumber 預設一秒才更新一次
+ * 開站一段時間的開獎 pid 應該無法預測.
+ * 若是小站當站開獎前開站, 則有被猜中的可能 */
+ attach_SHM();
+ bet = (SHM->UTMPnumber+getpid()) % 8;
+
+
+
+ money = ticket[bet] ? total * 95 / ticket[bet] : 9999999;
+
+ if((fp = fopen(BBSHOME "/etc/" FN_TICKET, "w")))
+ {
+ if (des[MAX_DES - 1][0])
+ n = 1;
+ else
+ n = 0;
+
+ for (; n < MAX_DES && des[n][0] != 0; n++)
+ {
+ fprintf(fp, des[n]);
+ }
+
+ printf("\n\n開獎時間: %s \n\n"
+ "開獎結果: %s \n\n"
+ "下注總金額: %d00 元 \n"
+ "中獎比例: %d張/%d張 (%f)\n"
+ "每張中獎彩票可得 %d 枚P幣 \n\n",
+ Cdatelite(&now), betname[bet], total, ticket[bet], total,
+ (float) ticket[bet] / total, money);
+
+ fprintf(fp, "%s 賭盤開出:%s 總金額:%d00 元 獎金/張:%d 元 機率:%1.2f\n",
+ Cdatelite(&now), betname[bet], total, money,
+ (float) ticket[bet] / total);
+ fclose(fp);
+
+ }
+
+ if (ticket[bet] && (fp = fopen(BBSHOME "/etc/" FN_TICKET_USER ".tmp", "r")))
+ {
+ int mybet, num, uid;
+ char userid[20], genbuf[200];
+ fileheader_t mymail;
+
+ while (fscanf(fp, "%s %d %d\n", userid, &mybet, &num) != EOF)
+ {
+ if (mybet == bet)
+ {
+ printf("恭喜 %-15s買了%9d 張 %s, 獲得 %d 枚P幣\n"
+ ,userid, num, betname[mybet], money * num);
+ if((uid=searchuser(userid, userid))==0) continue;
+ deumoney(uid, money * num);
+ sprintf(genbuf, BBSHOME "/home/%c/%s", userid[0], userid);
+ stampfile(genbuf, &mymail);
+ strcpy(mymail.owner, BBSNAME);
+ sprintf(mymail.title, "[%s] 中獎囉! $ %d", Cdatelite(&now), money * num);
+ unlink(genbuf);
+ Link(BBSHOME "/etc/ticket", genbuf);
+ sprintf(genbuf, BBSHOME "/home/%c/%s/.DIR", userid[0], userid);
+ append_record(genbuf, &mymail, sizeof(mymail));
+ }
+ }
+ }
+ unlink(BBSHOME "/etc/" FN_TICKET_RECORD ".tmp");
+ unlink(BBSHOME "/etc/" FN_TICKET_USER ".tmp");
+ return 0;
+}
diff --git a/pttbbs/util/openticket.sh b/pttbbs/util/openticket.sh
new file mode 100644
index 00000000..8274e5c3
--- /dev/null
+++ b/pttbbs/util/openticket.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+# $Id: openticket.sh,v 1.1 2002/03/07 15:13:46 in2 Exp $
+
+bin/openticket > etc/jackpot
+bin/post Record "彩券中獎名單" "[賭場報告]" etc/jackpot
+bin/post Record "猜數字中獎名單" "[猜數字報告]" etc/winguess.log
+bin/post Record "黑白棋對戰記錄" "[猜數字報告]" etc/othello.log
+rm -f etc/winguess.log
+rm -f etc/loseguess.log
+rm -f etc/othello.log
diff --git a/pttbbs/util/openvice.c b/pttbbs/util/openvice.c
new file mode 100644
index 00000000..588fc41d
--- /dev/null
+++ b/pttbbs/util/openvice.c
@@ -0,0 +1,46 @@
+/* $Id$ */
+/* 發票開獎小程式 */
+
+#include "bbs.h"
+
+#define VICE_SHOW BBSHOME "/etc/vice.show1"
+#define VICE_BINGO BBSHOME "/etc/vice.bingo"
+#define VICE_NEW "vice.new"
+#define VICE_DATA "vice.data"
+#define MAX_BINGO 99999999
+
+int main(int argc, char **argv)
+{
+ char *TABLE[5] = {"一", "二", "三", "四", "五"};
+ int i = 0, bingo, base = 0;
+
+
+ FILE *fp = fopen(VICE_SHOW, "w"), *fb = fopen(VICE_BINGO, "w");
+
+ // XXX: resolve_utmp();
+ attach_SHM();
+
+ srandom(SHM->number);
+ /* FIXME 小站的 SHM->number 變化不大, 可能導致開獎號碼固定 */
+
+ if (!fp || !fb )
+ perror("error open file");
+
+
+ bingo = random() % MAX_BINGO;
+ fprintf(fp, "%1c統一發票中獎號碼\n", ' ');
+ fprintf(fp, "%1c================\n", ' ');
+ fprintf(fp, "%1c特別獎: %08d\n\n", ' ', bingo);
+ fprintf(fb, "%d\n", bingo);
+
+ while (i < 5)
+ {
+ bingo = (base + random()) % MAX_BINGO;
+ fprintf(fp, "%1c第%s獎: %08d\n", ' ', TABLE[i], bingo);
+ fprintf(fb, "%08d\n", bingo);
+ i++;
+ }
+ fclose(fp);
+ fclose(fb);
+ return 0;
+}
diff --git a/pttbbs/util/outmail.c b/pttbbs/util/outmail.c
new file mode 100644
index 00000000..5d5c9d37
--- /dev/null
+++ b/pttbbs/util/outmail.c
@@ -0,0 +1,275 @@
+/* $Id$ */
+#include "bbs.h"
+
+#define SPOOL BBSHOME "/out"
+#define INDEX SPOOL "/.DIR"
+#define NEWINDEX SPOOL "/.DIR.sending"
+#define FROM ".bbs@" MYHOSTNAME
+#define SMTPPORT 25
+char *smtpname;
+int smtpport;
+char disclaimer[1024];
+
+/* qp_encode() modified from mutt-1.5.7/rfc2047.c q_encoder() */
+const char MimeSpecials[] = "@.,;:<>[]\\\"()?/= \t";
+char * qp_encode (char *s, size_t slen, const char *d, const char *tocode)
+{
+ char hex[] = "0123456789ABCDEF";
+ char *s0 = s;
+
+ memcpy (s, "=?", 2), s += 2;
+ memcpy (s, tocode, strlen (tocode)), s += strlen (tocode);
+ memcpy (s, "?Q?", 3), s += 3;
+ assert(s-s0+3<slen);
+
+ while (*d != '\0' && s-s0+6<slen)
+ {
+ unsigned char c = *d++;
+ if (c == ' ')
+ *s++ = '_';
+ else if (c >= 0x7f || c < 0x20 || c == '_' || strchr (MimeSpecials, c))
+ {
+ *s++ = '=';
+ *s++ = hex[(c & 0xf0) >> 4];
+ *s++ = hex[c & 0x0f];
+ }
+ else
+ *s++ = c;
+ }
+ memcpy (s, "?=", 2), s += 2;
+ *s='\0';
+ return s0;
+}
+
+int waitReply(int sock) {
+ char buf[256];
+
+ if(read(sock, buf, sizeof(buf)) <= 0)
+ return -1;
+ else
+ return buf[0] - '0';
+}
+
+int sendRequest(int sock, char *request) {
+ return write(sock, request, strlen(request)) < 0 ? -1 : 0;
+}
+
+int connectMailServer(char *servername, int serverport)
+{
+ int sock;
+ struct sockaddr_in addr;
+
+ if((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
+ perror("socket");
+ return -1;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+#ifdef __FreeBSD__
+ addr.sin_len = sizeof(addr);
+#endif
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(serverport);
+ addr.sin_addr.s_addr = inet_addr(servername);
+
+ if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ printf("servername: %s\n", servername);
+ perror(servername);
+ close(sock);
+ return -1;
+ }
+
+ if(waitReply(sock) != 2) {
+ close(sock);
+ return -1;
+ }
+
+ if(sendRequest(sock, "helo " MYHOSTNAME "\n") || waitReply(sock) != 2) {
+ close(sock);
+ return -1;
+ } else
+ return sock;
+}
+
+void disconnectMailServer(int sock) {
+ sendRequest(sock, "quit\n");
+ /* drop the reply :p */
+ close(sock);
+}
+
+void doSendBody(int sock, FILE *fp, char *from, char *to, char *subject) {
+ int n;
+ char buf[2048];
+ char subject_qp[STRLEN*3+100];
+ static int starttime = -1, msgid = 0;
+ if( starttime == -1 ){
+ srandom(starttime = (int)time(NULL));
+ msgid = random();
+ }
+ n = snprintf(buf, sizeof(buf),
+ "From: %s <%s>\r\n"
+ "To: %s\r\n"
+ "Subject: %s\r\n"
+ "X-Sender: outmail of pttbbs\r\n"
+ "Mime-Version: 1.0\r\n"
+ "Content-Type: text/plain; charset=\"big5\"\r\n"
+ "Content-Transfer-Encoding: 8bit\r\n"
+ "Message-Id: <%d.%x.outmail@" MYHOSTNAME ">\r\n"
+ "X-Disclaimer: %s\r\n\r\n",
+ from, from, to,
+ qp_encode(subject_qp, sizeof(subject_qp), subject, "big5"),
+ starttime,
+ (msgid += (int)(random() >> 24)),
+ disclaimer);
+ write(sock, buf, n);
+
+ while(fgets(buf, sizeof(buf), fp)) {
+ if(buf[0] == '.' && buf[1] == '\n')
+ strcpy(buf, "..\n");
+ write(sock, buf, strlen(buf));
+ }
+}
+
+void doSendMail(int sock, FILE *fp, char *from, char *to, char *subject) {
+ char buf[256];
+
+ snprintf(buf, sizeof(buf), "mail from: %s\n", from);
+ if(sendRequest(sock, buf) || waitReply(sock) != 2)
+ return;
+
+ snprintf(buf, sizeof(buf), "rcpt to: %s\n", to);
+ if(sendRequest(sock, buf) || waitReply(sock) != 2)
+ return;
+
+ if(sendRequest(sock, "data\n") || waitReply(sock) != 3)
+ return;
+
+ doSendBody(sock, fp, from, to, subject);
+
+ if(sendRequest(sock, "\n.\n") || waitReply(sock) != 2)
+ return;
+}
+
+void sendMail() {
+ int fd, smtpsock;
+ MailQueue mq;
+
+ if(access(NEWINDEX, R_OK | W_OK)) {
+ if(link(INDEX, NEWINDEX) || unlink(INDEX))
+ /* nothing to do */
+ return;
+ }
+
+ smtpsock = connectMailServer(smtpname, smtpport);
+
+ if( smtpsock < 0) {
+ fprintf(stderr, "connecting to relay server failure...\n");
+ return;
+ }
+
+ fd = open(NEWINDEX, O_RDONLY);
+ flock(fd, LOCK_EX);
+ while(read(fd, &mq, sizeof(mq)) > 0) {
+ FILE *fp;
+ char buf[256];
+
+ snprintf(buf, sizeof(buf), "%s%s", mq.sender, FROM);
+ if((fp = fopen(mq.filepath, "r"))) {
+ setproctitle("outmail: sending %s", mq.filepath);
+ printf("mailto: %s, relay server: %s:%d\n", mq.rcpt, smtpname, smtpport);
+ doSendMail(smtpsock, fp, buf, mq.rcpt, mq.subject);
+ fclose(fp);
+ unlink(mq.filepath);
+ } else {
+ perror(mq.filepath);
+ }
+ }
+ flock(fd, LOCK_UN);
+ close(fd);
+ unlink(NEWINDEX);
+
+ disconnectMailServer(smtpsock);
+}
+
+void listQueue() {
+ int fd;
+
+ if((fd = open(INDEX, O_RDONLY)) >= 0) {
+ int counter = 0;
+ MailQueue mq;
+
+ flock(fd, LOCK_EX);
+ while(read(fd, &mq, sizeof(mq)) > 0) {
+ printf("%s:%s -> %s:%s\n", mq.filepath, mq.username, mq.rcpt,
+ mq.subject);
+ counter++;
+ }
+ flock(fd, LOCK_UN);
+ close(fd);
+ printf("\nTotal: %d mails in queue\n", counter);
+ } else {
+ perror(INDEX);
+ }
+}
+
+void wakeup(int s) {
+}
+
+void parseserver(char *sx, char **name, int *port)
+{
+ char *save = strdup(sx);
+ char *ptr;
+ if( (ptr = strstr(save, ":")) == NULL ){
+ *name = strdup(save);
+ *port = 25;
+ }
+ else{
+ *ptr = 0;
+ *name = strdup(save);
+ *port = atoi(ptr + 1);
+ }
+ free(save);
+}
+
+int main(int argc, char **argv, char **envp) {
+ int ch;
+
+ Signal(SIGHUP, wakeup);
+ initsetproctitle(argc, argv, envp);
+
+ if(chdir(BBSHOME))
+ return 1;
+ while((ch = getopt(argc, argv, "qhs:o:")) != -1) {
+ switch(ch) {
+ case 's':
+ parseserver(optarg, &smtpname, &smtpport);
+ break;
+ case 'q':
+ listQueue();
+ return 0;
+ default:
+ printf("usage:\toutmail [-qh] -s host[:port]\n"
+ "\t-q\tlistqueue\n"
+ "\t-h\thelp\n"
+ "\t-s\tset default smtp server to host[:port]\n");
+ return 0;
+ }
+ }
+
+ if( smtpname == NULL ){
+#ifdef RELAY_SERVER_IP
+ smtpname = RELAY_SERVER_IP;
+#else
+ smtpname = "127.0.0.1";
+#endif
+ smtpport = 25;
+ }
+
+ qp_encode(disclaimer, sizeof(disclaimer), "[" BBSNAME "]對本信內容恕不負責", "big5");
+ for(;;) {
+ sendMail();
+ setproctitle("outmail: sleeping");
+ sleep(60); /* send mail every minute */
+ }
+ return 0;
+}
diff --git a/pttbbs/util/parsevar.pl b/pttbbs/util/parsevar.pl
new file mode 100644
index 00000000..97689a19
--- /dev/null
+++ b/pttbbs/util/parsevar.pl
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+# $Id$
+print << '.';
+/*
+ * This header file is auto-generated from pttbbs/mbbsd/var.c .
+ * Please do NOT edit this file directly.
+ */
+
+#ifndef INCLUDE_VAR_H
+#define INCLUDE_VAR_H
+#include "bbs.h"
+.
+while( <> ){
+ if( /^\w/ ){
+ chomp;
+ s|//.*||;
+ s|/\*.*?\*/||;
+ s/\=\s*\{.*?\}//;
+
+ s/\=.*?([,;])/$1/g;
+ s/\=.*?\{//g;
+ $_ .= ';' if( index($_, ';') == -1 );
+ s/\=.*?([,;])/$1/g;
+ s/\=.*?\{//g;
+ print "extern $_\n";
+ } elsif( /^\s*\#/ && !/include/ ){
+ print;
+ }
+}
+print "#endif /* INCLUDE_VAR_H */\n";
+
diff --git a/pttbbs/util/passwdconverter.c b/pttbbs/util/passwdconverter.c
new file mode 100644
index 00000000..2361b787
--- /dev/null
+++ b/pttbbs/util/passwdconverter.c
@@ -0,0 +1,146 @@
+#include "bbs.h"
+
+/* userec_t before revision 2275 */
+typedef struct old_userec_t {
+ char userid[IDLEN + 1];
+ char realname[20];
+ char nickname[24];
+ char passwd[PASSLEN];
+ unsigned char uflag;
+ unsigned int userlevel;
+ unsigned short numlogins;
+ unsigned short numposts;
+ time4_t firstlogin;
+ time4_t lastlogin;
+ char lasthost[16];
+ int money;
+ char remoteuser[3]; /* 靽 桀瘝典啁 */
+ char proverb;
+ char email[50];
+ char address[50];
+ char justify[REGLEN + 1];
+ unsigned char month;
+ unsigned char day;
+ unsigned char year;
+ unsigned char sex;
+ unsigned char state;
+ unsigned char pager;
+ unsigned char invisible;
+ unsigned int exmailbox;
+ chicken_t mychicken;
+ time4_t lastsong;
+ unsigned int loginview;
+ unsigned char channel; /* (unused?) */
+ unsigned short vl_count; /* ViolateLaw counter */
+ unsigned short five_win;
+ unsigned short five_lose;
+ unsigned short five_tie;
+ unsigned short chc_win;
+ unsigned short chc_lose;
+ unsigned short chc_tie;
+ int mobile;
+ char mind[4];
+ char ident[11];
+ unsigned int uflag2;
+ unsigned char signature;
+
+ unsigned char goodpost; /* 閰寧箏末蝡 */
+ unsigned char badpost; /* 閰寧箏蝡 */
+ unsigned char goodsale; /* 蝡嗆 憟賜閰 */
+ unsigned char badsale; /* 蝡嗆 憯閰 */
+ char myangel[IDLEN+1]; /* 撠憭拐蝙 */
+ unsigned short chess_elo_rating; /* 鞊⊥蝑蝝 */
+ unsigned int withme;
+ char pad[48];
+} old_userec_t;
+
+void transform(userec_t *new, old_userec_t *old)
+{
+ new->version = PASSWD_VERSION;
+
+ strlcpy(new->userid, old->userid, IDLEN + 1);
+ strlcpy(new->realname, old->realname, 20);
+ strlcpy(new->nickname, old->nickname, 24);
+ strlcpy(new->passwd, old->passwd, PASSLEN);
+ new->uflag = old->uflag;
+ new->userlevel = old->userlevel;
+ new->numlogins = old->numlogins;
+ new->numposts = old->numposts;
+ new->firstlogin = old->firstlogin;
+ new->lastlogin = old->lastlogin;
+ strlcpy(new->lasthost, old->lasthost, 16);
+ new->money = old->money;
+ strlcpy(new->remoteuser, old->remoteuser, 3);
+ new->proverb = old->proverb;
+ strlcpy(new->email, old->email, 50);
+ strlcpy(new->address, old->address, 50);
+ strlcpy(new->justify, old->justify, REGLEN + 1);
+ new->month = old->month;
+ new->day = old->day;
+ new->year = old->year;
+ new->sex = old->sex;
+ new->state = old->state;
+ new->pager = old->pager;
+ new->invisible = old->invisible;
+ new->exmailbox = old->exmailbox;
+ new->mychicken = old->mychicken;
+ new->lastsong = old->lastsong;
+ new->loginview = old->loginview;
+ new->channel = old->channel;
+ new->vl_count = old->vl_count;
+ new->five_win = old->five_win;
+ new->five_lose = old->five_lose;
+ new->five_tie = old->five_tie;
+ new->chc_win = old->chc_win;
+ new->chc_lose = old->chc_lose;
+ new->chc_tie = old->chc_tie;
+ new->mobile = old->mobile;
+ memcpy(new->mind, old->mind, 4);
+ memset(new->pad0, 0, sizeof(new->pad0)); // ident is not used anymore
+ new->uflag2 = old->uflag2;
+ new->signature = old->signature;
+
+ new->goodpost = old->goodpost;
+ new->badpost = old->badpost;
+ new->goodsale = old->goodsale;
+ new->badsale = old->badsale;
+ strlcpy(new->myangel, old->myangel, IDLEN+1);
+ new->chess_elo_rating = old->chess_elo_rating;
+ new->withme = old->withme;
+ memset(new->pad, 0, sizeof(new->pad));
+}
+
+int main(void)
+{
+ int fd, fdw;
+ userec_t new;
+ old_userec_t old;
+
+ printf("You're going to convert your .PASSWDS\n");
+ printf("The new file will be named .PASSWDS.trans.tmp\n");
+ printf("old size of userec_t is %d, and the new one is %d\n", sizeof(old_userec_t), sizeof(userec_t));
+/*
+ printf("Press any key to continue\n");
+ getchar();
+*/
+
+ if (chdir(BBSHOME) < 0) {
+ perror("chdir");
+ exit(-1);
+ }
+
+ if ((fd = open(FN_PASSWD, O_RDONLY)) < 0 ||
+ (fdw = open(FN_PASSWD".trans.tmp", O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0 ) {
+ perror("open");
+ exit(-1);
+ }
+
+ while (read(fd, &old, sizeof(old)) > 0) {
+ transform(&new, &old);
+ write(fdw, &new, sizeof(new));
+ }
+
+ close(fd);
+ close(fdw);
+ return 0;
+}
diff --git a/pttbbs/util/pmakev2.sh b/pttbbs/util/pmakev2.sh
new file mode 100755
index 00000000..e6e1509d
--- /dev/null
+++ b/pttbbs/util/pmakev2.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+# Modify Makefiles to work with PMake v2.
+# PMake v2 used '#if' to replace '.if'.
+
+TESTFN=pttbbs.mk
+
+echo "Checking directory..."
+
+if [ ! -f $TESTFN -a -f ../$TESTFN ] ; then
+ cd .. # more chance to locate it
+fi
+
+if [ ! -f $TESTFN ] ; then
+ echo "Please prepare your $TESTFN first and run this script in"
+ echo "same level directory with $TESTFN"
+ exit 1
+fi
+
+echo "Found $TESTFN, check pmake version..."
+
+if ( pmake -h 2>/dev/null >/dev/null ) ; then
+ echo "OK, you have pmake v2."
+else
+ echo "I can't find pmake v2. You are using v1 or pmake not installed."
+ echo "This script only works for pmake v2."
+ exit 2
+fi
+
+for X in `find . -name "Makefile"` `find . -name "*.mk"`
+do
+ echo "$X -> $X.orig"
+ mv -f $X $X.orig
+ # I'm not sure if very old sed has multiple command supporting,
+ # and what about their regex, so let's be stupid here.
+ cat $X.orig | sed 's/^\.if/#if/' \
+ | sed 's/^\.el/#el/'| sed 's/^\.end/#end/' \
+ | sed 's/^\.include/#include/' \
+ > $X
+done
+
+echo "Complete. Now invoke pmake and try."
diff --git a/pttbbs/util/post.c b/pttbbs/util/post.c
new file mode 100644
index 00000000..c3af5269
--- /dev/null
+++ b/pttbbs/util/post.c
@@ -0,0 +1,58 @@
+/* $Id$ */
+#include "bbs.h"
+
+void keeplog(FILE *fin, char *fpath, char *board, char *title, char *owner) {
+ fileheader_t fhdr;
+ char genbuf[256], buf[512];
+ FILE *fout;
+ int bid;
+
+ sprintf(genbuf, BBSHOME "/boards/%c/%s", board[0], board);
+ stampfile(genbuf, &fhdr);
+
+ if(!(fout = fopen(genbuf, "w"))) {
+ perror(genbuf);
+ return;
+ }
+
+ while(fgets(buf, 512, fin))
+ fputs(buf, fout);
+
+ fclose(fin);
+ fclose(fout);
+
+ strncpy(fhdr.title, title, sizeof(fhdr.title) - 1);
+ fhdr.title[sizeof(fhdr.title) - 1] = '\0';
+
+ strcpy(fhdr.owner, owner);
+ sprintf(genbuf, BBSHOME "/boards/%c/%s/.DIR", board[0], board);
+ append_record(genbuf, &fhdr, sizeof(fhdr));
+ /* XXX: bid of cache.c's getbnum starts from 1 */
+ if((bid = getbnum(board)) > 0)
+ touchbtotal(bid);
+
+}
+
+int main(int argc, char **argv)
+{
+ FILE *fp;
+
+ attach_SHM();
+ resolve_boards();
+ if(argc != 5) {
+ printf("usage: %s <board name> <title> <owner> <file>\n", argv[0]);
+ return 0;
+ }
+
+ if(strcmp(argv[4], "-") == 0)
+ fp = stdin;
+ else {
+ fp = fopen(argv[4], "r");
+ if(!fp) {
+ perror(argv[4]);
+ return 1;
+ }
+ }
+ keeplog(fp, argv[4], argv[1], argv[2], argv[3]);
+ return 0;
+}
diff --git a/pttbbs/util/poststat.c b/pttbbs/util/poststat.c
new file mode 100644
index 00000000..d18a3b70
--- /dev/null
+++ b/pttbbs/util/poststat.c
@@ -0,0 +1,345 @@
+/* $Id$ */
+/* 統計今日、週、月、年熱門話題 */
+
+#include "bbs.h"
+#include "fnv_hash.h"
+
+char *myfile[] =
+{"day", "week", "month", "year"};
+int mycount[4] =
+{7, 4, 12};
+int mytop[] =
+{10, 50, 100, 100};
+char *mytitle[] =
+{"日十", "週五十", "月百", "年度百"};
+
+
+#define HASHSIZE 1024
+#define TOPCOUNT 200
+
+
+struct postrec
+{
+ char author[13]; /* author name */
+ char board[13]; /* board name */
+ char title[66]; /* title name */
+ time4_t date; /* last post's date */
+ int number; /* post number */
+ struct postrec *next; /* next rec */
+}
+*bucket[HASHSIZE];
+
+
+/* 100 bytes */
+struct posttop
+{
+ char author[13]; /* author name */
+ char board[13]; /* board name */
+ char title[66]; /* title name */
+ time4_t date; /* last post's date */
+ int number; /* post number */
+}
+top[TOPCOUNT], *tp;
+
+/*
+ woju
+ Cross-fs rename()
+ */
+
+int Rename(const char *src, const char *dst)
+{
+
+ if (rename(src, dst) == 0)
+ return 0;
+/*
+ sprintf(cmd, "/bin/mv %s %s", src, dst);
+ return system(cmd);
+*/
+ return 0;
+}
+
+int
+ci_strcmp(s1, s2)
+ register char *s1, *s2;
+{
+ register int c1, c2, diff;
+
+ do
+ {
+ c1 = *s1++;
+ c2 = *s2++;
+ if (c1 >= 'A' && c1 <= 'Z')
+ c1 |= 32;
+ if (c2 >= 'A' && c2 <= 'Z')
+ c2 |= 32;
+ if((diff = c1 - c2))
+ return (diff);
+ }
+ while (c1);
+ return 0;
+}
+
+
+/* ---------------------------------- */
+/* hash structure : array + link list */
+/* ---------------------------------- */
+
+
+void
+search(t)
+ struct posttop *t;
+{
+ struct postrec *p, *q, *s;
+ int i, found = 0;
+
+ i = fnv1a_32_str(t->title, FNV1_32_INIT) % HASHSIZE;
+ q = NULL;
+ p = bucket[i];
+ while (p && (!found))
+ {
+ if (!strcmp(p->title, t->title) && !strcmp(p->board, t->board))
+ found = 1;
+ else
+ {
+ q = p;
+ p = p->next;
+ }
+ }
+ if (found)
+ {
+ p->number += t->number;
+ if (p->date < t->date) /* 取較近日期 */
+ p->date = t->date;
+ }
+ else
+ {
+ s = (struct postrec *) malloc(sizeof(struct postrec));
+ memcpy(s, t, sizeof(struct posttop));
+ s->next = NULL;
+ if (q == NULL)
+ bucket[i] = s;
+ else
+ q->next = s;
+ }
+}
+
+
+int
+sort(pp, count)
+ struct postrec *pp;
+{
+ int i, j;
+
+ for (i = 0; i <= count; i++)
+ {
+ if (pp->number > top[i].number)
+ {
+ if (count < TOPCOUNT - 1)
+ count++;
+ for (j = count - 1; j >= i; j--)
+ memcpy(&top[j + 1], &top[j], sizeof(struct posttop));
+
+ memcpy(&top[i], pp, sizeof(struct posttop));
+ break;
+ }
+ }
+ return count;
+}
+
+
+void
+load_stat(fname)
+ char *fname;
+{
+ FILE *fp;
+
+ if((fp = fopen(fname, "r")))
+ {
+ int count = fread(top, sizeof(struct posttop), TOPCOUNT, fp);
+ fclose(fp);
+ while (count)
+ search(&top[--count]);
+ }
+}
+
+
+int
+filter(board)
+ char *board;
+{
+ boardheader_t bh;
+ int bid;
+
+ /* XXX: bid of cache.c's getbnum starts from 1 */
+ bid = getbnum(board);
+ if (get_record(".BRD", &bh, sizeof(bh), bid) == -1)
+ return 1;
+ if (bh.brdattr & BRD_NOCOUNT)
+ return 1;
+
+ /* 板主設定不列入記錄 */
+ if (bh.brdattr & BRD_HIDE && !(bh.brdattr & BRD_BMCOUNT))
+ return 1;
+/*
+ if (bh.brdattr & BRD_POSTMASK)
+ return 0;
+ return (bh.brdattr & ~BRD_POSTMASK) ||
+ >= 32;
+*/
+ return 0;
+}
+
+
+void
+poststat(mytype)
+ int mytype;
+{
+ static char *logfile = ".post";
+ static char *oldfile = ".post.old";
+
+ FILE *fp;
+ char buf[40], curfile[40] = "etc/day.0", *p;
+ struct postrec *pp;
+ int i, j;
+
+ if (mytype < 0)
+ {
+ /* --------------------------------------- */
+ /* load .post and statictic processing */
+ /* --------------------------------------- */
+
+ remove(oldfile);
+ Rename(logfile, oldfile);
+ if ((fp = fopen(oldfile, "r")) == NULL)
+ return;
+ mytype = 0;
+ load_stat(curfile);
+
+ while (fread(top, sizeof(struct posttop), 1, fp))
+ search(top);
+ fclose(fp);
+ }
+ else
+ {
+ /* ---------------------------------------------- */
+ /* load previous results and statictic processing */
+ /* ---------------------------------------------- */
+
+ i = mycount[mytype];
+ p = myfile[mytype];
+ while (i)
+ {
+ sprintf(buf, "etc/%s.%d", p, i);
+ sprintf(curfile, "etc/%s.%d", p, --i);
+ load_stat(curfile);
+ Rename(curfile, buf);
+ }
+ mytype++;
+ }
+
+/* ---------------------------------------------- */
+/* sort top 100 issue and save results */
+/* ---------------------------------------------- */
+
+ memset(top, 0, sizeof(top));
+ for (i = j = 0; i < HASHSIZE; i++)
+ {
+ for (pp = bucket[i]; pp; pp = pp->next)
+ {
+#ifdef DEBUG
+ printf("Title : %s, Board: %s\nPostNo : %d, Author: %s\n"
+ ,pp->title
+ ,pp->board
+ ,pp->number
+ ,pp->author);
+#endif
+
+ j = sort(pp, j);
+ }
+ }
+
+ p = myfile[mytype];
+ sprintf(curfile, "etc/%s.0", p);
+ if((fp = fopen(curfile, "w")))
+ {
+ fwrite(top, sizeof(struct posttop), j, fp);
+ fclose(fp);
+ }
+
+ sprintf(curfile, "etc/%s", p);
+ if((fp = fopen(curfile, "w")))
+ {
+ int max, cnt;
+
+ fprintf(fp, "\t\t-----===== 本%s大熱門話題 =====-----\n\n", mytitle[mytype]);
+
+ max = mytop[mytype];
+ p = buf + 4;
+ for (i = cnt = 0; (cnt < max) && (i < j); i++)
+ {
+ tp = &top[i];
+ if (filter(tp->board))
+ continue;
+
+ strcpy(buf, ctime4(&(tp->date)));
+ buf[20] = 0;
+ fprintf(fp,
+ "%3d. 看板 : %-16s《 %s》%4d 篇%16s\n"
+ " 標題 : %-60.60s\n"
+ ,++cnt, tp->board, p, tp->number, tp->author, tp->title);
+ }
+ fclose(fp);
+ }
+
+/* free statistics */
+
+ for (i = 0; i < HASHSIZE; i++)
+ {
+ struct postrec *pp0;
+
+ pp = bucket[i];
+ while (pp)
+ {
+ pp0 = pp;
+ pp = pp->next;
+ free(pp0);
+ }
+
+ bucket[i] = NULL;
+ }
+}
+
+
+int main(argc, argv)
+ char *argv[];
+{
+ time4_t now;
+ struct tm *ptime;
+
+ attach_SHM();
+ if (argc < 2)
+ {
+ printf("Usage:\t%s bbshome [day]\n", argv[0]);
+ return (-1);
+ }
+ chdir(argv[1]);
+
+ if (argc == 3)
+ {
+ poststat(atoi(argv[2]));
+ return (0);
+ }
+ now = (time4_t)time(NULL);
+ ptime = localtime4(&now);
+ if (ptime->tm_hour == 0)
+ {
+ if (ptime->tm_mday == 1)
+ poststat(2);
+
+ if (ptime->tm_wday == 0)
+ poststat(1);
+ poststat(0);
+ }
+ poststat(-1);
+ return 0;
+}
diff --git a/pttbbs/util/r2014convert.c b/pttbbs/util/r2014convert.c
new file mode 100644
index 00000000..14de742b
--- /dev/null
+++ b/pttbbs/util/r2014convert.c
@@ -0,0 +1,63 @@
+#include "bbs.h"
+
+int main(){
+ int orig_fd, new_fd;
+ userec_t u;
+ int count = 0;
+
+ orig_fd = open(BBSHOME "/.AngelTrans", O_WRONLY | O_CREAT | O_EXCL, 0600);
+ if (orig_fd == -1) {
+ if (errno == EEXIST) {
+ char c;
+ printf("It seems your .PASSWD file has been transfered, "
+ "do it any way?[y/N] ");
+ fflush(stdout);
+ scanf("%c", &c);
+ if (c != 'y' && c != 'Y')
+ return 0;
+ } else {
+ perror("opening " BBSHOME "/.AngelTrans for marking");
+ return 0;
+ }
+ } else {
+ time4_t t = (time4_t)time(NULL);
+ char* str = ctime4(&t);
+ write(orig_fd, str, strlen(str));
+ }
+
+ orig_fd = open(BBSHOME "/.PASSWDS", O_RDONLY);
+ if( orig_fd < 0 ){
+ perror("opening " BBSHOME "/.PASSWDS for reading");
+ return 1;
+ }
+ printf("Reading from " BBSHOME "/.PASSWDS\n");
+
+ new_fd = open(BBSHOME "/.PASSWDS.NEW", O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if( new_fd < 0 ){
+ perror("opening " BBSHOME "/.PASSWDS.NEW for writing");
+ return 1;
+ }
+ printf("Writing to " BBSHOME "/.PASSWDS.NEW\n");
+
+ while(read(orig_fd, &u, sizeof(userec_t)) == sizeof(userec_t)){
+ // clear 0x400, 0x800, and 0x3000
+ u.uflag2 &= ~(0x400 | 0x800 | 0x3000);
+ if( u.userlevel & OLD_PERM_NOOUTMAIL )
+ u.uflag2 |= REJ_OUTTAMAIL;
+ u.userlevel &= ~000001000000;
+ bzero(u.myangel, IDLEN + 1);
+ write(new_fd, &u, sizeof(userec_t));
+ ++count;
+ }
+
+ close(orig_fd);
+ close(new_fd);
+ printf("Done, totally %d accounts translated\n", count);
+
+ printf("Moving files....\n");
+ system("mv -i " BBSHOME "/.PASSWDS " BBSHOME "/.PASSWDS.old");
+ system("mv -i " BBSHOME "/.PASSWDS.NEW " BBSHOME "/.PASSWDS");
+ printf("Done, old password file is now at " BBSHOME "/.PASSWDS.old\n");
+
+ return 0;
+}
diff --git a/pttbbs/util/reaper.c b/pttbbs/util/reaper.c
new file mode 100644
index 00000000..348224e9
--- /dev/null
+++ b/pttbbs/util/reaper.c
@@ -0,0 +1,64 @@
+/* $Id$ */
+#define _UTIL_C_
+#include "bbs.h"
+
+time4_t now;
+
+int invalid(char *userid) {
+ int i;
+
+ if(!isalpha(userid[0]))
+ return 1;
+
+ for(i = 1; i < IDLEN && userid[i]; i++)
+ if(!isalpha(userid[i]) && !isdigit(userid[i]))
+ return 1;
+ return 0;
+}
+
+int check(int n, userec_t *u) {
+ time4_t d;
+ char buf[256];
+
+ if(u->userid[0] != '\0') {
+ if(invalid(u->userid)) {
+ syslog(LOG_ERR, "bad userid(%d): %s", n, u->userid);
+ u->userid[0] = '\0';
+ } else {
+ d = now - u->lastlogin;
+ if((d > MAX_GUEST_LIFE && (u->userlevel & PERM_LOGINOK) == 0) ||
+ (d > MAX_LIFE && (u->userlevel & PERM_XEMPT) == 0)) {
+ /* expired */
+ int unum;
+
+ unum = searchuser(u->userid, u->userid);
+ strcpy(buf, ctime4(&u->lastlogin));
+ syslog(LOG_NOTICE, "kill user(%d): %s %s", unum, u->userid, buf);
+ sprintf(buf, "mv home/%c/%s tmp/", u->userid[0], u->userid);
+ if(system(buf))
+ syslog(LOG_ERR, "can't move user home: %s", u->userid);
+ u->userid[0] = '\0';
+ setuserid(unum, u->userid);
+ }
+ }
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ now = time(NULL);
+#ifdef Solaris
+ openlog("reaper", LOG_PID, SYSLOG_FACILITY);
+#else
+ openlog("reaper", LOG_PID | LOG_PERROR, SYSLOG_FACILITY);
+#endif
+ chdir(BBSHOME);
+
+ attach_SHM();
+ if(passwd_init())
+ exit(1);
+ passwd_apply(check);
+
+ return 0;
+}
diff --git a/pttbbs/util/rebuildaloha.pl b/pttbbs/util/rebuildaloha.pl
new file mode 100644
index 00000000..c8262e41
--- /dev/null
+++ b/pttbbs/util/rebuildaloha.pl
Binary files differ
diff --git a/pttbbs/util/shmctl.c b/pttbbs/util/shmctl.c
new file mode 100644
index 00000000..939fab39
--- /dev/null
+++ b/pttbbs/util/shmctl.c
@@ -0,0 +1,1221 @@
+/* $Id$ */
+#include "bbs.h"
+#include <sys/wait.h>
+#include <string.h>
+
+extern SHM_t *SHM;
+
+/* utmpfix ----------------------------------------------------------------- */
+/* TODO merge with mbbsd/talk.c logout_friend_online() */
+int logout_friend_online(userinfo_t *utmp)
+{
+ int my_friend_idx, thefriend;
+ int k;
+ int offset=(int) (utmp - &SHM->uinfo[0]);
+ userinfo_t *ui;
+ for(; utmp->friendtotal>0; utmp->friendtotal--) {
+ if( !(0 <= utmp->friendtotal && utmp->friendtotal < MAX_FRIEND) )
+ return 1;
+ my_friend_idx=utmp->friendtotal-1;
+ thefriend = (utmp->friend_online[my_friend_idx] & 0xFFFFFF);
+ utmp->friend_online[my_friend_idx]=0;
+
+ if( !(0 <= thefriend && thefriend < USHM_SIZE) ) {
+ printf("\tonline friend error(%d)\n", thefriend);
+ continue;
+ }
+
+ ui = &SHM->uinfo[thefriend];
+
+ if(ui->pid==0 || ui==utmp)
+ continue;
+ if(ui->friendtotal > MAX_FRIEND || ui->friendtotal<0) {
+ printf("\tfriend(%d) has too many/less(%d) friends\n", thefriend, ui->friendtotal);
+ continue;
+ }
+
+ for(k=0; k<ui->friendtotal && k<MAX_FRIEND &&
+ (int)(ui->friend_online[k] & 0xFFFFFF) !=offset; k++);
+ if( k < ui->friendtotal && k < MAX_FRIEND ){
+ ui->friendtotal--;
+ ui->friend_online[k]=ui->friend_online[ui->friendtotal];
+ ui->friend_online[ui->friendtotal]=0;
+ }
+ }
+ return 0;
+}
+
+void purge_utmp(userinfo_t *uentp)
+{
+ logout_friend_online(uentp);
+ //memset(uentp, 0, sizeof(userinfo_t));
+}
+
+typedef struct {
+ int index;
+ int idle;
+} IDLE_t;
+
+int sfIDLE(const void *a, const void *b)
+{
+ return ((IDLE_t *)b)->idle - ((IDLE_t *)a)->idle;
+}
+
+int utmpfix(int argc, char **argv)
+{
+ int i, fast = 0, nownum = SHM->UTMPnumber;
+ int which, nactive = 0, dofork = 1, daemonsleep = 0;
+ time_t now;
+ char *clean, buf[1024];
+ IDLE_t idle[USHM_SIZE];
+ char changeflag = 0;
+ time_t idletimeout = IDLE_TIMEOUT;
+ int lowerbound = 100, upperbound = 0;
+ char ch;
+
+ int killtop = 0;
+ struct {
+ pid_t pid;
+ int where;
+ } killlist[USHM_SIZE];
+
+ while( (ch = getopt(argc, argv, "nt:l:FD:u:")) != -1 )
+ switch( ch ){
+ case 'n':
+ fast = 1;
+ break;
+ case 't':
+ idletimeout = atoi(optarg);
+ break;
+ case 'l':
+ lowerbound = atoi(optarg);
+ break;
+ case 'F':
+ dofork = 0;
+ break;
+ case 'D':
+ daemonsleep = atoi(optarg);
+ break;
+ case 'u':
+ upperbound = atoi(optarg);
+ break;
+ default:
+ printf("usage:\tshmctl\tutmpfix [-n] [-t timeout] [-F] [-D sleep]\n");
+ return 1;
+ }
+
+ if( daemonsleep )
+ switch( fork() ){
+ case -1:
+ perror("fork()");
+ return 0;
+ case 0:
+ break;
+ default:
+ return 0;
+ }
+
+ if( daemonsleep || dofork ){
+ int times = 1000, status;
+ pid_t pid;
+ while( daemonsleep ? 1 : times-- )
+ switch( pid = fork() ){
+ case -1:
+ sleep(1);
+ break;
+ case 0:
+#ifndef VALGRIND
+ setproctitle("utmpfix");
+#endif
+ goto DoUtmpfix;
+ default:
+#ifndef VALGRIND
+ setproctitle(daemonsleep ? "utmpfixd(wait for %d)" :
+ "utmpfix(wait for %d)", (int)pid);
+#endif
+ waitpid(pid, &status, 0);
+ if( WIFEXITED(status) && !daemonsleep )
+ return 0;
+ if( !WIFEXITED(status) ){
+ /* last utmpfix fails, so SHM->UTMPbusystate is holded */
+ SHM->UTMPbusystate = 0;
+ }
+ }
+ return 0; // never reach
+ }
+
+ DoUtmpfix:
+ killtop=0;
+ changeflag=0;
+ for( i = 0 ; i < 5 ; ++i )
+ if( !SHM->UTMPbusystate )
+ break;
+ else{
+ puts("utmpshm is busy....");
+ sleep(1);
+ }
+ SHM->UTMPbusystate = 1;
+
+ printf("starting scaning... %s \n", (fast ? "(fast mode)" : ""));
+ nownum = SHM->UTMPnumber;
+#ifdef OUTTA_TIMER
+ now = SHM->GV2.e.now;
+#else
+ now = time(NULL);
+#endif
+ for( i = 0, nactive = 0 ; i < USHM_SIZE ; ++i )
+ if( SHM->uinfo[i].pid ){
+ idle[nactive].index = i;
+ idle[nactive].idle = now - SHM->uinfo[i].lastact;
+ ++nactive;
+ }
+ if( !fast )
+ qsort(idle, nactive, sizeof(IDLE_t), sfIDLE);
+
+ #define addkilllist(a) \
+ do { \
+ pid_t pid=SHM->uinfo[(a)].pid; \
+ if(pid > 0) { \
+ killlist[killtop].where = (a); \
+ killlist[killtop++].pid = pid; \
+ } \
+ } while( 0 )
+ for( i = 0 ; i < nactive ; ++i ){
+ which = idle[i].index;
+ clean = NULL;
+ if( !isalpha(SHM->uinfo[which].userid[0]) ){
+ clean = "userid error";
+ addkilllist(which);
+ }
+ else if( memchr(SHM->uinfo[which].userid, '\0', IDLEN + 1) == NULL ){
+ clean = "userid without z";
+ addkilllist(which);
+ }
+ else if( SHM->uinfo[which].friendtotal > MAX_FRIEND || SHM->uinfo[which].friendtotal<0 ){
+ clean = "too many/less friend";
+ addkilllist(which);
+ }
+ else if( searchuser(SHM->uinfo[which].userid, NULL) == 0 ){
+ clean = "user not exist";
+ addkilllist(which);
+ }
+ else if( kill(SHM->uinfo[which].pid, 0) < 0 ){
+ /* 此條件應放最後; 其他欄位沒問題但 process 不存在才 purge_utmp */
+ clean = "process error";
+ purge_utmp(&SHM->uinfo[which]);
+ }
+#ifdef DOTIMEOUT
+ else if( (strcasecmp(SHM->uinfo[which].userid, STR_GUEST)==0 &&
+ idle[i].idle > 60*15) ||
+ (!fast && nownum > lowerbound &&
+ idle[i].idle > idletimeout ) ) {
+ sprintf(buf, "timeout(%s",
+ ctime4(&SHM->uinfo[which].lastact));
+ buf[strlen(buf) - 1] = 0;
+ strcat(buf, ")");
+ clean = buf;
+ addkilllist(which);
+ purge_utmp(&SHM->uinfo[which]);
+ printf("%s\n", buf);
+ --nownum;
+ continue;
+ }
+#endif
+
+ if( clean ){
+ printf("clean %06d(%s), userid: %s\n",
+ i, clean, SHM->uinfo[which].userid);
+ memset(&SHM->uinfo[which], 0, sizeof(userinfo_t));
+ --nownum;
+ changeflag = 1;
+ }
+ }
+ for( i = 0 ; i < killtop ; ++i ){
+ printf("sending SIGHUP to %d\n", (int)killlist[i].pid);
+ kill(killlist[i].pid, SIGHUP);
+ }
+ sleep(3);
+ for( i = 0 ; i < killtop ; ++i )
+ // FIXME 前面已經 memset 把 SHM->uinfo[which] 清掉了, 此處檢查 pid 無用
+ if( SHM->uinfo[killlist[i].where].pid == killlist[i].pid &&
+ kill(killlist[i].pid, 0) == 0 ){ // still alive
+ printf("sending SIGKILL to %d\n", (int)killlist[i].pid);
+ kill(killlist[i].pid, SIGKILL);
+ purge_utmp(&SHM->uinfo[killlist[i].where]);
+ }
+ SHM->UTMPbusystate = 0;
+ if( changeflag )
+ SHM->UTMPneedsort = 1;
+
+ if( daemonsleep ){
+ do{
+ sleep(daemonsleep);
+ } while( upperbound && SHM->UTMPnumber < upperbound );
+ goto DoUtmpfix; /* XXX: goto */
+ }
+ return 0;
+}
+/* end of utmpfix ---------------------------------------------------------- */
+
+/* utmpsortd --------------------------------------------------------------- */
+static int
+cmputmpuserid(const void * i, const void * j)
+{
+ return strncasecmp(SHM->uinfo[*(int*)i].userid, SHM->uinfo[*(int*)j].userid, IDLEN);
+}
+
+static int
+cmputmpmode(const void * i, const void * j)
+{
+ return SHM->uinfo[*(int*)i].mode - SHM->uinfo[*(int*)j].mode;
+}
+
+static int
+cmputmpidle(const void * i, const void * j)
+{
+ return SHM->uinfo[*(int*)i].lastact - SHM->uinfo[*(int*)j].lastact;
+}
+
+static int
+cmputmpfrom(const void * i, const void * j)
+{
+ return strncmp(SHM->uinfo[*(int*)i].from, SHM->uinfo[*(int*)j].from, sizeof(SHM->uinfo[0].from));
+}
+
+static int
+cmputmpfive(const void * i, const void * j)
+{
+ userinfo_t *a=&SHM->uinfo[*(int*)i],*b=&SHM->uinfo[*(int*)j];
+ int played_a=(a->five_win+a->five_lose+a->five_tie)!=0;
+ int played_b=(b->five_win+b->five_lose+b->five_tie)!=0;
+ int type;
+
+ if ((type = played_b - played_a))
+ return type;
+ if (played_a == 0)
+ return 0;
+ if ((type = b->five_win - a->five_win))
+ return type;
+ if ((type = a->five_lose - b->five_lose))
+ return type;
+ return a->five_tie - b->five_tie;
+}
+
+static int
+cmputmpchc(const void * i, const void * j)
+{
+ userinfo_t *a=&SHM->uinfo[*(int*)i],*b=&SHM->uinfo[*(int*)j];
+ int total_a=a->chc_win+a->chc_lose+a->chc_tie;
+ int total_b=b->chc_win+b->chc_lose+b->chc_tie;
+ int played_a=(total_a!=0);
+ int played_b=(total_b!=0);
+ int type;
+
+ // NOTE: 目前 "別找我下棋" 不影響排序
+ /* 1. "找我下棋" 排最前面 */
+ if ((a->withme&WITHME_CHESS)!=(b->withme&WITHME_CHESS))
+ return (a->withme&WITHME_CHESS)?-1:1;
+#ifdef CHC_SORTBY_RATING
+ /* 2. 下超過十盤棋用等級分排序 */
+ if ((total_a>=10)!=(total_b>=10))
+ return (total_a>=10)?-1:1;
+ if (total_a>=10 && total_b>=10) {
+ if (a->chess_elo_rating!=b->chess_elo_rating)
+ return b->chess_elo_rating-a->chess_elo_rating;
+ }
+#endif
+ /* 3. 有下過棋的在沒下過的前面 */
+ if ((type = played_b - played_a))
+ return type;
+ if (played_a == 0)
+ return 0;
+ /* 4. 剩下(下不超過十盤或等級分相同, 或不用等級分排序)的人以勝負和排 */
+ if ((type = b->chc_win - a->chc_win))
+ return type;
+ if ((type = a->chc_lose - b->chc_lose))
+ return type;
+ return a->chc_tie - b->chc_tie;
+}
+
+static int
+cmputmpgo(const void * i, const void * j)
+{
+ userinfo_t *a=&SHM->uinfo[*(int*)i],*b=&SHM->uinfo[*(int*)j];
+ int played_a=(a->go_win+a->go_lose+a->go_tie)!=0;
+ int played_b=(b->go_win+b->go_lose+b->go_tie)!=0;
+ int type;
+
+ if ((type = played_b - played_a))
+ return type;
+ if (played_a == 0)
+ return 0;
+ if ((type = b->go_win - a->go_win))
+ return type;
+ if ((type = a->go_lose - b->go_lose))
+ return type;
+ return a->go_tie - b->go_tie;
+}
+
+static int
+cmputmppid(const void * i, const void * j)
+{
+ return SHM->uinfo[*(int*)i].pid - SHM->uinfo[*(int*)j].pid;
+}
+
+static int
+cmputmpuid(const void * i, const void * j)
+{
+ return SHM->uinfo[*(int*)i].uid - SHM->uinfo[*(int*)j].uid;
+}
+
+inline void utmpsort(int sortall)
+{
+ userinfo_t *uentp;
+ int count, i, ns;
+ short nusers[MAX_BOARD];
+
+
+ SHM->UTMPbusystate = 1;
+#ifdef OUTTA_TIMER
+ SHM->UTMPuptime = SHM->GV2.e.now;
+#else
+ SHM->UTMPuptime = time(NULL);
+#endif
+ ns = (SHM->currsorted ? 0 : 1);
+
+ for (uentp = &SHM->uinfo[0], count = i = 0;
+ i < USHM_SIZE;
+ ++i, uentp = &SHM->uinfo[i]) {
+ if (uentp->pid) {
+ if (uentp->sex < 0 || uentp->sex > 7)
+ purge_utmp(uentp);
+ else
+ SHM->sorted[ns][0][count++] = i;
+ }
+ }
+ SHM->UTMPnumber = count;
+ qsort(SHM->sorted[ns][0], count, sizeof(int), cmputmpuserid);
+ memcpy(SHM->sorted[ns][7],
+ SHM->sorted[ns][0], sizeof(int) * count);
+ memcpy(SHM->sorted[ns][8],
+ SHM->sorted[ns][0], sizeof(int) * count);
+ qsort(SHM->sorted[ns][7], count, sizeof(int), cmputmpuid);
+ qsort(SHM->sorted[ns][8], count, sizeof(int), cmputmppid);
+ if( sortall ){
+ memcpy(SHM->sorted[ns][1],
+ SHM->sorted[ns][0], sizeof(int) * count);
+ memcpy(SHM->sorted[ns][2],
+ SHM->sorted[ns][0], sizeof(int) * count);
+ memcpy(SHM->sorted[ns][3],
+ SHM->sorted[ns][0], sizeof(int) * count);
+ memcpy(SHM->sorted[ns][4],
+ SHM->sorted[ns][0], sizeof(int) * count);
+ memcpy(SHM->sorted[ns][5],
+ SHM->sorted[ns][0], sizeof(int) * count);
+ memcpy(SHM->sorted[ns][6],
+ SHM->sorted[ns][0], sizeof(int) * count);
+ qsort(SHM->sorted[ns][1], count, sizeof(int), cmputmpmode);
+ qsort(SHM->sorted[ns][2], count, sizeof(int), cmputmpidle);
+ qsort(SHM->sorted[ns][3], count, sizeof(int), cmputmpfrom);
+ qsort(SHM->sorted[ns][4], count, sizeof(int), cmputmpfive);
+ qsort(SHM->sorted[ns][5], count, sizeof(int), cmputmpchc);
+ qsort(SHM->sorted[ns][6], count, sizeof(int), cmputmpgo);
+ memset(nusers, 0, sizeof(nusers));
+ for (i = 0; i < count; ++i) {
+ uentp = &SHM->uinfo[SHM->sorted[ns][0][i]];
+ if (uentp && uentp->pid &&
+ 0 < uentp->brc_id && uentp->brc_id < MAX_BOARD)
+ ++nusers[uentp->brc_id - 1];
+ }
+ {
+#if HOTBOARDCACHE
+ int k, r, last = 0, top = 0;
+ int HBcache[HOTBOARDCACHE];
+ for (i = 0; i < HOTBOARDCACHE; i++) HBcache[i]=-1;
+#endif
+ for (i = 0; i < SHM->Bnumber; i++)
+ if (SHM->bcache[i].brdname[0] != 0){
+ SHM->bcache[i].nuser = nusers[i];
+#if HOTBOARDCACHE
+ if( nusers[i] > 8 &&
+ (top < HOTBOARDCACHE || nusers[i] > last) &&
+ IS_BOARD(&SHM->bcache[i]) &&
+#ifdef USE_COOLDOWN
+ !(SHM->bcache[i].brdattr & BRD_COOLDOWN) &&
+#endif
+ IS_OPENBRD(&SHM->bcache[i]) ){
+ for( k = top - 1 ; k >= 0 ; --k )
+ if(HBcache[k]>=0 &&
+ nusers[i] < SHM->bcache[HBcache[k]].nuser )
+ break;
+ if( top < HOTBOARDCACHE )
+ ++top;
+ for( r = top - 1 ; r > (k + 1) ; --r )
+ HBcache[r] = HBcache[r - 1];
+ HBcache[k + 1] = i;
+ last = nusers[HBcache[top - 1]];
+ }
+#endif
+ }
+#if HOTBOARDCACHE
+ memcpy(SHM->HBcache, HBcache, sizeof(HBcache));
+ SHM->nHOTs = top;
+#endif
+ }
+ }
+
+ SHM->currsorted = ns;
+ SHM->UTMPbusystate = 0;
+}
+
+int utmpsortd(int argc, char **argv)
+{
+ pid_t pid;
+ int interval; // sleep interval in microsecond(1/10**6)
+ int sortall, counter = 0;
+
+ if( fork() > 0 ){
+ puts("sortutmpd daemonized...");
+ return 0;
+ }
+
+ if( argc < 2 || (interval = atoi(argv[1])) < 500000 )
+ interval = 1000000; // default to 1 sec
+ sortall = ((argc < 3) ? 1 : atoi(argv[2]));
+
+#ifndef VALGRIND
+ setproctitle("shmctl utmpsortd");
+#endif
+
+ while( 1 ){
+ if( (pid = fork()) != 0 ){
+ int s;
+ waitpid(pid, &s, 0);
+ }
+ else{
+ while( 1 ){
+ int i;
+ for( i = 0 ; SHM->UTMPbusystate && i < 5 ; ++i )
+ usleep(300000);
+
+ if( SHM->UTMPneedsort ){
+ if( ++counter == sortall ){
+ utmpsort(1);
+ counter = 0;
+ }
+ else
+ utmpsort(0);
+ }
+
+ usleep(interval);
+ }
+ }
+ }
+}
+/* end of utmpsortd -------------------------------------------------------- */
+
+char *CTIMEx(char *buf, time4_t t)
+{
+ strcpy(buf, ctime4(&t));
+ buf[strlen(buf) - 1] = 0;
+ return buf;
+}
+int utmpstatus(int argc, char **argv)
+{
+ time_t now;
+ char upbuf[64], nowbuf[64];
+#ifdef OUTTA_TIMER
+ now = SHM->GV2.e.now;
+#else
+ now = time(NULL);
+#endif
+ CTIMEx(upbuf, SHM->UTMPuptime);
+ CTIMEx(nowbuf, now);
+ printf("now: %s\n", nowbuf);
+ printf("currsorted: %d\n", SHM->currsorted);
+ printf("uptime: %s\n", upbuf);
+ printf("number: %d\n", SHM->UTMPnumber);
+ printf("busystate: %d\n", SHM->UTMPbusystate);
+ return 0;
+}
+
+int utmpreset(int argc, char **argv)
+{
+ SHM->UTMPbusystate=0;
+ utmpstatus(0, NULL);
+ return 0;
+}
+
+#define TIMES 10
+int utmpwatch(int argc, char **argv)
+{
+ int i;
+ while( 1 ){
+ for( i = 0 ; i < TIMES ; ++i ){
+ usleep(300);
+ if( !SHM->UTMPbusystate )
+ break;
+ puts("busying...");
+ }
+ if( i == TIMES ){
+ puts("reset!");
+ SHM->UTMPbusystate = 0;
+ }
+ }
+ return 0;
+}
+
+int utmpnum(int argc, char **argv)
+{
+ printf("%d.0\n", SHM->UTMPnumber);
+ return 0;
+}
+
+char *GV2str[] = {"dymaxactive", "toomanyusers",
+ "noonlineuser","now", "nWelcomes", "shutdown", NULL};
+int showglobal(int argc, char **argv)
+{
+ int i;
+ for( i = 0 ; GV2str[i] != NULL ; ++i )
+ printf("GV2.%s = %d\n", GV2str[i], SHM->GV2.v[i]);
+ return 0;
+}
+
+int setglobal(int argc, char **argv)
+{
+ int where, value;
+ if( argc != 3 ){
+ puts("usage: shmctl setglobal (GV2) newvalue");
+ return 1;
+ }
+ value = atoi(argv[2]);
+
+ for( where = 0 ; GV2str[where] != NULL ; ++where )
+ if( strcmp(GV2str[where], argv[1]) == 0 ){
+ printf("GV2.%s = %d -> ", GV2str[where], SHM->GV2.v[where]);
+ printf("%d\n", SHM->GV2.v[where] = value);
+ return 0;
+ }
+ printf("SHM global variable %s not found\n", argv[1]);
+
+ return 1;
+}
+
+int listpid(int argc, char **argv)
+{
+ int i;
+ for( i = 0 ; i < USHM_SIZE ; ++i )
+ if( SHM->uinfo[i].pid > 0 )
+ printf("%d\n", SHM->uinfo[i].pid);
+ return 0;
+}
+
+int listbrd(int argc, char **argv)
+{
+ int i = 0;
+
+ if (argc == 2)
+ i = atoi(argv[1]);
+
+ if(i > 0 && i < MAX_BOARD)
+ {
+ int di = i;
+
+ /* print details */
+ boardheader_t b = bcache[di-1];
+ printf("brdname(bid):\t%s\n", b.brdname);
+ printf("title:\t%s\n", b.title);
+ printf("BM:\t%s\n", b.BM);
+ printf("brdattr:\t%08x ", b.brdattr);
+
+#define SHOWBRDATTR(x) if(b.brdattr & x) printf(#x " ");
+
+ SHOWBRDATTR(BRD_NOZAP);
+ SHOWBRDATTR(BRD_NOCOUNT);
+ SHOWBRDATTR(BRD_NOTRAN);
+ SHOWBRDATTR(BRD_GROUPBOARD);
+ SHOWBRDATTR(BRD_HIDE);
+ SHOWBRDATTR(BRD_POSTMASK);
+ SHOWBRDATTR(BRD_ANONYMOUS);
+ SHOWBRDATTR(BRD_DEFAULTANONYMOUS);
+ SHOWBRDATTR(BRD_BAD);
+ SHOWBRDATTR(BRD_VOTEBOARD);
+ SHOWBRDATTR(BRD_WARNEL);
+ SHOWBRDATTR(BRD_TOP);
+ SHOWBRDATTR(BRD_NORECOMMEND);
+ SHOWBRDATTR(BRD_BLOG);
+ SHOWBRDATTR(BRD_BMCOUNT);
+ SHOWBRDATTR(BRD_SYMBOLIC);
+ SHOWBRDATTR(BRD_NOBOO);
+ SHOWBRDATTR(BRD_LOCALSAVE);
+ SHOWBRDATTR(BRD_RESTRICTEDPOST);
+ SHOWBRDATTR(BRD_GUESTPOST);
+ SHOWBRDATTR(BRD_COOLDOWN);
+ SHOWBRDATTR(BRD_CPLOG);
+ SHOWBRDATTR(BRD_NOFASTRECMD);
+
+ printf("\n");
+
+ printf("post_limit_posts:\t%d\n", b.post_limit_posts);
+ printf("post_limit_logins:\t%d\n", b.post_limit_logins);
+ printf("post_limit_regtime:\t%d\n", b.post_limit_regtime);
+ printf("level:\t%d\n", b.level);
+ printf("gid:\t%d\n", b.gid);
+ printf("parent:\t%d\n", b.parent);
+ printf("childcount:\t%d\n", b.childcount);
+ printf("nuser:\t%d\n", b.nuser);
+
+ printf("next[0]:\t%d\n", b.next[0]);
+ printf("next[1]:\t%d\n", b.next[1]);
+ printf("firstchild[0]:\t%d\n", b.firstchild[0]);
+ printf("firstchild[1]:\t%d\n", b.firstchild[1]);
+ printf("---- children: ---- \n");
+ for (i = 0; i < MAX_BOARD; i++)
+ {
+ if(bcache[i].gid == di && bcache[i].brdname[0])
+ printf("%4d %-13s%-25.25s%s\n",
+ i+1, bcache[i].brdname,
+ bcache[i].BM, bcache[i].title);
+ }
+ } else
+ for( i = 0 ; i < MAX_BOARD ; ++i )
+ {
+ if(bcache[i].brdname[0])
+ printf("%03d %-13s%-25.25s%s\n", i+1, bcache[i].brdname, bcache[i].BM, bcache[i].title);
+ }
+ return 0;
+}
+
+#if 0
+static void update_brd(int i) {
+ if(substitute_record(BBSHOME "/" FN_BOARD, &bcache[i],sizeof(boardheader_t),i+1) < 0) {
+ printf("\n! CANNOT WRITE: " BBSHOME "/" FN_BOARD "\n");
+ exit(0);
+ }
+}
+#endif
+
+int fixbrd(int argc, char **argv)
+{
+ int i = 0;
+
+ for( i = 0 ; i < MAX_BOARD ; ++i )
+ {
+ if(!bcache[i].brdname[0])
+ continue;
+ /* do whatever you wanna fix here. */
+
+#if 0
+ /* upgrade from old NOFASTRECMD (default pause) to new format
+ * (BM config) */
+ if(bcache[i].brdattr & BRD_NOFASTRECMD)
+ {
+ printf("board with no fastrecmd: #%d [%s]\n",
+ i+1, bcache[i].brdname);
+ bcache[i].fastrecommend_pause = 90;
+ update_brd(i);
+ }
+#endif
+
+#if 0
+ /* fix parent, hope so */
+ if(bcache[i].parent > MAX_BOARD) {
+ printf("parent: #%d [%s] *%d\n", i+1, bcache[i].brdname, bcache[i].parent);
+ bcache[i].parent = 0;
+ update_brd(i);
+ }
+#endif
+
+#if 0
+ /* alert wrong gid */
+ if(bcache[i].gid < 1) {
+ printf("gid: #%d [%s] *%d\n", i+1, bcache[i].brdname, bcache[i].gid);
+ }
+#endif
+ }
+ return 0;
+}
+
+#ifdef OUTTA_TIMER
+int timed(int argc, char **argv)
+{
+ pid_t pid;
+ if( (pid = fork()) < 0 )
+ perror("fork()");
+ if( pid != 0 )
+ return 0;
+#ifndef VALGRIND
+ setproctitle("shmctl timed");
+#endif
+ while( 1 ){
+ SHM->GV2.e.now = time(NULL);
+ sleep(1);
+ }
+}
+#endif
+
+#if 0
+void buildclass(int bid, int level)
+{
+ boardheader_t *bptr;
+ if( level == 20 ){ /* for safty */
+ printf("is there something wrong? class level: %d\n", level);
+ return;
+ }
+ bptr = &bcache[bid];
+ if (bptr->firstchild[0] == NULL || bptr->childcount <= 0)
+ load_uidofgid(bid + 1, 1); /* 因為這邊 bid從 0開始, 所以再 +1 回來 */
+ if (bptr->firstchild[1] == NULL || bptr->childcount <= 0)
+ load_uidofgid(bid + 1, 1); /* 因為這邊 bid從 0開始, 所以再 +1 回來 */
+
+ for (bptr = bptr->firstchild[0]; bptr != NULL ; bptr = bptr->next[0]) {
+ if( bptr->brdattr & BRD_GROUPBOARD )
+ buildclass(bptr - bcache, level + 1);
+ }
+}
+#endif
+
+int bBMC(int argc, char **argv)
+{
+ int i;
+ for( i = 0 ; i < MAX_BOARD ; ++i )
+ if( bcache[i].brdname[0] )
+ buildBMcache(i + 1); /* XXXbid */
+ return 0;
+}
+
+#ifdef NOKILLWATERBALL
+int nkwbd(int argc, char **argv)
+{
+ int ch, sleeptime = 5, timeout = 5;
+ while( (ch = getopt(argc, argv, "s:t:h")) != -1 )
+ switch( ch ){
+ case 's':
+ if( (sleeptime = atoi(optarg)) <= 0 ){
+ fprintf(stderr, "sleeptime <= 0? set to 5");
+ sleeptime = 5;
+ }
+ break;
+
+ case 't':
+ if( (timeout = atoi(optarg)) <= 0 ){
+ fprintf(stderr, "timeout <= 0? set to 5");
+ timeout = 20;
+ }
+ break;
+
+ default:
+ fprintf(stderr, "usage: shmctl nkwbd [-s sleeptime] [-t timeout]\n");
+ return 0;
+ }
+
+#ifndef VALGRIND
+ setproctitle("shmctl nkwbd(sleep%d,timeout%d)", sleeptime, timeout);
+#endif
+
+ switch( fork() ){
+ case -1:
+ perror("fork()");
+ return 0;
+ break;
+
+ case 0: /* child */
+ while( 1 ){
+ int i;
+ time_t now, t;
+
+#ifdef OUTTA_TIMER
+ now = SHM->GV2.e.now;
+#else
+ now = time(NULL);
+#endif
+ t = now - timeout;
+
+ for( i = 0 ; i < USHM_SIZE ; ++i )
+ if( SHM->uinfo[i].pid &&
+ SHM->uinfo[i].wbtime &&
+ SHM->uinfo[i].wbtime < t ){
+ kill(SHM->uinfo[i].pid, SIGUSR2);
+ }
+ sleep(sleeptime);
+ }
+ break;
+
+ default: /* parent */
+ fprintf(stderr, "nkwbd\n");
+ return 0;
+ }
+ return 0;
+}
+#endif
+
+int SHMinit(int argc, char **argv)
+{
+ int ch;
+ int no_uhash_loader = 0;
+ while( (ch = getopt(argc, argv, "n")) != -1 )
+ switch( ch ){
+ case 'n':
+ no_uhash_loader = 1;
+ break;
+ default:
+ printf("usage:\tshmctl\tSHMinit\n");
+ return 0;
+ }
+
+ puts("loading uhash...");
+ system("bin/uhash_loader");
+
+ attach_SHM();
+
+#ifdef OUTTA_TIMER
+ puts("timed...");
+ timed(1, argv);
+#endif
+
+ puts("loading bcache...");
+ reload_bcache();
+
+ puts("building BMcache...");
+ bBMC(1, argv);
+
+#if 0
+ puts("building class...");
+ buildclass(0, 0);
+#endif
+
+ if( !no_uhash_loader ){
+ puts("utmpsortd...");
+ utmpsortd(1, argv);
+ }
+
+#ifdef NOKILLWATERBALL
+ puts("nkwbd...");
+ nkwbd(1, argv);
+#endif
+
+ return 0;
+}
+
+int hotboard(int argc, char **argv)
+{
+#define isvisiableboard(bptr) \
+ ((bptr)->brdname[0] && \
+ !((bptr)->brdattr & BRD_GROUPBOARD) && \
+ !(((bptr)->brdattr & (BRD_HIDE | BRD_TOP)) || \
+ ((bptr)->level && !((bptr)->brdattr & BRD_POSTMASK) && \
+ ((bptr)->level & \
+ ~(PERM_BASIC|PERM_CHAT|PERM_PAGE|PERM_POST|PERM_LOGINOK)))))
+
+ int ch, topn = 20, i, nbrds, j, k, nusers;
+ struct bs {
+ int nusers;
+ boardheader_t *b;
+ } *brd;
+
+ while( (ch = getopt(argc, argv, "t:h")) != -1 )
+ switch( ch ){
+ case 't':
+ topn = atoi(optarg);
+ if( topn <= 0 ){
+ goto hotboardusage;
+ return 1;
+ }
+ break;
+ case 'h':
+ default:
+ hotboardusage:
+ fprintf(stderr, "usage: shmctl hotboard [-t topn]\n");
+ return 1;
+ }
+
+ brd = (struct bs *)malloc(sizeof(struct bs) * topn);
+ brd[0].b = &SHM->bcache[0];
+ brd[0].nusers = brd[0].b->brdname[0] ? brd[0].b->nuser : 0;
+ nbrds = 1;
+
+ for( i = 1 ; i < MAX_BOARD ; ++i )
+ if( (isvisiableboard(&SHM->bcache[i])) &&
+ (nbrds != topn ||
+ SHM->bcache[i].nuser > brd[nbrds - 1].nusers) ){
+
+ nusers = SHM->bcache[i].nuser; // no race ?
+ for( k = nbrds - 2 ; k >= 0 ; --k )
+ if( brd[k].nusers > nusers )
+ break;
+
+ if( (k + 1) < nbrds && (k + 2) < topn )
+ for( j = nbrds - 1 ; j >= k + 1 ; --j )
+ brd[j] = brd[j - 1];
+ brd[k + 1].nusers = nusers;
+ brd[k + 1].b = &SHM->bcache[i];
+
+ if( nbrds < topn )
+ ++nbrds;
+ }
+
+ for( i = 0 ; i < nbrds ; ++i )
+ printf("%05d|%-12s|%s\n",
+ brd[i].nusers, brd[i].b->brdname, brd[i].b->title);
+ return 0;
+}
+
+int usermode(int argc, char **argv)
+{
+ int i, modes[MAX_MODES];
+ memset(modes, 0, sizeof(modes));
+ for( i = 0 ; i < USHM_SIZE ; ++i )
+ if( SHM->uinfo[i].userid[0] )
+ ++modes[ (int)SHM->uinfo[i].mode ];
+
+ for( i = 0 ; i < MAX_MODES ; ++i )
+ printf("%03d|%05d|%s\n", i, modes[i], ModeTypeTable[i]);
+ return 0;
+}
+
+int torb(int argc, char **argv)
+{
+ reload_bcache();
+ puts("bcache reloaded");
+ return 0;
+}
+
+int fixbcache(int argc, char **argv)
+{
+ void lockbcache(void)
+ {
+ int i;
+ for( i = 0 ; i < 10 && SHM->Bbusystate ; ++i ){
+ printf("SHM->Bbusystate is currently locked (value: %d). "
+ "please wait... ", SHM->Bbusystate);
+ sleep(1);
+ }
+ if( i == 10 )
+ puts("steal bcache lock\n");
+ SHM->Bbusystate = 1;
+ }
+ void unlockbcache(void)
+ {
+ SHM->Bbusystate = 0;
+ }
+ int n, fd, bid, changed = 0;
+ boardheader_t bh;
+
+ if( (fd = open(fn_board, O_RDONLY)) < 0 ){
+ perror("open .BRD");
+ return 1;
+ }
+
+ for( bid = 0 ;
+ (bid < MAX_BOARD && read(fd, &bh, sizeof(bh)) == sizeof(bh)) ;
+ ++bid ){
+ if( strcmp(bh.brdname, bcache[bid].brdname) != 0 ){
+ char fn[PATHLEN];
+ printf("bid: %d, brdname not match(.BRD: %s, bcache: %s). "
+ "fix it!\n",
+ bid + 1, bh.brdname, bcache[bid].brdname);
+ changed = 1;
+ lockbcache();
+ bcache[bid] = bh;
+ unlockbcache();
+
+ sprintf(fn, "boards/%c/%s/.DIR.bottom",
+ bh.brdname[0],
+ bh.brdname);
+ n = get_num_records(fn, sizeof(fileheader_t));
+ if( n > 5 )
+ n = 5;
+ SHM->n_bottom[bid] = n;
+ }
+ }
+ close(fd);
+ if( changed ){
+ puts("re-sort bcache");
+ sort_bcache();
+ }
+ return 0;
+}
+
+int rlfcache(int argc, char **argv)
+{
+ reload_fcache();
+ puts("fcache reloaded");
+ return 0;
+}
+
+int iszero(void *addr, int size)
+{
+ char *a=(char*)addr;
+ int i;
+ for(i=0;i<size;i++)
+ if(a[i]!=0) return 0;
+ return 1;
+}
+
+#define TESTZERO(x,i) do { if(!iszero((x), sizeof(x))) printf("%s is dirty(i=%d)\n",#x,i); } while(0);
+int testgap(int argc, char *argv[])
+{
+ int i;
+ TESTZERO(SHM->gap_1,0);
+ TESTZERO(SHM->gap_2,0);
+ TESTZERO(SHM->gap_3,0);
+ TESTZERO(SHM->gap_4,0);
+ TESTZERO(SHM->gap_5,0);
+ TESTZERO(SHM->gap_6,0);
+ TESTZERO(SHM->gap_7,0);
+ TESTZERO(SHM->gap_8,0);
+ TESTZERO(SHM->gap_9,0);
+ TESTZERO(SHM->gap_10,0);
+ TESTZERO(SHM->gap_11,0);
+ TESTZERO(SHM->gap_12,0);
+ TESTZERO(SHM->gap_13,0);
+ TESTZERO(SHM->gap_14,0);
+ TESTZERO(SHM->gap_15,0);
+ TESTZERO(SHM->gap_16,0);
+ TESTZERO(SHM->gap_17,0);
+ TESTZERO(SHM->gap_18,0);
+ TESTZERO(SHM->gap_19,0);
+ for(i=0; i<USHM_SIZE; i++) {
+ TESTZERO(SHM->uinfo[i].gap_1,i);
+ TESTZERO(SHM->uinfo[i].gap_2,i);
+ TESTZERO(SHM->uinfo[i].gap_3,i);
+ TESTZERO(SHM->uinfo[i].gap_4,i);
+ }
+ return 0;
+}
+
+int showstat(int argc, char *argv[])
+{
+ int i;
+ int flag_clear=0;
+ char *stat_desc[]={
+ "STAT_LOGIN",
+ "STAT_SHELLLOGIN",
+ "STAT_VEDIT",
+ "STAT_TALKREQUEST",
+ "STAT_WRITEREQUEST",
+ "STAT_MORE",
+ "STAT_SYSWRITESOCKET",
+ "STAT_SYSSELECT",
+ "STAT_SYSREADSOCKET",
+ "STAT_DOSEND",
+ "STAT_SEARCHUSER",
+ "STAT_THREAD",
+ "STAT_SELECTREAD",
+ "STAT_QUERY",
+ "STAT_DOTALK",
+ "STAT_FRIENDDESC",
+ "STAT_FRIENDDESC_FILE",
+ "STAT_PICKMYFRIEND",
+ "STAT_PICKBFRIEND",
+ "STAT_GAMBLE",
+ "STAT_DOPOST",
+ "STAT_READPOST",
+ "STAT_RECOMMEND",
+ "STAT_TODAYLOGIN_MIN",
+ "STAT_TODAYLOGIN_MAX",
+ "STAT_SIGINT",
+ "STAT_SIGQUIT",
+ "STAT_SIGILL",
+ "STAT_SIGABRT",
+ "STAT_SIGFPE",
+ "STAT_SIGBUS",
+ "STAT_SIGSEGV",
+ "STAT_READPOST_12HR",
+ "STAT_READPOST_1DAY",
+ "STAT_READPOST_3DAY",
+ "STAT_READPOST_7DAY",
+ "STAT_READPOST_OLD",
+ "STAT_SIGXCPU",
+ };
+
+ if(argv[1] && strcmp(argv[1],"-c")==0)
+ flag_clear=1;
+ for(i=0; i<STAT_NUM; i++) {
+ char *desc= i*sizeof(char*)<sizeof(stat_desc)?stat_desc[i]:"?";
+ printf("%s:\t%d\n", desc, SHM->statistic[i]);
+ }
+ if(flag_clear)
+ memset(SHM->statistic, 0, sizeof(SHM->statistic));
+ return 0;
+}
+
+int dummy(int argc, char *argv[])
+{
+ return 0;
+}
+
+struct {
+ int (*func)(int, char **);
+ char *cmd, *descript;
+} cmd[] = {
+ {dummy, "\b\b\b\bStart daemon:", ""},
+ {utmpsortd, "utmpsortd", "utmp sorting daemon"},
+#ifdef OUTTA_TIMER
+ {timed, "timed", "time daemon for OUTTA_TIMER"},
+#endif
+#ifdef NOKILLWATERBALL
+ {nkwbd, "nkwbd", "NOKillWaterBall daemon"},
+#endif
+
+ {dummy, "\b\b\b\bBuild cache/fix tool:", ""},
+ {torb, "reloadbcache", "reload bcache"},
+ {fixbcache, "fixbcache", "fix bcache"},
+ {rlfcache, "reloadfcache", "reload fcache"},
+ {bBMC, "bBMC", "build BM cache"},
+ {utmpfix, "utmpfix", "clear dead userlist entry & kick idle user"},
+ {utmpreset, "utmpreset", "SHM->busystate=0"},
+ {utmpwatch, "utmpwatch", "to see if busystate is always 1 then fix it"},
+
+ {dummy, "\b\b\b\bShow info:", ""},
+ {utmpnum, "utmpnum", "print SHM->number for snmpd"},
+ {utmpstatus, "utmpstatus", "list utmpstatus"},
+ {listpid, "listpid", "list all pids of mbbsd"},
+ {listbrd, "listbrd", "list board info in SHM"},
+ {fixbrd, "fixbrd", "fix board info in SHM"},
+ {hotboard, "hotboard", "list boards of most bfriends"},
+ {usermode, "usermode", "list #users in the same mode"},
+ {showstat, "showstat", "show statistics"},
+ {testgap, "testgap", "test SHM->gap zeroness"},
+
+ {dummy, "\b\b\b\bMisc:", ""},
+ {showglobal, "showglobal", "show GLOBALVAR[]"},
+ {setglobal, "setglobal", "set GLOBALVAR[]"},
+ {SHMinit, "SHMinit", "initialize SHM (including uhash_loader)"},
+ {NULL, NULL, NULL}
+};
+
+extern char ** environ;
+
+int main(int argc, char **argv)
+{
+ int i = 0;
+
+ chdir(BBSHOME);
+ initsetproctitle(argc, argv, environ);
+ if( argc >= 2 ){
+ if( strcmp(argv[1], "init") == 0 ){
+ /* in this case, do NOT run attach_SHM here.
+ because uhash_loader is not run yet. */
+ return SHMinit(argc - 1, &argv[1]);
+ }
+
+ attach_SHM();
+ /* shmctl doesn't need resolve_boards() first */
+ //resolve_boards();
+ resolve_garbage();
+ resolve_fcache();
+ for( i = 0 ; cmd[i].func != NULL ; ++i )
+ if( strcmp(cmd[i].cmd, argv[1]) == 0 ){
+ return cmd[i].func(argc - 1, &argv[1]);
+ }
+ }
+ if( argc == 1 || cmd[i].func == NULL ){
+ /* usage */
+ printf("usage: shmctl [command] [options]\n");
+ printf("commands:\n");
+ for( i = 0 ; cmd[i].func != NULL ; ++i )
+ printf("\t%-15s%s\n", cmd[i].cmd, cmd[i].descript);
+ }
+ return 0;
+}
diff --git a/pttbbs/util/showboard.c b/pttbbs/util/showboard.c
new file mode 100644
index 00000000..41627cfe
--- /dev/null
+++ b/pttbbs/util/showboard.c
@@ -0,0 +1,93 @@
+/* $Id$ */
+/* 看板一覽表(sorted) */
+#include "bbs.h"
+
+boardheader_t allbrd[MAX_BOARD];
+
+int board_cmp(boardheader_t *a, boardheader_t *b)
+{
+ return (strcasecmp(a->brdname, b->brdname));
+}
+
+
+int main(int argc, char *argv[])
+{
+ int inf, i = 0, detail_i = 0, count;
+
+ if (argc < 2) {
+ printf("Usage:\t%s .BRD [bid]\n", argv[0]);
+ exit(1);
+ }
+
+
+ inf = open(argv[1], O_RDONLY);
+ if (inf == -1) {
+ printf("error open file\n");
+ exit(1);
+ }
+
+/* read in all boards */
+
+ i = 0;
+ memset(allbrd, 0, MAX_BOARD * sizeof(boardheader_t));
+ while (read(inf, &allbrd[i], sizeof(boardheader_t)) == sizeof(boardheader_t)) {
+ if (allbrd[i].brdname[0] ) {
+ i++;
+ }
+ }
+ close(inf);
+
+/* sort them by name */
+ count = i;
+#if 0
+ qsort(allbrd, count, sizeof(boardheader_t), board_cmp);
+#endif
+
+/* write out the target file */
+
+ if (argc > 2) {
+ detail_i = atoi(argv[2]);
+ if (detail_i < 1 || detail_i > count)
+ detail_i = -1;
+ } else {
+ detail_i = -1;
+ }
+
+ if (detail_i < 0) {
+ printf(
+ " 看板名稱 板主 類別 中文敘述\n"
+ " -----------------------------------------------------------------\n");
+ for (i = 0; i < count; i++) {
+ printf("%4d %-13s%-25.25s%s\n", i+1, allbrd[i].brdname, allbrd[i].BM, allbrd[i].title);
+ }
+ } else {
+ /* print details */
+ boardheader_t b = allbrd[detail_i - 1];
+ printf("brdname(bid):\t%s\n", b.brdname);
+ printf("title:\t%s\n", b.title);
+ printf("BM:\t%s\n", b.BM);
+ printf("brdattr:\t%08x\n", b.brdattr);
+ printf("post_limit_posts:\t%d\n", b.post_limit_posts);
+ printf("post_limit_logins:\t%d\n", b.post_limit_logins);
+ printf("post_limit_regtime:\t%d\n", b.post_limit_regtime);
+ printf("level:\t%d\n", b.level);
+ printf("gid:\t%d\n", b.gid);
+ printf("parent:\t%d\n", b.parent);
+ printf("childcount:\t%d\n", b.childcount);
+ printf("nuser:\t%d\n", b.nuser);
+
+ printf("next[0]:\t%d\n", b.next[0]);
+ printf("next[1]:\t%d\n", b.next[1]);
+ printf("firstchild[0]:\t%d\n", b.firstchild[0]);
+ printf("firstchild[1]:\t%d\n", b.firstchild[1]);
+ /* traverse to find my children */
+ printf("---- children: ---- \n");
+ for (i = 0; i < count; i++) {
+ if(allbrd[i].gid == detail_i)
+ printf("%4d %-13s%-25.25s%s\n",
+ i+1, allbrd[i].brdname,
+ allbrd[i].BM, allbrd[i].title);
+ }
+ }
+ return 0;
+}
diff --git a/pttbbs/util/stock.perl b/pttbbs/util/stock.perl
new file mode 100644
index 00000000..e9d57580
--- /dev/null
+++ b/pttbbs/util/stock.perl
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+# $Id: stock.perl,v 1.1 2002/03/07 15:13:46 in2 Exp $
+#
+# 不能跑的話,看看 bbspost 的路徑是否正確。
+# 如果發出的 post 沒有氣象報告而是說 URL 找不到,則確定一下能不能看到
+# 中央氣象局的 WWW 及 URL 是否正確。
+# 理論上適用所有 Eagle BBS 系列。
+# -- Beagle Apr 13 1997
+open(BBSPOST, " >etc/stock.tmp");
+
+# Header
+# 內容
+my $url = 'http://sii.tse.com.tw/html/T31';
+open(WEATHER, "/usr/bin/lynx -dump $url |");
+while(<WEATHER>) {
+ next if $_ eq "\n";
+ last if m/^References/;
+
+ s/\[[0-9\]]*//g;
+
+ print BBSPOST $_;
+}
+close WEATHER;
+
+# 簽名檔
+print BBSPOST "\n--\n";
+print BBSPOST "我是beagle所有可愛的小餅乾...跨海為Ptt服務\n";
+print BBSPOST "--\n";
+print BBSPOST "☆ [Origin: ◎果醬小站◎] [From: [藍莓鬆餅屋] ] ";
+
+close BBSPOST;
+
diff --git a/pttbbs/util/stock.sh b/pttbbs/util/stock.sh
new file mode 100644
index 00000000..907ddb73
--- /dev/null
+++ b/pttbbs/util/stock.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+# $Id: stock.sh,v 1.1 2002/03/07 15:13:46 in2 Exp $
+#
+bin/stock.perl
+bin/post Record 今日股票收盤價 [股市小姐] etc/stock.tmp
diff --git a/pttbbs/util/tarqueue.pl b/pttbbs/util/tarqueue.pl
new file mode 100644
index 00000000..292fa32f
--- /dev/null
+++ b/pttbbs/util/tarqueue.pl
@@ -0,0 +1,81 @@
+#!/usr/bin/perl
+# $Id$
+use lib '/home/bbs/bin/';
+use LocalVars;
+use strict;
+use Mail::Sender;
+use POSIX;
+
+no strict 'subs';
+setpriority(PRIO_PROCESS, $$, 20);
+use strict subs;
+chdir $BBSHOME;
+open LOG, ">> log/tarqueue.log";
+
+foreach my $board ( <$JOBSPOOL/tarqueue.*> ){
+ $board =~ s/.*tarqueue\.//;
+ ProcessBoard($board);
+ unlink "$JOBSPOOL/tarqueue.$board";
+}
+close DIR;
+close LOG;
+
+sub ProcessBoard
+{
+ my($board)= @_;
+ my($cmd, $owner, $email, $bakboard, $bakman, $now);
+
+ $now = substr(POSIX::ctime(time()), 0, -1);
+ open FH, "< $JOBSPOOL/tarqueue.$board";
+ chomp($owner = <FH>);
+ chomp($email = <FH>);
+ chomp(($bakboard, $bakman) = split(/,/, <FH>));
+ close FH;
+
+ print LOG sprintf("%-28s %-12s %-12s %d %d %s\n",
+ $now, $owner, $board, $bakboard, $bakman, $email);
+
+ MakeMail({tartarget => "$TMP/$board.tgz",
+ tarsource => "boards/". substr($board, 0, 1). "/$board",
+ mailto => "$board的板主$owner <$email>",
+ subject => "$board的看板備份",
+ from => "$board的板主$owner <$owner.bbs\@$MYHOSTNAME>",
+ body =>
+ "\n\n\t $owner 您好,收到這封信,表示您已經收到看板備份。\n\n".
+ "\t謝謝您的耐心等待,以及使用 $hostname的看板備份系統,\n\n".
+ "\t如有任何疑問,歡迎寄信給站長,我們會很樂於給予協助。\n\n\n".
+ "\t最後,祝 $owner 平安快樂! ^_^\n\n\n".
+ "\t $hostname站長群. \n\t$now"
+ }) if( $bakboard );
+
+ MakeMail({tartarget => "$TMP/man.$board.tgz",
+ tarsource => "man/boards/". substr($board, 0, 1). "/$board",
+ mailto => "$board的板主$owner <$email>",
+ subject => "$board的精華區備份",
+ from => "$board的板主$owner <$owner.bbs\@$MYHOSTNAME>",
+ body =>
+ "\n\n\t $owner 您好,收到這封信,表示您已經收到精華區備份。\n\n".
+ "\t謝謝您的耐心等待,以及使用 $hostname的看板備份系統,\n\n".
+ "\t如有任何疑問,歡迎寄信給站長,我們會很樂於給予協助。\n\n\n".
+ "\t最後,祝 $owner 平安快樂! ^_^\n\n\n".
+ "\t $hostname站長群. \n\t$now"
+ }) if( $bakman );
+
+}
+
+sub MakeMail
+{
+ my($arg) = @_;
+ my $sender;
+ `$TAR zcf $arg->{tartarget} $arg->{tarsource}`;
+ $sender = new Mail::Sender{smtp => $SMTPSERVER,
+ from => $arg->{from}};
+ $sender->MailFile({to => $arg->{mailto},
+ subject => $arg->{subject},
+ msg => $arg->{body},
+ file => $arg->{tartarget},
+ b_charset => 'big5',
+ b_encoding => '8bit',
+ ctype => 'application/x-tar-gz'});
+ unlink $arg->{tartarget};
+}
diff --git a/pttbbs/util/toplazyBBM.c b/pttbbs/util/toplazyBBM.c
new file mode 100644
index 00000000..e4df67f0
--- /dev/null
+++ b/pttbbs/util/toplazyBBM.c
@@ -0,0 +1,196 @@
+/* $Id$ */
+#define _UTIL_C_
+#include "bbs.h"
+
+#define OUTFILE BBSHOME "/etc/toplazyBBM"
+#define FIREFILE BBSHOME "/etc/topfireBBM"
+
+extern boardheader_t *bcache;
+extern int numboards;
+
+boardheader_t allbrd[MAX_BOARD];
+typedef struct lostbm {
+ char bmname[IDLEN + 1];
+ char *title;
+ char *ctitle;
+ int lostdays;
+} lostbm;
+lostbm lostbms[MAX_BOARD];
+
+typedef struct BMarray{
+ char bmname[IDLEN + 1];
+ int flag;
+} BMArray;
+BMArray bms[3];
+
+int bmlostdays_cmp(const void *va, const void *vb)
+{
+ lostbm *a=(lostbm *)va, *b=(lostbm *)vb;
+ if (a->lostdays > b->lostdays) return -1;
+ else if (a->lostdays == b->lostdays) return 0;
+ else return 1;
+}
+
+
+int LINK(char* src, char* dst){
+ char cmd[200];
+ if(symlink(src,dst) == -1)
+ {
+ sprintf(cmd, "/bin/cp -R %s %s", src, dst);
+ return system(cmd);
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int bmid, i, j=0;
+ FILE *inf, *firef;
+
+ attach_SHM();
+ resolve_boards();
+
+ if(passwd_init())
+ exit(1);
+
+ memcpy(allbrd,bcache,numboards*sizeof(boardheader_t));
+
+ /* write out the target file */
+ printf("Starting Checking\n");
+ inf = fopen(OUTFILE, "w+");
+ if(inf == NULL){
+ printf("open file error : %s\n", OUTFILE);
+ exit(1);
+ }
+ firef = fopen(FIREFILE, "w+");
+ if(firef == NULL){
+ printf("open file error : %s\n", FIREFILE);
+ exit(1);
+ }
+
+ fprintf(inf, "警告: 板主若於兩個月未上站,將予於免職\n");
+ fprintf(inf,
+ "看板名稱 "
+ " 板主 幾天沒來啦\n"
+ "---------------------------------------------------"
+ "-------------------\n");
+
+ fprintf(firef, "免職板主\n");
+ fprintf(firef,
+ "看板名稱 "
+ " 板主 幾天沒來啦\n"
+ "---------------------------------------------------"
+ "-------------------\n");
+
+
+ j = 0 ;
+ for (i = 0; i < numboards; i++) {
+ char *p, bmbuf[IDLEN * 3 + 3];
+ int index = 0, flag = 0, k, n;
+ userec_t xuser;
+ p=strtok(allbrd[i].BM,"/ ");
+
+ if(p)
+ do
+ {
+ if(allbrd[i].brdname[0] == '\0' || (allbrd[i].brdattr & BRD_GROUPBOARD) ==0 ) continue;
+ if (*p == '[' ){p[strlen(p)-1]='\0'; p++;}
+ bmid=getuser(p, &xuser);
+ strlcpy(bms[index].bmname, p, sizeof(bms[index].bmname));
+ bms[index].flag = 0;
+ if (((((int)time(NULL)-(int)xuser.lastlogin)/(60*60*24))>=7)
+ //&& isalpha(allbrd[i].brdname[0])
+ //&& isalpha(allbrd[i].BM[0])
+ && !(xuser.userlevel & PERM_SYSOPHIDE)
+ && !(xuser.userlevel & PERM_SYSOP))
+ {
+ strlcpy(lostbms[j].bmname, p, sizeof(bms[index].bmname));
+ lostbms[j].title = allbrd[i].brdname;
+ lostbms[j].ctitle = allbrd[i].title;
+ lostbms[j].lostdays =
+ ((int)time(NULL)-(int)xuser.lastlogin)/(60*60*24);
+
+ printf("%s\n", lostbms[j].title);
+ //超過六十天 免職
+ if(lostbms[j].lostdays > 30){
+ xuser.userlevel &= ~PERM_BM;
+ bms[index].flag = 1;
+ flag = 1;
+ }
+ j++;
+ }
+ index++;
+ } while((p=strtok(NULL,"/ "))!=NULL);
+
+ //從板主名單拿掉名字
+
+ if(flag == 1){
+ bmbuf[0] = '\0';
+ for(k = 0 , n = 0; k < index; k++){
+ if(!bms[k].flag){
+ if( n++ != 0) strcat(bmbuf, "/");
+ strcat(bmbuf, bms[k].bmname);
+ }
+ }
+ strcpy(allbrd[i].BM, bmbuf);
+ }
+
+ }
+ qsort(lostbms, j, sizeof(lostbm), bmlostdays_cmp);
+
+ printf("Starting to mail\n");
+ //write to the etc/toplazyBBM
+ for ( i=0; i<j; i++)
+ {
+ if( lostbms[i].lostdays > 30){
+ fprintf(firef, "%-*.*s%-*.*s%-*.*s%3d天沒上站\n", IDLEN, IDLEN, lostbms[i].title,
+ BTLEN-10, BTLEN-10, lostbms[i].ctitle, IDLEN,IDLEN,
+ lostbms[i].bmname,lostbms[i].lostdays);
+ }else{
+ fprintf(inf, "%-*.*s%-*.*s%-*.*s%3d天沒上站\n", IDLEN, IDLEN, lostbms[i].title,
+ BTLEN-10, BTLEN-10, lostbms[i].ctitle, IDLEN,IDLEN,
+ lostbms[i].bmname,lostbms[i].lostdays);
+ }
+ }
+ fclose(inf);
+ fclose(firef);
+
+ printf("Total %d boards.\n", j);
+
+ //mail to the users
+ for( i=0; i<j; i++)
+ {
+ fileheader_t mymail;
+ char genbuf[200];
+ int lostdays;
+
+ lostdays = lostbms[i].lostdays;
+
+ if( (lostdays != 14) || (lostdays != 21) ) // 14 21 天不發信
+ continue;
+
+ sprintf(genbuf, BBSHOME "/home/%c/%s", lostbms[i].bmname[0], lostbms[i].bmname);
+ stampfile(genbuf, &mymail);
+
+ strcpy(mymail.owner, "[PTT警察局]");
+
+ if(lostdays <= 30){
+ sprintf(mymail.title,
+ "\033[32m [小組長免職警告通知] \033[m %s BM %s", lostbms[i].title, lostbms[i].bmname);
+ }else{
+ sprintf(mymail.title,
+ "\033[32m [小組長免職通知] \033[m %s BM %s", lostbms[i].title, lostbms[i].bmname);
+ }
+ unlink(genbuf);
+ if(lostdays <= 30){
+ LINK(OUTFILE, genbuf);
+ }else{
+ LINK(FIREFILE, genbuf);
+ }
+
+ sprintf(genbuf, BBSHOME "/home/%c/%s/.DIR", lostbms[i].bmname[0], lostbms[i].bmname);
+ append_record(genbuf, &mymail, sizeof(mymail));
+ }
+
+ return 0;
+}
diff --git a/pttbbs/util/toplazyBBM.sh b/pttbbs/util/toplazyBBM.sh
new file mode 100644
index 00000000..d1d94229
--- /dev/null
+++ b/pttbbs/util/toplazyBBM.sh
@@ -0,0 +1,3 @@
+bin/toplazyBBM
+bin/post Record 懶惰小組長排行榜 [Ptt警察局] etc/toplazyBBM
+bin/post ViolateLaw 今日免職小組長 [Ptt法院] etc/firelazyBBM
diff --git a/pttbbs/util/toplazyBM.c b/pttbbs/util/toplazyBM.c
new file mode 100644
index 00000000..8b7ff467
--- /dev/null
+++ b/pttbbs/util/toplazyBM.c
@@ -0,0 +1,206 @@
+/* $Id$ */
+#define _UTIL_C_
+#include "bbs.h"
+#define OUTFILE BBSHOME "/etc/toplazyBM"
+#define FIREFILE BBSHOME "/etc/firelazyBM"
+extern boardheader_t *bcache;
+extern int numboards;
+
+boardheader_t allbrd[MAX_BOARD];
+typedef struct lostbm {
+ char bmname[IDLEN + 1];
+ char *title;
+ char *ctitle;
+ int lostdays;
+} lostbm;
+lostbm lostbms[MAX_BOARD];
+
+typedef struct BMarray{
+ char bmname[IDLEN + 1];
+ int flag;
+} BMArray;
+BMArray bms[5];
+
+
+int bmlostdays_cmp(const void *va, const void *vb)
+{
+ lostbm *a=(lostbm *)va, *b=(lostbm *)vb;
+ if (a->lostdays > b->lostdays) return -1;
+ else if (a->lostdays == b->lostdays) return 0;
+ else return 1;
+}
+
+int LINK(char* src, char* dst)
+{
+ char cmd[200];
+ if(symlink(src,dst) == -1)
+ {
+ sprintf(cmd, "/bin/cp -R %s %s", src, dst);
+ return system(cmd);
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int bmid, i, j=0;
+ FILE *inf, *firef;
+ time4_t now=time(NULL);
+ attach_SHM();
+ resolve_boards();
+
+ if(passwd_init())
+ exit(1);
+
+ memcpy(allbrd,bcache,numboards*sizeof(boardheader_t));
+
+ /* write out the target file */
+ inf = fopen(OUTFILE, "w+");
+ if (inf == NULL) {
+ printf("open file error : %s\n", OUTFILE);
+ exit(1);
+ }
+
+ firef = fopen(FIREFILE, "w+");
+ if (firef == NULL) {
+ printf("open file error : %s\n", FIREFILE);
+ exit(1);
+ }
+
+ fprintf(inf, "警告: 板主若於兩個月未上站,將予於免職\n");
+ fprintf(inf,
+ "看板名稱 "
+ " 板主 幾天沒來啦\n"
+ "---------------------------------------------------"
+ "-------------------\n");
+
+ fprintf(firef, "免職板主\n");
+ fprintf(firef,
+ "看板名稱 "
+ " 板主 幾天沒來啦\n"
+ "---------------------------------------------------"
+ "-------------------\n");
+
+
+ j = 0;
+ for (i = 0; i < numboards; i++) {
+ char *p, bmbuf[IDLEN * 3 + 3];
+ int index = 0, flag = 0, k, n;
+ userec_t xuser;
+ p = allbrd[i].BM;
+
+ if (*p == '[') p++;
+ if (allbrd[i].brdname[0] == '\0' ||
+ !isalpha(allbrd[i].brdname[0])
+ ) continue;
+
+ p = strtok(p,"/ ]");
+ for(index=0; p && index<5; index++) {
+ int diff;
+ if(!p[0]) {
+ index--;
+ p=strtok(NULL,"/ ]");
+ continue;
+ }
+ bmid=getuser(p, &xuser);
+ strlcpy(bms[index].bmname, p, sizeof(bms[index].bmname));
+ bms[index].flag = 0;
+
+ diff = now - xuser.lastlogin;
+ if (diff < 0)
+ diff = 0;
+
+ if (diff >= 45 * 86400
+ && !(xuser.userlevel & PERM_SYSOPHIDE)
+ && !(xuser.userlevel & PERM_SYSOP)) {
+ strlcpy(lostbms[j].bmname, p, sizeof(bms[index].bmname));
+ lostbms[j].title = allbrd[i].brdname;
+ lostbms[j].ctitle = allbrd[i].title;
+ lostbms[j].lostdays = diff / 86400;
+
+ //超過90天 免職
+ if (lostbms[j].lostdays > 90) {
+ xuser.userlevel &= ~PERM_BM;
+ bms[index].flag = 1;
+ flag = 1;
+ passwd_update(bmid, &xuser);
+ }
+ j++;
+ }
+ p = strtok(NULL,"/ ]");
+ }
+
+ if (flag == 1) {
+ bmbuf[0] = '\0';
+ for (k = 0, n = 0; k < index; k++) {
+ if (!bms[k].flag) {
+ if (n++ != 0) strcat(bmbuf, "/");
+ strcat(bmbuf, bms[k].bmname);
+ }
+ }
+ strcpy(allbrd[i].BM, bmbuf);
+ if (substitute_record(BBSHOME"/"FN_BOARD, &allbrd[i],
+ sizeof(boardheader_t), i+1) == -1) {
+ printf("Update Board Faile : %s\n", allbrd[i].brdname);
+ }
+ reset_board(i+1);
+ }
+ }
+ qsort(lostbms, j, sizeof(lostbm), bmlostdays_cmp);
+
+ //write to the etc/toplazyBM
+ for (i = 0; i < j; i++) {
+ if (lostbms[i].lostdays > 90) {
+ fprintf(firef, "%-*.*s%-*.*s%-*.*s%3d天沒上站\n",
+ IDLEN, IDLEN, lostbms[i].title, BTLEN-10,
+ BTLEN-10, lostbms[i].ctitle, IDLEN,IDLEN,
+ lostbms[i].bmname,lostbms[i].lostdays);
+ } else {
+ fprintf(inf, "%-*.*s%-*.*s%-*.*s%3d天沒上站\n",
+ IDLEN, IDLEN, lostbms[i].title, BTLEN-10,
+ BTLEN-10, lostbms[i].ctitle, IDLEN,IDLEN,
+ lostbms[i].bmname,lostbms[i].lostdays);
+ }
+ }
+ fclose(inf);
+ fclose(firef);
+
+ //printf("Total %d boards.\n", count);
+
+ //mail to the users
+ for (i=0; i<j; i++) {
+ fileheader_t mymail;
+ char genbuf[200];
+ int lostdays;
+
+ lostdays = lostbms[i].lostdays;
+
+ if (lostdays != 45 && lostdays != 60 && lostdays!=75 &&(lostdays <= 90))
+ continue;
+
+ sprintf(genbuf, BBSHOME "/home/%c/%s",
+ lostbms[i].bmname[0], lostbms[i].bmname);
+ stampfile(genbuf, &mymail);
+
+ strcpy(mymail.owner, "[PTT警察局]");
+ if (lostdays <= 90)
+ sprintf(mymail.title,
+ "\033[32m版主通知\033[m %s版版主%s",
+ lostbms[i].title, lostbms[i].bmname);
+ else
+ sprintf(mymail.title,
+ "\033[32m版主自動免職通知\033[m %s 版主 %s",
+ lostbms[i].title, lostbms[i].bmname);
+
+ unlink(genbuf);
+ if (lostdays <= 90)
+ LINK(OUTFILE, genbuf);
+ else
+ LINK(FIREFILE, genbuf);
+
+ sprintf(genbuf, BBSHOME "/home/%c/%s/.DIR",
+ lostbms[i].bmname[0], lostbms[i].bmname);
+ append_record(genbuf, &mymail, sizeof(mymail));
+ }
+ return 0;
+}
diff --git a/pttbbs/util/toplazyBM.sh b/pttbbs/util/toplazyBM.sh
new file mode 100644
index 00000000..033e545f
--- /dev/null
+++ b/pttbbs/util/toplazyBM.sh
@@ -0,0 +1,3 @@
+bin/toplazyBM
+bin/post Record 懶惰版主排行榜 [Ptt警察局] etc/toplazyBM
+bin/post ViolateLaw 今日免職版主 [Ptt法'|'] etc/firelazyBM
diff --git a/pttbbs/util/topsong.sh b/pttbbs/util/topsong.sh
new file mode 100644
index 00000000..19e9663a
--- /dev/null
+++ b/pttbbs/util/topsong.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+# $Id: topsong.sh,v 1.1 2002/03/07 15:13:46 in2 Exp $
+#
+bin/post Record "上半個月點歌排行榜" "[Ptt流行網]" etc/topsong
+mv ussong tmp
diff --git a/pttbbs/util/topusr.c b/pttbbs/util/topusr.c
new file mode 100644
index 00000000..d2679d39
--- /dev/null
+++ b/pttbbs/util/topusr.c
@@ -0,0 +1,181 @@
+/* $Id$ */
+/* 使用者 上站記錄/文章篇數 排行榜 */
+#define _UTIL_C_
+#include "bbs.h"
+
+#define REAL_INFO
+struct manrec
+{
+ char userid[IDLEN + 1];
+ char nickname[23];
+ int values[3];
+};
+typedef struct manrec manrec;
+struct manrec *allman[3];
+
+userec_t aman;
+manrec theman;
+int num;
+FILE *fp;
+
+#define TYPE_POST 0
+#define TYPE_LOGIN 1
+#define TYPE_MONEY 2
+
+
+void
+ top(type)
+{
+ static char *str_type[3] =
+ {"發表次數", "進站次數", " 大富翁 "};
+ int i, j, rows = (num + 1) / 2;
+ char buf1[80], buf2[80];
+
+ if (type != 2)
+ fprintf(fp, "\n\n");
+
+ fprintf(fp, "\
+ ╭─────╮ [%dm %8.8s排行榜  ╭─────╮\n\
+ 名次─代號───暱稱──────數目──名次─代號───暱稱──────數目\
+", type + 44, str_type[type]);
+ for (i = 0; i < rows; i++)
+ {
+ char ch=' ';
+ int value;
+
+ if(allman[type][i].values[type] > 1000000000)
+ { value=allman[type][i].values[type]/1000000; ch='M';}
+ else if(allman[type][i].values[type] > 1000000)
+ { value=allman[type][i].values[type]/1000; ch='K';}
+ else {value=allman[type][i].values[type]; ch=' ';}
+ sprintf(buf1, "[%2d] %-11.11s%-16.16s%5d%c",
+ i + 1, allman[type][i].userid, allman[type][i].nickname,
+ value, ch);
+ j = i + rows;
+ if(allman[type][j].values[type] > 1000000000)
+ { value=allman[type][j].values[type]/1000000; ch='M';}
+ else if(allman[type][j].values[type] > 1000000)
+ { value=allman[type][j].values[type]/1000; ch='K';}
+ else {value=allman[type][j].values[type]; ch=' ';}
+
+ sprintf(buf2, "[%2d] %-11.11s%-16.16s%4d%c",
+ j + 1, allman[type][j].userid, allman[type][j].nickname,
+ value, ch);
+ if (i < 3)
+ fprintf(fp, "\n [1;%dm%-40s%s", 31 + i, buf1, buf2);
+ else
+ fprintf(fp, "\n %-40s%s", buf1, buf2);
+ }
+}
+
+
+#ifdef HAVE_TIN
+int
+ post_in_tin(char *name)
+{
+ char buf[256];
+ FILE *fh;
+ int counter = 0;
+
+ sprintf(buf, "%s/home/%c/%s/.tin/posted", home_path, name[0], name);
+ fh = fopen(buf, "r");
+ if (fh == NULL)
+ return 0;
+ else
+ {
+ while (fgets(buf, 255, fh) != NULL)
+ counter++;
+ fclose(fh);
+ return counter;
+ }
+}
+#endif /* HAVE_TIN */
+
+int
+bad_user_id(const char *userid)
+{
+ register char ch;
+ if (strlen(userid) < 2)
+ return 1;
+ if (not_alpha(*userid))
+ return 1;
+ while((ch = *(++userid)))
+ {
+ if (not_alnum(ch))
+ return 1;
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int i, j;
+
+ if (argc < 3)
+ {
+ printf("Usage: %s <num_top> <out-file>\n", argv[0]);
+ exit(1);
+ }
+
+ num = atoi(argv[1]);
+ if (num == 0)
+ num = 30;
+
+ attach_SHM();
+ if(passwd_init())
+ {
+ printf("Sorry, the data is not ready.\n");
+ exit(0);
+ }
+ for(i=0; i<3; i++)
+ {
+ allman[i]=malloc(sizeof(manrec) * num);
+ memset(allman[i],0,sizeof(manrec) * num);
+ }
+ for(j = 1; j <= MAX_USERS; j++) {
+ passwd_query(j, &aman);
+ aman.userid[IDLEN]=0;
+ aman.nickname[22]=0;
+ if((aman.userlevel & PERM_NOTOP) || !aman.userid[0] ||
+ bad_user_id(aman.userid) ||
+ strchr(aman.userid, '.'))
+ {
+ continue;
+ }
+ else {
+ strcpy(theman.userid, aman.userid);
+ strcpy(theman.nickname, aman.nickname);
+ theman.values[TYPE_LOGIN] = aman.numlogins;
+ theman.values[TYPE_POST] = aman.numposts;
+ theman.values[TYPE_MONEY] = aman.money;
+ for(i=0; i<3; i++)
+ {
+ int k,l;
+ for(k=num-1; k>=0 && allman[i][k].values[i]<theman.values[i];
+ k--);
+ k++;
+ if(k<num)
+ {
+ for(l=num-1; l>k; l--)
+ memcpy(&allman[i][l], &allman[i][l-1],
+ sizeof(manrec));
+ memcpy(&allman[i][k], &theman, sizeof(manrec));
+ }
+ }
+ }
+ }
+
+
+ if ((fp = fopen(argv[2], "w")) == NULL)
+ {
+ printf("cann't open topusr\n");
+ return 0;
+ }
+
+ top(TYPE_MONEY);
+ top(TYPE_POST);
+ top(TYPE_LOGIN);
+
+ fclose(fp);
+ return 0;
+}
diff --git a/pttbbs/util/transman.c b/pttbbs/util/transman.c
new file mode 100644
index 00000000..711e8ead
--- /dev/null
+++ b/pttbbs/util/transman.c
@@ -0,0 +1,56 @@
+/* $Id$ */
+// tools to translate the format of eagle bbs -> Ptt bbs */
+
+#include "bbs.h"
+
+int transman(char *path)
+{
+ char name[128];
+ char buf[512], filename[512], *direct="";
+ int n=0;
+ fileheader_t fh;
+ FILE *fp;
+
+ chdir(path);
+
+ fp = fopen(".Names", "r");
+ if(fp)
+ for(n=0; fgets(buf,512,fp)>0; n++)
+ {
+ strtok(buf,"\r\n");
+ if(buf[0]=='#') continue;
+ if(buf[0]=='N')
+ strcpy(name, buf+5);
+ else
+ if(buf[0]=='P')
+ {
+ direct = buf+7;
+ strcpy(filename, ".");
+ stampfile(filename, &fh);
+ unlink(filename);
+ if(dashd(direct))
+ {
+ sprintf(fh.title, "◆ %s", name);
+ transman(direct);
+ }
+ else
+ sprintf(fh.title, "◇ %s", name);
+ rename(direct, filename);
+ append_record(".DIR", &fh, sizeof(fh));
+ }
+ }
+ chdir("..");
+ return n;
+}
+
+int main(int argc, char* argv[])
+{
+ if(argc < 2)
+ {
+ printf("%s <path>\n", argv[0]);
+ return 0;
+ }
+
+ transman(argv[1]);
+ return 0;
+}
diff --git a/pttbbs/util/tunepasswd.c b/pttbbs/util/tunepasswd.c
new file mode 100644
index 00000000..b617fb77
--- /dev/null
+++ b/pttbbs/util/tunepasswd.c
@@ -0,0 +1,69 @@
+/* $Id$ */
+#include "bbs.h"
+
+int tune(int num) {
+ int i, j, fin, fout;
+ userec_t u;
+
+ if((fin = open(FN_PASSWD, O_RDONLY)) == -1) {
+ perror(FN_PASSWD);
+ return 1;
+ }
+ if(flock(fin, LOCK_EX)) {
+ printf("Lock failed!\n");
+ return 1;
+ }
+ if((fout = open(FN_PASSWD ".tune" , O_WRONLY | O_CREAT, 0600)) == -1) {
+ perror(FN_PASSWD ".tune");
+ flock(fin, LOCK_UN);
+ close(fin);
+ return 1;
+ }
+
+ for(i = j = 0; i < num; i++) {
+ read(fin, &u, sizeof(u));
+ if(u.userid[0]) {
+ if(j == MAX_USERS) {
+ printf("MAX_USERS is too small!\n");
+ close(fout);
+ unlink(FN_PASSWD ".tune");
+ flock(fin, LOCK_UN);
+ close(fin);
+ return 1;
+ }
+ write(fout, &u, sizeof(u));
+ j++;
+ }
+ }
+ for(memset(&u, 0, sizeof(u)); j < MAX_USERS; j++) {
+ write(fout, &u, sizeof(u));
+ }
+ close(fout);
+
+ /* backup */
+ unlink(FN_PASSWD "~");
+ link(FN_PASSWD, FN_PASSWD "~");
+ unlink(FN_PASSWD);
+ link(FN_PASSWD ".tune", FN_PASSWD);
+ unlink(FN_PASSWD ".tune");
+
+ flock(fin, LOCK_UN);
+ close(fin);
+ return 0;
+}
+
+int main() {
+ struct stat sb;
+
+ if(stat(FN_PASSWD, &sb)) {
+ perror("stat");
+ return 1;
+ }
+ if(sb.st_size != sizeof(userec_t) * MAX_USERS) {
+ printf("size and MAX_USERS do not match!\n");
+ if(tune(sb.st_size / sizeof(userec_t)) == 0)
+ printf(FN_PASSWD " has been tuned successfully!\n");
+ } else
+ printf("Nothing to do.\n");
+ return 0;
+}
diff --git a/pttbbs/util/udnnews.pl b/pttbbs/util/udnnews.pl
new file mode 100644
index 00000000..fd02efc3
--- /dev/null
+++ b/pttbbs/util/udnnews.pl
@@ -0,0 +1,119 @@
+#!/usr/bin/perl
+#
+# 請註意!
+# 本程式版權屬於 PttBBS ,
+# 但只表示您可以公開免費取得本程式,
+# 並不表示您可以使用本程式.
+#
+# 本程式將直接連至 udnnews網站取得當前新聞,
+# 而新聞內容的版權是屬於 聯合新聞網 所有.
+# 亦即, 您若沒有聯合新聞網之書面授權,
+# 您並「不能」使用本程式下載該網新聞.
+#
+
+use lib '/home/bbs/bin/';
+use LocalVars;
+use strict;
+use vars qw/@titles/;
+
+chdir '/home/bbs';
+getudnnewstitle(\@titles);
+foreach( @titles ){
+ postout({brdname => 'udnnews',
+ title => strreplace(FormatChinese($_->[1])),
+ owner => 'udnnews.',
+ content => getudnnewscontent("http://www.udn.com/NEWS/FOCUSNEWS/$_->[0]", $_)});
+}
+
+sub strreplace
+{
+ my($str) = @_;
+ $str =~ s/十/十/g;
+ $str =~ s/卅/卅/g;
+ $str =~ s/游錫\&\#22531\;/游揆/g;
+ return $str;
+}
+
+sub getudnnewscontent($$)
+{
+ my($url, $title) = @_;
+ my($buf, $content, $ret);
+ $buf = `$LYNX -source '$url'`;
+ ($content) = $buf =~ m|<!-- start of content -->(.*?)<!-- end of content -->|s;
+# ($content) = $buf =~ m|<p><font color="#CC0033" class="text12">(.*?)<tr valign="top">|s if( !$content );
+ $content =~ s/<p>/\n/gi;
+ $content =~ s/<.*?>//g;
+ $content =~ s/&nbsp;//g;
+ $content =~ s/\r//g;
+ $content =~ s/\n+/\n/gs;
+ $content = strreplace($content);
+ undef $ret;
+ foreach( split(/\n/, $content) ){
+ s/ //g;
+ $ret .= FormatChinese($_, 60). "\n" if( $_ );
+ }
+ return
+ "作者: udnnews.(聯合新聞網) 看板: udnnews\n".
+ "標題: $title\n".
+ "時間: 即時\n".
+ "※ [轉錄自 $url ]\n\n$ret\n\n".
+ "--\n\n 聯合新聞網 http://www.udn.com/ 獨家授權批踢踢實業坊 ".
+ "\n 未經允許\請勿擅自使用 ";
+}
+
+sub getudnnewstitle($)
+{
+ my($ra_titles) = @_;
+ my($url, $title);
+ open FH, "$LYNX -source http://www.udn.com/NEWS/FOCUSNEWS/ | $GREP '<font color=\"#FF9933\">' |";
+ while( <FH> ){
+ ($url, $title) = $_ =~ m|<font color="#FF9933">.</font><a href="(.*?)"><font color="#003333">(.*?)</font></a><font color="#003333">|;
+ $title =~ s/<.*?>//g;
+ push @{$ra_titles}, [$url, $title];
+ }
+ close FH;
+ return @{$ra_titles};
+}
+
+sub FormatChinese
+{
+ my($str, $length) = @_;
+ my($i, $ret, $count, $s);
+ return if( !$str );
+ for( $i = 0 ; $i < length($str) ; ++$i ){
+ if( ord(substr($str, $i, 1)) > 127 ){
+ $ret .= substr($str, $i, 2);
+ ++$i;
+ }
+ else{
+ for( $count = 0, $s = $i ; $i < length($str) ; ++$i, ++$count ){
+ last if( ord(substr($str, $i, 1)) > 127 );
+ }
+ --$i;
+ $ret .= ' ' if( $count % 2 == 1);
+ $ret .= substr($str, $s, $count);
+ }
+ }
+ if( $length ){
+ $str = $ret;
+ undef $ret;
+ while( $str ){
+ $ret .= substr($str, 0, $length)."\n";
+ $str = substr($str, $length);
+ }
+ }
+ return $ret;
+}
+
+sub postout
+{
+ my($param) = @_;
+ return if( !$param->{title} );
+ open FH, ">/tmp/postout.$$";
+ print FH $param->{content};
+#print "$param->{content}";
+ close FH;
+
+ system("bin/post '$param->{brdname}' '$param->{title}' '$param->{owner}' /tmp/postout.$$");
+ unlink "/tmp/postout.$$";
+}
diff --git a/pttbbs/util/uhash_loader.c b/pttbbs/util/uhash_loader.c
new file mode 100644
index 00000000..3400e153
--- /dev/null
+++ b/pttbbs/util/uhash_loader.c
@@ -0,0 +1,167 @@
+/* $Id$ */
+/* standalone uhash loader -- jochang */
+#include "bbs.h"
+#include "fnv_hash.h"
+
+unsigned string_hash(unsigned char *s);
+void userec_add_to_uhash(int n, userec_t *id, int onfly);
+void fill_uhash(int onfly);
+void load_uhash(void);
+
+SHM_t *SHM;
+
+int main()
+{
+ setgid(BBSGID);
+ setuid(BBSUID);
+ chdir(BBSHOME);
+ load_uhash();
+ return 0;
+}
+
+void load_uhash(void) {
+ int shmid, err;
+ shmid = shmget(SHM_KEY, SHMSIZE,
+#ifdef USE_HUGETLB
+ SHM_HUGETLB |
+#endif
+ 0600 | IPC_CREAT | IPC_EXCL);
+ err = errno;
+ if( err == EEXIST )
+ shmid = shmget(SHM_KEY, SHMSIZE,
+#ifdef USE_HUGETLB
+ SHM_HUGETLB |
+#endif
+ 0600 | IPC_CREAT);
+
+ if( shmid < 0 ){
+ perror("shmget");
+ exit(1);
+ }
+ SHM = (void *) shmat(shmid, NULL, 0);
+ if( SHM == (void *)-1 ){
+ perror("shmat");
+ exit(1);
+ }
+ if( err != EEXIST ) {
+ SHM->number=SHM->loaded = 0;
+ SHM->version = SHM_VERSION;
+ }
+
+ if(SHM->version != SHM_VERSION) {
+ fprintf(stderr, "Error: SHM->version(%d) != SHM_VERSION(%d)\n", SHM->version, SHM_VERSION);
+ fprintf(stderr, "Please use the source code version corresponding to SHM,\n"
+ "or use ipcrm(1) command to clean share memory.\n");
+ exit(1);
+ }
+
+// in case it's not assumed zero, this becomes a race...
+ if( SHM->number == 0 && SHM->loaded == 0 ){
+ SHM->loaded = 0;
+ fill_uhash(0);
+ SHM->loaded = 1;
+ }
+ else{
+ fill_uhash(1);
+ }
+}
+
+void checkhash(int h)
+{
+ int *p = &(SHM->hash_head[h]), ch, deep=0;
+ while(*p != -1)
+ {
+ if(*p <-1 || *p >= MAX_USERS) {*p=-1; return;}
+ ch = string_hash( SHM->userid[*p])%(1<<HASH_BITS);
+ if(ch!=h)
+ {
+ printf("remove %d %d!=%d %d [%s] next:%d\n",
+ deep, h, ch, *p, SHM->userid[*p],
+ SHM->next_in_hash[*p]);
+ *p = SHM->next_in_hash[*p]; //remove from link
+ // *p=-1; Ptt: cut it?
+ //return;
+ }
+ else
+ p = &(SHM->next_in_hash[*p]);
+ deep++;
+ }
+}
+void fill_uhash(int onfly)
+{
+ int fd, usernumber;
+ usernumber = 0;
+
+ for (fd = 0; fd < (1 << HASH_BITS); fd++)
+ if(!onfly)
+ SHM->hash_head[fd] = -1;
+ else
+ checkhash(fd);
+
+ if ((fd = open(FN_PASSWD, O_RDWR)) > 0)
+ {
+ struct stat stbuf;
+ caddr_t fimage, mimage;
+
+ fstat(fd, &stbuf);
+ fimage = mmap(NULL, stbuf.st_size, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
+ if (fimage == (char *) -1)
+ {
+ perror("mmap");
+ exit(1);
+ }
+ close(fd);
+ fd = stbuf.st_size / sizeof(userec_t);
+ if (fd > MAX_USERS)
+ fd = MAX_USERS;
+
+ for (mimage = fimage; usernumber < fd; mimage += sizeof(userec_t))
+ {
+ userec_add_to_uhash(usernumber, (userec_t *)mimage, onfly);
+ usernumber++;
+ }
+ munmap(fimage, stbuf.st_size);
+ }
+ else
+ {
+ perror("open");
+ exit(1);
+ }
+ SHM->number = usernumber;
+
+ printf("total %d names %s.\n", usernumber, onfly ? "checked":"loaded");
+}
+unsigned string_hash(unsigned char *s)
+{
+ return fnv1a_32_strcase(s, FNV1_32_INIT);
+}
+
+void userec_add_to_uhash(int n, userec_t *user, int onfly)
+{
+ int *p, h, l=0;
+
+ h = string_hash(user->userid)%(1<<HASH_BITS);
+
+ p = &(SHM->hash_head[h]);
+ if(!onfly || SHM->userid[n][0] != user->userid[0] ||
+ strncmp(SHM->userid[n], user->userid, IDLEN-1))
+ {
+ strcpy(SHM->userid[n], user->userid);
+ SHM->money[n] = user->money;
+#ifdef USE_COOLDOWN
+ SHM->cooldowntime[n] = 0;
+#endif
+ if(onfly)
+ printf("add %s\n", user->userid);
+ }
+ while (*p != -1)
+ {
+ if(onfly && *p==n ) // already in hash
+ return;
+ l++;
+ p = &(SHM->next_in_hash[*p]);
+ }
+ if(onfly)
+ printf("add %d %d %d [%s] in hash\n", l, h, n, user->userid);
+ SHM->next_in_hash[*p = n] = -1;
+}
diff --git a/pttbbs/util/userlist.c b/pttbbs/util/userlist.c
new file mode 100644
index 00000000..67558985
--- /dev/null
+++ b/pttbbs/util/userlist.c
@@ -0,0 +1,53 @@
+/* $Id$ */
+#include "bbs.h"
+
+SHM_t *SHM;
+
+int main(int argc, char **argv) {
+ int i, shm, counter;
+
+ shm = shmget(SHM_KEY, SHMSIZE,
+#ifdef USE_HUGETLB
+ SHM_HUGETLB |
+#endif
+ SHM_R | SHM_W);
+ if(shm == -1) {
+ perror("shmget");
+ exit(0);
+ }
+
+ if( (SHM = shmat(shm, NULL, 0)) < 0 ){
+ perror("shmat");
+ exit(0);
+ }
+
+ if(argc == 2) {
+ /* list specific id */
+ for (i = 0; i < USHM_SIZE; i++)
+ {
+ userinfo_t *f = &SHM->uinfo[i];
+ if(!f->pid)
+ continue;
+ if(strcmp(f->userid, argv[1]) != 0)
+ continue;
+ printf(
+ "id=%s money=%d\n",
+ f->userid, SHM->money[f->uid - 1]);
+ }
+ }
+ else
+ {
+ for(i = counter = 0; i < USHM_SIZE; i++)
+ if(SHM->uinfo[i].pid) {
+ userinfo_t *f;
+
+ f = &SHM->uinfo[i];
+ printf(
+ "%4d(%d) p[%d] i[%d] u[%s] n[%s] f[%s] m[%d] t[%d]\n",
+ ++counter, i, f->pager, f->invisible, f->userid,
+ f->nickname, f->from, f->mode, f->lastact);
+ }
+ printf("\nTotal: %d(%d)\n", counter, SHM->number);
+ }
+ return 0;
+}
diff --git a/pttbbs/util/waterball.pl b/pttbbs/util/waterball.pl
new file mode 100644
index 00000000..3f9f8f2f
--- /dev/null
+++ b/pttbbs/util/waterball.pl
@@ -0,0 +1,107 @@
+#!/usr/bin/perl
+# $Id$
+use lib '/home/bbs/bin/';
+use Time::Local;
+use LocalVars;
+use Mail::Sender;
+use IO::All;
+use strict;
+
+my(%water);
+sub main
+{
+ my($fndes, $userid, $mailto, $outmode, $fnsrc);
+ chdir($BBSHOME);
+ foreach $fndes ( <$JOBSPOOL/water.des.*> ){
+ ($userid, $mailto, $outmode, $fnsrc) = parsedes($fndes);
+ next if( !$userid || !-e $fnsrc );
+
+ $mailto = "$userid.bbs\@$MYHOSTNAME" if( $mailto eq '.');
+ undef %water;
+ process($fnsrc, "$TMP/water/", $outmode, $userid);
+ output("$userid.bbs\@$MYHOSTNAME", $mailto, $mailto =~ /\.bbs/);
+ unlink($fndes, $fnsrc);
+ }
+}
+
+sub parsedes($)
+{
+ my $t < io($_[0]);
+ my $fnsrc = $_[0];
+ $fnsrc =~ s/\.des\./\.src\./;
+ return (split("\n", $t), $fnsrc);
+}
+
+sub process($$$$)
+{
+ my($fn, $outdir, $outmode, $me) = @_;
+ my($cmode, $who, $time, $say, $orig, %LAST, $len) = ();
+ open DIN, "<$fn";
+ while( <DIN> ){
+ next if( !(($cmode, $who, $time, $say, $orig) = parse($_)) || !$who );
+
+ if( $outmode ){
+ $water{$who} .= $orig;
+ } else {
+ next if( $say =~ /<<(上|下)站通知>> -- 我(走|來)囉!/ );
+ if( $time - $LAST{$who} > 1800 ){
+ $water{$who} .= (scalar localtime($LAST{$who}))."\n\n"
+ if( $LAST{$who} );
+ $water{$who} .= scalar localtime($time) . "\n";
+ }
+
+ $len = max(length($who), length($me)) + 1;
+ $water{$who} .= sprintf("%-${len}s %s\n",
+ ($cmode ? $who : $me).':' ,
+ $say);
+ $LAST{$who} = $time;
+ }
+ }
+ if( $outmode == 0 ){
+ $water{$_} .= scalar localtime($LAST{$_})
+ foreach( keys %LAST );
+ }
+}
+
+sub parse($)
+{
+ my($str) = @_;
+ my($cmode, $who, $year, $month, $day, $hour, $min, $sec, $say);
+ $cmode = ($str =~ /^To/) ? 0 : 1;
+ ($who, $say, $month, $day, $year, $hour, $min, $sec) =
+ $cmode ?
+ $str =~ m|★(.+?)\[37;45m\s*(.*).*?\[(\w+)/(\w+)/(\w+) (\w+):(\w+):(\w+)\]| :
+ $str =~ m|^To (.+?):\s*(.*)\[(\d+)/(\d+)/(\d+) (\d+):(\d+):(\d+)\]|;
+ return ( !(1 <= $month && $month <= 12 &&
+ 1 <= $day && $day <= 31 &&
+ 0 <= $hour && $hour <= 23 &&
+ 0 <= $min && $min <= 59 &&
+ 0 <= $sec && $sec <= 59 &&
+ 1970 <= $year && $year <= 2038) ?
+ () :
+ ($cmode, $who,
+ timelocal($sec, $min, $hour, $day, $month - 1, $year),
+ $say, $_[0]) );
+}
+
+sub output
+{
+ my($from, $tomail, $bbsmail) = @_;
+ my $ms = new Mail::Sender{smtp => $SMTPSERVER,
+ from => $from,
+ charset => 'big5'};
+
+ foreach( keys %water ){
+ $ms->MailMsg({to => $tomail,
+ subject => "和 $_ 的水球記錄",
+ msg => $water{$_}});
+ }
+}
+
+sub max
+{
+ return $_[0] > $_[1] ? $_[0] : $_[1];
+}
+
+main();
+1;
diff --git a/pttbbs/util/weather.perl b/pttbbs/util/weather.perl
new file mode 100644
index 00000000..36d773d7
--- /dev/null
+++ b/pttbbs/util/weather.perl
@@ -0,0 +1,37 @@
+#!/usr/bin/perl
+# $Id: weather.perl,v 1.8 2003/05/06 16:54:42 victor Exp $
+#
+# 不能跑的話,看看 bbspost 的路徑是否正確。
+# 如果發出的 post 沒有氣象報告而是說 URL 找不到,則確定一下能不能看到
+# 中央氣象局的 WWW 及 URL 是否正確。
+# 理論上適用所有 Eagle BBS 系列。
+# -- Beagle Apr 13 1997
+
+use lib '/home/bbs/bin';
+use LocalVars;
+
+weather_report('etc/weather.today', 'ftp://ftpsv.cwb.gov.tw/pub/forecast/W002.txt');
+weather_report('etc/weather.tomorrow', 'ftp://ftpsv.cwb.gov.tw/pub/forecast/W003.txt');
+
+sub weather_report
+{
+ my ($file, $link) = @_;
+ open(BBSPOST, "> $file");
+
+# Header
+# 內容
+ open(WEATHER, "$LYNX -assume_charset=big5 -assume_local_charset=big5 -dump -nolist $link|");
+
+ while (<WEATHER>) {
+ print BBSPOST if ($_ ne "\n");
+ }
+ close WEATHER;
+
+# 簽名檔
+ print BBSPOST "\n--\n";
+ print BBSPOST "我是beagle所有可愛的小餅乾...跨海為Ptt服務\n";
+ print BBSPOST "--\n";
+ print BBSPOST "☆ [Origin: ◎果醬小站◎] [From: [藍莓鬆餅屋] ] ";
+
+ close BBSPOST;
+}
diff --git a/pttbbs/util/weather.sh b/pttbbs/util/weather.sh
new file mode 100644
index 00000000..58a1bdbf
--- /dev/null
+++ b/pttbbs/util/weather.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+# $Id: weather.sh,v 1.1 2002/03/07 15:13:46 in2 Exp $
+#
+bin/weather.perl
+bin/post Record 全省今日各地天氣預報 [氣象小姐] etc/weather.today
+bin/post Record 全省明日各地天氣預報 [氣象小姐] etc/weather.tomorrow
diff --git a/pttbbs/util/wretch_man.c b/pttbbs/util/wretch_man.c
new file mode 100644
index 00000000..d055980d
--- /dev/null
+++ b/pttbbs/util/wretch_man.c
@@ -0,0 +1,124 @@
+/* $Id$ */
+/* tool to convert wretch bbs to ptt bbs */
+
+#include "bbs.h"
+
+struct wretch_fheader_t {
+ time4_t chrono;
+ char pad[4];
+ int xmode;
+ int xid;
+ char xname[32]; /* 檔案名稱 */
+ char owner[80]; /* 作者 (E-mail address) */
+ char nick[50]; /* 暱稱 */
+ char date[9]; /* [96/12/01] */
+ char title[72]; /* 主題 (TTLEN + 1) */
+ char score;
+ char pad2[4];
+};
+
+#define POST_MARKED 0x00000002 /* marked */
+#define POST_BOTTOM1 0x00002000 /* 置底文章的正本 */
+
+#define GEM_RESTRICT 0x0800 /* 限制級精華區,須 manager 才能看 */
+#define GEM_RESERVED 0x1000 /* 限制級精華區,須 sysop 才能更改 */
+#define GEM_FOLDER 0x00010000 /* folder / article */
+#define GEM_BOARD 0x00020000 /* 看板精華區 */
+
+static const char radix32[32] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
+ 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+};
+
+char *path;
+char *board;
+
+int transman(char *fname, char *newpath)
+{
+ int fd;
+ char *p;
+ char buf[PATHLEN];
+ struct wretch_fheader_t whdr;
+ fileheader_t fhdr;
+
+ if (fname[0] == '.')
+ snprintf(buf, PATHLEN, "%s/%s", path, fname);
+ else
+ snprintf(buf, PATHLEN, "%s/%c/%s", path, fname[7], fname);
+
+ if ((fd = open(buf, O_RDONLY, 0)) == -1)
+ return -1;
+
+ while (read(fd, &whdr, sizeof(whdr)) == sizeof(whdr)) {
+ if (strcmp(whdr.xname, "..") == 0 || strchr(whdr.xname, '/'))
+ continue;
+
+ if (!(whdr.xmode & 0xffff0000)) {
+ /* article */
+ stampfile(newpath, &fhdr);
+ strlcpy(fhdr.title, "◇ ", sizeof(fhdr.title));
+
+ if (whdr.xname[0] == '@')
+ snprintf(buf, sizeof(buf), "%s/@/%s", path, whdr.xname);
+ else
+ snprintf(buf, sizeof(buf), "%s/%c/%s", path, whdr.xname[7], whdr.xname);
+
+ copy_file(buf, newpath);
+ }
+ else if (whdr.xmode & GEM_FOLDER) {
+ /* folder */
+ stampdir(newpath, &fhdr);
+ strlcpy(fhdr.title, "◆ ", sizeof(fhdr.title));
+
+ transman(whdr.xname, newpath);
+ }
+
+ p = strrchr(newpath, '/');
+ *p = '\0';
+
+ if (whdr.xmode & 0x0800)
+ fhdr.filemode |= FILE_BM;
+
+ strncat(fhdr.title, whdr.title, sizeof(fhdr.title) - strlen(fhdr.title) - 1);
+ strlcpy(fhdr.owner, whdr.owner, sizeof(fhdr.title));
+
+ setadir(buf, newpath);
+ append_record(buf, &fhdr, sizeof(fhdr));
+ }
+
+ close(fd);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ char buf[PATHLEN];
+
+ if (argc < 2) {
+ printf("%s <path> <board>\n", argv[0]);
+ return 0;
+ }
+
+ path = argv[1];
+ board = argv[2];
+
+ if (!dashd(path)) {
+ printf("%s is not directory\n", path);
+ return 0;
+ }
+
+ setapath(buf, board);
+
+ if (!dashd(buf)) {
+ printf("%s is not directory\n", buf);
+ return 0;
+ }
+
+ attach_SHM();
+
+ transman(".DIR", buf);
+
+ return 0;
+}
diff --git a/pttbbs/util/writemoney.c b/pttbbs/util/writemoney.c
new file mode 100644
index 00000000..0db8d99d
--- /dev/null
+++ b/pttbbs/util/writemoney.c
@@ -0,0 +1,39 @@
+/* $Id$ */
+/* SHM 銝剔 money 券典神 .PASSWDS */
+#define _UTIL_C_
+#include "bbs.h"
+
+time4_t now;
+extern SHM_t *SHM;
+
+int invalid(char *userid) {
+ int i;
+
+ if(!isalpha(userid[0]))
+ return 1;
+
+ for(i = 1; i < IDLEN && userid[i]; i++)
+ if(!isalpha(userid[i]) && !isdigit(userid[i]))
+ return 1;
+ return 0;
+}
+
+int main()
+{
+ int num, pwdfd, money;
+ userec_t u;
+
+ attach_SHM();
+
+ if ((pwdfd = open(fn_passwd, O_WRONLY)) < 0)
+ exit(1);
+ for (num=1;num <= SHM->number;num++) {
+ lseek(pwdfd, sizeof(userec_t) * (num - 1) +
+ ((char *)&u.money - (char *)&u), SEEK_SET);
+ money = moneyof(num);
+ write(pwdfd, &money, sizeof(int));
+ }
+ close(pwdfd);
+
+ return 0;
+}
diff --git a/pttbbs/util/xchatd.c b/pttbbs/util/xchatd.c
new file mode 100644
index 00000000..8e2373b5
--- /dev/null
+++ b/pttbbs/util/xchatd.c
@@ -0,0 +1,3122 @@
+/* $Id$ */
+#include "bbs.h"
+#include "xchatd.h"
+
+#define SERVER_USAGE
+#undef MONITOR /* 監督 chatroom 活動以解決糾紛 */
+#undef DEBUG /* 程式除錯之用 */
+
+#ifdef DEBUG
+#define MONITOR
+#endif
+
+/* self-test:
+ * random test, 隨機產生各種 client input, 目的為找到讓 server
+ * crash 的狀況, 因此 client 並未檢驗 server 傳過來的 data.
+ * server 也不會讀取 bbs 實際的檔案或 SHM.
+ * 根據 gcov(1) 此 self-test coverage 約 90%
+ *
+ * 如何測試:
+ * define SELFTEST & SELFTESTER, 執行時隨意加參數(argc>1)就會跑 test child.
+ * test server 僅進行 100 秒.
+ *
+ * Hint:
+ * 配合 valgrind 尋找 memory related bug.
+ */
+//#define SELFTEST
+//#define SELFTESTER
+
+#ifdef SELFTEST
+// 另開 port
+#undef NEW_CHATPORT
+#define NEW_CHATPORT 12333
+// only test 100 secs
+#undef CHAT_INTERVAL
+#define CHAT_INTERVAL 100
+#endif
+
+#define CHAT_PIDFILE "log/chat.pid"
+#define CHAT_LOGFILE "log/chat.log"
+#define CHAT_INTERVAL (60 * 30)
+#define SOCK_QLEN 1
+
+
+/* name of the main room (always exists) */
+
+
+#define MAIN_NAME "main"
+#define MAIN_TOPIC "烹茶可貢西天佛"
+
+
+#define ROOM_LOCKED 1
+#define ROOM_SECRET 2
+#define ROOM_OPENTOPIC 4
+#define ROOM_HANDUP 8
+#define ROOM_ALL (NULL)
+
+
+#define LOCKED(room) (room->rflag & ROOM_LOCKED)
+#define SECRET(room) (room->rflag & ROOM_SECRET)
+#define OPENTOPIC(room) (room->rflag & ROOM_OPENTOPIC)
+#define RHANDUP(room) (room->rflag & ROOM_HANDUP)
+
+#define RESTRICTED(usr) (usr->uflag == 0) /* guest */
+#define CHATSYSOP(usr) (usr->uflag & ( PERM_SYSOP | PERM_CHATROOM))
+/* Thor: SYSOP 與 CHATROOM都是 chat總管 */
+#define PERM_ROOMOP PERM_CHAT /* Thor: 借 PERM_CHAT為 PERM_ROOMOP */
+#define PERM_HANDUP PERM_BM /* 借 PERM_BM 為有沒有舉手過 */
+#define PERM_SAY PERM_NOTOP /* 借 PERM_NOTOP 為有沒有發表權 */
+
+/* 進入時需清空 */
+/* Thor: ROOMOP為房間管理員 */
+#define ROOMOP(usr) (usr->uflag & ( PERM_ROOMOP | PERM_SYSOP | PERM_CHATROOM))
+#define CLOAK(usr) (usr->uflag & PERM_CLOAK)
+#define HANDUP(usr) (usr->uflag & PERM_HANDUP)
+#define SAY(usr) (usr->uflag & PERM_SAY)
+/* Thor: 聊天室隱身術 */
+
+
+/* ----------------------------------------------------- */
+/* ChatRoom data structure */
+/* ----------------------------------------------------- */
+
+typedef struct ChatRoom ChatRoom;
+typedef struct ChatUser ChatUser;
+typedef struct UserList UserList;
+typedef struct ChatCmd ChatCmd;
+typedef struct ChatAction ChatAction;
+
+struct ChatUser
+{
+ struct ChatUser *unext;
+ int sock; /* user socket */
+ ChatRoom *room;
+ UserList *ignore;
+ int userno;
+ int uflag;
+ int clitype; /* Xshadow: client type. 1 for common client,
+ * 0 for bbs only client */
+ time4_t uptime; /* Thor: unused */
+ char userid[IDLEN + 1]; /* real userid */
+ char chatid[9]; /* chat id */
+ char lasthost[30]; /* host address */
+ char ibuf[80]; /* buffer for non-blocking receiving */
+ int isize; /* current size of ibuf */
+};
+
+
+struct ChatRoom
+{
+ struct ChatRoom *next, *prev;
+ char name[IDLEN];
+ char topic[48]; /* Let the room op to define room topic */
+ int rflag; /* ROOM_LOCKED, ROOM_SECRET, ROOM_OPENTOPIC */
+ int occupants; /* number of users in room */
+ UserList *invite;
+};
+
+
+struct UserList
+{
+ struct UserList *next;
+ int userno;
+ char userid[IDLEN + 1];
+};
+
+
+struct ChatCmd
+{
+ char *cmdstr;
+ void (*cmdfunc) ();
+ int exact;
+};
+
+
+static ChatRoom mainroom;
+static ChatUser *mainuser;
+static fd_set mainfds;
+static int maxfds; /* number of sockets to select on */
+static int totaluser; /* current number of connections */
+static char chatbuf[256]; /* general purpose buffer */
+static int common_client_command;
+
+static char msg_not_op[] = "◆ 您不是這間聊天室的 Op";
+static char msg_no_such_id[] = "◆ 目前沒有人使用 [%s] 這個聊天代號";
+static char msg_not_here[] = "◆ [%s] 不在這間聊天室";
+
+
+#define FUZZY_USER ((ChatUser *) -1)
+
+
+typedef struct userec_t ACCT;
+
+/* ----------------------------------------------------- */
+/* acct_load for check acct */
+/* ----------------------------------------------------- */
+
+int
+acct_load(ACCT *acct, char *userid)
+{
+ int id;
+#ifdef SELFTEST
+ memset(acct, 0, sizeof(ACCT));
+ acct->userlevel |= PERM_BASIC|PERM_CHAT;
+ if(random()%4==0) acct->userlevel |= PERM_CHATROOM;
+ if(random()%8==0) acct->userlevel |= PERM_SYSOP;
+ return atoi(userid);
+#endif
+ if((id=searchuser(userid, NULL))<0)
+ return -1;
+ return get_record(FN_PASSWD, acct, sizeof(ACCT), id);
+}
+
+/* ----------------------------------------------------- */
+/* usr_fpath for check acct */
+/* ----------------------------------------------------- */
+char *str_home_file = "home/%c/%s/%s";
+
+void
+usr_fpath(char *buf, char *userid, char *fname)
+{
+ sprintf(buf, str_home_file, userid[0], userid, fname);
+}
+
+/* ----------------------------------------------------- */
+/* chkpasswd for check passwd */
+/* ----------------------------------------------------- */
+char *crypt(const char*, const char*);
+
+int
+chkpasswd(const char *passwd, const char *test)
+{
+ char *pw;
+ char pwbuf[PASSLEN];
+
+ strlcpy(pwbuf, test, PASSLEN);
+ pw = crypt(pwbuf, passwd);
+ return (!strncmp(pw, passwd, PASSLEN));
+}
+
+/* ----------------------------------------------------- */
+/* operation log and debug information */
+/* ----------------------------------------------------- */
+
+
+static int flog; /* log file descriptor */
+
+
+static void
+logit(char *key, char *msg)
+{
+ time4_t now;
+ struct tm *p;
+ char buf[512];
+
+ now = (time4_t)time(NULL);
+ p = localtime4(&now);
+ snprintf(buf, sizeof(buf), "%02d/%02d %02d:%02d:%02d %-13s%s\n",
+ p->tm_mon + 1, p->tm_mday,
+ p->tm_hour, p->tm_min, p->tm_sec, key, msg);
+ write(flog, buf, strlen(buf));
+}
+
+
+static void
+log_init()
+{
+ flog = open(CHAT_LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0644);
+ logit("START", "chat daemon");
+}
+
+
+static void
+log_close()
+{
+ close(flog);
+}
+
+
+#ifdef DEBUG
+static void
+debug_user()
+{
+ register ChatUser *user;
+ int i;
+ char buf[80];
+
+ i = 0;
+ for (user = mainuser; user; user = user->unext)
+ {
+ snprintf(buf, sizeof(buf), "%d) %s %s", ++i, user->userid, user->chatid);
+ logit("DEBUG_U", buf);
+ }
+}
+
+
+static void
+debug_room()
+{
+ register ChatRoom *room;
+ int i;
+ char buf[80];
+
+ i = 0;
+ room = &mainroom;
+
+ do
+ {
+ snprintf(buf, sizeof(buf), "%d) %s %d", ++i, room->name, room->occupants);
+ logit("DEBUG_R", buf);
+ } while (room = room->next);
+}
+#endif /* DEBUG */
+
+
+/* ----------------------------------------------------- */
+/* string routines */
+/* ----------------------------------------------------- */
+
+
+static int valid_chatid(register char *id) {
+ register int ch, len;
+
+ for(len = 0; (ch = *id); id++) {
+ /* Thor: check for endless */
+ if(ch == '/' || ch == '*' || ch == ':')
+ return 0;
+ if(++len > 8)
+ return 0;
+ }
+ return len;
+}
+
+/* Case Independent strcmp : 1 ==> euqal */
+
+
+static int
+str_equal(unsigned char *s1, unsigned char *s2)
+{
+ return strcasecmp(s1, s2)==0;
+}
+
+
+/* ----------------------------------------------------- */
+/* match strings' similarity case-insensitively */
+/* ----------------------------------------------------- */
+/* str_match(keyword, string) */
+/* ----------------------------------------------------- */
+/* 0 : equal ("foo", "foo") */
+/* -1 : mismatch ("abc", "xyz") */
+/* ow : similar ("goo", "good") */
+/* ----------------------------------------------------- */
+
+
+static int
+str_match(unsigned char *s1, unsigned char *s2)
+{
+ register int c1, c2;
+
+ for (;;)
+ { /* Thor: check for endless */
+ c2 = *s2;
+ c1 = *s1;
+ if (!c1)
+ {
+ return c2;
+ }
+
+ if (c1 >= 'A' && c1 <= 'Z')
+ c1 |= 32;
+
+ if (c2 >= 'A' && c2 <= 'Z')
+ c2 |= 32;
+
+ if (c1 != c2)
+ return -1;
+
+ s1++;
+ s2++;
+ }
+}
+
+
+/* ----------------------------------------------------- */
+/* search user/room by its ID */
+/* ----------------------------------------------------- */
+
+
+static ChatUser *
+cuser_by_userid(char *userid)
+{
+ register ChatUser *cu;
+
+ for (cu = mainuser; cu; cu = cu->unext)
+ {
+ if (str_equal(userid, cu->userid))
+ break;
+ }
+ return cu;
+}
+
+
+static ChatUser *
+cuser_by_chatid(char *chatid)
+{
+ register ChatUser *cu;
+
+ for (cu = mainuser; cu; cu = cu->unext)
+ {
+ if (str_equal(chatid, cu->chatid))
+ break;
+ }
+ return cu;
+}
+
+
+static ChatUser *
+fuzzy_cuser_by_chatid(char *chatid)
+{
+ register ChatUser *cu, *xuser;
+ int mode;
+ int count=0;
+
+ xuser = NULL;
+
+ for (cu = mainuser; cu; cu = cu->unext)
+ {
+ mode = str_match(chatid, cu->chatid);
+ if (mode == 0)
+ return cu;
+
+ if (mode > 0) {
+ xuser = cu;
+ count++;
+ }
+ }
+ if(count>1) return FUZZY_USER;
+ return xuser;
+}
+
+
+static ChatRoom *croom_by_roomid(char *roomid) {
+ ChatRoom *room;
+
+ for(room=&mainroom; room; room=room->next)
+ if(str_equal(roomid, room->name))
+ break;
+ return room;
+}
+
+
+/* ----------------------------------------------------- */
+/* UserList routines */
+/* ----------------------------------------------------- */
+
+
+static void
+list_free(UserList *list)
+{
+ UserList *tmp;
+
+ while (list)
+ {
+ tmp = list->next;
+
+ free(list);
+ list = tmp;
+ }
+}
+
+
+static void
+list_add(UserList **list, ChatUser *user)
+{
+ UserList *node;
+
+ if((node = (UserList *) malloc(sizeof(UserList)))) {
+ /* Thor: 防止空間不夠 */
+ strcpy(node->userid, user->userid);
+ node->userno = user->userno;
+ node->next = *list;
+ *list = node;
+ }
+}
+
+
+static int
+list_delete(UserList **list, char *userid)
+{
+ UserList *node;
+
+ while((node = *list)) {
+ if (str_equal(node->userid, userid))
+ {
+ *list = node->next;
+ free(node);
+ return 1;
+ }
+ list = &node->next; /* Thor: list要跟著前進 */
+ }
+
+ return 0;
+}
+
+
+static int
+list_belong(UserList *list, int userno)
+{
+ while (list)
+ {
+ if (userno == list->userno)
+ return 1;
+ list = list->next;
+ }
+ return 0;
+}
+
+
+/* ------------------------------------------------------ */
+/* non-blocking socket routines : send message to users */
+/* ------------------------------------------------------ */
+
+
+static void
+Xdo_send(int nfds, fd_set *wset, char *msg)
+{
+ struct timeval zerotv; /* timeval for selecting */
+ int sr;
+
+ /* Thor: for future reservation bug */
+
+ zerotv.tv_sec = 0;
+ zerotv.tv_usec = 16384; /* Ptt: 改成16384 避免不按時for loop吃cpu time
+ 16384 約每秒64次 */
+#ifdef SELFTEST
+ zerotv.tv_usec = 0;
+#endif
+
+ sr = select(nfds + 1, NULL, wset, NULL, &zerotv);
+
+ /* FIXME 若 select() timeout, 或有的 write ready 有的沒有. 則可能會漏接 msg? */
+ if (sr > 0)
+ {
+ register int len;
+
+ len = strlen(msg) + 1;
+ while (nfds >= 0)
+ {
+ if (FD_ISSET(nfds, wset))
+ {
+ send(nfds, msg, len, 0);/* Thor: 如果buffer滿了, 仍會 block */
+ if (--sr <= 0)
+ return;
+ }
+ nfds--;
+ }
+ }
+}
+
+
+static void
+send_to_room(ChatRoom *room, char *msg, int userno, int number)
+{
+ ChatUser *cu;
+ fd_set wset, *wptr;
+ int sock, max;
+ char sendbuf[256];
+ int clitype; /* 分為 bbs client 及 common client 兩次處理 */
+
+ for (clitype = (number == MSG_MESSAGE || !number) ? 0 : 1; clitype < 2; clitype++)
+ {
+
+ FD_ZERO(wptr = &wset);
+ max = -1;
+
+ for (cu = mainuser; cu; cu = cu->unext)
+ {
+ if (room == cu->room || room == ROOM_ALL)
+ {
+ if (cu->clitype == clitype && (!userno || !list_belong(cu->ignore, userno)))
+ {
+ sock = cu->sock;
+ FD_SET(sock, wptr);
+ if (max < sock)
+ max = sock;
+ }
+ }
+ }
+
+ if (max < 0)
+ continue;
+
+ if (clitype)
+ {
+ if (strlen(msg))
+ snprintf(sendbuf, sizeof(sendbuf), "%3d %s", number, msg);
+ else
+ snprintf(sendbuf, sizeof(sendbuf), "%3d", number);
+
+ Xdo_send(max, wptr, sendbuf);
+ }
+ else
+ Xdo_send(max, wptr, msg);
+ }
+}
+
+
+static void
+send_to_user(ChatUser *user, char *msg, int userno, int number)
+{
+ if (!user->clitype && number && number != MSG_MESSAGE)
+ return;
+
+ if (!userno || !list_belong(user->ignore, userno))
+ {
+ fd_set wset, *wptr;
+ int sock;
+ char sendbuf[256];
+
+ sock = user->sock;
+ FD_ZERO(wptr = &wset);
+ FD_SET(sock, wptr);
+
+ if (user->clitype)
+ {
+ if (strlen(msg))
+ snprintf(sendbuf, sizeof(sendbuf), "%3d %s", number, msg);
+ else
+ snprintf(sendbuf, sizeof(sendbuf), "%3d", number);
+ Xdo_send(sock, wptr, sendbuf);
+ }
+ else
+ Xdo_send(sock, wptr, msg);
+ }
+}
+
+#if 0
+static void
+send_to_sock(int sock, char *msg) /* Thor: unused */
+{
+ fd_set wset, *wptr;
+
+ FD_ZERO(wptr = &wset);
+ FD_SET(sock, wptr);
+ Xdo_send(sock, wptr, msg);
+}
+#endif
+
+/* ----------------------------------------------------- */
+
+static void
+room_changed(ChatRoom *room)
+{
+ if (!room)
+ return;
+
+ snprintf(chatbuf, sizeof(chatbuf), "= %s %d %d %s", room->name, room->occupants, room->rflag, room->topic);
+ send_to_room(ROOM_ALL, chatbuf, 0, MSG_ROOMNOTIFY);
+}
+
+static void
+user_changed(ChatUser *cu)
+{
+ if (!cu)
+ return;
+
+ snprintf(chatbuf, sizeof(chatbuf), "= %s %s %s %s", cu->userid, cu->chatid, cu->room->name, cu->lasthost);
+ if (ROOMOP(cu))
+ strcat(chatbuf, " Op");
+ send_to_room(cu->room, chatbuf, 0, MSG_USERNOTIFY);
+}
+
+static void
+exit_room(ChatUser *user, int mode, char *msg)
+{
+ ChatRoom *room;
+
+ if(user->room == NULL)
+ return;
+
+ room = user->room;
+ user->room = NULL;
+ user->uflag &= ~PERM_ROOMOP;
+
+ room->occupants--;
+
+ if (room->occupants > 0)
+ {
+ char *chatid;
+
+ chatid = user->chatid;
+ switch (mode)
+ {
+ case EXIT_LOGOUT:
+
+ sprintf(chatbuf, "◆ %s 離開了 ...", chatid);
+ if (msg && *msg)
+ {
+ strcat(chatbuf, ": ");
+ strncat(chatbuf, msg, 80);
+ }
+ break;
+
+ case EXIT_LOSTCONN:
+
+ sprintf(chatbuf, "◆ %s 成了斷線的風箏囉", chatid);
+ break;
+
+ case EXIT_KICK:
+
+ sprintf(chatbuf, "◆ 哈哈!%s 被踢出去了", chatid);
+ break;
+ }
+ if (!CLOAK(user)) /* Thor: 聊天室隱身術 */
+ send_to_room(room, chatbuf, 0, MSG_MESSAGE);
+
+ if (list_belong(room->invite, user->userno)) {
+ list_delete(&(room->invite), user->userid);
+ }
+
+ sprintf(chatbuf, "- %s", user->userid);
+ send_to_room(room, chatbuf, 0, MSG_USERNOTIFY);
+ room_changed(room);
+
+ return;
+ }
+
+ /* Now, room->occupants==0 */
+ if (room != &mainroom)
+ { /* Thor: 人數為0時,不是mainroom才free */
+ register ChatRoom *next;
+
+ sprintf(chatbuf, "- %s", room->name);
+ send_to_room(ROOM_ALL, chatbuf, 0, MSG_ROOMNOTIFY);
+
+ room->prev->next = room->next;
+ if((next = room->next))
+ next->prev = room->prev;
+ list_free(room->invite);
+
+ free(room);
+ }
+}
+
+
+/* ----------------------------------------------------- */
+/* chat commands */
+/* ----------------------------------------------------- */
+
+/* ----------------------------------------------------- */
+/* (.ACCT) 使用者帳號 (account) subroutines */
+/* ----------------------------------------------------- */
+
+static char datemsg[32];
+
+char *
+Ctime(time4_t *clock)
+{
+ struct tm *t = localtime4(clock);
+ const char *week = "日一二三四五六";
+
+ snprintf(datemsg, sizeof(datemsg), "%d年%2d月%2d日%3d:%02d:%02d 星期%.2s",
+ t->tm_year - 11, t->tm_mon + 1, t->tm_mday,
+ t->tm_hour, t->tm_min, t->tm_sec, &week[t->tm_wday << 1]);
+ return (datemsg);
+}
+
+static void
+chat_query(ChatUser *cu, char *msg)
+{
+ char str[256];
+ int i;
+ ACCT xuser;
+ FILE *fp;
+
+ if (acct_load(&xuser, msg) >= 0)
+ {
+ snprintf(chatbuf, sizeof(chatbuf), "%s(%s) 共上站 %d 次,文章 %d 篇",
+ xuser.userid, xuser.nickname, xuser.numlogins, xuser.numposts);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+
+ snprintf(chatbuf, sizeof(chatbuf), "最近(%s)從(%s)上站", Ctime(&xuser.lastlogin),
+ (xuser.lasthost[0] ? xuser.lasthost : "外太空"));
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+
+ usr_fpath(chatbuf, xuser.userid, "plans");
+ fp = fopen(chatbuf, "rt");
+ if(fp) {
+ i = 0;
+ while (fgets(str, sizeof(str), fp)) {
+ int len=strlen(str);
+ if (len==0)
+ continue;
+
+ str[len - 1] = 0;
+ send_to_user(cu, str, 0, MSG_MESSAGE);
+ if (++i >= MAX_QUERYLINES)
+ break;
+ }
+ fclose(fp);
+ }
+ }
+ else
+ {
+ snprintf(chatbuf, sizeof(chatbuf), msg_no_such_id, msg);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ }
+}
+
+static void
+chat_clear(ChatUser *cu, char *msg)
+{
+ if (cu->clitype)
+ send_to_user(cu, "", 0, MSG_CLRSCR);
+ else
+ send_to_user(cu, "/c", 0, MSG_MESSAGE);
+}
+
+static void
+chat_date(ChatUser *cu, char *msg)
+{
+ time4_t thetime;
+
+ thetime = time(NULL);
+ sprintf(chatbuf, "◆ 標準時間: %s", Ctime(&thetime));
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+}
+
+
+static void
+chat_topic(ChatUser *cu, char *msg)
+{
+ ChatRoom *room;
+ char *topic;
+
+ if (!ROOMOP(cu) && !OPENTOPIC(cu->room))
+ {
+ send_to_user(cu, msg_not_op, 0, MSG_MESSAGE);
+ return;
+ }
+
+ if (*msg == '\0')
+ {
+ send_to_user(cu, "※ 請指定話題", 0, MSG_MESSAGE);
+ return;
+ }
+
+ room = cu->room;
+ assert(room);
+ topic = room->topic;
+ strlcpy(topic, msg, sizeof(room->topic));
+
+ if (cu->clitype)
+ send_to_room(room, topic, 0, MSG_TOPIC);
+ else
+ {
+ sprintf(chatbuf, "/t%s", topic);
+ send_to_room(room, chatbuf, 0, 0);
+ }
+
+ room_changed(room);
+
+ sprintf(chatbuf, "◆ %s 將話題改為 %s", cu->chatid, topic);
+ if (!CLOAK(cu)) /* Thor: 聊天室隱身術 */
+ send_to_room(room, chatbuf, 0, MSG_MESSAGE);
+}
+
+
+static void
+chat_version(ChatUser *cu, char *msg)
+{
+ sprintf(chatbuf, "%d %d", XCHAT_VERSION_MAJOR, XCHAT_VERSION_MINOR);
+ send_to_user(cu, chatbuf, 0, MSG_VERSION);
+}
+
+static void
+chat_nick(ChatUser *cu, char *msg)
+{
+ char *chatid, *str;
+ ChatUser *xuser;
+
+ chatid = nextword(&msg);
+ chatid[8] = '\0';
+ if (!valid_chatid(chatid))
+ {
+ send_to_user(cu, "※ 這個聊天代號是不正確的", 0, MSG_MESSAGE);
+ return;
+ }
+
+ xuser = cuser_by_chatid(chatid);
+ if (xuser != NULL && xuser != cu)
+ {
+ send_to_user(cu, "※ 已經有人捷足先登囉", 0, MSG_MESSAGE);
+ return;
+ }
+
+ str = cu->chatid;
+
+ snprintf(chatbuf, sizeof(chatbuf), "※ %s 將聊天代號改為 %s", str, chatid);
+ if (!CLOAK(cu)) /* Thor: 聊天室隱身術 */
+ send_to_room(cu->room, chatbuf, cu->userno, MSG_MESSAGE);
+
+ strcpy(str, chatid);
+
+ user_changed(cu);
+
+ if (cu->clitype)
+ send_to_user(cu, chatid, 0, MSG_NICK);
+ else
+ {
+ snprintf(chatbuf, sizeof(chatbuf), "/n%s", chatid);
+ send_to_user(cu, chatbuf, 0, 0);
+ }
+}
+
+static void
+chat_list_rooms(ChatUser *cuser, char *msg)
+{
+ ChatRoom *cr;
+
+ if (RESTRICTED(cuser))
+ {
+ send_to_user(cuser, "※ 您沒有權限列出現有的聊天室", 0, MSG_MESSAGE);
+ return;
+ }
+
+ if (common_client_command)
+ send_to_user(cuser, "", 0, MSG_ROOMLISTSTART);
+ else
+ send_to_user(cuser, " 談天室名稱 │人數│話題 ", 0, MSG_MESSAGE);
+
+ for(cr = &mainroom; cr; cr = cr->next) {
+ if (!SECRET(cr) || CHATSYSOP(cuser) || (cr == cuser->room && ROOMOP(cuser)))
+ {
+ if (common_client_command)
+ {
+ snprintf(chatbuf, sizeof(chatbuf), "%s %d %d %s", cr->name, cr->occupants, cr->rflag, cr->topic);
+ send_to_user(cuser, chatbuf, 0, MSG_ROOMLIST);
+ }
+ else
+ {
+ snprintf(chatbuf, sizeof(chatbuf), " %-12s│%4d│%s", cr->name, cr->occupants, cr->topic);
+ if (LOCKED(cr))
+ strcat(chatbuf, " [鎖住]");
+ if (SECRET(cr))
+ strcat(chatbuf, " [秘密]");
+ if (OPENTOPIC(cr))
+ strcat(chatbuf, " [話題]");
+ send_to_user(cuser, chatbuf, 0, MSG_MESSAGE);
+ }
+
+ }
+ }
+
+ if (common_client_command)
+ send_to_user(cuser, "", 0, MSG_ROOMLISTEND);
+}
+
+
+static void
+chat_do_user_list(ChatUser *cu, char *msg, ChatRoom *theroom)
+{
+ ChatRoom *myroom, *room;
+ ChatUser *user;
+
+ int start, stop, curr = 0;
+ start = atoi(nextword(&msg));
+ stop = atoi(nextword(&msg));
+
+ myroom = cu->room;
+
+#ifdef DEBUG
+ logit(cu->chatid, "do user list");
+#endif
+
+ if (common_client_command)
+ send_to_user(cu, "", 0, MSG_USERLISTSTART);
+ else
+ send_to_user(cu, " 聊天代號│使用者代號 │聊天室 ", 0, MSG_MESSAGE);
+
+ for (user = mainuser; user; user = user->unext)
+ {
+
+ room = user->room;
+ if ((theroom != ROOM_ALL) && (theroom != room))
+ continue;
+
+ if (myroom != room)
+ {
+ if (RESTRICTED(cu) ||
+ (room && SECRET(room) && !CHATSYSOP(cu)))
+ continue;
+ }
+
+ if (CLOAK(user)) /* Thor: 隱身術 */
+ continue;
+
+
+ curr++;
+ if (start && curr < start)
+ continue;
+ else if (stop && (curr > stop))
+ break;
+
+ if (common_client_command)
+ {
+ if (!room)
+ continue; /* Xshadow: 還沒進入任何房間的就不列出 */
+
+ snprintf(chatbuf, sizeof(chatbuf), "%s %s %s %s", user->chatid, user->userid, room->name, user->lasthost);
+ if (ROOMOP(user))
+ strcat(chatbuf, " Op");
+ }
+ else
+ {
+ snprintf(chatbuf, sizeof(chatbuf), " %-8s│%-12s│%s", user->chatid, user->userid, room ? room->name : "[在門口徘徊]");
+ if (ROOMOP(user))
+ strcat(chatbuf, " [Op]");
+ }
+
+#ifdef DEBUG
+ logit("list_U", chatbuf);
+#endif
+
+ send_to_user(cu, chatbuf, 0, common_client_command ? MSG_USERLIST : MSG_MESSAGE);
+ }
+ if (common_client_command)
+ send_to_user(cu, "", 0, MSG_USERLISTEND);
+}
+
+static void
+chat_list_by_room(ChatUser *cu, char *msg)
+{
+ ChatRoom *whichroom;
+ char *roomstr;
+
+ roomstr = nextword(&msg);
+ if (*roomstr == '\0')
+ whichroom = cu->room;
+ else
+ {
+ if ((whichroom = croom_by_roomid(roomstr)) == NULL)
+ {
+ snprintf(chatbuf, sizeof(chatbuf), "※ 沒有 [%s] 這個聊天室", roomstr);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ return;
+ }
+
+ if (whichroom != cu->room && SECRET(whichroom) && !CHATSYSOP(cu))
+ { /* Thor: 要不要測同一room雖SECRET但可以列?
+ * Xshadow: 我改成同一 room 就可以列 */
+ send_to_user(cu, "※ 無法列出在秘密聊天室的使用者", 0, MSG_MESSAGE);
+ return;
+ }
+ }
+ chat_do_user_list(cu, msg, whichroom);
+}
+
+
+static void
+chat_list_users(ChatUser *cu, char *msg)
+{
+ chat_do_user_list(cu, msg, ROOM_ALL);
+}
+
+static void
+chat_chatroom(ChatUser *cu, char *msg)
+{
+ if (common_client_command)
+ send_to_user(cu, "批踢踢茶藝館 4 21", 0, MSG_CHATROOM);
+}
+
+static void
+chat_map_chatids(ChatUser *cu, ChatRoom *whichroom) /* Thor: 還沒有作不同間的 */
+{
+ int c;
+ ChatRoom *myroom, *room;
+ ChatUser *user;
+
+ myroom = whichroom;
+ send_to_user(cu,
+ " 聊天代號 使用者代號 │ 聊天代號 使用者代號 │ 聊天代號 使用者代號 ", 0, MSG_MESSAGE);
+
+ c = 0;
+
+ for (user = mainuser; user; user = user->unext)
+ {
+ room = user->room;
+ if (whichroom != ROOM_ALL && whichroom != room)
+ continue;
+ if (myroom != room)
+ {
+ if (RESTRICTED(cu) || /* Thor: 要先check room 是不是空的 */
+ (room && SECRET(room) && !CHATSYSOP(cu)))
+ continue;
+ }
+ if (CLOAK(user)) /* Thor:隱身術 */
+ continue;
+ sprintf(chatbuf + (c * 24), " %-8s%c%-12s%s",
+ user->chatid, ROOMOP(user) ? '*' : ' ',
+ user->userid, (c < 2 ? "│" : " "));
+ if (++c == 3)
+ {
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ c = 0;
+ }
+ }
+ if (c > 0)
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+}
+
+
+static void
+chat_map_chatids_thisroom(ChatUser *cu, char *msg)
+{
+ chat_map_chatids(cu, cu->room);
+}
+
+
+static void
+chat_setroom(ChatUser *cu, char *msg)
+{
+ char *modestr;
+ ChatRoom *room;
+ char *chatid;
+ int sign;
+ int flag;
+ char *fstr = NULL;
+
+ if (!ROOMOP(cu))
+ {
+ send_to_user(cu, msg_not_op, 0, MSG_MESSAGE);
+ return;
+ }
+
+ modestr = nextword(&msg);
+ sign = 1;
+ if (*modestr == '+')
+ modestr++;
+ else if (*modestr == '-')
+ {
+ modestr++;
+ sign = 0;
+ }
+ if (*modestr == '\0')
+ {
+ send_to_user(cu,
+ "※ 請指定狀態: {[+(設定)][-(取消)]}{[l(鎖住)][s(秘密)][t(開放話題)}", 0, MSG_MESSAGE);
+ return;
+ }
+
+ room = cu->room;
+ chatid = cu->chatid;
+
+ while (*modestr)
+ {
+ flag = 0;
+ switch (*modestr)
+ {
+ case 'l':
+ case 'L':
+ flag = ROOM_LOCKED;
+ fstr = "鎖住";
+ break;
+
+ case 's':
+ case 'S':
+ flag = ROOM_SECRET;
+ fstr = "秘密";
+ break;
+
+ case 't':
+ case 'T':
+ flag = ROOM_OPENTOPIC;
+ fstr = "開放話題";
+ break;
+ case 'h':
+ case 'H':
+ flag = ROOM_HANDUP;
+ fstr = "舉手發言";
+ break;
+
+ default:
+ sprintf(chatbuf, "※ 狀態錯誤:[%c]", *modestr);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ }
+
+ /* Thor: check room 是不是空的, 應該不是空的 */
+ if (flag && (room->rflag & flag) != sign * flag)
+ {
+ room->rflag ^= flag;
+ sprintf(chatbuf, "※ 本聊天室被 %s %s [%s] 狀態",
+ chatid, sign ? "設定為" : "取消", fstr);
+ if (!CLOAK(cu)) /* Thor: 聊天室隱身術 */
+ send_to_room(room, chatbuf, 0, MSG_MESSAGE);
+ }
+ modestr++;
+ }
+ room_changed(room);
+}
+
+static char *chat_msg[] =
+{
+ "[//]help", "MUD-like 社交動詞",
+ "[/h]elp op", "談天室管理員專用指令",
+ "[/a]ct <msg>", "做一個動作",
+ "[/b]ye [msg]", "道別",
+ "[/c]lear [/d]ate", "清除螢幕 目前時間",
+ /* "[/d]ate", "目前時間", *//* Thor: 指令太多 */
+
+#if 0
+ "[/f]ire <user> <msg>", "發送熱訊", /* Thor.0727: 和 flag 衝key */
+#endif
+
+ "[/i]gnore [user]", "忽略使用者",
+ "[/j]oin <room>", "建立或加入談天室",
+ "[/l]ist [start [stop]]", "列出談天室使用者",
+ "[/m]sg <id|user> <msg>", "跟 <id> 說悄悄話",
+ "[/n]ick <id>", "將談天代號換成 <id>",
+ "[/p]ager", "切換呼叫器",
+ "[/q]uery <user>", "查詢網友",
+ "[/r]oom", "列出一般談天室",
+ "[/t]ape", "開關錄音機",
+ "[/u]nignore <user>", "取消忽略",
+
+#if 0
+ "[/u]sers", "列出站上使用者",
+#endif
+
+ "[/w]ho", "列出本談天室使用者",
+ "[/w]hoin <room>", "列出談天室<room> 的使用者",
+ NULL
+};
+
+
+static char *room_msg[] =
+{
+ "[/f]lag [+-][lsth]", "設定鎖定、秘密、開放話題、舉手發言",
+ "[/i]nvite <id>", "邀請 <id> 加入談天室",
+ "[/kick] <id>", "將 <id> 踢出談天室",
+ "[/o]p <id>", "將 Op 的權力轉移給 <id>",
+ "[/topic] <text>", "換個話題",
+ "[/w]all", "廣播 (站長專用)",
+ NULL
+};
+
+
+static void
+chat_help(ChatUser *cu, char *msg)
+{
+ char **table, *str;
+
+ if (str_equal(nextword(&msg), "op"))
+ {
+ send_to_user(cu, "談天室管理員專用指令", 0, MSG_MESSAGE);
+ table = room_msg;
+ }
+ else
+ {
+ table = chat_msg;
+ }
+
+ while((str = *table++)) {
+ snprintf(chatbuf, sizeof(chatbuf), " %-20s- %s", str, *table++);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ }
+}
+
+
+static void
+chat_private(ChatUser *cu, char *msg)
+{
+ char *recipient;
+ ChatUser *xuser;
+ int userno;
+
+ userno = 0;
+ recipient = nextword(&msg);
+ xuser = (ChatUser *) fuzzy_cuser_by_chatid(recipient);
+ if (xuser == NULL)
+ { /* Thor.0724: 用 userid也可傳悄悄話 */
+ xuser = cuser_by_userid(recipient);
+ }
+ if (xuser == NULL)
+ {
+ sprintf(chatbuf, msg_no_such_id, recipient);
+ }
+ else if (xuser == FUZZY_USER)
+ { /* ambiguous */
+ strcpy(chatbuf, "※ 請指明聊天代號");
+ }
+ else if (*msg)
+ {
+ userno = cu->userno;
+ sprintf(chatbuf, "*%s* ", cu->chatid);
+ strncat(chatbuf, msg, 80);
+ send_to_user(xuser, chatbuf, userno, MSG_MESSAGE);
+
+ if (xuser->clitype)
+ { /* Xshadow: 如果對方是用 client 上來的 */
+ sprintf(chatbuf, "%s %s ", cu->userid, cu->chatid);
+ strncat(chatbuf, msg, 80);
+ send_to_user(xuser, chatbuf, userno, MSG_PRIVMSG);
+ }
+ if (cu->clitype)
+ {
+ sprintf(chatbuf, "%s %s ", xuser->userid, xuser->chatid);
+ strncat(chatbuf, msg, 80);
+ send_to_user(cu, chatbuf, 0, MSG_MYPRIVMSG);
+ }
+
+ sprintf(chatbuf, "%s> ", xuser->chatid);
+ strncat(chatbuf, msg, 80);
+ }
+ else
+ {
+ sprintf(chatbuf, "※ 您想對 %s 說什麼話呢?", xuser->chatid);
+ }
+ send_to_user(cu, chatbuf, userno, MSG_MESSAGE); /* Thor: userno 要改成 0
+ * 嗎? */
+}
+
+
+static void
+chat_cloak(ChatUser *cu, char *msg)
+{
+ if (CHATSYSOP(cu))
+ {
+ cu->uflag ^= PERM_CLOAK;
+ sprintf(chatbuf, "◆ %s", CLOAK(cu) ? MSG_CLOAKED : MSG_UNCLOAK);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ }
+}
+
+
+
+/* ----------------------------------------------------- */
+
+
+static void
+arrive_room(ChatUser *cuser, ChatRoom *room)
+{
+ char *rname;
+
+ /* Xshadow: 不必送給自己, 反正換房間就會重新 build user list */
+ snprintf(chatbuf, sizeof(chatbuf), "+ %s %s %s %s", cuser->userid, cuser->chatid, room->name, cuser->lasthost);
+ if (ROOMOP(cuser))
+ strcat(chatbuf, " Op");
+ send_to_room(room, chatbuf, 0, MSG_USERNOTIFY);
+
+ cuser->room = room;
+ room->occupants++;
+ rname = room->name;
+
+ room_changed(room);
+
+ if (cuser->clitype)
+ {
+ send_to_user(cuser, rname, 0, MSG_ROOM);
+ send_to_user(cuser, room->topic, 0, MSG_TOPIC);
+ }
+ else
+ {
+ sprintf(chatbuf, "/r%s", rname);
+ send_to_user(cuser, chatbuf, 0, 0);
+ sprintf(chatbuf, "/t%s", room->topic);
+ send_to_user(cuser, chatbuf, 0, 0);
+ }
+
+ sprintf(chatbuf, "※ %s 進入 [%s] 包廂",
+ cuser->chatid, rname);
+ if (!CLOAK(cuser)) /* Thor: 聊天室隱身術 */
+ send_to_room(room, chatbuf, cuser->userno, MSG_MESSAGE);
+}
+
+
+static int
+enter_room(ChatUser *cuser, char *rname, char *msg)
+{
+ ChatRoom *room;
+ int create;
+
+ create = 0;
+ room = croom_by_roomid(rname);
+ if (room == NULL)
+ {
+ /* new room */
+
+#ifdef MONITOR
+ logit(cuser->userid, "create new room");
+#endif
+
+ room = (ChatRoom *) malloc(sizeof(ChatRoom));
+ if (room == NULL)
+ {
+ send_to_user(cuser, "※ 無法再新闢包廂了", 0, MSG_MESSAGE);
+ return 0;
+ }
+
+ memset(room, 0, sizeof(ChatRoom));
+ strlcpy(room->name, rname, IDLEN);
+ strcpy(room->topic, "這是一個新天地");
+
+ snprintf(chatbuf, sizeof(chatbuf), "+ %s 1 0 %s", room->name, room->topic);
+ send_to_room(ROOM_ALL, chatbuf, 0, MSG_ROOMNOTIFY);
+
+ if (mainroom.next != NULL)
+ mainroom.next->prev = room;
+ room->next = mainroom.next;
+ mainroom.next = room;
+ room->prev = &mainroom;
+
+ create = 1;
+ }
+ else
+ {
+ if (cuser->room == room)
+ {
+ sprintf(chatbuf, "※ 您本來就在 [%s] 聊天室囉 :)", rname);
+ send_to_user(cuser, chatbuf, 0, MSG_MESSAGE);
+ return 0;
+ }
+
+ if (!CHATSYSOP(cuser) && LOCKED(room) && !list_belong(room->invite, cuser->userno))
+ {
+ send_to_user(cuser, "※ 內有惡犬,非請莫入", 0, MSG_MESSAGE);
+ return 0;
+ }
+ }
+
+ exit_room(cuser, EXIT_LOGOUT, msg);
+ arrive_room(cuser, room);
+
+ if (create)
+ cuser->uflag |= PERM_ROOMOP;
+
+ return 0;
+}
+
+
+static void
+logout_user(ChatUser *cuser)
+{
+ int sock;
+ ChatUser *xuser, *prev;
+
+ sock = cuser->sock;
+ shutdown(sock, 2);
+ close(sock);
+
+ FD_CLR(sock, &mainfds);
+
+#if 0 /* Thor: 也許不差這一個 */
+ if (sock >= maxfds)
+ maxfds = sock - 1;
+#endif
+
+ list_free(cuser->ignore);
+
+ xuser = mainuser;
+ if (xuser == cuser)
+ {
+ mainuser = cuser->unext;
+ }
+ else
+ {
+ do
+ {
+ prev = xuser;
+ xuser = xuser->unext;
+ if (xuser == cuser)
+ {
+ prev->unext = cuser->unext;
+ break;
+ }
+ } while (xuser);
+ }
+
+#ifdef DEBUG
+ sprintf(chatbuf, "%p", cuser);
+ logit("free cuser", chatbuf);
+#endif
+
+ free(cuser);
+
+ totaluser--;
+}
+
+
+static void
+print_user_counts(ChatUser *cuser)
+{
+ ChatRoom *room;
+ int num, userc, suserc, roomc, number;
+
+ userc = suserc = roomc = 0;
+
+ for(room = &mainroom; room; room = room->next) {
+ num = room->occupants;
+ if (SECRET(room))
+ {
+ suserc += num;
+ if (CHATSYSOP(cuser))
+ roomc++;
+ }
+ else
+ {
+ userc += num;
+ roomc++;
+ }
+ }
+
+ number = (cuser->clitype) ? MSG_MOTD : MSG_MESSAGE;
+
+ sprintf(chatbuf,
+ "☉ 歡迎光臨【批踢踢茶藝館】,目前開了 %d 間包廂", roomc);
+ send_to_user(cuser, chatbuf, 0, number);
+
+ sprintf(chatbuf, "☉ 共有 %d 人來擺\龍門陣", userc);
+ if (suserc)
+ sprintf(chatbuf + strlen(chatbuf), " [%d 人在秘密聊天室]", suserc);
+ send_to_user(cuser, chatbuf, 0, number);
+}
+
+
+static int
+login_user(ChatUser *cu, char *msg)
+{
+ int utent;
+
+ char *passwd;
+ char *userid;
+ char *chatid;
+ struct sockaddr_in from;
+ int fromlen;
+ struct hostent *hp;
+
+
+ ACCT acct;
+ int level;
+
+ /*
+ * Thor.0819: SECURED_CHATROOM : /! userid chatid passwd , userno
+ * el 在check完passwd後取得
+ */
+ /* Xshadow.0915: common client support : /-! userid chatid password */
+
+ /* 傳參數:userlevel, userid, chatid */
+
+ /* client/server 版本依據 userid 抓 .PASSWDS 判斷 userlevel */
+
+ userid = nextword(&msg);
+ chatid = nextword(&msg);
+
+
+#ifdef DEBUG
+ logit("ENTER", userid);
+#endif
+ /* Thor.0730: parse space before passwd */
+ passwd = msg;
+
+ /* Thor.0813: 跳過一空格即可, 因為反正如果chatid有空格, 密碼也不對 */
+ /* 就算密碼對, 也不會怎麼樣:p */
+ /* 可是如果密碼第一個字是空格, 那跳太多空格會進不來... */
+ if (*passwd == ' ')
+ passwd++;
+
+ /* Thor.0729: load acct */
+ if (!*userid || (acct_load(&acct, userid) < 0))
+ {
+
+#ifdef DEBUG
+ logit("noexist", chatid);
+#endif
+
+ if (cu->clitype)
+ send_to_user(cu, "錯誤的使用者代號", 0, ERR_LOGIN_NOSUCHUSER);
+ else
+ send_to_user(cu, CHAT_LOGIN_INVALID, 0, 0);
+
+ return -1;
+ }
+ else if(strncmp(passwd, acct.passwd, PASSLEN) &&
+ !chkpasswd(acct.passwd, passwd))
+ {
+#ifdef DEBUG
+ logit("fake", chatid);
+#endif
+
+ if (cu->clitype)
+ send_to_user(cu, "密碼錯誤", 0, ERR_LOGIN_PASSERROR);
+ else
+ send_to_user(cu, CHAT_LOGIN_INVALID, 0, 0);
+ return -1;
+ }
+
+ /* Thor.0729: if ok, read level. */
+ level = acct.userlevel;
+ /* Thor.0819: read userno for client/server bbs */
+#ifdef SELFTEST
+ utent = atoi(userid)+1;
+#else
+ utent = searchuser(acct.userid, NULL);
+#endif
+ assert(utent);
+
+ /* Thor.0819: for client/server bbs */
+/*
+ for (xuser = mainuser; xuser; xuser = xuser->unext)
+ {
+ if (xuser->userno == utent)
+ {
+
+ #ifdef DEBUG
+ logit("enter", "bogus");
+ #endif
+ if (cu->clitype)
+ send_to_user(cu, "請勿派遣分身進入聊天室 !!", 0, ERR_LOGIN_USERONLINE);
+ else
+ send_to_user(cu, CHAT_LOGIN_BOGUS, 0, 0);
+ return -1;
+ }
+ }
+*/
+ if (!valid_chatid(chatid))
+ {
+#ifdef DEBUG
+ logit("enter", chatid);
+#endif
+
+ if (cu->clitype)
+ send_to_user(cu, "不合法的聊天室代號 !!", 0, ERR_LOGIN_NICKERROR);
+ else
+ send_to_user(cu, CHAT_LOGIN_INVALID, 0, 0);
+ return 0;
+ }
+
+ if (cuser_by_chatid(chatid) != NULL)
+ {
+ /* chatid in use */
+
+#ifdef DEBUG
+ logit("enter", "duplicate");
+#endif
+
+ if (cu->clitype)
+ send_to_user(cu, "這個代號已經有人使用", 0, ERR_LOGIN_NICKINUSE);
+ else
+ send_to_user(cu, CHAT_LOGIN_EXISTS, 0, 0);
+ return 0;
+ }
+
+ cu->userno = utent;
+ cu->uflag = level & ~(PERM_ROOMOP | PERM_CLOAK | PERM_HANDUP | PERM_SAY);
+ /* Thor: 進來先清空ROOMOP(同PERM_CHAT), CLOAK */
+ strcpy(cu->userid, userid);
+ strlcpy(cu->chatid, chatid, sizeof(cu->chatid));
+
+ /* Xshadow: 取得 client 的來源 */
+ fromlen = sizeof(from);
+ if (!getpeername(cu->sock, (struct sockaddr *) & from, &fromlen))
+ {
+ if ((hp = gethostbyaddr((char *) &from.sin_addr, sizeof(struct in_addr), from.sin_family)))
+ strcpy(cu->lasthost, hp->h_name);
+ else
+ strcpy(cu->lasthost, (char *) inet_ntoa(from.sin_addr));
+ }
+ else
+ strcpy(cu->lasthost, "[外太空]");
+
+ if (cu->clitype)
+ send_to_user(cu, "順利", 0, MSG_LOGINOK);
+ else
+ send_to_user(cu, CHAT_LOGIN_OK, 0, 0);
+
+ arrive_room(cu, &mainroom);
+
+ send_to_user(cu, "", 0, MSG_MOTDSTART);
+ print_user_counts(cu);
+ send_to_user(cu, "", 0, MSG_MOTDEND);
+
+#ifdef DEBUG
+ logit("enter", "OK");
+#endif
+
+ return 0;
+}
+
+
+static void
+chat_act(ChatUser *cu, char *msg)
+{
+ if (*msg && (!RHANDUP(cu->room) || SAY(cu) || ROOMOP(cu)))
+ {
+ sprintf(chatbuf, "%s %s", cu->chatid, msg);
+ send_to_room(cu->room, chatbuf, cu->userno, MSG_MESSAGE);
+ }
+}
+
+
+static void
+chat_ignore(ChatUser *cu, char *msg)
+{
+
+ if (RESTRICTED(cu))
+ {
+ strcpy(chatbuf, "※ 您沒有 ignore 別人的權利");
+ }
+ else
+ {
+ char *ignoree;
+
+ ignoree = nextword(&msg);
+ if (*ignoree)
+ {
+ ChatUser *xuser;
+
+ xuser = cuser_by_userid(ignoree);
+
+ if (xuser == NULL)
+ {
+ sprintf(chatbuf, msg_no_such_id, ignoree);
+ }
+ else if (xuser == cu || CHATSYSOP(xuser) ||
+ (ROOMOP(xuser) && (xuser->room == cu->room)))
+ {
+ sprintf(chatbuf, "◆ 不可以 ignore [%s]", ignoree);
+ }
+ else
+ {
+
+ if (list_belong(cu->ignore, xuser->userno))
+ {
+ sprintf(chatbuf, "※ %s 已經被凍結了", xuser->chatid);
+ }
+ else
+ {
+ list_add(&(cu->ignore), xuser);
+ sprintf(chatbuf, "◆ 將 [%s] 打入冷宮了 :p", xuser->chatid);
+ }
+ }
+ }
+ else
+ {
+ UserList *list;
+
+ if((list = cu->ignore))
+ {
+ int len;
+ char buf[16];
+
+ send_to_user(cu, "◆ 這些人被打入冷宮了:", 0, MSG_MESSAGE);
+ len = 0;
+ do
+ {
+ sprintf(buf, "%-13s", list->userid);
+ strcpy(chatbuf + len, buf);
+ len += 13;
+ if (len >= 78)
+ {
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ len = 0;
+ }
+ } while((list = list->next));
+
+ if (len == 0)
+ return;
+ }
+ else
+ {
+ strcpy(chatbuf, "◆ 您目前並沒有 ignore 任何人");
+ }
+ }
+ }
+
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+}
+
+
+static void
+chat_unignore(ChatUser *cu, char *msg)
+{
+ char *ignoree;
+
+ ignoree = nextword(&msg);
+
+ if (*ignoree)
+ {
+ sprintf(chatbuf, (list_delete(&(cu->ignore), ignoree)) ?
+ "◆ [%s] 不再被你冷落了" :
+ "◆ 您並未 ignore [%s] 這號人物", ignoree);
+ }
+ else
+ {
+ strcpy(chatbuf, "◆ 請指明 user ID");
+ }
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+}
+
+
+static void
+chat_join(ChatUser *cu, char *msg)
+{
+ if (RESTRICTED(cu))
+ {
+ send_to_user(cu, "※ 您沒有加入其他聊天室的權限", 0, MSG_MESSAGE);
+ }
+ else
+ {
+ char *roomid = nextword(&msg);
+
+ if (*roomid)
+ enter_room(cu, roomid, msg);
+ else
+ send_to_user(cu, "※ 請指定聊天室的名字", 0, MSG_MESSAGE);
+ }
+}
+
+
+static void
+chat_kick(ChatUser *cu, char *msg)
+{
+ char *twit;
+ ChatUser *xuser;
+ ChatRoom *room;
+
+ if (!ROOMOP(cu))
+ {
+ send_to_user(cu, msg_not_op, 0, MSG_MESSAGE);
+ return;
+ }
+
+ twit = nextword(&msg);
+ xuser = cuser_by_chatid(twit);
+
+ if (xuser == NULL)
+ {
+ sprintf(chatbuf, msg_no_such_id, twit);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ return;
+ }
+
+ room = cu->room;
+ if (room != xuser->room || CLOAK(xuser))
+ { /* Thor: 聊天室隱身術 */
+ sprintf(chatbuf, msg_not_here, twit);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ return;
+ }
+
+ if (CHATSYSOP(xuser))
+ { /* Thor: 踢不走 CHATSYSOP */
+ sprintf(chatbuf, "◆ 不可以 kick [%s]", twit);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ return;
+ }
+
+ exit_room(xuser, EXIT_KICK, (char *) NULL);
+
+ if (room == &mainroom)
+ logout_user(xuser);
+ else
+ enter_room(xuser, MAIN_NAME, (char *) NULL);
+}
+
+
+static void
+chat_makeop(ChatUser *cu, char *msg)
+{
+ char *newop;
+ ChatUser *xuser;
+ ChatRoom *room;
+
+ if (!ROOMOP(cu))
+ {
+ send_to_user(cu, msg_not_op, 0, MSG_MESSAGE);
+ return;
+ }
+
+ newop = nextword(&msg);
+ xuser = cuser_by_chatid(newop);
+
+ if (xuser == NULL)
+ {
+ sprintf(chatbuf, msg_no_such_id, newop);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ return;
+ }
+
+ if (cu == xuser)
+ {
+ sprintf(chatbuf, "※ 您早就已經是 Op 了啊");
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ return;
+ }
+
+ room = cu->room;
+
+ if (room != xuser->room || CLOAK(xuser))
+ { /* Thor: 聊天室隱身術 */
+ sprintf(chatbuf, msg_not_here, xuser->chatid);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ return;
+ }
+
+ cu->uflag &= ~PERM_ROOMOP;
+ xuser->uflag |= PERM_ROOMOP;
+
+ user_changed(cu);
+ user_changed(xuser);
+
+ sprintf(chatbuf, "※ %s 將 Op 權力轉移給 %s",
+ cu->chatid, xuser->chatid);
+ if (!CLOAK(cu)) /* Thor: 聊天室隱身術 */
+ send_to_room(room, chatbuf, 0, MSG_MESSAGE);
+}
+
+
+
+static void
+chat_invite(ChatUser *cu, char *msg)
+{
+ char *invitee;
+ ChatUser *xuser;
+ ChatRoom *room;
+ UserList **list;
+
+ if (!ROOMOP(cu))
+ {
+ send_to_user(cu, msg_not_op, 0, MSG_MESSAGE);
+ return;
+ }
+
+ invitee = nextword(&msg);
+ xuser = cuser_by_chatid(invitee);
+ if (xuser == NULL)
+ {
+ sprintf(chatbuf, msg_no_such_id, invitee);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ return;
+ }
+
+ room = cu->room;
+ assert(room);
+ list = &(room->invite);
+
+ if (list_belong(*list, xuser->userno))
+ {
+ sprintf(chatbuf, "※ %s 已經接受過邀請了", xuser->chatid);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ return;
+ }
+ list_add(list, xuser);
+
+ sprintf(chatbuf, "※ %s 邀請您到 [%s] 聊天室",
+ cu->chatid, room->name);
+ send_to_user(xuser, chatbuf, 0, MSG_MESSAGE); /* Thor: 要不要可以 ignore? */
+ sprintf(chatbuf, "※ %s 收到您的邀請了", xuser->chatid);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+}
+
+
+static void
+chat_broadcast(ChatUser *cu, char *msg)
+{
+ if (!CHATSYSOP(cu))
+ {
+ send_to_user(cu, "※ 您沒有在聊天室廣播的權力!", 0, MSG_MESSAGE);
+ return;
+ }
+ if (*msg == '\0')
+ {
+ send_to_user(cu, "※ 請指定廣播內容", 0, MSG_MESSAGE);
+ return;
+ }
+ sprintf(chatbuf, "※ " BBSNAME "談天室廣播中 [%s].....",
+ cu->chatid);
+ send_to_room(ROOM_ALL, chatbuf, 0, MSG_MESSAGE);
+ sprintf(chatbuf, "◆ %s", msg);
+ send_to_room(ROOM_ALL, chatbuf, 0, MSG_MESSAGE);
+}
+
+
+static void
+chat_goodbye(ChatUser *cu, char *msg)
+{
+ exit_room(cu, EXIT_LOGOUT, msg);
+ /* Thor: 要不要加 logout_user(cu) ? */
+}
+
+
+/* --------------------------------------------- */
+/* MUD-like social commands : action */
+/* --------------------------------------------- */
+
+struct ChatAction
+{
+ char *verb; /* 動詞 */
+ char *chinese; /* 中文翻譯 */
+ char *part1_msg; /* 介詞 */
+ char *part2_msg; /* 動作 */
+};
+
+
+static ChatAction party_data[] =
+{
+ {"aluba", "阿魯巴", "把", "架上柱子阿魯巴!!"},
+ {"adore", "景仰", "對", "的景仰有如滔滔江水,連綿不絕……"},
+ {"bearhug", "熱擁", "熱情的擁抱", ""},
+ {"blade", "一刀", "一刀啟程把", "送上西天"},
+ {"bless", "祝福", "祝福", "心想事成"},
+ {"board", "主機板", "把", "抓去跪主機板"},
+ {"bokan", "氣功\", "雙掌微合,蓄勢待發……突然間,電光乍現,對", "使出了Bo--Kan!"},
+ {"bow", "鞠躬", "畢躬畢敬的向", "鞠躬"},
+ {"box", "幕之內", "開始輪擺\式移位,對", "作肝臟攻擊"},
+ {"boy", "平底鍋", "從背後拿出了平底鍋,把", "敲昏了"},
+ {"bye", "掰掰", "向", "說掰掰!!"},
+ {"call", "呼喚", "大聲的呼喚,啊~~", "啊~~~你在哪裡啊啊啊啊~~~~"},
+ {"caress", "輕撫", "輕輕的撫摸著", ""},
+ {"clap", "鼓掌", "向", "熱烈鼓掌"},
+ {"claw", "抓抓", "從貓咪樂園借了隻貓爪,把", "抓得死去活來"},
+ {"comfort", "安慰", "溫言安慰", ""},
+ {"cong", "恭喜", "從背後拿出了拉炮,呯!呯!恭喜", ""},
+ {"cpr", "口對口", "對著", "做口對口人工呼吸"},
+ {"cringe", "乞憐", "向", "卑躬屈膝,搖尾乞憐"},
+ {"cry", "大哭", "向", "嚎啕大哭"},
+ {"dance", "跳舞", "拉了", "的手翩翩起舞" },
+ {"destroy", "毀滅", "祭起了『極大毀滅咒文』,轟向", ""},
+ {"dogleg", "狗腿", "對", "狗腿"},
+ {"drivel", "流口水", "對著", "流口水"},
+ {"envy", "羨慕", "向", "流露出羨慕的眼光"},
+ {"eye", "送秋波", "對", "頻送秋波"},
+ {"fire", "銬問", "拿著火紅的鐵棒走向", ""},
+ {"forgive", "原諒", "接受道歉,原諒了", ""},
+ {"french", "法式吻", "把舌頭伸到", "喉嚨裡∼∼∼哇!一個浪漫的法國氏深吻"},
+ {"giggle", "傻笑", "對著", "傻傻的呆笑"},
+ {"glue", "補心", "用三秒膠,把", "的心黏了起來"},
+ {"goodbye", "告別", "淚\眼汪汪的向", "告別"},
+ {"grin", "奸笑", "對", "露出邪惡的笑容"},
+ {"growl", "咆哮", "對", "咆哮不已"},
+ {"hand", "握手", "跟", "握手"},
+ {"hide", "躲", "躲在", "背後"},
+ {"hospitl", "送醫院", "把", "送進醫院"},
+ {"hug", "擁抱", "輕輕地擁抱", ""},
+ {"hrk", "昇龍拳", "沉穩了身形,匯聚了內勁,對", "使出了一記Ho--Ryu--Kan!!!"},
+ {"jab", "戳人", "溫柔的戳著", ""},
+ {"judo", "過肩摔", "抓住了", "的衣襟,轉身……啊,是一記過肩摔!"},
+ {"kickout", "踢", "用大腳把", "踢到山下去了"},
+ {"kick", "踢人", "把", "踢的死去活來"},
+ {"kiss", "輕吻", "輕吻", "的臉頰"},
+ {"laugh", "嘲笑", "大聲嘲笑", ""},
+ {"levis", "給我", "說:給我", "!其餘免談!"},
+ {"lick", "舔", "狂舔", ""},
+ {"lobster", "壓制", "施展逆蝦形固定,把", "壓制在地板上"},
+ {"love", "表白", "對", "深情的表白"},
+ {"marry", "求婚", "捧著九百九十九朵玫瑰向", "求婚"},
+ {"no", "不要啊", "拼命對著", "搖頭~~~~不要啊~~~~"},
+ {"nod", "點頭", "向", "點頭稱是"},
+ {"nudge", "頂肚子", "用手肘頂", "的肥肚子"},
+ {"pad", "拍肩膀", "輕拍", "的肩膀"},
+ {"pettish", "撒嬌", "跟", "嗲聲嗲氣地撒嬌"},
+ {"pili", "霹靂", "使出 君子風 天地根 般若懺 三式合一打向", "~~~~~~"},
+ {"pinch", "擰人", "用力的把", "擰的黑青"},
+ {"roll", "打滾", "放出多爾袞的音樂,", "在地上滾來滾去"},
+ {"protect", "保護", "保護著", ""},
+ {"pull", "拉", "死命地拉住", "不放"},
+ {"punch", "揍人", "狠狠揍了", "一頓"},
+ {"rascal", "耍賴", "跟", "耍賴"},
+ {"recline", "入懷", "鑽到", "的懷裡睡著了……"},
+ {"respond", "負責", "安慰", "說:『不要哭,我會負責的……』"},
+ {"shrug", "聳肩", "無奈地向", "聳了聳肩膀"},
+ {"sigh", "歎氣", "對", "歎了一口氣"},
+ {"slap", "打耳光", "啪啪的巴了", "一頓耳光"},
+ {"smooch", "擁吻", "擁吻著", ""},
+ {"snicker", "竊笑", "嘿嘿嘿..的對", "竊笑"},
+ {"sniff", "不屑", "對", "嗤之以鼻"},
+ {"spank", "打屁屁", "用巴掌打", "的臀部"},
+ {"squeeze", "緊擁", "緊緊地擁抱著", ""},
+ {"sysop", "召喚", "叫出了批踢踢,把", "踩扁了!"},
+ {"thank", "感謝", "向", "感謝得五體投地"},
+ {"tickle", "搔癢", "咕嘰!咕嘰!搔", "的癢"},
+ {"wake", "搖醒", "輕輕地把", "搖醒"},
+ {"wave", "揮手", "對著", "拼命的搖手"},
+ {"welcome", "歡迎", "歡迎", "進來八卦一下"},
+ {"what", "什麼", "說:『", "哩公瞎密哇隴聽某???﹖?』"},
+ {"whip", "鞭子", "手上拿著蠟燭,用鞭子痛打", ""},
+ {"wink", "眨眼", "對", "神秘的眨眨眼睛"},
+ {"zap", "猛攻", "對", "瘋狂的攻擊"},
+ {NULL, NULL, NULL, NULL}
+};
+
+static int
+party_action(ChatUser *cu, char *cmd, char *party)
+{
+ ChatAction *cap;
+ char *verb;
+
+ for (cap = party_data; (verb = cap->verb); cap++)
+ {
+ if (!str_equal(cmd, verb))
+ continue;
+ if (*party == '\0')
+ party = "大家";
+ else
+ {
+ ChatUser *xuser;
+
+ xuser = fuzzy_cuser_by_chatid(party);
+ if (xuser == NULL)
+ { /* Thor.0724: 用 userid也嘛通 */
+ xuser = cuser_by_userid(party);
+ }
+
+ if (xuser == NULL)
+ {
+ sprintf(chatbuf, msg_no_such_id, party);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ return 0;
+ }
+ else if (xuser == FUZZY_USER)
+ {
+ sprintf(chatbuf, "※ 請指明聊天代號");
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ return 0;
+ }
+ else if (cu->room != xuser->room || CLOAK(xuser))
+ {
+ sprintf(chatbuf, msg_not_here, party);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ return 0;
+ }
+ else
+ {
+ party = xuser->chatid;
+ }
+ }
+ sprintf(chatbuf, "%s %s %s %s",
+ cu->chatid, cap->part1_msg, party, cap->part2_msg);
+ send_to_room(cu->room, chatbuf, cu->userno, MSG_MESSAGE);
+ return 0;
+ }
+ return 1;
+}
+
+
+/* --------------------------------------------- */
+/* MUD-like social commands : speak */
+/* --------------------------------------------- */
+
+
+static ChatAction speak_data[] =
+{
+ { "ask", "詢問", "問", NULL },
+ { "chant", "歌頌", "高聲歌頌", NULL },
+ { "cheer", "喝采", "喝采", NULL },
+ { "chuckle", "輕笑", "輕笑", NULL },
+ { "curse", "暗幹", "暗幹", NULL },
+ /* {"curse", "咒罵", NULL}, */
+ { "demand", "要求", "要求", NULL },
+ { "frown", "皺眉頭", "蹙眉", NULL },
+ { "groan", "呻吟", "呻吟", NULL },
+ { "grumble", "發牢騷", "發牢騷", NULL },
+ { "guitar", "彈唱", "邊彈著吉他,邊唱著", NULL },
+ /* {"helpme", "呼救","大聲呼救",NULL}, */
+ { "hum", "喃喃", "喃喃自語", NULL },
+ { "moan", "怨嘆", "怨嘆", NULL },
+ { "notice", "強調", "強調", NULL },
+ { "order", "命令", "命令", NULL },
+ { "ponder", "沈思", "沈思", NULL },
+ { "pout", "噘嘴", "噘著嘴說", NULL },
+ { "pray", "祈禱", "祈禱", NULL },
+ { "request", "懇求", "懇求", NULL },
+ { "shout", "大罵", "大罵", NULL },
+ { "sing", "唱歌", "唱歌", NULL },
+ { "smile", "微笑", "微笑", NULL },
+ { "smirk", "假笑", "假笑", NULL },
+ { "swear", "發誓", "發誓", NULL },
+ { "tease", "嘲笑", "嘲笑", NULL },
+ { "whimper", "嗚咽", "嗚咽的說", NULL },
+ { "yawn", "哈欠", "邊打哈欠邊說", NULL },
+ { "yell", "大喊", "大喊", NULL },
+ { NULL, NULL, NULL, NULL }
+};
+
+
+static int
+speak_action(ChatUser *cu, char *cmd, char *msg)
+{
+ ChatAction *cap;
+ char *verb;
+
+ for (cap = speak_data; (verb = cap->verb); cap++)
+ {
+ if (!str_equal(cmd, verb))
+ continue;
+ sprintf(chatbuf, "%s %s: %s",
+ cu->chatid, cap->part1_msg, msg);
+ send_to_room(cu->room, chatbuf, cu->userno, MSG_MESSAGE);
+ return 0;
+ }
+ return 1;
+}
+
+
+/* -------------------------------------------- */
+/* MUD-like social commands : condition */
+/* -------------------------------------------- */
+
+
+static ChatAction condition_data[] =
+{
+ { "applaud", "拍手", "啪啪啪啪啪啪啪....", NULL },
+ { "ayo", "唉呦喂", "唉呦喂~~~", NULL },
+ { "back", "坐回來", "回來坐正繼續奮戰", NULL },
+ { "blood", "在血中", "倒在血泊之中", NULL },
+ { "blush", "臉紅", "臉都紅了", NULL },
+ { "broke", "心碎", "的心破碎成一片一片的", NULL },
+ /* {"bokan", "Bo Kan! Bo Kan!", NULL}, */
+ { "careles", "沒人理", "嗚∼∼都沒有人理我 :~~~~", NULL },
+ { "chew", "嗑瓜子", "很悠閒的嗑起瓜子來了", NULL },
+ { "climb", "爬山", "自己慢慢爬上山來……", NULL },
+ { "cold", "感冒了", "感冒了,媽媽不讓我出去玩 :~~~(", NULL },
+ { "cough", "咳嗽", "咳了幾聲", NULL },
+ { "die", "暴斃", "當場暴斃", NULL },
+ { "faint", "昏倒", "當場昏倒", NULL },
+ { "flop", "香蕉皮", "踩到香蕉皮... 滑倒!", NULL },
+ { "fly", "飄飄然", "飄飄然", NULL },
+ { "frown", "蹙眉", "蹙眉", NULL },
+ { "gold", "拿金牌", "唱著:『金ㄍㄠˊ金ㄍㄠˊ 出國比賽! 得冠軍,拿金牌,光榮倒鄧來!』", NULL },
+ { "gulu", "肚子餓", "的肚子發出咕嚕~~~咕嚕~~~的聲音", NULL },
+ { "haha", "哇哈哈", "哇哈哈哈.....^o^", NULL },
+ /* {"haha", "大笑","哇哈哈哈哈哈哈哈哈~~~~!!!!!", NULL}, */
+ { "helpme", "求救", "大喊~~~救命啊~~~~", NULL },
+ { "hoho", "呵呵笑", "呵呵呵笑個不停", NULL },
+ { "happy", "高興", "高興得在地上打滾", NULL },
+ /* {"happy", "高興", "YA! *^_^*", NULL}, */
+ /* {"happy", "", "r-o-O-m....聽了真爽!", NULL}, */
+ /* {"hurricane", "Ho---Ryu--Kan!!!", NULL}, */
+ { "idle", "呆住了", "呆住了", NULL },
+ { "jacky", "晃晃", "痞子般的晃來晃去", NULL },
+
+#if 0
+ /* Thor.0729: 不知其意 */
+ { "lag", "網路慢", "lllllllaaaaaaaaaaaagggggggggggggg.................", NULL },
+#endif
+
+ { "luck", "幸運", "哇!福氣啦!", NULL },
+ { "macarn", "一種舞", "開始跳起了MaCaReNa∼∼∼∼", NULL },
+ { "miou", "喵喵", "喵喵口苗口苗∼∼∼∼∼", NULL },
+ { "mouth", "扁嘴", "扁嘴中!!", NULL },
+ { "nani", "怎麼會", ":奈ㄝ啊捏??", NULL },
+ { "nose", "流鼻血", "流鼻血", NULL },
+ { "puke", "嘔吐", "嘔吐中", NULL },
+ /* {"puke", "真噁心,我聽了都想吐", NULL}, */
+ { "rest", "休息", "休息中,請勿打擾", NULL },
+ { "reverse", "翻肚", "翻肚", NULL },
+ { "room", "開房間", "r-o-O-m-r-O-O-Mmm-rRR........", NULL },
+ { "shake", "搖頭", "搖了搖頭", NULL },
+ { "sleep", "睡著", "趴在鍵盤上睡著了,口水流進鍵盤,造成當機!", NULL },
+ /* {"sleep", "Zzzzzzzzzz,真無聊,都快睡著了", NULL}, */
+ { "so", "就醬子", "就醬子!!", NULL },
+ { "sorry", "道歉", "嗚啊!!我對不起大家,我對不起國家社會~~~~~~嗚啊~~~~~", NULL },
+ { "story", "講古", "開始講古了", NULL },
+ { "strut", "搖擺\走", "大搖大擺\地走", NULL },
+ { "suicide", "自殺", "自殺", NULL },
+ { "tea", "泡茶", "泡了壺好茶", NULL },
+ { "think", "思考", "歪著頭想了一下", NULL },
+ { "tongue", "吐舌", "吐了吐舌頭", NULL },
+ { "wall", "撞牆", "跑去撞牆", NULL },
+ { "wawa", "哇哇", "哇哇哇~~~~~!!!!! ~~~>_<~~~", NULL },
+ /* {"wawa","哇哇哇......>_<",NULL}, */
+ { "www", "汪汪", "汪汪汪!!!", NULL },
+ { "zzz", "打呼", "呼嚕~~~~ZZzZzzZZZzzZzzzZZ...", NULL },
+ { NULL, NULL, NULL, NULL }
+};
+
+
+static int
+condition_action(ChatUser *cu, char *cmd)
+{
+ ChatAction *cap;
+ char *verb;
+
+ for (cap = condition_data; (verb = cap->verb); cap++)
+ {
+ if (str_equal(cmd, verb))
+ {
+ sprintf(chatbuf, "%s %s",
+ cu->chatid, cap->part1_msg);
+ send_to_room(cu->room, chatbuf, cu->userno, MSG_MESSAGE);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/* --------------------------------------------- */
+/* MUD-like social commands : help */
+/* --------------------------------------------- */
+
+
+static char *dscrb[] =
+{
+ "【 Verb + Nick: 動詞 + 對方名字 】 例://kick piggy",
+ "【 Verb + Message:動詞 + 要說的話 】 例://sing 天天天藍",
+ "【 Verb:動詞 】 ↑↓:舊話重提", NULL
+};
+ChatAction *catbl[] =
+{
+ party_data, speak_data, condition_data, NULL
+};
+
+static void
+chat_partyinfo(ChatUser *cu, char *msg)
+{
+ if (!common_client_command)
+ return; /* only allow common client to retrieve it */
+
+ sprintf(chatbuf, "3 動作 交談 狀態");
+ send_to_user(cu, chatbuf, 0, MSG_PARTYINFO);
+}
+
+static void
+chat_party(ChatUser *cu, char *msg)
+{
+ int kind, i;
+ ChatAction *cap;
+
+ if (!common_client_command)
+ return;
+
+ kind = atoi(nextword(&msg));
+ if (kind < 0 || kind > 2)
+ return;
+
+ sprintf(chatbuf, "%d %s", kind, kind == 2 ? "I" : "");
+
+ /* Xshadow: 只有 condition 才是 immediate mode */
+ send_to_user(cu, chatbuf, 0, MSG_PARTYLISTSTART);
+
+ cap = catbl[kind];
+ for (i = 0; cap[i].verb; i++)
+ {
+ sprintf(chatbuf, "%-10s %-20s", cap[i].verb, cap[i].chinese);
+ /* for (j=0;j<1000000;j++); */
+ send_to_user(cu, chatbuf, 0, MSG_PARTYLIST);
+ }
+
+ sprintf(chatbuf, "%d", kind);
+ send_to_user(cu, chatbuf, 0, MSG_PARTYLISTEND);
+}
+
+
+#define SCREEN_WIDTH 80
+#define MAX_VERB_LEN 8
+#define VERB_NO 10
+
+static void
+view_action_verb(ChatUser *cu, char cmd) /* Thor.0726: 新加動詞分類顯示 */
+{
+ register int i;
+ register char *p, *q, *data, *expn;
+ register ChatAction *cap;
+
+ send_to_user(cu, "/c", 0, MSG_CLRSCR);
+
+ data = chatbuf;
+
+ if (cmd < '1' || cmd > '3')
+ { /* Thor.0726: 寫得不好, 想辦法改進... */
+ for (i = 0; (p = dscrb[i]); i++)
+ {
+ sprintf(data, " [//]help %d - MUD-like 社交動詞 第 %d 類", i + 1, i + 1);
+ send_to_user(cu, data, 0, MSG_MESSAGE);
+ send_to_user(cu, p, 0, MSG_MESSAGE);
+ send_to_user(cu, " ", 0, MSG_MESSAGE); /* Thor.0726: 換行, 需要 " "
+ * 嗎? */
+ }
+ }
+ else
+ {
+ send_to_user(cu, dscrb[cmd-'1'], 0, MSG_MESSAGE);
+
+ expn = chatbuf + 100; /* Thor.0726: 應該不會overlap吧? */
+
+ *data = '\0';
+ *expn = '\0';
+
+ cap = catbl[cmd-'1'];
+
+ for (i = 0; (p = cap[i].verb); i++)
+ {
+ q = cap[i].chinese;
+
+ strcat(data, p);
+ strcat(expn, q);
+
+ if (((i + 1) % VERB_NO) == 0 || cap[i+1].verb==NULL)
+ {
+ send_to_user(cu, data, 0, MSG_MESSAGE);
+ send_to_user(cu, expn, 0, MSG_MESSAGE); /* Thor.0726: 顯示中文註解 */
+ *data = '\0';
+ *expn = '\0';
+ }
+ else
+ {
+ strncat(data, " ", MAX_VERB_LEN - strlen(p));
+ strncat(expn, " ", MAX_VERB_LEN - strlen(q));
+ }
+ }
+ }
+ /* send_to_user(cu, " ",0); *//* Thor.0726: 換行, 需要 " " 嗎? */
+}
+
+/* ----------------------------------------------------- */
+/* chat user service routines */
+/* ----------------------------------------------------- */
+
+
+static ChatCmd chatcmdlist[] =
+{
+ {"act", chat_act, 0},
+ {"bye", chat_goodbye, 0},
+ {"chatroom", chat_chatroom, 1}, /* Xshadow: for common client */
+ {"clear", chat_clear, 0},
+ {"cloak", chat_cloak, 2},
+ {"date", chat_date, 0},
+ {"flags", chat_setroom, 0},
+ {"help", chat_help, 0},
+ {"ignore", chat_ignore, 1},
+ {"invite", chat_invite, 0},
+ {"join", chat_join, 0},
+ {"kick", chat_kick, 1},
+ {"msg", chat_private, 0},
+ {"nick", chat_nick, 0},
+ {"operator", chat_makeop, 0},
+ {"party", chat_party, 1}, /* Xshadow: party data for common client */
+ {"partyinfo", chat_partyinfo, 1}, /* Xshadow: party info for common
+ * client */
+
+ {"query", chat_query, 0},
+
+ {"room", chat_list_rooms, 0},
+ {"unignore", chat_unignore, 1},
+ {"whoin", chat_list_by_room, 1},
+ {"wall", chat_broadcast, 2},
+
+ {"who", chat_map_chatids_thisroom, 0},
+ {"list", chat_list_users, 0},
+ {"topic", chat_topic, 1},
+ {"version", chat_version, 1},
+
+ {NULL, NULL, 0}
+};
+
+/* Thor: 0 不用 exact, 1 要 exactly equal, 2 秘密指令 */
+
+
+static int
+command_execute(ChatUser *cu)
+{
+ char *cmd, *msg;
+ ChatCmd *cmdrec;
+ int match;
+
+ msg = cu->ibuf;
+
+ /* Validation routine */
+
+ if (cu->room == NULL)
+ {
+ /* MUST give special /! or /-! command if not in the room yet */
+ if(strncmp(msg, "/!", 2)==0) {
+ cu->clitype = 0;
+ return login_user(cu, msg+2);
+ }
+ if(strncmp(msg, "/-!", 3)==0) {
+ cu->clitype = 1;
+ return login_user(cu, msg+3);
+ }
+ return -1;
+ }
+
+ if(msg[0] == '\0')
+ return 0;
+
+ /* If not a "/"-command, it goes to the room. */
+ if (msg[0] != '/')
+ {
+ char buf[16];
+
+ sprintf(buf, "%s:", cu->chatid);
+ sprintf(chatbuf, "%-10s%s", buf, msg);
+ if (!CLOAK(cu)) /* Thor: 聊天室隱身術 */
+ send_to_room(cu->room, chatbuf, cu->userno, MSG_MESSAGE);
+ return 0;
+ }
+
+ msg++;
+ cmd = nextword(&msg);
+ match = 0;
+
+ if (*cmd == '/')
+ {
+ cmd++;
+ if (!*cmd || str_equal(cmd, "help"))
+ {
+ /* Thor.0726: 動詞分類 */
+ cmd = nextword(&msg);
+ view_action_verb(cu, *cmd);
+ match = 1;
+ }
+ else if (party_action(cu, cmd, msg) == 0)
+ match = 1;
+ else if (speak_action(cu, cmd, msg) == 0)
+ match = 1;
+ else
+ match = condition_action(cu, cmd);
+ }
+ else
+ {
+ char *str;
+
+ common_client_command = 0;
+ if((*cmd == '-')) {
+ if(cu->clitype) {
+ cmd++; /* Xshadow: 指令從下一個字元才開始 */
+ common_client_command = 1;
+ }
+ }
+ for(cmdrec = chatcmdlist; (str = cmdrec->cmdstr); cmdrec++)
+ {
+ switch (cmdrec->exact)
+ {
+ case 1: /* exactly equal */
+ match = str_equal(cmd, str);
+ break;
+ case 2: /* Thor: secret command */
+ if (CHATSYSOP(cu))
+ match = str_equal(cmd, str);
+ break;
+ default: /* not necessary equal */
+ match = str_match(cmd, str) >= 0;
+ break;
+ }
+
+ if (match)
+ {
+ cmdrec->cmdfunc(cu, msg);
+ break;
+ }
+ }
+ }
+
+ if (!match)
+ {
+ sprintf(chatbuf, "◆ 指令錯誤:/%s", cmd);
+ send_to_user(cu, chatbuf, 0, MSG_MESSAGE);
+ }
+ return 0;
+}
+
+
+/* ----------------------------------------------------- */
+/* serve chat_user's connection */
+/* ----------------------------------------------------- */
+
+
+static int
+cuser_serve(ChatUser *cu)
+{
+ register int ch, len, isize;
+ register char *str, *cmd;
+ char buf[80];
+
+ str = buf;
+ len = recv(cu->sock, str, sizeof(buf) - 1, 0);
+ if (len <= 0)
+ {
+ /* disconnected */
+
+ exit_room(cu, EXIT_LOSTCONN, (char *) NULL);
+ return -1;
+ }
+
+#if 0
+ /* Xshadow: 將送達的資料忠實紀錄下來 */
+ memcpy(logbuf, buf, sizeof(buf));
+ for (ch = 0; ch < sizeof(buf); ch++)
+ if (!logbuf[ch])
+ logbuf[ch] = '$';
+
+ logbuf[len + 1] = '\0';
+ logit("recv: ", logbuf);
+#endif
+
+#if 0
+ logit(cu->userid, str);
+#endif
+
+ isize = cu->isize;
+ cmd = cu->ibuf;
+ while (len--) {
+ ch = *str++;
+
+ if (ch == '\r' || ch == '\0')
+ continue;
+ if (ch == '\n')
+ {
+ cmd[isize]='\0';
+ isize = 0;
+
+ if (command_execute(cu) < 0)
+ return -1;
+
+ continue;
+ }
+ if (isize < sizeof(cu->ibuf)-1)
+ cmd[isize++] = ch;
+ }
+ cu->isize = isize;
+ return 0;
+}
+
+
+/* ----------------------------------------------------- */
+/* chatroom server core routines */
+/* ----------------------------------------------------- */
+
+static int
+start_daemon()
+{
+ int fd, value;
+ char buf[80];
+ struct sockaddr_in fsin;
+ struct linger ld;
+ struct rlimit limit;
+ time_t dummy;
+ struct tm *dummy_time;
+
+ /*
+ * More idiot speed-hacking --- the first time conversion makes the C
+ * library open the files containing the locale definition and time zone.
+ * If this hasn't happened in the parent process, it happens in the
+ * children, once per connection --- and it does add up.
+ */
+
+ time(&dummy);
+ dummy_time = gmtime(&dummy);
+ dummy_time = localtime(&dummy);
+ strftime(buf, 80, "%d/%b/%Y:%H:%M:%S", dummy_time);
+
+ /* --------------------------------------------------- */
+ /* speed-hacking DNS resolve */
+ /* --------------------------------------------------- */
+
+ gethostname(buf, sizeof(buf));
+
+ /* Thor: 萬一server尚未接受connection, 就回去的話, client 第一次會進入失敗 */
+ /* 所以移至 listen 後 */
+
+ /* --------------------------------------------------- */
+ /* detach daemon process */
+ /* --------------------------------------------------- */
+
+ close(0);
+ close(1);
+ close(2);
+
+#ifndef SELFTEST
+ if (fork())
+ exit(0);
+#endif
+
+ chdir(BBSHOME);
+
+ setsid();
+
+#ifndef SELFTEST
+ attach_SHM();
+#endif
+ /* --------------------------------------------------- */
+ /* adjust the resource limit */
+ /* --------------------------------------------------- */
+
+ getrlimit(RLIMIT_NOFILE, &limit);
+ limit.rlim_cur = limit.rlim_max;
+ setrlimit(RLIMIT_NOFILE, &limit);
+
+#if 0
+ while (fd)
+ {
+ close(--fd);
+ }
+
+ value = getpid();
+ setpgrp(0, value);
+
+ if ((fd = open("/dev/tty", O_RDWR)) >= 0)
+ {
+ ioctl(fd, TIOCNOTTY, 0); /* Thor : 為什麼還要用 tty? */
+ close(fd);
+ }
+#endif
+
+ fd = open(CHAT_PIDFILE, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (fd >= 0)
+ {
+ /* sprintf(buf, "%5d\n", value); */
+ sprintf(buf, "%5d\n", (int)getpid());
+ write(fd, buf, 6);
+ close(fd);
+ }
+
+#if 0
+ /* ------------------------------ */
+ /* trap signals */
+ /* ------------------------------ */
+
+ for (fd = 1; fd < NSIG; fd++)
+ {
+
+ Signal(fd, SIG_IGN);
+ }
+#endif
+
+ fd = socket(PF_INET, SOCK_STREAM, 0);
+
+#if 0
+ value = fcntl(fd, F_GETFL, 0);
+ fcntl(fd, F_SETFL, value | O_NDELAY);
+#endif
+
+ value = 1;
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &value, sizeof(value));
+
+#if 0
+ setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *) &value, sizeof(value));
+
+ value = 81920;
+ setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *) &value, sizeof(value));
+#endif
+
+ ld.l_onoff = ld.l_linger = 0;
+ setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &ld, sizeof(ld));
+
+ memset((char *) &fsin, 0, sizeof(fsin));
+ fsin.sin_family = AF_INET;
+ fsin.sin_port = htons(NEW_CHATPORT);
+ fsin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ if (bind(fd, (struct sockaddr *) & fsin, sizeof(fsin)) < 0)
+ exit(1);
+
+ listen(fd, SOCK_QLEN);
+
+ return fd;
+}
+
+
+static void
+free_resource(int fd)
+{
+ static int loop = 0;
+ register ChatUser *user;
+ register int sock, num;
+
+ /* 重新計算 maxfd */
+ num = 0;
+ for (user = mainuser; user; user = user->unext)
+ {
+ num++;
+ sock = user->sock;
+ if (fd < sock)
+ fd = sock;
+ }
+
+ sprintf(chatbuf, "%d, %d user (maxfds %d -> %d)", ++loop, num, maxfds, fd+1);
+ logit("LOOP", chatbuf);
+
+ maxfds = fd + 1;
+}
+
+
+#ifdef SERVER_USAGE
+static void
+server_usage()
+{
+ struct rusage ru;
+ char buf[2048];
+
+ if (getrusage(RUSAGE_SELF, &ru))
+ return;
+
+ sprintf(buf, "\n[Server Usage]\n\n"
+ "user time: %.6f\n"
+ "system time: %.6f\n"
+ "maximum resident set size: %lu P\n"
+ "integral resident set size: %lu\n"
+ "page faults not requiring physical I/O: %ld\n"
+ "page faults requiring physical I/O: %ld\n"
+ "swaps: %ld\n"
+ "block input operations: %ld\n"
+ "block output operations: %ld\n"
+ "messages sent: %ld\n"
+ "messages received: %ld\n"
+ "signals received: %ld\n"
+ "voluntary context switches: %ld\n"
+ "involuntary context switches: %ld\n"
+ "\n",
+
+ (double) ru.ru_utime.tv_sec + (double) ru.ru_utime.tv_usec / 1000000.0,
+ (double) ru.ru_stime.tv_sec + (double) ru.ru_stime.tv_usec / 1000000.0,
+ ru.ru_maxrss,
+ ru.ru_idrss,
+ ru.ru_minflt,
+ ru.ru_majflt,
+ ru.ru_nswap,
+ ru.ru_inblock,
+ ru.ru_oublock,
+ ru.ru_msgsnd,
+ ru.ru_msgrcv,
+ ru.ru_nsignals,
+ ru.ru_nvcsw,
+ ru.ru_nivcsw);
+
+ write(flog, buf, strlen(buf));
+}
+#endif
+
+
+static void
+abort_server()
+{
+ log_close();
+ exit(1);
+}
+
+
+static void
+reaper()
+{
+ int state;
+
+ while (waitpid(-1, &state, WNOHANG | WUNTRACED) > 0)
+ {
+ }
+}
+
+#ifdef SELFTESTER
+#define MAXTESTUSER 20
+
+int selftest_connect(void)
+{
+ struct sockaddr_in sin;
+ struct hostent *h;
+ int cfd;
+
+ if (!(h = gethostbyname("localhost"))) {
+ perror("gethostbyname");
+ return -1;
+ }
+ memset(&sin, 0, sizeof sin);
+#ifdef __FreeBSD__
+ sin.sin_len = sizeof(sin);
+#endif
+ sin.sin_family = PF_INET;
+ memcpy(&sin.sin_addr, h->h_addr, h->h_length);
+ sin.sin_port = htons(NEW_CHATPORT);
+ cfd = socket(sin.sin_family, SOCK_STREAM, 0);
+ if (connect(cfd, (struct sockaddr *) & sin, sizeof sin) != 0) {
+ perror("connect");
+ return -1;
+ }
+ return cfd;
+}
+
+static int
+selftest_send(int fd, char *buf)
+{
+ int len;
+ char genbuf[200];
+
+ snprintf(genbuf, sizeof(genbuf), "%s\n", buf);
+ len = strlen(genbuf);
+ return (send(fd, genbuf, len, 0) == len);
+}
+void selftest_testing(void)
+{
+ int cfd;
+ char userid[IDLEN+1];
+ char inbuf[1024], buf[1024];
+
+ cfd=selftest_connect();
+ if(cfd<0) exit(1);
+ while(1) {
+ snprintf(userid, sizeof(userid), "%ld", random()%(MAXTESTUSER*2));
+ sprintf(buf, "/%s! %s %s %s", random()%4==0?"-":"",userid, userid, "passwd");
+ selftest_send(cfd, buf);
+ if (recv(cfd, inbuf, 3, 0) != 3) {
+ close(cfd);
+ return;
+ }
+ if (!strcmp(inbuf, CHAT_LOGIN_OK))
+ break;
+ }
+
+ if(random()%4!=0) {
+ sprintf(buf, "/j %d", random()%5);
+ selftest_send(cfd, buf);
+ }
+
+ while(1) {
+ int i;
+ int r;
+ int inlen;
+ fd_set rset,xset;
+ struct timeval zerotv;
+
+ FD_ZERO(&rset);
+ FD_SET(cfd, &rset);
+ FD_ZERO(&xset);
+ FD_SET(cfd, &xset);
+ zerotv.tv_sec=0;
+ zerotv.tv_usec=0;
+ select(cfd+1, &rset, NULL, &xset, &zerotv);
+ if(FD_ISSET(cfd, &rset)) {
+ inlen=read(cfd, inbuf, sizeof(inbuf));
+ if(inlen<0) break;
+ }
+ if(FD_ISSET(cfd, &xset)) {
+ inlen=read(cfd, inbuf, sizeof(inbuf));
+ if(inlen<0) break;
+ }
+
+
+ if(random()%10==0) {
+ switch(random()%4) {
+ case 0:
+ r=random()%(sizeof(party_data)/sizeof(party_data[0])-1);
+ sprintf(buf, "//%s",party_data[r].verb);
+ break;
+ case 1:
+ r=random()%(sizeof(speak_data)/sizeof(speak_data[0])-1);
+ sprintf(buf, "//%s",speak_data[r].verb);
+ break;
+ case 2:
+ r=random()%(sizeof(condition_data)/sizeof(condition_data[0])-1);
+ sprintf(buf, "//%s",condition_data[r].verb);
+ break;
+ case 3:
+ sprintf(buf, "blah");
+ break;
+ }
+ } else {
+ r=random()%(sizeof(chatcmdlist)/sizeof(chatcmdlist[0])-1);
+ sprintf(buf, "/%s",chatcmdlist[r].cmdstr);
+ if(strncmp("/flag",buf,5)==0) {
+ if(random()%2)
+ strcat(buf," +");
+ else
+ strcat(buf," -");
+ strcat(buf,random()%2?"l":"L");
+ strcat(buf,random()%2?"h":"H");
+ strcat(buf,random()%2?"s":"S");
+ strcat(buf,random()%2?"t":"T");
+ } else if(strncmp("/bye",buf,4)==0) {
+ switch(random()%10) {
+ case 0: strcpy(buf,"//"); break;
+ case 1: strcpy(buf,"//1"); break;
+ case 2: strcpy(buf,"//2"); break;
+ case 3: strcpy(buf,"//3"); break;
+ case 4: strcpy(buf,"/bye"); break;
+ case 5: strcpy(buf,"/help op"); break;
+ case 6: close(cfd); return; break;
+ case 7: strcpy(buf,"/help"); break;
+ case 8: strcpy(buf,"/help"); break;
+ case 9: strcpy(buf,"/help"); break;
+ }
+ }
+ }
+ for(i=random()%3; i>0; i--) {
+ char tmp[1024];
+ sprintf(tmp," %ld", random()%(MAXTESTUSER*2));
+ strcat(buf, tmp);
+ }
+
+ selftest_send(cfd, buf);
+ }
+ close(cfd);
+}
+
+void selftest_test(void)
+{
+ while(1) {
+ fprintf(stderr,".");
+ selftest_testing();
+ usleep(1000);
+ }
+}
+
+void selftest(void)
+{
+ int i;
+ pid_t pid;
+
+ pid=fork();
+ if(pid<0) exit(1);
+ if(pid) return;
+ sleep(1);
+
+ for(i=0; i<MAXTESTUSER; i++) {
+ if(fork()==0)
+ selftest_test();
+ sleep(1);
+ random();
+ }
+
+ exit(0);
+}
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ register int msock, csock, nfds;
+ register ChatUser *cu, *cunext;
+ register fd_set *rptr, *xptr;
+ fd_set rset, xset;
+ struct timeval tv;
+ time4_t uptime, tmaintain;
+
+#ifdef SELFTESTER
+ if(argc>1) {
+ Signal(SIGPIPE, SIG_IGN);
+ selftest();
+ return 0;
+ }
+#endif
+ msock = start_daemon();
+
+ setgid(BBSGID);
+ setuid(BBSUID);
+
+ log_init();
+
+// Signal(SIGBUS, SIG_IGN);
+// Signal(SIGSEGV, SIG_IGN);
+ Signal(SIGPIPE, SIG_IGN);
+ Signal(SIGURG, SIG_IGN);
+
+ Signal(SIGCHLD, reaper);
+ Signal(SIGTERM, abort_server);
+
+#ifdef SERVER_USAGE
+ Signal(SIGPROF, server_usage);
+#endif
+
+ /* ----------------------------- */
+ /* init variable : rooms & users */
+ /* ----------------------------- */
+
+ mainuser = NULL;
+ memset(&mainroom, 0, sizeof(mainroom));
+ strcpy(mainroom.name, MAIN_NAME);
+ strcpy(mainroom.topic, MAIN_TOPIC);
+
+ /* ----------------------------------- */
+ /* main loop */
+ /* ----------------------------------- */
+
+ FD_ZERO(&mainfds);
+ FD_SET(msock, &mainfds);
+ rptr = &rset;
+ xptr = &xset;
+ maxfds = msock + 1;
+ tmaintain = time(0) + CHAT_INTERVAL;
+
+ for (;;)
+ {
+ uptime = time(0);
+ if (tmaintain < uptime)
+ {
+ tmaintain = uptime + CHAT_INTERVAL;
+
+ /* client/server 版本利用 ping-pong 方法判斷 user 是不是還活著 */
+ /* 如果 client 已經結束了,就釋放其 resource */
+
+ free_resource(msock);
+#ifdef SELFTEST
+ break;
+#endif
+ }
+
+ memcpy(rptr, &mainfds, sizeof(fd_set));
+ memcpy(xptr, &mainfds, sizeof(fd_set));
+
+ /* Thor: for future reservation bug */
+
+ tv.tv_sec = CHAT_INTERVAL;
+ tv.tv_usec = 0;
+
+ nfds = select(maxfds, rptr, NULL, xptr, &tv);
+
+ /* free idle user & chatroom's resource when no traffic */
+ if (nfds == 0)
+ continue;
+
+ /* check error condition */
+ if (nfds < 0)
+ continue;
+
+ /* accept new connection */
+ if (FD_ISSET(msock, rptr)) {
+ csock = accept(msock, NULL, NULL);
+
+ if(csock < 0) {
+ if(errno != EINTR) {
+ // TODO
+ }
+ } else {
+ cu = (ChatUser *) malloc(sizeof(ChatUser));
+ if(cu == NULL) {
+ close(csock);
+ logit("accept", "malloc fail");
+ } else {
+ memset(cu, 0, sizeof(ChatUser));
+ cu->sock = csock;
+ cu->unext = mainuser;
+ mainuser = cu;
+
+ totaluser++;
+ FD_SET(csock, &mainfds);
+ if (csock >= maxfds)
+ maxfds = csock + 1;
+
+#ifdef DEBUG
+ logit("accept", "OK");
+#endif
+ }
+ }
+ }
+
+ for (cu = mainuser; cu && nfds>0; cu = cunext) {
+ /* logout_user() 會 free(cu); 先把 cu->next 記下來 */
+ /* FIXME 若剛好 cu 在 main room /kick cu->next, 則 cu->next 會被 free 掉 */
+ cunext = cu->unext;
+ csock = cu->sock;
+ if (FD_ISSET(csock, xptr)) {
+ logout_user(cu);
+ nfds--;
+ } else if (FD_ISSET(csock, rptr)) {
+ if (cuser_serve(cu) < 0)
+ logout_user(cu);
+ nfds--;
+ }
+ }
+
+ /* end of main loop */
+ }
+ return 0;
+}
diff --git a/pttbbs/util/xchatd.h b/pttbbs/util/xchatd.h
new file mode 100644
index 00000000..192b038f
--- /dev/null
+++ b/pttbbs/util/xchatd.h
@@ -0,0 +1,109 @@
+/* $Id$ */
+
+#ifndef _XCHAT_H_
+#define _XCHAT_H_
+
+#define XCHAT_VERSION_MAJOR 3
+#define XCHAT_VERSION_MINOR 0
+
+/* ----------------------------------------------------- */
+/* XCHAT response code : RFI 3-digit */
+/* ----------------------------------------------------- */
+/* Response : */
+/* 1xx Informative message */
+/* 2xx Command ok */
+/* 3xx Command ok so far, send the rest of it */
+/* 4xx Command correct, but NG for some reason */
+/* 5xx Command unimplemented, incorrect, or serious */
+/* program error occurred */
+/* Function : */
+/* x0x Connection, setup, and miscellaneous messages */
+/* x1x Newsgroup selection */
+/* x2x Article selection */
+/* x3x Distribution functions */
+/* x4x Posting */
+/* x8x Nonstandard extensions (AUTHINFO, XGTITLE) */
+/* x9x Debugging output */
+/* Information : */
+/* No defined semantics */
+/* ----------------------------------------------------- */
+
+/* 供新版 client 使用 */
+
+#define MSG_LOGINOK 100
+#define MSG_VERSION 103
+#define MSG_MESSAGE 106
+
+#define MSG_CHATROOM 110
+#define MSG_TOPIC 113
+#define MSG_ROOM 116
+#define MSG_NICK 118
+#define MSG_CLRSCR 120
+
+#define MSG_MOTDSTART 130
+#define MSG_MOTD 330
+#define MSG_MOTDEND 230
+
+#define MSG_ROOMLISTSTART 133
+#define MSG_ROOMLIST 333
+#define MSG_ROOMLISTEND 233
+#define MSG_ROOMNOTIFY 134
+
+#define MSG_USERLISTSTART 136
+#define MSG_USERLIST 336
+#define MSG_USERLISTEND 236
+#define MSG_USERNOTIFY 137
+
+#define MSG_PARTYINFO 140
+#define MSG_PARTYLISTSTART 340
+#define MSG_PARTYLIST 240
+#define MSG_PARTYLISTEND 141
+
+#define MSG_PRIVMSG 145
+#define MSG_MYPRIVMSG 146
+
+#define ERR_LOGIN_NICKINUSE 501
+#define ERR_LOGIN_NICKERROR 502
+#define ERR_LOGIN_USERONLINE 503
+#define ERR_LOGIN_NOSUCHUSER 504
+#define ERR_LOGIN_PASSERROR 505
+
+static int
+Isspace (int ch)
+{
+ return (ch == ' ' || ch == '\t' || ch == 10 || ch == 13);
+}
+
+static char *
+nextword (char **str)
+{
+ char *head, *tail;
+ int ch;
+
+ head = *str;
+ for (;;) {
+
+ ch = *head;
+ if (!ch) {
+ *str = head;
+ return head;
+ }
+ if (!Isspace (ch))
+ break;
+ head++;
+ }
+
+ tail = head + 1;
+ while((ch = *tail)) {
+ if(Isspace (ch)) {
+ *tail++ = '\0';
+ break;
+ }
+ tail++;
+ }
+ *str = tail;
+
+ return head;
+}
+
+#endif /* _XCHAT_H_ */
diff --git a/pttbbs/util/yearsold.c b/pttbbs/util/yearsold.c
new file mode 100644
index 00000000..1418cd59
--- /dev/null
+++ b/pttbbs/util/yearsold.c
@@ -0,0 +1,105 @@
+/* $Id$ */
+/* 站上年齡統計 */
+#define _UTIL_C_
+#include "bbs.h"
+
+#define YEARSOLD_MAX_LINE 16
+
+struct userec_t user;
+
+void
+fouts(fp, buf, mode)
+FILE *fp;
+char buf[], mode;
+{
+ static char state = '0';
+
+ if (state != mode)
+ fprintf(fp, "[3%cm", state = mode);
+ if (buf[0])
+ {
+ fprintf(fp, buf);
+ buf[0] = 0;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int i, j, k;
+ char buf[256];
+ FILE *fp;
+ int year, max, item, maxyear, totalyear;
+ int act[25];
+ time4_t now;
+ struct tm *ptime;
+
+ now = time(NULL);
+ ptime = localtime4(&now);
+
+ attach_SHM();
+ if(passwd_init())
+ exit(1);
+
+ memset(act, 0, sizeof(act));
+ for(k = 1; k <= MAX_USERS; k++) {
+ passwd_query(k, &user);
+ if (((ptime->tm_year - user.year) < 10) || ((ptime->tm_year - user.year) >
+ 33))
+ continue;
+
+ act[ptime->tm_year - user.year - 10]++;
+ act[24]++;
+ }
+
+ for (i = max = totalyear = maxyear = 0; i < 24; i++)
+ {
+ totalyear += act[i] * (i + 10);
+ if (act[i] > max)
+ {
+ max = act[i];
+ maxyear = i;
+ }
+ }
+
+ item = max / YEARSOLD_MAX_LINE + 1;
+
+ if ((fp = fopen(BBSHOME"/etc/yearsold", "w")) == NULL)
+ {
+ printf("cann't open etc/yearsold\n");
+ return 1;
+ }
+
+ fprintf(fp, "\t\t\t  " BBSNAME
+ " 年齡統計 [%02d/%02d/%02d] \n\n",
+ ptime->tm_year % 100, ptime->tm_mon + 1, ptime->tm_mday);
+ for (i = YEARSOLD_MAX_LINE + 1; i > 0; i--)
+ {
+ strcpy(buf, " ");
+ for (j = 0; j < 24; j++)
+ {
+ max = item * i;
+ year = act[j];
+ if (year && (max > year) && (max - item <= year))
+ {
+ fouts(fp, buf, '7');
+ fprintf(fp, "%-3d", year);
+ }
+ else if (max <= year)
+ {
+ fouts(fp, buf, '4');
+ fprintf(fp, "█ ");
+ }
+ else
+ strcat(buf, " ");
+ }
+ fprintf(fp, "\n");
+ }
+
+
+ fprintf(fp, " "
+ "10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33\n\n"
+ "\t\t 有效統計人次:%-9d平均年齡:%d\n"
+ ,act[24], (int)totalyear / act[24]);
+ fclose(fp);
+ return 0;
+}
diff --git a/pttbbs/util/zero_limits.c b/pttbbs/util/zero_limits.c
new file mode 100644
index 00000000..435ed0a3
--- /dev/null
+++ b/pttbbs/util/zero_limits.c
@@ -0,0 +1,51 @@
+/* $Id$ */
+/* Vote limits were added in r2342 which uses previously unused pads.
+ * This program zero outs those pads. */
+#include "bbs.h"
+
+void transform(boardheader_t *new, boardheader_t *old)
+{
+ memcpy(new, old, sizeof(boardheader_t));
+ new->post_limit_posts = (unsigned char) 0;
+ new->post_limit_logins = (unsigned char) 0;
+ new->post_limit_regtime = (unsigned char) 0;
+ new->vote_limit_posts = (unsigned char) 0;
+ new->vote_limit_logins = (unsigned char) 0;
+ new->vote_limit_regtime = (unsigned char) 0;
+ memset(new->pad, 0, sizeof(new->pad));
+ memset(new->pad2, 0, sizeof(new->pad2));
+ memset(new->pad3, 0, sizeof(new->pad3));
+}
+
+int main(void)
+{
+ int fd, fdw;
+ boardheader_t new, old;
+
+ printf("You're going to zero out vote limits in your .BRD\n");
+ printf("The new file will be named .BRD.trans.tmp\n");
+/*
+ printf("Press any key to continue\n");
+ getchar();
+*/
+
+ if (chdir(BBSHOME) < 0) {
+ perror("chdir");
+ exit(-1);
+ }
+
+ if ((fd = open(FN_BOARD, O_RDONLY)) < 0 ||
+ (fdw = open(FN_BOARD".trans.tmp", O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0 ) {
+ perror("open");
+ exit(-1);
+ }
+
+ while (read(fd, &old, sizeof(old)) > 0) {
+ transform(&new, &old);
+ write(fdw, &new, sizeof(new));
+ }
+
+ close(fd);
+ close(fdw);
+ return 0;
+}